use super::expenses_for_periods::ExpensesForPeriods;
use crate::prelude::*;
#[derive(Clone, Debug, Serialize, PartialEq, Deserialize, Getters)]
pub struct ExpensedPeriods<Period: IsPeriod> {
explanation: String,
#[getset(get = "pub")]
expenses_for_periods: IndexMap<Period, ExpensesForPeriods>,
}
impl<Period: IsPeriod + HasSample> HasSample for ExpensedPeriods<Period> {
fn sample() -> Self {
Self::new(IndexMap::from_iter([(
Period::sample(),
vec![Item::sample_expense_breakfast()],
)]))
}
fn sample_other() -> Self {
Self::new(IndexMap::from_iter([(
Period::sample_other(),
vec![Item::sample_expense_coffee()],
)]))
}
}
impl<Period: IsPeriod> Default for ExpensedPeriods<Period> {
fn default() -> Self {
Self::new(IndexMap::default())
}
}
impl<Period: IsPeriod> ExpensedPeriods<Period> {
pub fn new(expenses_for_periods: IndexMap<Period, Vec<Item>>) -> Self {
Self {
explanation: "Expenses for periods".to_string(),
expenses_for_periods: expenses_for_periods
.into_iter()
.map(|(month, items)| (month, ExpensesForPeriods::new(items)))
.collect(),
}
}
pub fn contains(&self, period: &Period) -> bool {
self.expenses_for_periods.contains_key(period)
}
pub fn get(&self, target_period: &Period) -> Result<Vec<Item>> {
if let Some(items) = self.expenses_for_periods().get(target_period) {
Ok(items.items())
} else {
Err(Error::TargetPeriodMustHaveExpenses {
target_period: format!("{:?}", target_period),
})
}
}
pub fn insert_expenses(&mut self, period: &Period, items: Vec<Item>) {
match self.expenses_for_periods.entry(period.clone()) {
indexmap::map::Entry::Occupied(mut entry) => {
entry.get_mut().insert(items);
}
indexmap::map::Entry::Vacant(entry) => {
entry.insert(ExpensesForPeriods::new(items));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
type Sut = ExpensedPeriods<YearAndMonth>;
#[test]
fn equality() {
assert_eq!(Sut::sample(), Sut::sample());
assert_eq!(Sut::sample_other(), Sut::sample_other());
}
#[test]
fn inequality() {
assert_ne!(Sut::sample(), Sut::sample_other());
}
#[test]
fn test_expensed_months_contains() {
let month = YearAndMonth::january(2024);
let expenses = ExpensedPeriods::new(IndexMap::from_iter([(
month,
vec![Item::sample_expense_coffee()],
)]));
assert!(expenses.contains(&month));
assert!(!expenses.contains(&YearAndMonth::february(2024)));
}
#[test]
fn test_get_not_found() {
let expenses = ExpensedPeriods::sample();
let target_month = YearAndMonth::january(1970);
let result = expenses.get(&target_month);
assert!(result.is_err());
}
#[test]
fn test_get() {
let target_month = YearAndMonth::january(2024);
let expenses_for_months = IndexMap::from_iter([(
YearAndMonth::january(2024),
vec![Item::sample_expense_breakfast()],
)]);
let expensed_months = ExpensedPeriods::new(expenses_for_months);
let expenses = expensed_months.get(&target_month);
assert_eq!(expenses.unwrap().len(), 1); }
#[test]
fn test_insert_expenses_different() {
let mut expensed_months = ExpensedPeriods::new(IndexMap::new());
let month = YearAndMonth::january(2024);
let items = vec![Item::sample_expense_coffee()];
expensed_months.insert_expenses(&month, items.clone());
assert!(expensed_months.contains(&month));
let retrieved_items = expensed_months.get(&month).unwrap();
assert_eq!(retrieved_items.len(), 1);
}
#[test]
fn test_insert_expenses_same_except_quantity() {
let mut expensed_months = ExpensedPeriods::new(IndexMap::new());
let month = YearAndMonth::january(2024);
let item1 = Item::builder()
.name("Coffee".into())
.unit_price(UnitPrice::from(dec!(2.5)))
.currency(Currency::EUR)
.quantity(Quantity::from(dec!(3.0)))
.transaction_date(Date::from_str("2024-01-01").unwrap())
.build();
let item2 = Item::builder()
.name("Coffee".into())
.unit_price(UnitPrice::from(dec!(2.5)))
.currency(Currency::EUR)
.quantity(Quantity::from(dec!(4.0)))
.transaction_date(Date::from_str("2024-01-01").unwrap())
.build();
expensed_months.insert_expenses(&month, vec![item1.clone(), item2.clone()]);
assert!(expensed_months.contains(&month));
let retrieved_items = expensed_months.get(&month).unwrap();
assert_eq!(retrieved_items.len(), 1);
assert_eq!(*retrieved_items[0].quantity(), Quantity::from(dec!(7.0))); }
#[test]
fn test_insert_expenses_same_except_quantity_added_in_two_batches() {
let mut expensed_months = ExpensedPeriods::new(IndexMap::new());
let month = YearAndMonth::january(2024);
let item1 = Item::builder()
.name("Coffee".into())
.unit_price(UnitPrice::from(dec!(2.5)))
.currency(Currency::EUR)
.quantity(Quantity::from(dec!(3.0)))
.transaction_date(Date::from_str("2024-01-01").unwrap())
.build();
let item2 = Item::builder()
.name("Coffee".into())
.unit_price(UnitPrice::from(dec!(2.5)))
.currency(Currency::EUR)
.quantity(Quantity::from(dec!(4.0)))
.transaction_date(Date::from_str("2024-01-01").unwrap())
.build();
expensed_months.insert_expenses(&month, vec![item1.clone()]);
expensed_months.insert_expenses(&month, vec![item2.clone()]);
assert!(expensed_months.contains(&month));
let retrieved_items = expensed_months.get(&month).unwrap();
assert_eq!(retrieved_items.len(), 1);
assert_eq!(*retrieved_items[0].quantity(), Quantity::from(dec!(7.0))); }
#[test]
fn default_is_empty() {
let expensed_periods = ExpensedPeriods::<YearAndMonth>::default();
assert!(expensed_periods.expenses_for_periods.is_empty());
}
}