grid_tariffs/
taxes.rs

1use chrono::{Local, NaiveDate};
2use serde::Serialize;
3
4use crate::{Money, minivec::MiniVec};
5
6#[derive(Debug, Clone, Copy, Serialize)]
7#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
8pub enum TaxAppliedBy {
9    KwhConsumed,
10}
11
12// NOTE: Just increate N when we need it
13type TaxesInner = MiniVec<&'static Tax, 2>;
14
15#[derive(Debug, Clone, Copy, Serialize)]
16#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
17pub struct Taxes(TaxesInner);
18
19pub(crate) struct TaxesBuilder {
20    t: TaxesInner,
21}
22
23impl TaxesBuilder {
24    pub(crate) const fn new() -> Self {
25        Self {
26            t: TaxesInner::new(),
27        }
28    }
29
30    pub(crate) const fn add(mut self, t: &'static Tax) -> Self {
31        self.t.push(t);
32        self
33    }
34
35    pub(crate) const fn build(self) -> Taxes {
36        Taxes(self.t)
37    }
38}
39
40impl Taxes {
41    /// Get taxes that are current for the given date
42    pub fn for_date(&self, today: NaiveDate) -> Vec<Tax> {
43        self.0
44            .iter()
45            .filter(|tax| tax.valid_for(today))
46            .copied()
47            .collect()
48    }
49
50    /// Tax total for the given date
51    pub fn kwh_total(&self, today: NaiveDate) -> Money {
52        self.for_date(today)
53            .into_iter()
54            .filter(|tax| tax.is_kwh_based())
55            .map(|tax| tax.amount())
56            .sum()
57    }
58
59    pub(crate) fn with_current_only(mut self) -> Taxes {
60        let today = Local::now().date_naive();
61        self.0 = self.0.iter().filter(|tax| tax.valid_for(today)).collect();
62        self
63    }
64}
65
66#[derive(Debug, Clone, Copy, Serialize)]
67#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
68pub struct Tax {
69    description: &'static str,
70    amount: Money,
71    applied_by: TaxAppliedBy,
72    from_date: NaiveDate,
73    /// Last date when this tax applies
74    to_date: Option<NaiveDate>,
75}
76
77impl Tax {
78    pub(crate) const fn new(
79        description: &'static str,
80        amount: Money,
81        applied_by: TaxAppliedBy,
82        from_date: NaiveDate,
83        to_date: Option<NaiveDate>,
84    ) -> Self {
85        Self {
86            description,
87            amount,
88            applied_by,
89            from_date,
90            to_date,
91        }
92    }
93
94    pub fn description(&self) -> &'static str {
95        self.description
96    }
97
98    pub fn amount(&self) -> Money {
99        self.amount
100    }
101
102    pub(crate) fn valid_for(&self, today: NaiveDate) -> bool {
103        today >= self.from_date && self.to_date.is_none_or(|to_date| today <= to_date)
104    }
105
106    pub(crate) fn is_kwh_based(&self) -> bool {
107        matches!(self.applied_by, TaxAppliedBy::KwhConsumed)
108    }
109
110    /// Reduce the tax by the given amount
111    pub(crate) const fn reduce_by(mut self, reduce_amount: Money) -> Self {
112        self.amount = self.amount.reduce_by(reduce_amount);
113        self
114    }
115}