Skip to main content

koprogo_api/domain/copropriete/
consignation_pv.rs

1//! La consignation et la transmission du procès-verbal.
2//!
3//! Art. 3.87 § 12 :
4//!
5//! > « Le syndic **consigne** les décisions visées aux paragraphes 10 et 11
6//! > dans le **registre prévu à l'article 3.93, § 4**, dans les **trente
7//! > jours** suivant l'assemblée générale, et **transmet** celles-ci, **dans
8//! > le même délai**, à tout titulaire d'un droit réel sur un lot disposant
9//! > [...] du droit de vote à l'assemblée générale, et **aux autres syndics**.
10//! > Si l'un d'eux n'a pas reçu le procès-verbal dans le délai fixé, il en
11//! > informe le syndic par écrit. »
12//!
13//! Deux obligations distinctes, un seul délai. On peut satisfaire l'une sans
14//! l'autre — consigner au registre sans rien envoyer est un cas courant, et
15//! reste un manquement.
16//!
17//! La transmission se suit **destinataire par destinataire**. Un envoi global
18//! qui a échoué pour un seul copropriétaire est un défaut vis-à-vis de
19//! celui-là, et c'est lui qui pourra s'en prévaloir : la loi lui donne
20//! d'ailleurs le droit d'en informer le syndic par écrit, ce qui suppose qu'il
21//! puisse constater le manque.
22//!
23//! Le délai court **depuis l'assemblée**, pas depuis la signature du PV ni
24//! depuis sa rédaction. Un syndic qui tarde à rédiger consomme son propre
25//! délai.
26//!
27//! Le § 11 est inclus : les décisions prises à l'unanimité par écrit, sans
28//! assemblée, suivent le même régime. Leur point de départ est alors la date
29//! de la décision.
30//!
31//! Voir issue #744.
32
33use chrono::{DateTime, Duration, Utc};
34use uuid::Uuid;
35
36/// Le délai unique de l'Art. 3.87 § 12.
37pub const DELAI_JOURS: i64 = 30;
38
39/// Où en est le syndic de ses deux obligations.
40#[derive(Debug, Clone, PartialEq)]
41pub struct EtatConsignation {
42    /// Date de l'assemblée, ou de la décision écrite unanime (§ 11).
43    pub tenue_le: DateTime<Utc>,
44    /// Date limite, commune aux deux obligations.
45    pub echeance: DateTime<Utc>,
46    /// Date de consignation au registre de l'Art. 3.93 § 4.
47    pub consigne_le: Option<DateTime<Utc>>,
48    /// Destinataires à qui le PV n'a pas encore été transmis.
49    pub non_transmis: Vec<Uuid>,
50}
51
52impl EtatConsignation {
53    pub fn consignation_faite(&self) -> bool {
54        self.consigne_le.is_some()
55    }
56
57    pub fn transmission_faite(&self) -> bool {
58        self.non_transmis.is_empty()
59    }
60
61    /// Le syndic est-il en défaut à cette date ?
62    ///
63    /// Avant l'échéance, un PV non consigné n'est pas un manquement : le
64    /// syndic est simplement dans son délai.
65    pub fn en_defaut_le(&self, moment: DateTime<Utc>) -> bool {
66        moment > self.echeance && !(self.consignation_faite() && self.transmission_faite())
67    }
68
69    /// Ce qu'il reste à faire, dit en clair.
70    pub fn manquements(&self, moment: DateTime<Utc>) -> Vec<String> {
71        if !self.en_defaut_le(moment) {
72            return Vec::new();
73        }
74        let mut manques = Vec::new();
75        if !self.consignation_faite() {
76            manques.push(format!(
77                "Art. 3.87 § 12 : procès-verbal non consigné au registre (échéance {})",
78                self.echeance.date_naive()
79            ));
80        }
81        if !self.transmission_faite() {
82            manques.push(format!(
83                "Art. 3.87 § 12 : procès-verbal non transmis à {} destinataire(s) (échéance {})",
84                self.non_transmis.len(),
85                self.echeance.date_naive()
86            ));
87        }
88        manques
89    }
90}
91
92/// L'échéance des deux obligations : trente jours après l'assemblée.
93pub fn echeance(tenue_le: DateTime<Utc>) -> DateTime<Utc> {
94    tenue_le + Duration::days(DELAI_JOURS)
95}
96
97/// Fait le point sur une assemblée.
98///
99/// `destinataires` sont tous ceux à qui la loi impose la transmission :
100/// titulaires d'un droit réel disposant du droit de vote, et les autres
101/// syndics. `transmis_a` sont ceux qui l'ont effectivement reçu.
102pub fn etat(
103    tenue_le: DateTime<Utc>,
104    consigne_le: Option<DateTime<Utc>>,
105    destinataires: &[Uuid],
106    transmis_a: &[Uuid],
107) -> EtatConsignation {
108    EtatConsignation {
109        tenue_le,
110        echeance: echeance(tenue_le),
111        consigne_le,
112        non_transmis: destinataires
113            .iter()
114            .filter(|d| !transmis_a.contains(d))
115            .copied()
116            .collect(),
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    fn il_y_a(jours: i64) -> DateTime<Utc> {
125        Utc::now() - Duration::days(jours)
126    }
127
128    #[test]
129    fn happy_lecheance_tombe_trente_jours_apres_lassemblee() {
130        let ag = il_y_a(0);
131        assert_eq!(echeance(ag), ag + Duration::days(30));
132    }
133
134    #[test]
135    fn happy_tout_fait_dans_les_temps_ne_signale_rien() {
136        let a = Uuid::new_v4();
137        let b = Uuid::new_v4();
138        let etat = etat(il_y_a(10), Some(il_y_a(8)), &[a, b], &[a, b]);
139
140        assert!(etat.consignation_faite());
141        assert!(etat.transmission_faite());
142        assert!(!etat.en_defaut_le(Utc::now()));
143        assert!(etat.manquements(Utc::now()).is_empty());
144    }
145
146    /// Avant l'échéance, un PV non consigné n'est pas un manquement.
147    #[test]
148    fn happy_dans_le_delai_rien_nest_encore_reproche() {
149        let etat = etat(il_y_a(10), None, &[Uuid::new_v4()], &[]);
150        assert!(!etat.en_defaut_le(Utc::now()));
151    }
152
153    /// Les deux obligations sont distinctes : consigner sans transmettre reste
154    /// un manquement.
155    #[test]
156    fn negative_consigner_sans_transmettre_reste_un_defaut() {
157        let destinataire = Uuid::new_v4();
158        let etat = etat(il_y_a(40), Some(il_y_a(35)), &[destinataire], &[]);
159
160        assert!(etat.consignation_faite());
161        assert!(!etat.transmission_faite());
162        assert!(etat.en_defaut_le(Utc::now()));
163
164        let manques = etat.manquements(Utc::now());
165        assert_eq!(manques.len(), 1);
166        assert!(manques[0].contains("non transmis"));
167    }
168
169    #[test]
170    fn negative_transmettre_sans_consigner_aussi() {
171        let destinataire = Uuid::new_v4();
172        let etat = etat(il_y_a(40), None, &[destinataire], &[destinataire]);
173
174        let manques = etat.manquements(Utc::now());
175        assert_eq!(manques.len(), 1);
176        assert!(manques[0].contains("non consigné"));
177    }
178
179    #[test]
180    fn negative_ne_rien_faire_cumule_les_deux_manquements() {
181        let etat = etat(il_y_a(40), None, &[Uuid::new_v4()], &[]);
182        assert_eq!(etat.manquements(Utc::now()).len(), 2);
183    }
184
185    /// La transmission se suit destinataire par destinataire.
186    ///
187    /// Un envoi global qui a échoué pour un seul copropriétaire est un défaut
188    /// vis-à-vis de celui-là, et c'est lui qui pourra s'en prévaloir.
189    #[test]
190    fn security_un_seul_destinataire_oublie_suffit_a_faire_defaut() {
191        let servis = vec![Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()];
192        let oublie = Uuid::new_v4();
193        let mut tous = servis.clone();
194        tous.push(oublie);
195
196        let etat = etat(il_y_a(40), Some(il_y_a(35)), &tous, &servis);
197
198        assert_eq!(etat.non_transmis, vec![oublie]);
199        assert!(etat.en_defaut_le(Utc::now()));
200    }
201
202    /// @edge — le trentième jour, le syndic est encore dans son délai.
203    #[test]
204    fn edge_le_jour_de_lecheance_nest_pas_encore_un_defaut() {
205        let ag = il_y_a(30);
206        let etat = etat(ag, None, &[Uuid::new_v4()], &[]);
207        assert!(
208            !etat.en_defaut_le(etat.echeance),
209            "« dans les trente jours » inclut le trentième"
210        );
211        assert!(etat.en_defaut_le(etat.echeance + Duration::seconds(1)));
212    }
213
214    /// Le délai court depuis l'assemblée, pas depuis la rédaction du PV.
215    ///
216    /// Un syndic qui tarde à rédiger consomme son propre délai.
217    #[test]
218    fn edge_le_delai_court_depuis_lassemblee() {
219        let ag = il_y_a(40);
220        let etat = etat(ag, Some(il_y_a(2)), &[], &[]);
221        assert!(
222            etat.consigne_le.unwrap() > etat.echeance,
223            "consigné trente-huit jours après l'AG : hors délai"
224        );
225    }
226
227    /// @negative — une assemblée sans destinataire n'a rien à transmettre.
228    ///
229    /// Le cas se produit sur les décisions écrites unanimes du § 11, prises
230    /// par tous : il ne reste personne à informer.
231    #[test]
232    fn negative_sans_destinataire_seule_la_consignation_compte() {
233        let etat = etat(il_y_a(40), Some(il_y_a(35)), &[], &[]);
234        assert!(etat.transmission_faite());
235        assert!(!etat.en_defaut_le(Utc::now()));
236    }
237}