Skip to main content

tackler_core/
report.rs

1/*
2 * Tackler-NG 2023-2025
3 * SPDX-License-Identifier: Apache-2.0
4 */
5use crate::config::{FormatType, ReportType};
6use crate::kernel::{BalanceGroupSettings, RegisterSettings, Settings};
7use crate::model::TxnSet;
8use crate::tackler;
9pub use balance_group_reporter::BalanceGroupReporter;
10pub use balance_reporter::BalanceReporter;
11pub use register_reporter::RegisterReporter;
12use std::io;
13use std::io::Write;
14use std::path::{Path, PathBuf};
15use tackler_api::metadata::Metadata;
16use tackler_api::metadata::items::TimeZoneInfo;
17use tackler_rs::create_output_file;
18
19mod balance_group_reporter;
20mod balance_reporter;
21mod register_reporter;
22
23pub enum FormatWriter<'w> {
24    TxtFormat(Box<dyn io::Write + 'w>),
25    JsonFormat(Box<dyn io::Write + 'w>),
26}
27
28pub trait Report {
29    /// # Errors
30    /// Returns `Err` in case of error
31    fn write_txt_report<'w, W: io::Write + ?Sized + 'w>(
32        &self,
33        cfg: &Settings,
34        w: &'w mut W,
35        txn_data: &TxnSet<'_>,
36    ) -> Result<(), tackler::Error> {
37        let mut writers: Vec<FormatWriter<'_>> = vec![FormatWriter::TxtFormat(Box::new(w))];
38        self.write_reports::<dyn io::Write>(cfg, &mut writers, None, txn_data)
39    }
40
41    /// # Errors
42    /// Returns `Err` in case of error
43    fn write_reports<W: io::Write + ?Sized>(
44        &self,
45        cfg: &Settings,
46        writers: &mut Vec<FormatWriter<'_>>,
47        metadata: Option<&Metadata>,
48        txns: &TxnSet<'_>,
49    ) -> Result<(), tackler::Error>;
50}
51
52pub(crate) fn report_timezone(cfg: &Settings) -> Result<TimeZoneInfo, tackler::Error> {
53    Ok(TimeZoneInfo {
54        zone_id: if let Some(tz) = cfg.report.tz.iana_name() {
55            tz.to_string()
56        } else {
57            let msg = "no name for tz!?!";
58            return Err(msg.into());
59        },
60    })
61}
62
63fn report_output<W: io::Write + ?Sized>(
64    prog_writer: &mut Option<Box<W>>,
65    paths: Vec<(String, String)>,
66    title: &str,
67) -> Result<(), tackler::Error> {
68    if let Some(pw) = prog_writer.as_mut() {
69        for p in paths {
70            writeln!(pw, "{:>21} ({}) : {}", title, p.0, p.1)?;
71        }
72    }
73    Ok(())
74}
75type ReportWriters<'w> = (Vec<FormatWriter<'w>>, Vec<(String, String)>);
76
77fn report_writers<'w>(
78    output_dir: &Path,
79    output_prefix: &str,
80    report_type: &ReportType,
81    settings: &Settings,
82) -> Result<ReportWriters<'w>, tackler::Error> {
83    match report_type {
84        ReportType::Balance => {
85            let mut writers = Vec::new();
86            let mut paths = Vec::new();
87
88            for rt in &settings.report.formats {
89                match rt {
90                    FormatType::Txt => {
91                        let (txt_writer, txt_path) =
92                            create_output_file(output_dir, output_prefix, "bal", "txt")?;
93
94                        writers.push(FormatWriter::TxtFormat(Box::new(txt_writer)));
95                        paths.push(("TEXT".to_string(), txt_path));
96                    }
97                    FormatType::Json => {
98                        let (json_writer, json_path) =
99                            create_output_file(output_dir, output_prefix, "bal", "json")?;
100
101                        writers.push(FormatWriter::JsonFormat(Box::new(json_writer)));
102                        paths.push(("JSON".to_string(), json_path));
103                    }
104                }
105            }
106            Ok((writers, paths))
107        }
108        ReportType::BalanceGroup => {
109            let mut writers = Vec::new();
110            let mut paths = Vec::new();
111
112            for rt in &settings.report.formats {
113                match rt {
114                    FormatType::Txt => {
115                        let (txt_writer, txt_path) =
116                            create_output_file(output_dir, output_prefix, "balgrp", "txt")?;
117
118                        writers.push(FormatWriter::TxtFormat(Box::new(txt_writer)));
119                        paths.push(("TEXT".to_string(), txt_path));
120                    }
121                    FormatType::Json => {
122                        let (json_writer, json_path) =
123                            create_output_file(output_dir, output_prefix, "balgrp", "json")?;
124
125                        writers.push(FormatWriter::JsonFormat(Box::new(json_writer)));
126                        paths.push(("JSON".to_string(), json_path));
127                    }
128                }
129            }
130            Ok((writers, paths))
131        }
132        ReportType::Register => {
133            let mut writers = Vec::new();
134            let mut paths = Vec::new();
135
136            for rt in &settings.report.formats {
137                match rt {
138                    FormatType::Txt => {
139                        let (txt_writer, txt_path) =
140                            create_output_file(output_dir, output_prefix, "reg", "txt")?;
141
142                        writers.push(FormatWriter::TxtFormat(Box::new(txt_writer)));
143                        paths.push(("TEXT".to_string(), txt_path));
144                    }
145                    FormatType::Json => {
146                        let (json_writer, json_path) =
147                            create_output_file(output_dir, output_prefix, "reg", "json")?;
148
149                        writers.push(FormatWriter::JsonFormat(Box::new(json_writer)));
150                        paths.push(("JSON".to_string(), json_path));
151                    }
152                }
153            }
154            Ok((writers, paths))
155        }
156    }
157}
158
159/// # Errors
160/// Return `Err` in case of error
161#[allow(clippy::too_many_arguments)]
162pub fn write_txt_reports<W: io::Write + ?Sized>(
163    console_writer: &mut Option<Box<W>>,
164    output_dir: Option<&PathBuf>,
165    output_prefix: &Option<String>,
166    reports: &Vec<ReportType>,
167    txn_set: &TxnSet<'_>,
168    settings: &Settings,
169    prog_writer: &mut Option<Box<W>>,
170) -> Result<(), tackler::Error> {
171    if !(output_dir.is_some() && output_prefix.is_some() && console_writer.is_none()
172        || output_dir.is_none() && output_prefix.is_none() && console_writer.is_some())
173    {
174        return Err("IE: Logic error, console output is not supported with file ouput".into());
175    }
176
177    let report_separator_len = 82;
178
179    let metadata = &txn_set
180        .metadata()
181        .map(|md| format!("{}\n", md.text(settings.report.tz.clone())))
182        .unwrap_or_default();
183
184    if let Some(cw) = console_writer.as_mut() {
185        write!(cw, "{metadata}")?;
186    }
187
188    for r in reports {
189        match r {
190            ReportType::Balance => {
191                let bal_reporter = BalanceReporter::try_from(settings)?;
192
193                if let (Some(output_name), Some(output_dir)) = (output_prefix, output_dir) {
194                    let (mut writers, paths) =
195                        report_writers(output_dir, output_name, r, settings)?;
196
197                    bal_reporter.write_reports::<dyn io::Write>(
198                        settings,
199                        &mut writers,
200                        txn_set.metadata(),
201                        txn_set,
202                    )?;
203
204                    report_output(prog_writer, paths, "Balance Report")?;
205                } else {
206                    let Some(mut cw) = console_writer.as_mut() else {
207                        return Err("IE: Logic error: console output".into());
208                    };
209
210                    writeln!(cw, "{}", "*".repeat(report_separator_len))?;
211                    bal_reporter.write_txt_report(settings, &mut cw, txn_set)?;
212                    writeln!(cw, "{}", "#".repeat(report_separator_len))?;
213                }
214            }
215            ReportType::BalanceGroup => {
216                let bal_group_reporter = BalanceGroupReporter {
217                    report_settings: BalanceGroupSettings::try_from(settings)?,
218                };
219                if let (Some(output_name), Some(output_dir)) = (output_prefix, output_dir) {
220                    let (mut writers, paths) =
221                        report_writers(output_dir, output_name, r, settings)?;
222
223                    bal_group_reporter.write_reports::<dyn io::Write>(
224                        settings,
225                        &mut writers,
226                        txn_set.metadata(),
227                        txn_set,
228                    )?;
229                    report_output(prog_writer, paths, "Balance Group Report")?;
230                } else {
231                    let Some(mut cw) = console_writer.as_mut() else {
232                        return Err("IE: Logic error: console output".into());
233                    };
234
235                    writeln!(cw, "{}", "*".repeat(report_separator_len))?;
236                    bal_group_reporter.write_txt_report(settings, &mut cw, txn_set)?;
237                    writeln!(cw, "{}", "#".repeat(report_separator_len))?;
238                }
239            }
240            ReportType::Register => {
241                let reg_reporter = RegisterReporter {
242                    report_settings: RegisterSettings::try_from(settings)?,
243                };
244
245                if let (Some(output_name), Some(output_dir)) = (output_prefix, output_dir) {
246                    let (mut writers, paths) =
247                        report_writers(output_dir, output_name, r, settings)?;
248
249                    reg_reporter.write_reports::<dyn io::Write>(
250                        settings,
251                        &mut writers,
252                        txn_set.metadata(),
253                        txn_set,
254                    )?;
255
256                    report_output(prog_writer, paths, "Register Report")?;
257                } else {
258                    let Some(mut cw) = console_writer.as_mut() else {
259                        return Err("IE: Logic error: console output".into());
260                    };
261
262                    writeln!(cw, "{}", "*".repeat(report_separator_len))?;
263                    reg_reporter.write_txt_report(settings, &mut cw, txn_set)?;
264                    writeln!(cw, "{}", "#".repeat(report_separator_len))?;
265                }
266            }
267        }
268    }
269    Ok(())
270}