1use crate::domain::services::PcnReportLine;
2use printpdf::*;
3use std::io::BufWriter;
4
5pub struct PcnExporter;
7
8impl PcnExporter {
9 pub fn export_to_pdf(
12 building_name: &str,
13 report_lines: &[PcnReportLine],
14 total_amount: f64,
15 ) -> Result<Vec<u8>, String> {
16 let (doc, page1, layer1) = PdfDocument::new("Rapport PCN", Mm(210.0), Mm(297.0), "Layer 1");
18 let current_layer = doc.get_page(page1).get_layer(layer1);
19
20 let font = doc
22 .add_builtin_font(BuiltinFont::Helvetica)
23 .map_err(|e| e.to_string())?;
24 let font_bold = doc
25 .add_builtin_font(BuiltinFont::HelveticaBold)
26 .map_err(|e| e.to_string())?;
27
28 current_layer.use_text(
30 "Rapport PCN - Plan Comptable Normalisé".to_string(),
31 24.0,
32 Mm(20.0),
33 Mm(270.0),
34 &font_bold,
35 );
36
37 current_layer.use_text(
39 format!("Immeuble: {}", building_name),
40 14.0,
41 Mm(20.0),
42 Mm(260.0),
43 &font,
44 );
45
46 let mut y = 245.0;
48 current_layer.use_text("Code", 12.0, Mm(20.0), Mm(y), &font_bold);
49 current_layer.use_text("Libellé", 12.0, Mm(50.0), Mm(y), &font_bold);
50 current_layer.use_text("Montant (€)", 12.0, Mm(140.0), Mm(y), &font_bold);
51 current_layer.use_text("Nb", 12.0, Mm(180.0), Mm(y), &font_bold);
52
53 y -= 10.0;
55 for line in report_lines {
56 current_layer.use_text(&line.account.code, 10.0, Mm(20.0), Mm(y), &font);
57 current_layer.use_text(&line.account.label_fr, 10.0, Mm(50.0), Mm(y), &font);
58 current_layer.use_text(
59 format!("{:.2}", line.total_amount),
60 10.0,
61 Mm(140.0),
62 Mm(y),
63 &font,
64 );
65 current_layer.use_text(
66 format!("{}", line.entry_count),
67 10.0,
68 Mm(180.0),
69 Mm(y),
70 &font,
71 );
72 y -= 7.0;
73 }
74
75 y -= 5.0;
77 current_layer.use_text("TOTAL:", 12.0, Mm(50.0), Mm(y), &font_bold);
78 current_layer.use_text(
79 format!("{:.2} €", total_amount),
80 12.0,
81 Mm(140.0),
82 Mm(y),
83 &font_bold,
84 );
85
86 let mut buffer = Vec::new();
88 doc.save(&mut BufWriter::new(&mut buffer))
89 .map_err(|e| e.to_string())?;
90
91 Ok(buffer)
92 }
93
94 pub fn export_to_excel(
97 building_name: &str,
98 report_lines: &[PcnReportLine],
99 total_amount: f64,
100 ) -> Result<Vec<u8>, String> {
101 use rust_xlsxwriter::*;
102
103 let mut workbook = Workbook::new();
105 let worksheet = workbook.add_worksheet();
106
107 worksheet
109 .set_column_width(0, 10)
110 .map_err(|e| e.to_string())?; worksheet
112 .set_column_width(1, 35)
113 .map_err(|e| e.to_string())?; worksheet
115 .set_column_width(2, 35)
116 .map_err(|e| e.to_string())?; worksheet
118 .set_column_width(3, 35)
119 .map_err(|e| e.to_string())?; worksheet
121 .set_column_width(4, 35)
122 .map_err(|e| e.to_string())?; worksheet
124 .set_column_width(5, 15)
125 .map_err(|e| e.to_string())?; worksheet
127 .set_column_width(6, 10)
128 .map_err(|e| e.to_string())?; let bold_format = Format::new().set_bold();
132 let currency_format = Format::new().set_num_format("#,##0.00 €");
133 let header_format = Format::new()
134 .set_bold()
135 .set_background_color(Color::RGB(0xD3D3D3));
136
137 worksheet
139 .write_string_with_format(0, 0, "Rapport PCN - Plan Comptable Normalisé", &bold_format)
140 .map_err(|e| e.to_string())?;
141
142 worksheet
144 .write_string_with_format(
145 1,
146 0,
147 format!("Immeuble: {}", building_name).as_str(),
148 &Format::new(),
149 )
150 .map_err(|e| e.to_string())?;
151
152 worksheet
154 .write_string_with_format(3, 0, "Code PCN", &header_format)
155 .map_err(|e| e.to_string())?;
156 worksheet
157 .write_string_with_format(3, 1, "Nederlands (NL)", &header_format)
158 .map_err(|e| e.to_string())?;
159 worksheet
160 .write_string_with_format(3, 2, "Français (FR)", &header_format)
161 .map_err(|e| e.to_string())?;
162 worksheet
163 .write_string_with_format(3, 3, "Deutsch (DE)", &header_format)
164 .map_err(|e| e.to_string())?;
165 worksheet
166 .write_string_with_format(3, 4, "English (EN)", &header_format)
167 .map_err(|e| e.to_string())?;
168 worksheet
169 .write_string_with_format(3, 5, "Montant", &header_format)
170 .map_err(|e| e.to_string())?;
171 worksheet
172 .write_string_with_format(3, 6, "Nb Écritures", &header_format)
173 .map_err(|e| e.to_string())?;
174
175 let mut row = 4;
177 for line in report_lines {
178 worksheet
179 .write_string(row, 0, &line.account.code)
180 .map_err(|e| e.to_string())?;
181 worksheet
182 .write_string(row, 1, &line.account.label_nl)
183 .map_err(|e| e.to_string())?;
184 worksheet
185 .write_string(row, 2, &line.account.label_fr)
186 .map_err(|e| e.to_string())?;
187 worksheet
188 .write_string(row, 3, &line.account.label_de)
189 .map_err(|e| e.to_string())?;
190 worksheet
191 .write_string(row, 4, &line.account.label_en)
192 .map_err(|e| e.to_string())?;
193 worksheet
194 .write_number_with_format(row, 5, line.total_amount, ¤cy_format)
195 .map_err(|e| e.to_string())?;
196 worksheet
197 .write_number(row, 6, line.entry_count as f64)
198 .map_err(|e| e.to_string())?;
199 row += 1;
200 }
201
202 row += 1;
204 worksheet
205 .write_string_with_format(row, 4, "TOTAL:", &bold_format)
206 .map_err(|e| e.to_string())?;
207 worksheet
208 .write_number_with_format(
209 row,
210 5,
211 total_amount,
212 &Format::new().set_bold().set_num_format("#,##0.00 €"),
213 )
214 .map_err(|e| e.to_string())?;
215
216 let buffer = workbook.save_to_buffer().map_err(|e| e.to_string())?;
218
219 Ok(buffer)
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::domain::entities::ExpenseCategory;
227 use crate::domain::services::PcnMapper;
228
229 fn create_test_report() -> (Vec<PcnReportLine>, f64) {
230 let lines = vec![
231 PcnReportLine {
232 account: PcnMapper::map_expense_to_pcn(&ExpenseCategory::Maintenance),
233 total_amount: 1500.0,
234 entry_count: 5,
235 },
236 PcnReportLine {
237 account: PcnMapper::map_expense_to_pcn(&ExpenseCategory::Utilities),
238 total_amount: 800.0,
239 entry_count: 3,
240 },
241 PcnReportLine {
242 account: PcnMapper::map_expense_to_pcn(&ExpenseCategory::Insurance),
243 total_amount: 2000.0,
244 entry_count: 1,
245 },
246 ];
247 let total = lines.iter().map(|l| l.total_amount).sum();
248 (lines, total)
249 }
250
251 #[test]
254 fn test_export_pdf_returns_bytes() {
255 let (lines, total) = create_test_report();
256
257 let result = PcnExporter::export_to_pdf("Test Building", &lines, total);
258
259 assert!(result.is_ok());
260 let pdf_bytes = result.unwrap();
261 assert!(!pdf_bytes.is_empty());
262
263 assert_eq!(&pdf_bytes[0..4], b"%PDF");
265 }
266
267 #[test]
268 fn test_export_pdf_empty_report() {
269 let result = PcnExporter::export_to_pdf("Empty Building", &[], 0.0);
270
271 assert!(result.is_ok());
272 let pdf_bytes = result.unwrap();
273 assert!(!pdf_bytes.is_empty());
274 assert_eq!(&pdf_bytes[0..4], b"%PDF");
275 }
276
277 #[test]
278 fn test_export_pdf_contains_building_name() {
279 let (lines, total) = create_test_report();
280
281 let result = PcnExporter::export_to_pdf("My Test Building", &lines, total);
282
283 assert!(result.is_ok());
284 }
286
287 #[test]
290 fn test_export_excel_returns_bytes() {
291 let (lines, total) = create_test_report();
292
293 let result = PcnExporter::export_to_excel("Test Building", &lines, total);
294
295 assert!(result.is_ok());
296 let excel_bytes = result.unwrap();
297 assert!(!excel_bytes.is_empty());
298
299 assert_eq!(&excel_bytes[0..2], b"PK");
301 }
302
303 #[test]
304 fn test_export_excel_empty_report() {
305 let result = PcnExporter::export_to_excel("Empty Building", &[], 0.0);
306
307 assert!(result.is_ok());
308 let excel_bytes = result.unwrap();
309 assert!(!excel_bytes.is_empty());
310 assert_eq!(&excel_bytes[0..2], b"PK");
311 }
312
313 #[test]
314 fn test_export_excel_has_correct_row_count() {
315 let (lines, total) = create_test_report();
316
317 let result = PcnExporter::export_to_excel("Test Building", &lines, total);
318
319 assert!(result.is_ok());
320 }
323}