koprogo_api/domain/copropriete/decompte_des_lots.rs
1//! Le décompte légal des lots.
2//!
3//! Deux articles emploient **la même définition**, et il n'y en a qu'une :
4//!
5//! > Art. 3.89 § 5, 15° — « Toute copropriété **de moins de vingt lots à
6//! > l'exclusion des caves, des garages et parkings** est autorisée à tenir
7//! > une comptabilité simplifiée. »
8//!
9//! > Art. 3.90 § 1er — « Dans tout immeuble ou groupe d'immeubles **d'au moins
10//! > vingt lots à l'exclusion des caves, garages et parkings**, un conseil de
11//! > copropriété est constitué. »
12//!
13//! Le décompte vit ici, dans le contexte `copropriete`, parce qu'un lot est
14//! une notion de l'acte de base (Art. 3.85 § 1er) et non une notion comptable.
15//! La comptabilité **dérive** son régime de ce décompte ; l'inverse n'aurait
16//! pas de sens, et le test d'architecture l'interdit — c'est d'ailleurs lui qui
17//! a signalé que ce code s'était d'abord retrouvé du mauvais côté de la
18//! frontière.
19//!
20//! **Les bornes s'opposent, et c'est voulu** : « de moins de vingt » pour la
21//! comptabilité simplifiée, « d'au moins vingt » pour le conseil. Elles se
22//! complètent exactement, et les inverser ferait basculer une copropriété de
23//! vingt lots du mauvais côté des deux.
24//!
25//! Le piège commun aux deux : **ce n'est pas le nombre de lots de l'acte de
26//! base**. Un immeuble de quinze appartements avec quinze caves et vingt
27//! parkings compte cinquante lots à l'acte et **quinze** au sens de ces
28//! articles.
29
30use super::unit::UnitType;
31
32/// Le seuil légal, commun aux deux articles.
33pub const SEUIL_LEGAL: usize = 20;
34
35/// Un lot compte-t-il dans le décompte légal ?
36///
37/// Les articles excluent « les caves, les garages et parkings ». `Other` est
38/// compté : le doute profite à l'obligation la plus stricte, puisque se tromper
39/// dans ce sens fait tenir une comptabilité plus détaillée que nécessaire ou
40/// constituer un conseil qu'on aurait pu ne pas constituer, tandis que
41/// l'erreur inverse met l'ACP en défaut.
42pub fn compte_dans_le_seuil(nature: UnitType) -> bool {
43 !matches!(nature, UnitType::Cellar | UnitType::Parking)
44}
45
46/// Le nombre de lots au sens des Art. 3.89 § 5, 15° et 3.90 § 1er.
47pub fn lots_comptes(natures: &[UnitType]) -> usize {
48 natures
49 .iter()
50 .copied()
51 .filter(|n| compte_dans_le_seuil(*n))
52 .count()
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 fn lots(appartements: usize, caves: usize, parkings: usize) -> Vec<UnitType> {
60 let mut v = vec![UnitType::Apartment; appartements];
61 v.extend(vec![UnitType::Cellar; caves]);
62 v.extend(vec![UnitType::Parking; parkings]);
63 v
64 }
65
66 /// Le piège, avec ses chiffres.
67 #[test]
68 fn happy_caves_et_parkings_sortent_du_decompte() {
69 let natures = lots(15, 15, 20);
70 assert_eq!(natures.len(), 50, "cinquante lots à l'acte de base");
71 assert_eq!(lots_comptes(&natures), 15, "quinze au sens de la loi");
72 }
73
74 #[test]
75 fn happy_les_commerces_comptent() {
76 let mut natures = lots(18, 0, 0);
77 natures.extend(vec![UnitType::Commercial; 2]);
78 assert_eq!(lots_comptes(&natures), 20);
79 }
80
81 /// @security — un lot de nature indéterminée compte.
82 #[test]
83 fn security_un_lot_de_nature_indeterminee_compte() {
84 assert!(compte_dans_le_seuil(UnitType::Other));
85 }
86
87 #[test]
88 fn negative_une_acp_sans_lot_compte_zero() {
89 assert_eq!(lots_comptes(&[]), 0);
90 }
91}