koprogo_api/domain/copropriete/
commissaire_aux_comptes.rs1use uuid::Uuid;
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum DesignationRefusee {
41 LeSyndicNePeutPasControlerSesComptes,
43 CollegeVide,
45 DoublonDansLeCollege { titulaire: Uuid },
47}
48
49impl std::fmt::Display for DesignationRefusee {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 match self {
52 Self::LeSyndicNePeutPasControlerSesComptes => write!(
53 f,
54 "Art. 3.89 § 9 : le syndic ne peut pas être commissaire aux comptes de \
55 l'ACP qu'il gère."
56 ),
57 Self::CollegeVide => write!(
58 f,
59 "Art. 3.91 : l'assemblée doit désigner au moins un commissaire aux comptes."
60 ),
61 Self::DoublonDansLeCollege { titulaire } => write!(
62 f,
63 "Art. 3.91 : {titulaire} figure deux fois dans le collège."
64 ),
65 }
66 }
67}
68
69#[derive(Debug, Clone, PartialEq)]
71pub struct Commissariat {
72 pub titulaires: Vec<Uuid>,
74 pub exercice: i32,
77}
78
79impl Commissariat {
80 pub fn designer(
85 titulaires: Vec<Uuid>,
86 exercice: i32,
87 syndic_owner_id: Option<Uuid>,
88 ) -> Result<Self, DesignationRefusee> {
89 if titulaires.is_empty() {
90 return Err(DesignationRefusee::CollegeVide);
91 }
92 if let Some(syndic) = syndic_owner_id {
93 if titulaires.contains(&syndic) {
94 return Err(DesignationRefusee::LeSyndicNePeutPasControlerSesComptes);
95 }
96 }
97 for (i, titulaire) in titulaires.iter().enumerate() {
98 if titulaires[i + 1..].contains(titulaire) {
99 return Err(DesignationRefusee::DoublonDansLeCollege {
100 titulaire: *titulaire,
101 });
102 }
103 }
104 Ok(Self {
105 titulaires,
106 exercice,
107 })
108 }
109
110 pub fn est_un_college(&self) -> bool {
111 self.titulaires.len() > 1
112 }
113
114 pub fn couvre(&self, exercice: i32) -> bool {
120 self.exercice == exercice
121 }
122}
123
124pub fn commissariat_de_lexercice(
129 designations: &[Commissariat],
130 exercice: i32,
131) -> Option<&Commissariat> {
132 designations.iter().find(|d| d.couvre(exercice))
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn happy_un_commissaire_unique_est_valable() {
141 let c = Commissariat::designer(vec![Uuid::new_v4()], 2026, None).unwrap();
142 assert!(!c.est_un_college());
143 }
144
145 #[test]
147 fn happy_un_college_de_trois_est_valable() {
148 let c = Commissariat::designer(
149 vec![Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()],
150 2026,
151 None,
152 )
153 .unwrap();
154 assert!(c.est_un_college());
155 assert_eq!(c.titulaires.len(), 3);
156 }
157
158 #[test]
161 fn happy_un_expert_externe_peut_etre_designe() {
162 let externe = Uuid::new_v4();
163 let c = Commissariat::designer(vec![externe], 2026, Some(Uuid::new_v4())).unwrap();
164 assert_eq!(c.titulaires, vec![externe]);
165 }
166
167 #[test]
169 fn security_le_syndic_ne_peut_pas_controler_ses_propres_comptes() {
170 let syndic = Uuid::new_v4();
171 assert_eq!(
172 Commissariat::designer(vec![Uuid::new_v4(), syndic], 2026, Some(syndic)),
173 Err(DesignationRefusee::LeSyndicNePeutPasControlerSesComptes)
174 );
175 }
176
177 #[test]
178 fn negative_un_college_vide_est_refuse() {
179 assert_eq!(
180 Commissariat::designer(vec![], 2026, None),
181 Err(DesignationRefusee::CollegeVide)
182 );
183 }
184
185 #[test]
186 fn negative_un_doublon_dans_le_college_est_refuse() {
187 let deux_fois = Uuid::new_v4();
188 assert_eq!(
189 Commissariat::designer(vec![deux_fois, Uuid::new_v4(), deux_fois], 2026, None),
190 Err(DesignationRefusee::DoublonDansLeCollege {
191 titulaire: deux_fois
192 })
193 );
194 }
195
196 #[test]
199 fn happy_une_designation_couvre_son_exercice() {
200 let designations = vec![Commissariat::designer(vec![Uuid::new_v4()], 2026, None).unwrap()];
201 assert!(commissariat_de_lexercice(&designations, 2026).is_some());
202 }
203
204 #[test]
210 fn security_une_designation_ne_se_reconduit_pas_tacitement() {
211 let designations = vec![Commissariat::designer(vec![Uuid::new_v4()], 2026, None).unwrap()];
212 assert!(
213 commissariat_de_lexercice(&designations, 2027).is_none(),
214 "la désignation de 2026 ne couvre pas 2027"
215 );
216 }
217
218 #[test]
219 fn happy_plusieurs_exercices_se_suivent_sans_se_confondre() {
220 let designations = vec![
221 Commissariat::designer(vec![Uuid::new_v4()], 2025, None).unwrap(),
222 Commissariat::designer(vec![Uuid::new_v4()], 2026, None).unwrap(),
223 ];
224 assert_ne!(
225 commissariat_de_lexercice(&designations, 2025)
226 .unwrap()
227 .titulaires,
228 commissariat_de_lexercice(&designations, 2026)
229 .unwrap()
230 .titulaires
231 );
232 }
233}