koprogo_api/domain/copropriete/
solidarite.rs1use super::unit_owner::OwnershipType;
29use rust_decimal::Decimal;
30use uuid::Uuid;
31
32#[derive(Debug, Clone, PartialEq)]
34pub struct Titulaire {
35 pub owner_id: Uuid,
36 pub nature: OwnershipType,
37 pub quote_part: Decimal,
39}
40
41#[derive(Debug, Clone, PartialEq)]
43pub struct Obligation {
44 pub owner_id: Uuid,
45 pub montant: Decimal,
47 pub solidaire: bool,
54}
55
56fn greve_dusufruit(titulaires: &[Titulaire]) -> bool {
62 titulaires
63 .iter()
64 .any(|t| t.nature == OwnershipType::Usufruct)
65}
66
67pub fn repartir_charge(titulaires: &[Titulaire], montant: Decimal) -> Vec<Obligation> {
73 if titulaires.is_empty() {
74 return Vec::new();
75 }
76
77 if greve_dusufruit(titulaires) {
78 return titulaires
79 .iter()
80 .map(|t| Obligation {
81 owner_id: t.owner_id,
82 montant,
83 solidaire: true,
84 })
85 .collect();
86 }
87
88 titulaires
89 .iter()
90 .map(|t| Obligation {
91 owner_id: t.owner_id,
92 montant: (montant * t.quote_part).round_dp(2),
93 solidaire: false,
94 })
95 .collect()
96}
97
98pub fn poursuivables_pour_le_tout(titulaires: &[Titulaire]) -> Vec<Uuid> {
103 if !greve_dusufruit(titulaires) {
104 return Vec::new();
105 }
106 titulaires.iter().map(|t| t.owner_id).collect()
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use rust_decimal_macros::dec;
113
114 fn titulaire(nature: OwnershipType, quote_part: Decimal) -> Titulaire {
115 Titulaire {
116 owner_id: Uuid::new_v4(),
117 nature,
118 quote_part,
119 }
120 }
121
122 #[test]
123 fn happy_un_plein_proprietaire_doit_tout_sans_solidarite() {
124 let titulaires = vec![titulaire(OwnershipType::FullOwner, dec!(1))];
125 let obligations = repartir_charge(&titulaires, dec!(1200));
126
127 assert_eq!(obligations.len(), 1);
128 assert_eq!(obligations[0].montant, dec!(1200));
129 assert!(
130 !obligations[0].solidaire,
131 "seul, il n'y a personne avec qui être solidaire"
132 );
133 }
134
135 #[test]
136 fn happy_lusufruit_rend_les_deux_titulaires_tenus_du_tout() {
137 let titulaires = vec![
138 titulaire(OwnershipType::Usufruct, dec!(0.4)),
139 titulaire(OwnershipType::BareOwner, dec!(0.6)),
140 ];
141 let obligations = repartir_charge(&titulaires, dec!(1200));
142
143 assert_eq!(obligations.len(), 2);
144 for obligation in &obligations {
145 assert_eq!(
146 obligation.montant,
147 dec!(1200),
148 "chacun doit LE TOUT : les obligations solidaires ne s'additionnent pas"
149 );
150 assert!(obligation.solidaire);
151 }
152 }
153
154 #[test]
155 fn happy_lindivision_ordinaire_repartit_au_prorata() {
156 let titulaires = vec![
157 titulaire(OwnershipType::Indivisaire, dec!(0.5)),
158 titulaire(OwnershipType::Indivisaire, dec!(0.5)),
159 ];
160 let obligations = repartir_charge(&titulaires, dec!(1200));
161
162 for obligation in &obligations {
163 assert_eq!(obligation.montant, dec!(600));
164 assert!(
165 !obligation.solidaire,
166 "l'Art. 3.86 § 3 al. 8 ne vise que l'usufruit : réclamer le tout \
167 à un indivisaire lui ferait payer ce qu'il ne doit pas"
168 );
169 }
170 }
171
172 #[test]
173 fn edge_un_nu_proprietaire_seul_nest_pas_solidaire() {
174 let titulaires = vec![titulaire(OwnershipType::BareOwner, dec!(1))];
177 let obligations = repartir_charge(&titulaires, dec!(1200));
178
179 assert!(!obligations[0].solidaire);
180 assert!(poursuivables_pour_le_tout(&titulaires).is_empty());
181 }
182
183 #[test]
184 fn happy_le_syndic_choisit_qui_poursuivre_en_cas_dusufruit() {
185 let usufruitier = titulaire(OwnershipType::Usufruct, dec!(0.4));
188 let nu_proprietaire = titulaire(OwnershipType::BareOwner, dec!(0.6));
189 let attendus = vec![usufruitier.owner_id, nu_proprietaire.owner_id];
190
191 let poursuivables = poursuivables_pour_le_tout(&[usufruitier, nu_proprietaire]);
192
193 assert_eq!(poursuivables, attendus);
194 }
195
196 #[test]
197 fn negative_aucun_titulaire_ne_produit_aucune_obligation() {
198 assert!(repartir_charge(&[], dec!(1200)).is_empty());
199 }
200
201 #[test]
202 fn edge_larrondi_de_la_repartition_reste_au_centime() {
203 let tiers = dec!(0.333333);
206 let titulaires = vec![
207 titulaire(OwnershipType::Indivisaire, tiers),
208 titulaire(OwnershipType::Indivisaire, tiers),
209 titulaire(OwnershipType::Indivisaire, tiers),
210 ];
211 let obligations = repartir_charge(&titulaires, dec!(1000));
212
213 for obligation in &obligations {
214 assert_eq!(obligation.montant, dec!(333.33));
215 }
216 }
217}