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