1use 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>(
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 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#[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}