use crate::{
database::{Database, Error, Result},
entities::{DateTime, Duration, RoundMode, RunningWork, Work, WorkTime},
jobber::*,
};
impl JobberWork for Database {
fn start_work(
&mut self,
job_id: usize,
start: DateTime,
subject: Option<impl ToString>,
tags: impl IntoIterator<Item = impl ToString>,
) -> Result<RunningWork> {
let job = self.get_job_mut(job_id)?;
let scheme = &job.scheme(job_id)?;
let start = scheme.round(&start, RoundMode::Past);
if let Some(current) = &job.running {
return Err(Error::AlreadyWorking {
start: scheme.format_date_time(current.start),
desc: current.subject.as_ref().unwrap_or(&job.subject).clone(),
client: job.client.clone(),
});
}
let running_work = RunningWork {
start,
subject: subject.map(|d| d.to_string()),
tags: tags.into_iter().map(|t| t.to_string()).collect(),
};
job.running = Some(running_work.clone());
Ok(running_work)
}
fn end_work(
&mut self,
job_id: usize,
end: DateTime,
subject: Mod<impl ToString>,
tags: impl IntoIterator<Item = impl ToString>,
) -> Result<Work> {
let job = self.get_job_mut(job_id)?;
let end = job.scheme(job_id)?.round(&end, RoundMode::Future);
let scheme = &job.scheme(job_id)?;
if let Some(current) = &job.running {
let subject = match (¤t.subject, subject) {
(Some(s), Mod::None) => s.clone(),
(None, Mod::None) => {
return Err(Error::DescriptionMissing);
}
(None, Mod::Set(s)) | (None, Mod::Replace(s)) | (None, Mod::Append(s)) => {
s.to_string()
}
(Some(old), Mod::Set(_)) => {
return Err(Error::DescriptionExists(old.to_string()));
}
(Some(_), Mod::Replace(new)) => new.to_string(),
(Some(old), Mod::Append(new)) => format!("{}\n{}", old, new.to_string()),
};
let tags = current
.tags
.iter()
.cloned()
.chain(tags.into_iter().map(|s| s.to_string()))
.collect();
if current.start > end {
return Err(Error::EndsBeforeStart(
scheme.format_date_time(current.start),
scheme.format_date_time(end),
));
}
let work = Work {
work_time: WorkTime::new(current.start, end),
subject,
tags,
..Default::default()
};
job.work.push(work.clone());
job.running = None;
Ok(work)
} else {
Err(Error::NotWorking)
}
}
fn add_work(
&mut self,
job_id: usize,
start: DateTime,
end: DateTime,
subject: impl ToString,
tags: impl IntoIterator<Item = impl ToString>,
) -> Result<Work> {
let scheme = &self.get_job(job_id)?.scheme(job_id)?;
let start = scheme.round(&start, RoundMode::Past);
let end = scheme.round(&end, RoundMode::Future);
if start > end {
Err(Error::EndsBeforeStart(
scheme.format_date_time(start),
scheme.format_date_time(end),
))?;
}
let running = self.cancel_work(job_id).ok();
self.start_work(job_id, start, Some(subject), tags)?;
let work = self.end_work(job_id, end, Mod::<String>::None, Vec::<String>::new())?;
if let Some(running) = running {
self.start_work(job_id, running.start, running.subject, running.tags)?;
}
Ok(work)
}
fn cancel_work(&mut self, job: usize) -> Result<RunningWork> {
let job = self.get_job_mut(job)?;
if let Some(current) = job.running.take() {
Ok(current)
} else {
Err(Error::NotWorking)
}
}
fn sort(&mut self, job_id: usize) -> Result<()> {
let job = self.get_job_mut(job_id)?;
job.work.sort();
Ok(())
}
fn duration_by_client(&self, client_id: impl ToString) -> Duration {
let id = client_id.to_string();
self.jobs
.iter()
.filter(|job| job.client == id && !job.deleted)
.flat_map(|job| job.work.iter())
.map(|work| work.duration())
.sum()
}
fn duration_by_worker(&self, worker_id: impl ToString) -> Duration {
let id = worker_id.to_string();
self.jobs
.iter()
.filter(|job| job.worker == id && !job.deleted)
.flat_map(|job| job.work.iter())
.map(|work| work.duration())
.sum()
}
fn duration_by_day(&self, job_id: Option<usize>, day: DateTime) -> Duration {
let year = day.year();
let month = day.month();
let day = day.day();
let day_start = DateTime::from_ymd(year, month, day).expect("internal date/time error");
let day_end = day_start + Duration::days(1);
if let Some(work) = self
.jobs
.iter()
.enumerate()
.filter(|(_, job)| !job.deleted)
.find_map(|(id, job)| job_id.is_none_or(|job_id| job_id == id).then_some(job))
.map(|job| job.work.iter())
{
work.map(|w| w.work_time.duration_intersect(day_start, day_end))
.sum()
} else {
Duration::zero()
}
}
fn work_range(&self, job_id: &Option<usize>, deleted: Deleted) -> (DateTime, DateTime) {
self.iter_jobs(deleted)
.filter(|(id, _)| job_id.is_none_or(|job_id| job_id == *id))
.flat_map(|(_, job)| job.work.iter())
.filter(|work| deleted.check(work.deleted))
.fold((DateTime::max(), DateTime::min()), |(min, max), work| {
(min.min(work.start()), max.max(work.end()))
})
}
}