openfare_lib/lock/plan/
mod.rs

1use anyhow::{format_err, Result};
2
3pub mod conditions;
4
5use super::payee;
6
7pub type Id = String;
8pub type Plans = std::collections::BTreeMap<Id, Plan>;
9pub type Shares = std::collections::BTreeMap<payee::Label, u64>;
10pub type SplitPercent = String;
11
12pub fn next_id(plans: &Plans) -> Result<Id> {
13    let mut ids = plans.iter().map(|(id, _plan)| id).collect::<Vec<_>>();
14    ids.sort();
15    for (count, id) in ids.iter().enumerate() {
16        let id = id.parse::<usize>().expect("parse plan ID as number");
17        if count < id {
18            return Ok(count.to_string());
19        }
20    }
21    Ok(ids.len().to_string())
22}
23
24#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum PlanType {
27    Compulsory,
28    Voluntary,
29}
30
31impl std::str::FromStr for PlanType {
32    type Err = anyhow::Error;
33    fn from_str(value: &str) -> std::result::Result<Self, anyhow::Error> {
34        Ok(match value {
35            "compulsory" => PlanType::Compulsory,
36            "voluntary" => PlanType::Voluntary,
37            _ => {
38                return Err(format_err!(
39                    "Unsupported plan type: {}. Supported values: [compulsory|voluntary].",
40                    value
41                ));
42            }
43        })
44    }
45}
46
47#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
48pub struct Plan {
49    pub r#type: PlanType,
50    pub conditions: conditions::Conditions,
51    pub payments: Payments,
52}
53
54impl Plan {
55    pub fn is_applicable(
56        &self,
57        parameters: &crate::lock::plan::conditions::Parameters,
58    ) -> Result<bool> {
59        Ok(match self.r#type {
60            PlanType::Voluntary => {
61                // Voluntary plans are subject to conditions.
62                parameters.include_voluntary_plans && self.conditions.evaluate(&parameters)?
63            }
64            PlanType::Compulsory => {
65                // Non-commercial not subject to compulsory plans.
66                if !parameters.commercial {
67                    false
68                } else {
69                    // Commercial are subject to compulsory plans based on conditions.
70                    self.conditions.evaluate(&parameters)?
71                }
72            }
73        })
74    }
75}
76
77/// Filter for applicable plans.
78pub fn filter_applicable(
79    plans: &Plans,
80    parameters: &crate::lock::plan::conditions::Parameters,
81) -> Result<Plans> {
82    // TODO: Return None if no applicable plans found.
83    let mut applicable_plans = Plans::new();
84    for (plan_id, plan) in plans {
85        if plan.is_applicable(&parameters)? {
86            applicable_plans.insert(plan_id.clone(), plan.clone());
87        }
88    }
89    Ok(applicable_plans)
90}
91
92#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
93pub struct Payments {
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub total: Option<crate::price::Price>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub shares: Option<Shares>,
98}