koprogo_api/domain/copropriete/
releve_notaire.rs1use chrono::{DateTime, Duration, Utc};
28use uuid::Uuid;
29
30pub const DELAI_JOURS: i64 = 30;
35
36#[derive(Debug, Clone, PartialEq)]
38pub struct DemandeDeReleve {
39 pub id: Uuid,
40 pub notaire: String,
41 pub unit_id: Uuid,
43 pub recue_le: DateTime<Utc>,
45 pub echeance: DateTime<Utc>,
46 pub fournie_le: Option<DateTime<Utc>>,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum EtatDemande {
53 HonoreeATemps,
55 HonoreeEnRetard,
57 EnCours,
59 EnDefaut,
61}
62
63impl DemandeDeReleve {
64 pub fn nouvelle(notaire: String, unit_id: Uuid, recue_le: DateTime<Utc>) -> Self {
65 Self {
66 id: Uuid::new_v4(),
67 notaire,
68 unit_id,
69 recue_le,
70 echeance: recue_le + Duration::days(DELAI_JOURS),
71 fournie_le: None,
72 }
73 }
74
75 pub fn fournir(&mut self, le: DateTime<Utc>) {
76 self.fournie_le = Some(le);
77 }
78
79 pub fn etat(&self, moment: DateTime<Utc>) -> EtatDemande {
80 match self.fournie_le {
81 Some(fournie) if fournie <= self.echeance => EtatDemande::HonoreeATemps,
82 Some(_) => EtatDemande::HonoreeEnRetard,
83 None if moment <= self.echeance => EtatDemande::EnCours,
84 None => EtatDemande::EnDefaut,
85 }
86 }
87
88 pub fn jours_restants(&self, moment: DateTime<Utc>) -> i64 {
93 (self.echeance - moment).num_days()
94 }
95}
96
97pub fn a_relancer(
102 demandes: &[DemandeDeReleve],
103 moment: DateTime<Utc>,
104 seuil_alerte_jours: i64,
105) -> Vec<&DemandeDeReleve> {
106 let mut urgentes: Vec<&DemandeDeReleve> = demandes
107 .iter()
108 .filter(|d| {
109 matches!(d.etat(moment), EtatDemande::EnCours | EtatDemande::EnDefaut)
110 && d.jours_restants(moment) <= seuil_alerte_jours
111 })
112 .collect();
113 urgentes.sort_by_key(|d| d.echeance);
114 urgentes
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 fn il_y_a(jours: i64) -> DateTime<Utc> {
122 Utc::now() - Duration::days(jours)
123 }
124
125 fn demande(recue_il_y_a: i64) -> DemandeDeReleve {
126 DemandeDeReleve::nouvelle(
127 "Me Dupont".to_string(),
128 Uuid::new_v4(),
129 il_y_a(recue_il_y_a),
130 )
131 }
132
133 #[test]
134 fn happy_lecheance_tombe_trente_jours_apres_la_demande() {
135 let d = demande(0);
136 assert_eq!(d.echeance, d.recue_le + Duration::days(30));
137 }
138
139 #[test]
140 fn happy_un_releve_fourni_dans_les_temps_est_honore() {
141 let mut d = demande(20);
142 d.fournir(il_y_a(5));
143 assert_eq!(d.etat(Utc::now()), EtatDemande::HonoreeATemps);
144 }
145
146 #[test]
147 fn happy_avant_lecheance_la_demande_est_simplement_en_cours() {
148 assert_eq!(demande(10).etat(Utc::now()), EtatDemande::EnCours);
149 }
150
151 #[test]
153 fn negative_passe_trente_jours_sans_releve_le_syndic_est_en_defaut() {
154 assert_eq!(demande(40).etat(Utc::now()), EtatDemande::EnDefaut);
155 }
156
157 #[test]
159 fn edge_le_jour_de_lecheance_nest_pas_encore_un_defaut() {
160 let d = demande(30);
161 assert_eq!(d.etat(d.echeance), EtatDemande::EnCours);
162 assert_eq!(
163 d.etat(d.echeance + Duration::seconds(1)),
164 EtatDemande::EnDefaut
165 );
166 }
167
168 #[test]
173 fn negative_un_releve_fourni_en_retard_reste_marque_comme_tel() {
174 let mut d = demande(40);
175 d.fournir(il_y_a(2));
176 assert_eq!(d.etat(Utc::now()), EtatDemande::HonoreeEnRetard);
177 }
178
179 #[test]
180 fn happy_le_compte_a_rebours_previent_avant_lecheance() {
181 let d = demande(25);
186 let vingt_cinq_jours_apres = d.recue_le + Duration::days(25);
187 assert_eq!(d.jours_restants(vingt_cinq_jours_apres), 5);
188 }
189
190 #[test]
191 fn happy_il_devient_negatif_une_fois_lecheance_passee() {
192 assert!(demande(40).jours_restants(Utc::now()) < 0);
193 }
194
195 #[test]
197 fn happy_les_demandes_urgentes_remontent_triees_par_echeance() {
198 let demandes = vec![demande(28), demande(40), demande(5), demande(26)];
199 let urgentes = a_relancer(&demandes, Utc::now(), 7);
200
201 assert_eq!(
202 urgentes.len(),
203 3,
204 "celle reçue il y a 5 jours n'est pas urgente"
205 );
206 assert!(
207 urgentes[0].echeance <= urgentes[1].echeance,
208 "la plus pressée en premier"
209 );
210 assert!(urgentes[0].echeance <= urgentes[2].echeance);
211 }
212
213 #[test]
214 fn happy_une_demande_deja_honoree_ne_se_relance_pas() {
215 let mut honoree = demande(28);
216 honoree.fournir(il_y_a(1));
217 assert!(a_relancer(&[honoree], Utc::now(), 7).is_empty());
218 }
219}