jobber 0.1.0-alpha

Minimalistic console work time tracker
use crate::entities::{Bill, DateTime, Duration, Money, Result};
use derive_more::{Deref, DerefMut};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};

#[derive(Debug, Deref, DerefMut, Clone, Serialize, Deserialize)]
pub(crate) struct Invoice {
    pub(crate) client: String,
    pub(crate) subject: Option<String>,
    #[deref]
    #[deref_mut]
    pub(crate) bills: Vec<Bill>,
    pub(crate) date: DateTime,
    pub(crate) deleted: bool,
}

impl Invoice {
    pub(crate) fn hours(&self) -> f64 {
        self.bills
            .iter()
            .map(|b| b.worked())
            .sum::<Duration>()
            .num_minutes() as f64
            / 60.0
    }

    pub(crate) fn worked(&self) -> Duration {
        self.bills.iter().map(|b| b.worked()).sum()
    }

    pub(crate) fn revenue(&self) -> Result<Money> {
        let revenues = self.revenues()?;
        if let Some(first) = &revenues.first() {
            let init = Money::zero(first.currency());
            Ok(revenues.into_iter().try_fold(init, |acc, x| acc.add(x))?)
        } else {
            todo!("error: no revenues")
        }
    }

    pub(crate) fn revenues(&self) -> Result<Vec<Money>> {
        self.bills
            .iter()
            .map(|b| {
                if b.scheme.rate.is_none() {
                    todo!("error: no payment rate in invoice")
                }
                b.revenue()
            })
            .collect::<Result<_>>()
    }

    pub(crate) fn tax(&self) -> Result<Money> {
        let tax = Decimal::from_f64_retain(0.19).unwrap();
        self.revenue()?.mul(tax)
    }

    pub(crate) fn revenue_taxed(&self) -> Result<Money> {
        let taxed = Decimal::from_f64_retain(1.19).unwrap();
        self.revenue()?.mul(taxed)
    }
}