You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
4.3 KiB
132 lines
4.3 KiB
use crate::{
|
|
data::{Delta, Invoice, Session},
|
|
error::{UserError, UserResult},
|
|
Date, Time,
|
|
};
|
|
|
|
/// A timeline entry of sessions and invoices
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub(crate) enum Entry {
|
|
Session(Session),
|
|
Invoice(Invoice),
|
|
}
|
|
|
|
impl From<Session> for Entry {
|
|
fn from(s: Session) -> Self {
|
|
Self::Session(s)
|
|
}
|
|
}
|
|
|
|
impl From<Invoice> for Entry {
|
|
fn from(i: Invoice) -> Self {
|
|
Self::Invoice(i)
|
|
}
|
|
}
|
|
|
|
/// A timeline of sessions and invoices, ordered chronologically
|
|
#[derive(Debug, Default, Clone)]
|
|
pub(crate) struct Timeline {
|
|
inner: Vec<Entry>,
|
|
}
|
|
|
|
impl Timeline {
|
|
/// Take a set of sessions and invoices to sort into a timeline
|
|
pub(crate) fn build(s: Vec<Session>, i: Vec<Invoice>) -> Self {
|
|
let mut inner: Vec<_> = s.into_iter().map(|s| Entry::Session(s)).collect();
|
|
inner.append(&mut i.into_iter().map(|i| Entry::Invoice(i)).collect());
|
|
Self { inner }
|
|
}
|
|
|
|
/// Utility function to get the last session in the timeline
|
|
fn last_session(&mut self) -> Option<&mut Session> {
|
|
self.inner
|
|
.iter_mut()
|
|
.find(|e| match e {
|
|
Entry::Session(_) => true,
|
|
_ => false,
|
|
})
|
|
.map(|e| match e {
|
|
Entry::Session(ref mut s) => s,
|
|
_ => unreachable!(),
|
|
})
|
|
}
|
|
|
|
/// Utility function to get the last invoice in the timeline
|
|
fn last_invoice(&self) -> Option<&Invoice> {
|
|
self.inner
|
|
.iter()
|
|
.find(|e| match e {
|
|
Entry::Invoice(_) => true,
|
|
_ => false,
|
|
})
|
|
.map(|e| match e {
|
|
Entry::Invoice(ref s) => s,
|
|
_ => unreachable!(),
|
|
})
|
|
}
|
|
|
|
/// Get a list of sessions that happened up to a certain invoice date
|
|
///
|
|
/// **WARNING** If there is no invoice with the given date, this
|
|
/// function will return garbage data, so don't call it with
|
|
/// invoice dates that don't exist.
|
|
///
|
|
/// Because: if the date passes other invoices on the way, the accumulator
|
|
/// will be discarded and a new count will be started.
|
|
pub(crate) fn session_iter(&self, date: &Date) -> Vec<&Session> {
|
|
self.inner
|
|
.iter()
|
|
.fold((false, vec![]), |(mut done, mut acc), entry| {
|
|
match (done, entry) {
|
|
// Put sessions into the accumulator
|
|
(false, Entry::Session(ref s)) => acc.push(s),
|
|
// When we reach the target invoice, terminate the iterator
|
|
(false, Entry::Invoice(ref i)) if &i.date == date => done = true,
|
|
// When we hit another invoice, empty accumulator
|
|
(false, Entry::Invoice(_)) => acc.clear(),
|
|
// When we are ever "done", skip all other entries
|
|
(true, _) => {}
|
|
}
|
|
|
|
(done, acc)
|
|
})
|
|
.1
|
|
}
|
|
|
|
/// Start a new session, if no active session is already in progress
|
|
pub(crate) fn start(&mut self, time: Time) -> UserResult<Delta> {
|
|
match self.last_session() {
|
|
Some(s) if !s.finished() => Err(UserError::ActiveSessionExists),
|
|
_ => Ok(()),
|
|
}?;
|
|
|
|
self.inner.push(Session::start(time.clone()).into());
|
|
Ok(Delta::Start(time))
|
|
}
|
|
|
|
/// Stop an ongoing session, if one exists
|
|
pub(crate) fn stop(&mut self, time: Time) -> UserResult<Delta> {
|
|
match self.last_session() {
|
|
Some(s) if s.finished() => Err(UserError::NoActiveSession),
|
|
_ => Ok(()),
|
|
}?;
|
|
|
|
self.last_session().unwrap().stop(time.clone());
|
|
Ok(Delta::Stop(time))
|
|
}
|
|
|
|
/// Create a new invoice on the given day
|
|
pub(crate) fn invoice(&mut self, date: Date) -> UserResult<Delta> {
|
|
match self.last_invoice() {
|
|
// If an invoice on the same day exists already
|
|
Some(i) if i.date == date => Err(UserError::SameDayInvoice),
|
|
// If there was no work since the last invoice
|
|
Some(ref i) if self.session_iter(&i.date).len() == 0 => Err(UserError::NoWorkInvoice),
|
|
// Otherwise everything is coolio
|
|
_ => Ok(()),
|
|
}?;
|
|
|
|
self.inner.push(Invoice::new(date.clone()).into());
|
|
Ok(Delta::Invoice(date))
|
|
}
|
|
}
|
|
|