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