Skip to main content

koprogo_api/domain/copropriete/
signatures_pv.rs

1//! Les signatures du procès-verbal d'assemblée.
2//!
3//! Art. 3.87 § 10 :
4//!
5//! > « Le syndic rédige le procès-verbal des décisions prises par l'assemblée
6//! > générale **avec indication des majorités obtenues et du nom des
7//! > copropriétaires qui ont voté contre ou qui se sont abstenus**.
8//! >
9//! > **A la fin de la séance et après lecture**, ce procès-verbal est signé
10//! > par **le président de l'assemblée générale**, par **le secrétaire désigné
11//! > lors de l'ouverture de la séance** et par **tous les copropriétaires
12//! > encore présents à ce moment** ou leurs mandataires. »
13//!
14//! Le procès-verbal était produit, avec ses majorités. Les signatures, elles,
15//! n'étaient pas un état du domaine : un PV non signé était indiscernable d'un
16//! PV signé, alors que la signature conditionne sa valeur probante.
17//!
18//! Quatre exigences, et chacune est piégeuse à sa façon :
19//!
20//! 1. **le président est un copropriétaire** (Art. 3.87 § 5, alinéa 1er). Un
21//!    syndic qui préside — pratique courante et commode — vicie le PV ;
22//! 2. **le secrétaire est désigné à l'ouverture**, donc avant les votes. Le
23//!    désigner après coup, quand il s'agit de trouver un signataire, ne
24//!    respecte pas l'article ;
25//! 3. **tous les copropriétaires encore présents** signent. Pas tous les
26//!    convoqués, pas tous les votants : ceux qui sont encore là à la fin de la
27//!    séance. Les partis en cours de route ne manquent à rien ;
28//! 4. **à la fin de la séance et après lecture** : la signature est
29//!    contemporaine, pas différée à la semaine suivante.
30//!
31//! La distinction du 3° compte : exiger la signature d'un copropriétaire parti
32//! bloquerait un PV parfaitement régulier, et se contenter du président
33//! laisserait passer un PV qui ne l'est pas.
34//!
35//! Voir issue #750 et [`super::consignation_pv`], qui traite la suite —
36//! consignation au registre et transmission sous trente jours.
37
38use uuid::Uuid;
39
40/// Qui a présidé, tenu la plume, et qui était encore là.
41#[derive(Debug, Clone, PartialEq)]
42pub struct SeanceCloturee {
43    /// Le président, qui doit être copropriétaire (Art. 3.87 § 5).
44    pub president: Uuid,
45    pub president_est_coproprietaire: bool,
46    /// Le secrétaire, désigné à l'ouverture de la séance.
47    pub secretaire: Option<Uuid>,
48    /// Les copropriétaires — ou leurs mandataires — encore présents à la
49    /// clôture.
50    pub presents_a_la_cloture: Vec<Uuid>,
51}
52
53/// Ce qui manque à un procès-verbal pour être valablement signé.
54#[derive(Debug, Clone, PartialEq)]
55pub enum SignatureManquante {
56    /// Le président n'a pas signé.
57    President { president: Uuid },
58    /// Aucun secrétaire n'a été désigné à l'ouverture.
59    SecretaireNonDesigne,
60    /// Le secrétaire n'a pas signé.
61    Secretaire { secretaire: Uuid },
62    /// Des copropriétaires présents à la clôture n'ont pas signé.
63    CoproprietairesPresents { manquants: Vec<Uuid> },
64    /// Le président n'est pas copropriétaire — souvent, c'est le syndic.
65    PresidentNonCoproprietaire { president: Uuid },
66}
67
68impl std::fmt::Display for SignatureManquante {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            Self::President { president } => {
72                write!(
73                    f,
74                    "Art. 3.87 § 10 : le président {president} n'a pas signé."
75                )
76            }
77            Self::SecretaireNonDesigne => write!(
78                f,
79                "Art. 3.87 § 10 : aucun secrétaire n'a été désigné à l'ouverture de la séance."
80            ),
81            Self::Secretaire { secretaire } => {
82                write!(
83                    f,
84                    "Art. 3.87 § 10 : le secrétaire {secretaire} n'a pas signé."
85                )
86            }
87            Self::CoproprietairesPresents { manquants } => write!(
88                f,
89                "Art. 3.87 § 10 : {} copropriétaire(s) encore présent(s) à la clôture n'ont \
90                 pas signé.",
91                manquants.len()
92            ),
93            Self::PresidentNonCoproprietaire { president } => write!(
94                f,
95                "Art. 3.87 § 5 : {president} a présidé sans être copropriétaire. \
96                 « L'assemblée générale est présidée par un copropriétaire » — un syndic \
97                 qui préside vicie le procès-verbal."
98            ),
99        }
100    }
101}
102
103/// Vérifie les signatures d'un procès-verbal.
104///
105/// `signataires` sont ceux qui ont effectivement signé, à la fin de la séance.
106///
107/// Renvoie **tous** les manquements : un PV qu'on doit faire resigner appelle
108/// la liste complète, pas un nom à la fois.
109pub fn verifier_signatures(
110    seance: &SeanceCloturee,
111    signataires: &[Uuid],
112) -> Vec<SignatureManquante> {
113    let mut manquements = Vec::new();
114
115    if !seance.president_est_coproprietaire {
116        manquements.push(SignatureManquante::PresidentNonCoproprietaire {
117            president: seance.president,
118        });
119    }
120    if !signataires.contains(&seance.president) {
121        manquements.push(SignatureManquante::President {
122            president: seance.president,
123        });
124    }
125
126    match seance.secretaire {
127        None => manquements.push(SignatureManquante::SecretaireNonDesigne),
128        Some(secretaire) if !signataires.contains(&secretaire) => {
129            manquements.push(SignatureManquante::Secretaire { secretaire })
130        }
131        Some(_) => {}
132    }
133
134    let manquants: Vec<Uuid> = seance
135        .presents_a_la_cloture
136        .iter()
137        .filter(|p| !signataires.contains(p))
138        .copied()
139        .collect();
140    if !manquants.is_empty() {
141        manquements.push(SignatureManquante::CoproprietairesPresents { manquants });
142    }
143
144    manquements
145}
146
147/// Le procès-verbal est-il valablement signé ?
148pub fn pv_valablement_signe(seance: &SeanceCloturee, signataires: &[Uuid]) -> bool {
149    verifier_signatures(seance, signataires).is_empty()
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    fn seance(president: Uuid, secretaire: Uuid, presents: Vec<Uuid>) -> SeanceCloturee {
157        SeanceCloturee {
158            president,
159            president_est_coproprietaire: true,
160            secretaire: Some(secretaire),
161            presents_a_la_cloture: presents,
162        }
163    }
164
165    #[test]
166    fn happy_un_pv_signe_par_tous_est_valable() {
167        let president = Uuid::new_v4();
168        let secretaire = Uuid::new_v4();
169        let present = Uuid::new_v4();
170        let s = seance(president, secretaire, vec![present]);
171
172        assert!(pv_valablement_signe(&s, &[president, secretaire, present]));
173    }
174
175    #[test]
176    fn negative_un_pv_que_le_president_na_pas_signe_est_incomplet() {
177        let president = Uuid::new_v4();
178        let secretaire = Uuid::new_v4();
179        let s = seance(president, secretaire, vec![]);
180
181        assert_eq!(
182            verifier_signatures(&s, &[secretaire]),
183            vec![SignatureManquante::President { president }]
184        );
185    }
186
187    /// @security — le piège le plus courant : le syndic préside.
188    ///
189    /// Art. 3.87 § 5 : « L'assemblée générale est présidée par un
190    /// copropriétaire. » C'est commode et c'est vicié.
191    #[test]
192    fn security_un_president_non_coproprietaire_vicie_le_pv() {
193        let syndic = Uuid::new_v4();
194        let secretaire = Uuid::new_v4();
195        let s = SeanceCloturee {
196            president: syndic,
197            president_est_coproprietaire: false,
198            secretaire: Some(secretaire),
199            presents_a_la_cloture: vec![],
200        };
201
202        let manquements = verifier_signatures(&s, &[syndic, secretaire]);
203        assert_eq!(
204            manquements,
205            vec![SignatureManquante::PresidentNonCoproprietaire { president: syndic }],
206            "il a bien signé, mais il n'aurait pas dû présider"
207        );
208    }
209
210    /// Le secrétaire est désigné **à l'ouverture**. Ne pas l'avoir désigné est
211    /// un manquement distinct de ne pas l'avoir fait signer.
212    #[test]
213    fn negative_sans_secretaire_designe_le_pv_est_incomplet() {
214        let president = Uuid::new_v4();
215        let s = SeanceCloturee {
216            president,
217            president_est_coproprietaire: true,
218            secretaire: None,
219            presents_a_la_cloture: vec![],
220        };
221
222        assert_eq!(
223            verifier_signatures(&s, &[president]),
224            vec![SignatureManquante::SecretaireNonDesigne]
225        );
226    }
227
228    /// « Tous les copropriétaires **encore présents à ce moment** » : ceux qui
229    /// sont partis en cours de séance ne manquent à rien.
230    ///
231    /// Exiger leur signature bloquerait un PV parfaitement régulier.
232    #[test]
233    fn happy_un_coproprietaire_parti_en_cours_de_seance_na_pas_a_signer() {
234        let president = Uuid::new_v4();
235        let secretaire = Uuid::new_v4();
236        let reste = Uuid::new_v4();
237        let _parti_plus_tot = Uuid::new_v4();
238
239        // `presents_a_la_cloture` ne contient que ceux encore là.
240        let s = seance(president, secretaire, vec![reste]);
241
242        assert!(pv_valablement_signe(&s, &[president, secretaire, reste]));
243    }
244
245    /// @security — mais ceux qui sont restés doivent bien signer.
246    #[test]
247    fn security_un_present_a_la_cloture_qui_ne_signe_pas_est_releve() {
248        let president = Uuid::new_v4();
249        let secretaire = Uuid::new_v4();
250        let recalcitrant = Uuid::new_v4();
251        let s = seance(president, secretaire, vec![recalcitrant]);
252
253        assert_eq!(
254            verifier_signatures(&s, &[president, secretaire]),
255            vec![SignatureManquante::CoproprietairesPresents {
256                manquants: vec![recalcitrant]
257            }]
258        );
259    }
260
261    /// La liste est complète, pas un manquement à la fois.
262    ///
263    /// Un PV qu'on doit faire resigner appelle tous les noms d'un coup : les
264    /// signataires ne se réunissent pas deux fois.
265    #[test]
266    fn negative_tous_les_manquements_remontent_ensemble() {
267        let syndic = Uuid::new_v4();
268        let present = Uuid::new_v4();
269        let s = SeanceCloturee {
270            president: syndic,
271            president_est_coproprietaire: false,
272            secretaire: None,
273            presents_a_la_cloture: vec![present],
274        };
275
276        let manquements = verifier_signatures(&s, &[]);
277        assert_eq!(
278            manquements.len(),
279            4,
280            "président non copropriétaire, président non signataire, \
281             secrétaire non désigné, présent non signataire"
282        );
283    }
284
285    #[test]
286    fn happy_une_seance_sans_personne_a_la_cloture_ne_demande_que_deux_signatures() {
287        // Cas réel : tout le monde est parti sauf le bureau.
288        let president = Uuid::new_v4();
289        let secretaire = Uuid::new_v4();
290        let s = seance(president, secretaire, vec![]);
291
292        assert!(pv_valablement_signe(&s, &[president, secretaire]));
293    }
294}