1use crate::domain::entities::{Building, Owner, Unit};
2use chrono::{DateTime, Utc};
3use printpdf::*;
4use std::io::BufWriter;
5
6pub struct OwnershipContractExporter;
10
11impl OwnershipContractExporter {
12 pub fn export_to_pdf(
24 building: &Building,
25 unit: &Unit,
26 owner: &Owner,
27 ownership_percentage: f64, ownership_start_date: DateTime<Utc>,
29 ) -> Result<Vec<u8>, String> {
30 let (doc, page1, layer1) =
32 PdfDocument::new("Contrat de Copropriété", Mm(210.0), Mm(297.0), "Layer 1");
33 let current_layer = doc.get_page(page1).get_layer(layer1);
34
35 let font = doc
37 .add_builtin_font(BuiltinFont::Helvetica)
38 .map_err(|e| e.to_string())?;
39 let font_bold = doc
40 .add_builtin_font(BuiltinFont::HelveticaBold)
41 .map_err(|e| e.to_string())?;
42
43 let mut y = 270.0; current_layer.use_text(
47 "CONTRAT DE COPROPRIÉTÉ".to_string(),
48 18.0,
49 Mm(20.0),
50 Mm(y),
51 &font_bold,
52 );
53 y -= 15.0;
54
55 current_layer.use_text(
56 format!("Date d'établissement: {}", Utc::now().format("%d/%m/%Y")),
57 10.0,
58 Mm(20.0),
59 Mm(y),
60 &font,
61 );
62 y -= 15.0;
63
64 current_layer.use_text(
66 "ARTICLE 1 - IMMEUBLE CONCERNÉ".to_string(),
67 12.0,
68 Mm(20.0),
69 Mm(y),
70 &font_bold,
71 );
72 y -= 8.0;
73
74 current_layer.use_text(
75 format!("Dénomination: {}", building.name),
76 10.0,
77 Mm(20.0),
78 Mm(y),
79 &font,
80 );
81 y -= 6.0;
82
83 current_layer.use_text(
84 format!(
85 "Adresse: {}, {} {}, {}",
86 building.address, building.postal_code, building.city, building.country
87 ),
88 10.0,
89 Mm(20.0),
90 Mm(y),
91 &font,
92 );
93 y -= 6.0;
94
95 current_layer.use_text(
96 format!("Nombre total de lots: {}", building.total_units),
97 10.0,
98 Mm(20.0),
99 Mm(y),
100 &font,
101 );
102 y -= 6.0;
103
104 if let Some(year) = building.construction_year {
105 current_layer.use_text(
106 format!("Année de construction: {}", year),
107 10.0,
108 Mm(20.0),
109 Mm(y),
110 &font,
111 );
112 y -= 6.0;
113 }
114 y -= 8.0;
115
116 current_layer.use_text(
118 "ARTICLE 2 - DESCRIPTION DU LOT".to_string(),
119 12.0,
120 Mm(20.0),
121 Mm(y),
122 &font_bold,
123 );
124 y -= 8.0;
125
126 current_layer.use_text(
127 format!("Numéro de lot: {}", unit.unit_number),
128 10.0,
129 Mm(20.0),
130 Mm(y),
131 &font,
132 );
133 y -= 6.0;
134
135 if let Some(floor) = unit.floor {
136 current_layer.use_text(format!("Étage: {}", floor), 10.0, Mm(20.0), Mm(y), &font);
137 y -= 6.0;
138 }
139
140 current_layer.use_text(
141 format!("Superficie: {:.2} m²", unit.surface_area),
142 10.0,
143 Mm(20.0),
144 Mm(y),
145 &font,
146 );
147 y -= 6.0;
148
149 current_layer.use_text(
150 format!("Type: {:?}", unit.unit_type),
151 10.0,
152 Mm(20.0),
153 Mm(y),
154 &font,
155 );
156 y -= 6.0;
157
158 let tantiemes = (ownership_percentage * building.total_tantiemes as f64) as i32;
159 current_layer.use_text(
160 format!("Tantièmes: {} sur {}", tantiemes, building.total_tantiemes),
161 10.0,
162 Mm(20.0),
163 Mm(y),
164 &font_bold,
165 );
166 y -= 6.0;
167
168 current_layer.use_text(
169 format!("Quote-part: {:.2}%", ownership_percentage * 100.0),
170 10.0,
171 Mm(20.0),
172 Mm(y),
173 &font_bold,
174 );
175 y -= 8.0;
176
177 current_layer.use_text(
179 "ARTICLE 3 - COPROPRIÉTAIRE".to_string(),
180 12.0,
181 Mm(20.0),
182 Mm(y),
183 &font_bold,
184 );
185 y -= 8.0;
186
187 let owner_name = format!("{} {}", owner.first_name, owner.last_name);
188
189 current_layer.use_text(format!("Nom: {}", owner_name), 10.0, Mm(20.0), Mm(y), &font);
190 y -= 6.0;
191
192 current_layer.use_text(
193 format!("Email: {}", owner.email),
194 10.0,
195 Mm(20.0),
196 Mm(y),
197 &font,
198 );
199 y -= 6.0;
200
201 if let Some(ref phone) = owner.phone {
202 current_layer.use_text(
203 format!("Téléphone: {}", phone),
204 10.0,
205 Mm(20.0),
206 Mm(y),
207 &font,
208 );
209 y -= 6.0;
210 }
211
212 current_layer.use_text(
213 format!(
214 "Date d'entrée en copropriété: {}",
215 ownership_start_date.format("%d/%m/%Y")
216 ),
217 10.0,
218 Mm(20.0),
219 Mm(y),
220 &font,
221 );
222 y -= 8.0;
223
224 current_layer.use_text(
226 "ARTICLE 4 - DROITS ET OBLIGATIONS".to_string(),
227 12.0,
228 Mm(20.0),
229 Mm(y),
230 &font_bold,
231 );
232 y -= 8.0;
233
234 let rights_text = [
235 "Le copropriétaire dispose des droits suivants:",
236 "• Droit d'usage exclusif du lot ci-dessus désigné",
237 "• Droit de participation aux assemblées générales",
238 "• Droit de vote proportionnel à sa quote-part",
239 "• Droit d'accès aux parties communes",
240 "",
241 "Le copropriétaire est tenu aux obligations suivantes:",
242 "• Paiement des charges communes proportionnellement à sa quote-part",
243 "• Respect du règlement de copropriété",
244 "• Participation aux travaux votés en assemblée générale",
245 "• Entretien de son lot privatif",
246 ];
247
248 for line in rights_text.iter() {
249 if y < 80.0 {
250 break;
251 }
252 current_layer.use_text(line.to_string(), 9.0, Mm(20.0), Mm(y), &font);
253 y -= 5.0;
254 }
255 y -= 5.0;
256
257 current_layer.use_text(
259 "ARTICLE 5 - RÉPARTITION DES CHARGES".to_string(),
260 12.0,
261 Mm(20.0),
262 Mm(y),
263 &font_bold,
264 );
265 y -= 8.0;
266
267 current_layer.use_text(
268 format!(
269 "Les charges communes sont réparties selon la quote-part de {:.2}%",
270 ownership_percentage * 100.0
271 ),
272 10.0,
273 Mm(20.0),
274 Mm(y),
275 &font,
276 );
277 y -= 6.0;
278
279 current_layer.use_text(
280 "correspondant aux tantièmes du lot.".to_string(),
281 10.0,
282 Mm(20.0),
283 Mm(y),
284 &font,
285 );
286 y -= 10.0;
287
288 if y < 40.0 {
290 y = 40.0;
291 }
292
293 current_layer.use_text("SIGNATURES".to_string(), 12.0, Mm(20.0), Mm(y), &font_bold);
294 y -= 10.0;
295
296 current_layer.use_text(
297 "Le Syndic: ________________".to_string(),
298 10.0,
299 Mm(20.0),
300 Mm(y),
301 &font,
302 );
303
304 current_layer.use_text(
305 "Le Copropriétaire: ________________".to_string(),
306 10.0,
307 Mm(120.0),
308 Mm(y),
309 &font,
310 );
311 y -= 6.0;
312
313 current_layer.use_text(
314 "Date: ________________".to_string(),
315 10.0,
316 Mm(20.0),
317 Mm(y),
318 &font,
319 );
320
321 current_layer.use_text(
322 "Date: ________________".to_string(),
323 10.0,
324 Mm(120.0),
325 Mm(y),
326 &font,
327 );
328
329 let mut buffer = Vec::new();
331 doc.save(&mut BufWriter::new(&mut buffer))
332 .map_err(|e| e.to_string())?;
333
334 Ok(buffer)
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341 use uuid::Uuid;
342
343 #[test]
344 fn test_export_ownership_contract_pdf() {
345 let building = Building {
346 id: Uuid::new_v4(),
347 name: "Les Jardins de Bruxelles".to_string(),
348 address: "123 Avenue Louise".to_string(),
349 city: "Bruxelles".to_string(),
350 postal_code: "1000".to_string(),
351 country: "Belgium".to_string(),
352 total_units: 10,
353 total_tantiemes: 1000,
354 construction_year: Some(1990),
355 syndic_name: None,
356 syndic_email: None,
357 syndic_phone: None,
358 syndic_address: None,
359 syndic_office_hours: None,
360 syndic_emergency_contact: None,
361 slug: None,
362 organization_id: Uuid::new_v4(),
363 created_at: Utc::now(),
364 updated_at: Utc::now(),
365 };
366
367 let unit = Unit {
368 id: Uuid::new_v4(),
369 organization_id: building.organization_id,
370 building_id: building.id,
371 unit_number: "A1".to_string(),
372 unit_type: crate::domain::entities::UnitType::Apartment,
373 floor: Some(1),
374 surface_area: 75.5,
375 quota: 150.0,
376 owner_id: None,
377 created_at: Utc::now(),
378 updated_at: Utc::now(),
379 };
380
381 let owner = Owner {
382 id: Uuid::new_v4(),
383 organization_id: building.organization_id,
384 user_id: None,
385 first_name: "Jean".to_string(),
386 last_name: "Dupont".to_string(),
387 email: "jean@example.com".to_string(),
388 phone: Some("+32 2 123 45 67".to_string()),
389 address: "123 Rue de Test".to_string(),
390 city: "Bruxelles".to_string(),
391 postal_code: "1000".to_string(),
392 country: "Belgium".to_string(),
393 created_at: Utc::now(),
394 updated_at: Utc::now(),
395 };
396
397 let result = OwnershipContractExporter::export_to_pdf(
398 &building,
399 &unit,
400 &owner,
401 0.15, Utc::now() - chrono::Duration::days(365), );
404
405 assert!(result.is_ok());
406 let pdf_bytes = result.unwrap();
407 assert!(!pdf_bytes.is_empty());
408 assert!(pdf_bytes.len() > 100);
409 }
410}