koprogo_api/domain/copropriete/
comptes_de_lacp.rs1use uuid::Uuid;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum AffectationDuCompte {
36 FondsDeRoulement,
38 FondsDeReserve,
40 Autre,
42}
43
44#[derive(Debug, Clone, PartialEq)]
46pub struct CompteBancaire {
47 pub id: Uuid,
48 pub iban: String,
49 pub affectation: AffectationDuCompte,
50 pub titulaire_acp_id: Option<Uuid>,
55}
56
57impl CompteBancaire {
58 pub fn au_nom_de(&self, acp_id: Uuid) -> bool {
60 self.titulaire_acp_id == Some(acp_id)
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum ManquementBancaire {
67 FondsDeRoulementSansCompte,
69 FondsDeReserveSansCompte,
71 FondsConfondusSurUnSeulCompte { iban: String },
76 CompteHorsDuNomDeLacp {
78 iban: String,
79 affectation: AffectationDuCompte,
80 },
81}
82
83impl std::fmt::Display for ManquementBancaire {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 match self {
86 Self::FondsDeRoulementSansCompte => write!(
87 f,
88 "Art. 3.86 § 3 : aucun compte distinct pour le fonds de roulement."
89 ),
90 Self::FondsDeReserveSansCompte => write!(
91 f,
92 "Art. 3.86 § 3 : aucun compte distinct pour le fonds de réserve."
93 ),
94 Self::FondsConfondusSurUnSeulCompte { iban } => write!(
95 f,
96 "Art. 3.86 § 3 : le fonds de roulement et le fonds de réserve partagent le \
97 compte {iban}. Les comptes doivent être distincts — sinon rien n'empêche \
98 de puiser dans la réserve pour payer les charges courantes."
99 ),
100 Self::CompteHorsDuNomDeLacp { iban, affectation } => write!(
101 f,
102 "Art. 3.86 § 3 : le compte {iban} ({affectation:?}) n'est pas ouvert au nom \
103 de l'association des copropriétaires."
104 ),
105 }
106 }
107}
108
109pub fn verifier(comptes: &[CompteBancaire], acp_id: Uuid) -> Vec<ManquementBancaire> {
114 let mut manquements = Vec::new();
115
116 let roulement: Vec<&CompteBancaire> = comptes
117 .iter()
118 .filter(|c| c.affectation == AffectationDuCompte::FondsDeRoulement)
119 .collect();
120 let reserve: Vec<&CompteBancaire> = comptes
121 .iter()
122 .filter(|c| c.affectation == AffectationDuCompte::FondsDeReserve)
123 .collect();
124
125 if roulement.is_empty() {
126 manquements.push(ManquementBancaire::FondsDeRoulementSansCompte);
127 }
128 if reserve.is_empty() {
129 manquements.push(ManquementBancaire::FondsDeReserveSansCompte);
130 }
131
132 for compte_roulement in &roulement {
134 if reserve.iter().any(|r| r.iban == compte_roulement.iban) {
135 manquements.push(ManquementBancaire::FondsConfondusSurUnSeulCompte {
136 iban: compte_roulement.iban.clone(),
137 });
138 }
139 }
140
141 for compte in comptes {
142 if !compte.au_nom_de(acp_id) {
143 manquements.push(ManquementBancaire::CompteHorsDuNomDeLacp {
144 iban: compte.iban.clone(),
145 affectation: compte.affectation,
146 });
147 }
148 }
149
150 manquements
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 fn compte(iban: &str, affectation: AffectationDuCompte, acp: Option<Uuid>) -> CompteBancaire {
158 CompteBancaire {
159 id: Uuid::new_v4(),
160 iban: iban.to_string(),
161 affectation,
162 titulaire_acp_id: acp,
163 }
164 }
165
166 #[test]
167 fn happy_deux_comptes_distincts_au_nom_de_lacp_sont_en_regle() {
168 let acp = Uuid::new_v4();
169 let comptes = vec![
170 compte(
171 "BE68 5390 0754 7034",
172 AffectationDuCompte::FondsDeRoulement,
173 Some(acp),
174 ),
175 compte(
176 "BE71 0961 2345 6769",
177 AffectationDuCompte::FondsDeReserve,
178 Some(acp),
179 ),
180 ];
181 assert!(verifier(&comptes, acp).is_empty());
182 }
183
184 #[test]
187 fn happy_un_troisieme_compte_nest_pas_une_irregularite() {
188 let acp = Uuid::new_v4();
189 let comptes = vec![
190 compte(
191 "BE68 5390 0754 7034",
192 AffectationDuCompte::FondsDeRoulement,
193 Some(acp),
194 ),
195 compte(
196 "BE71 0961 2345 6769",
197 AffectationDuCompte::FondsDeReserve,
198 Some(acp),
199 ),
200 compte("BE62 5100 0754 7061", AffectationDuCompte::Autre, Some(acp)),
201 ];
202 assert!(verifier(&comptes, acp).is_empty());
203 }
204
205 #[test]
210 fn security_un_compte_unique_pour_les_deux_fonds_est_signale() {
211 let acp = Uuid::new_v4();
212 let comptes = vec![
213 compte(
214 "BE68 5390 0754 7034",
215 AffectationDuCompte::FondsDeRoulement,
216 Some(acp),
217 ),
218 compte(
219 "BE68 5390 0754 7034",
220 AffectationDuCompte::FondsDeReserve,
221 Some(acp),
222 ),
223 ];
224 let manquements = verifier(&comptes, acp);
225 assert_eq!(
226 manquements,
227 vec![ManquementBancaire::FondsConfondusSurUnSeulCompte {
228 iban: "BE68 5390 0754 7034".to_string()
229 }]
230 );
231 }
232
233 #[test]
238 fn security_un_compte_au_nom_du_syndic_est_signale() {
239 let acp = Uuid::new_v4();
240 let comptes = vec![
241 compte(
242 "BE68 5390 0754 7034",
243 AffectationDuCompte::FondsDeRoulement,
244 None,
245 ),
246 compte(
247 "BE71 0961 2345 6769",
248 AffectationDuCompte::FondsDeReserve,
249 Some(acp),
250 ),
251 ];
252 let manquements = verifier(&comptes, acp);
253 assert_eq!(manquements.len(), 1);
254 assert!(matches!(
255 manquements[0],
256 ManquementBancaire::CompteHorsDuNomDeLacp { .. }
257 ));
258 }
259
260 #[test]
262 fn security_un_compte_au_nom_dune_autre_acp_est_signale() {
263 let acp = Uuid::new_v4();
264 let voisine = Uuid::new_v4();
265 let comptes = vec![
266 compte(
267 "BE68 5390 0754 7034",
268 AffectationDuCompte::FondsDeRoulement,
269 Some(voisine),
270 ),
271 compte(
272 "BE71 0961 2345 6769",
273 AffectationDuCompte::FondsDeReserve,
274 Some(acp),
275 ),
276 ];
277 assert_eq!(verifier(&comptes, acp).len(), 1);
278 }
279
280 #[test]
281 fn negative_une_acp_sans_compte_cumule_les_deux_manquements() {
282 let manquements = verifier(&[], Uuid::new_v4());
283 assert_eq!(
284 manquements,
285 vec![
286 ManquementBancaire::FondsDeRoulementSansCompte,
287 ManquementBancaire::FondsDeReserveSansCompte
288 ]
289 );
290 }
291
292 #[test]
297 fn happy_tous_les_manquements_remontent_ensemble() {
298 let acp = Uuid::new_v4();
299 let comptes = vec![compte(
300 "BE68 5390 0754 7034",
301 AffectationDuCompte::FondsDeRoulement,
302 None,
303 )];
304 let manquements = verifier(&comptes, acp);
305
306 assert_eq!(
307 manquements.len(),
308 2,
309 "réserve absente ET compte hors du nom"
310 );
311 }
312}