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: rust_decimal::Decimal, 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 use rust_decimal::prelude::ToPrimitive;
159 let tantiemes_dec =
160 ownership_percentage * rust_decimal::Decimal::from(building.total_tantiemes);
161 let tantiemes = tantiemes_dec.trunc().to_i32().unwrap_or(0);
162 current_layer.use_text(
163 format!("Tantièmes: {} sur {}", tantiemes, building.total_tantiemes),
164 10.0,
165 Mm(20.0),
166 Mm(y),
167 &font_bold,
168 );
169 y -= 6.0;
170
171 current_layer.use_text(
172 format!(
173 "Quote-part: {:.2}%",
174 ownership_percentage * rust_decimal_macros::dec!(100)
175 ),
176 10.0,
177 Mm(20.0),
178 Mm(y),
179 &font_bold,
180 );
181 y -= 8.0;
182
183 current_layer.use_text(
185 "ARTICLE 3 - COPROPRIÉTAIRE".to_string(),
186 12.0,
187 Mm(20.0),
188 Mm(y),
189 &font_bold,
190 );
191 y -= 8.0;
192
193 let owner_name = format!("{} {}", owner.first_name, owner.last_name);
194
195 current_layer.use_text(format!("Nom: {}", owner_name), 10.0, Mm(20.0), Mm(y), &font);
196 y -= 6.0;
197
198 current_layer.use_text(
199 format!("Email: {}", owner.email),
200 10.0,
201 Mm(20.0),
202 Mm(y),
203 &font,
204 );
205 y -= 6.0;
206
207 if let Some(ref phone) = owner.phone {
208 current_layer.use_text(
209 format!("Téléphone: {}", phone),
210 10.0,
211 Mm(20.0),
212 Mm(y),
213 &font,
214 );
215 y -= 6.0;
216 }
217
218 current_layer.use_text(
219 format!(
220 "Date d'entrée en copropriété: {}",
221 ownership_start_date.format("%d/%m/%Y")
222 ),
223 10.0,
224 Mm(20.0),
225 Mm(y),
226 &font,
227 );
228 y -= 8.0;
229
230 current_layer.use_text(
232 "ARTICLE 4 - DROITS ET OBLIGATIONS".to_string(),
233 12.0,
234 Mm(20.0),
235 Mm(y),
236 &font_bold,
237 );
238 y -= 8.0;
239
240 let rights_text = [
241 "Le copropriétaire dispose des droits suivants:",
242 "• Droit d'usage exclusif du lot ci-dessus désigné",
243 "• Droit de participation aux assemblées générales",
244 "• Droit de vote proportionnel à sa quote-part",
245 "• Droit d'accès aux parties communes",
246 "",
247 "Le copropriétaire est tenu aux obligations suivantes:",
248 "• Paiement des charges communes proportionnellement à sa quote-part",
249 "• Respect du règlement de copropriété",
250 "• Participation aux travaux votés en assemblée générale",
251 "• Entretien de son lot privatif",
252 ];
253
254 for line in rights_text.iter() {
255 if y < 80.0 {
256 break;
257 }
258 current_layer.use_text(line.to_string(), 9.0, Mm(20.0), Mm(y), &font);
259 y -= 5.0;
260 }
261 y -= 5.0;
262
263 current_layer.use_text(
265 "ARTICLE 5 - RÉPARTITION DES CHARGES".to_string(),
266 12.0,
267 Mm(20.0),
268 Mm(y),
269 &font_bold,
270 );
271 y -= 8.0;
272
273 current_layer.use_text(
274 format!(
275 "Les charges communes sont réparties selon la quote-part de {:.2}%",
276 ownership_percentage * rust_decimal_macros::dec!(100)
277 ),
278 10.0,
279 Mm(20.0),
280 Mm(y),
281 &font,
282 );
283 y -= 6.0;
284
285 current_layer.use_text(
286 "correspondant aux tantièmes du lot.".to_string(),
287 10.0,
288 Mm(20.0),
289 Mm(y),
290 &font,
291 );
292 y -= 10.0;
293
294 if y < 40.0 {
296 y = 40.0;
297 }
298
299 current_layer.use_text("SIGNATURES".to_string(), 12.0, Mm(20.0), Mm(y), &font_bold);
300 y -= 10.0;
301
302 current_layer.use_text(
303 "Le Syndic: ________________".to_string(),
304 10.0,
305 Mm(20.0),
306 Mm(y),
307 &font,
308 );
309
310 current_layer.use_text(
311 "Le Copropriétaire: ________________".to_string(),
312 10.0,
313 Mm(120.0),
314 Mm(y),
315 &font,
316 );
317 y -= 6.0;
318
319 current_layer.use_text(
320 "Date: ________________".to_string(),
321 10.0,
322 Mm(20.0),
323 Mm(y),
324 &font,
325 );
326
327 current_layer.use_text(
328 "Date: ________________".to_string(),
329 10.0,
330 Mm(120.0),
331 Mm(y),
332 &font,
333 );
334
335 let mut buffer = Vec::new();
337 doc.save(&mut BufWriter::new(&mut buffer))
338 .map_err(|e| e.to_string())?;
339
340 Ok(buffer)
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use uuid::Uuid;
348
349 #[test]
350 fn test_export_ownership_contract_pdf() {
351 let building = Building {
352 id: Uuid::new_v4(),
353 name: "Les Jardins de Bruxelles".to_string(),
354 address: "123 Avenue Louise".to_string(),
355 city: "Bruxelles".to_string(),
356 postal_code: "1000".to_string(),
357 country: "Belgium".to_string(),
358 total_units: 10,
359 total_tantiemes: 1000,
360 construction_year: Some(1990),
361 syndic_name: None,
362 syndic_email: None,
363 syndic_phone: None,
364 syndic_address: None,
365 syndic_office_hours: None,
366 syndic_emergency_contact: None,
367 slug: None,
368 organization_id: Uuid::new_v4(),
369 created_at: Utc::now(),
370 updated_at: Utc::now(),
371 };
372
373 let unit = Unit {
374 id: Uuid::new_v4(),
375 organization_id: building.organization_id,
376 building_id: building.id,
377 unit_number: "A1".to_string(),
378 unit_type: crate::domain::entities::UnitType::Apartment,
379 floor: Some(1),
380 surface_area: 75.5,
381 quota: rust_decimal_macros::dec!(150),
382 owner_id: None,
383 created_at: Utc::now(),
384 updated_at: Utc::now(),
385 };
386
387 let owner = Owner {
388 id: Uuid::new_v4(),
389 organization_id: building.organization_id,
390 user_id: None,
391 first_name: "Jean".to_string(),
392 last_name: "Dupont".to_string(),
393 email: "jean@example.com".to_string(),
394 phone: Some("+32 2 123 45 67".to_string()),
395 address: "123 Rue de Test".to_string(),
396 city: "Bruxelles".to_string(),
397 postal_code: "1000".to_string(),
398 country: "Belgium".to_string(),
399 created_at: Utc::now(),
400 updated_at: Utc::now(),
401 };
402
403 let result = OwnershipContractExporter::export_to_pdf(
404 &building,
405 &unit,
406 &owner,
407 rust_decimal_macros::dec!(0.15), Utc::now() - chrono::Duration::days(365), );
410
411 assert!(result.is_ok());
412 let pdf_bytes = result.unwrap();
413 assert!(!pdf_bytes.is_empty());
414 assert!(pdf_bytes.len() > 100);
415 }
416}