Skip to main content

koprogo_api/domain/comptabilite/
regime_comptable.rs

1//! Le régime comptable applicable à une ACP.
2//!
3//! Art. 3.89 § 5, 15° — le syndic est chargé :
4//!
5//! > « de tenir les comptes de l'association des copropriétaires de manière
6//! > claire, précise et détaillée suivant le **plan comptable minimum
7//! > normalisé** à établir par le Roi. Toute copropriété **de moins de vingt
8//! > lots à l'exclusion des caves, des garages et parkings** est autorisée à
9//! > tenir une **comptabilité simplifiée** reflétant au minimum les recettes
10//! > et les dépenses, la situation de trésorerie ainsi que les mouvements des
11//! > disponibilités. »
12//!
13//! Le plan comptable minimum normalisé est celui de l'arrêté royal du
14//! 12 juillet 2012, implémenté depuis longtemps ici. Ce qui manquait, c'est la
15//! **question préalable** : cette ACP y est-elle seulement tenue ?
16//!
17//! Deux pièges dans le décompte, et ils vont dans le même sens — celui de
18//! surestimer le nombre de lots et d'imposer une comptabilité complète à une
19//! petite copropriété qui a le droit de faire plus simple :
20//!
21//! 1. **caves, garages et parkings sont exclus**. Ce n'est pas le nombre de
22//!    lots de l'acte de base : un immeuble de 15 appartements avec 15 caves et
23//!    20 parkings compte 15 lots au sens de cet article, pas 50 ;
24//! 2. **« moins de vingt »** est strict. Vingt lots imposent le plan complet ;
25//!    dix-neuf ne l'imposent pas.
26//!
27//! Le régime n'est jamais saisi à la main : il se **dérive** du décompte. Une
28//! copropriété qui franchit le seuil change de régime parce qu'elle a vendu un
29//! lot, pas parce qu'un syndic l'a décidé.
30//!
31//! Voir issue #746.
32
33pub use crate::domain::copropriete::decompte_des_lots::lots_comptes;
34use crate::domain::copropriete::unit::UnitType;
35
36/// Le régime comptable auquel une ACP est tenue.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum RegimeComptable {
39    /// Plan comptable minimum normalisé (AR du 12/07/2012), obligatoire dès
40    /// vingt lots.
41    Complet,
42    /// Comptabilité simplifiée : recettes, dépenses, situation de trésorerie,
43    /// mouvements des disponibilités.
44    Simplifie,
45}
46
47impl RegimeComptable {
48    /// Le seuil légal, en lots comptés au sens de l'Art. 3.89 § 5, 15°.
49    ///
50    /// Repris du décompte de `copropriete` : un lot est une notion de l'acte
51    /// de base, pas une notion comptable. Le régime **dérive** du décompte.
52    pub const SEUIL_LEGAL: usize = crate::domain::copropriete::decompte_des_lots::SEUIL_LEGAL;
53
54    pub fn est_simplifie(&self) -> bool {
55        matches!(self, Self::Simplifie)
56    }
57}
58
59impl std::fmt::Display for RegimeComptable {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        match self {
62            Self::Complet => write!(f, "plan comptable minimum normalisé (AR du 12/07/2012)"),
63            Self::Simplifie => write!(f, "comptabilité simplifiée (Art. 3.89 § 5, 15°)"),
64        }
65    }
66}
67
68/// Le décompte légal, réexporté depuis `copropriete`.
69///
70/// La règle ne vit pas ici : un lot est une notion de l'acte de base
71/// (Art. 3.85 § 1er), pas une notion comptable, et l'Art. 3.90 § 1er emploie
72/// exactement le même décompte pour le conseil de copropriété. Deux
73/// définitions du même comptage finiraient par diverger.
74///
75/// Réexporté pour que le lecteur du régime comptable trouve la règle sans
76/// changer de contexte.
77pub use crate::domain::copropriete::decompte_des_lots::compte_dans_le_seuil;
78
79/// Le régime auquel une ACP est tenue, dérivé de ses lots.
80pub fn regime_applicable(natures: &[UnitType]) -> RegimeComptable {
81    if lots_comptes(natures) < RegimeComptable::SEUIL_LEGAL {
82        RegimeComptable::Simplifie
83    } else {
84        RegimeComptable::Complet
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    fn lots(appartements: usize, caves: usize, parkings: usize, commerces: usize) -> Vec<UnitType> {
93        let mut v = vec![UnitType::Apartment; appartements];
94        v.extend(vec![UnitType::Cellar; caves]);
95        v.extend(vec![UnitType::Parking; parkings]);
96        v.extend(vec![UnitType::Commercial; commerces]);
97        v
98    }
99
100    #[test]
101    fn happy_une_petite_copropriete_peut_tenir_une_comptabilite_simplifiee() {
102        let regime = regime_applicable(&lots(12, 0, 0, 0));
103        assert_eq!(regime, RegimeComptable::Simplifie);
104    }
105
106    #[test]
107    fn happy_une_grande_copropriete_est_tenue_au_plan_normalise() {
108        let regime = regime_applicable(&lots(25, 0, 0, 0));
109        assert_eq!(regime, RegimeComptable::Complet);
110    }
111
112    /// Le piège du décompte : caves et parkings ne comptent pas.
113    ///
114    /// Cet immeuble a cinquante lots à l'acte de base et quinze au sens de
115    /// l'article. Le compter à cinquante lui imposerait une comptabilité
116    /// complète à laquelle la loi ne l'oblige pas.
117    #[test]
118    fn happy_caves_et_parkings_sortent_du_decompte() {
119        let natures = lots(15, 15, 20, 0);
120        assert_eq!(natures.len(), 50, "cinquante lots à l'acte de base");
121        assert_eq!(lots_comptes(&natures), 15, "quinze au sens de l'article");
122        assert_eq!(regime_applicable(&natures), RegimeComptable::Simplifie);
123    }
124
125    /// @edge — « moins de vingt » est strict : la borne est exclusive.
126    #[test]
127    fn edge_dix_neuf_lots_restent_simplifies() {
128        assert_eq!(
129            regime_applicable(&lots(19, 30, 40, 0)),
130            RegimeComptable::Simplifie
131        );
132    }
133
134    /// @edge — et vingt lots basculent.
135    #[test]
136    fn edge_vingt_lots_imposent_le_plan_normalise() {
137        assert_eq!(
138            regime_applicable(&lots(20, 0, 0, 0)),
139            RegimeComptable::Complet
140        );
141    }
142
143    /// Les commerces comptent : l'article n'exclut que caves, garages et
144    /// parkings.
145    #[test]
146    fn happy_les_commerces_comptent_dans_le_seuil() {
147        assert_eq!(
148            regime_applicable(&lots(18, 0, 0, 2)),
149            RegimeComptable::Complet
150        );
151    }
152
153    /// @security — un lot de nature indéterminée compte.
154    ///
155    /// Le doute profite à l'obligation la plus stricte : se tromper dans ce
156    /// sens fait tenir une comptabilité plus détaillée que nécessaire, alors
157    /// que l'erreur inverse met l'ACP en défaut.
158    #[test]
159    fn security_un_lot_de_nature_indeterminee_compte() {
160        let mut natures = lots(19, 0, 0, 0);
161        natures.push(UnitType::Other);
162        assert_eq!(regime_applicable(&natures), RegimeComptable::Complet);
163    }
164
165    /// @negative — une ACP sans lot encodé n'est tenue à rien de complet.
166    ///
167    /// Le cas se produit avant que le SuperAdmin ait fini d'encoder l'acte de
168    /// base. Il ne doit pas déclencher une obligation comptable sur du vide.
169    #[test]
170    fn negative_une_acp_sans_lot_reste_simplifiee() {
171        assert_eq!(regime_applicable(&[]), RegimeComptable::Simplifie);
172    }
173}