1#![deny(clippy::unwrap_used)]
2use std::path::{Path, PathBuf};
8
9pub mod compressed;
10pub mod control_export;
11pub mod csv_sink;
12pub mod esg_export;
13pub mod fast_csv;
14pub mod formats;
15pub mod json_sink;
16pub mod parquet_sink;
17pub mod project_accounting_export;
18pub mod streaming;
19pub mod tax_export;
20pub mod treasury_export;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct OutputRootConfig {
33 pub root_dir: PathBuf,
35 pub per_entity_subtree: bool,
39 pub entity_code: Option<String>,
43}
44
45impl OutputRootConfig {
46 pub fn flat(root_dir: impl Into<PathBuf>) -> Self {
48 Self {
49 root_dir: root_dir.into(),
50 per_entity_subtree: false,
51 entity_code: None,
52 }
53 }
54
55 pub fn per_entity(root_dir: impl Into<PathBuf>, entity_code: impl Into<String>) -> Self {
58 Self {
59 root_dir: root_dir.into(),
60 per_entity_subtree: true,
61 entity_code: Some(entity_code.into()),
62 }
63 }
64
65 pub fn effective_dir(&self) -> PathBuf {
73 match (self.per_entity_subtree, &self.entity_code) {
74 (true, Some(code)) => self.root_dir.join("entities").join(code),
75 (false, _) | (true, None) => self.root_dir.clone(),
76 }
77 }
78
79 pub fn root_path(&self) -> &Path {
83 &self.root_dir
84 }
85}
86
87impl Default for OutputRootConfig {
88 fn default() -> Self {
89 Self::flat(PathBuf::from("."))
90 }
91}
92
93pub use compressed::{CompressedWriter, CompressionConfig};
94pub use control_export::*;
95pub use csv_sink::*;
96pub use esg_export::*;
97pub use formats::{
98 saft_naive_date, write_anla, write_bsad, write_bsak, write_bsas, write_bsid, write_bsik,
99 write_bsis, write_cepc, write_csks, write_ekko, write_ekpo, write_fec_csv,
100 write_gobd_accounts_csv, write_gobd_index_xml, write_gobd_journal_csv, write_kna1, write_knb1,
101 write_lfa1, write_lfb1, write_likp, write_lips, write_mara, write_mard, write_mkpf, write_mseg,
102 write_saft, write_ska1, write_skb1, write_vbak, write_vbap, NetSuiteExporter,
103 NetSuiteJournalEntry, NetSuiteJournalLine, OracleExporter, OracleJeHeader, OracleJeLine,
104 SaftConfig, SaftData, SaftJurisdiction, SapAsset, SapAssetExportable, SapClearedItemRow,
105 SapCostCenter, SapCostCenterExportable, SapCustomer, SapCustomerCompanyCode,
106 SapCustomerCompanyCodeExportable, SapCustomerExportable, SapDeliveryExportable,
107 SapDeliveryHeader, SapDeliveryItem, SapDialect, SapExportConfig, SapExporter,
108 SapGlAccountCompanyCode, SapGlAccountExportable, SapGlAccountGeneral, SapMatDocExportable,
109 SapMatDocHeader, SapMatDocItem, SapMaterial, SapMaterialExportable, SapMaterialStorage,
110 SapMaterialStorageExportable, SapOpenItemRow, SapPoExportable, SapPoHeader, SapPoItem,
111 SapProfitCenter, SapProfitCenterExportable, SapSoExportable, SapSoHeader, SapSoItem,
112 SapTableType, SapVendor, SapVendorCompanyCode, SapVendorCompanyCodeExportable,
113 SapVendorExportable, XbrlExporter,
114};
115pub use json_sink::*;
116pub use parquet_sink::*;
117pub use project_accounting_export::*;
118pub use streaming::{
119 CsvStreamingSink, JsonStreamingSink, NdjsonStreamingSink, ParquetStreamingSink,
120};
121pub use tax_export::*;
122pub use treasury_export::*;
123
124#[cfg(test)]
125#[allow(clippy::unwrap_used)]
126mod test_helpers;
127
128#[cfg(test)]
129mod output_root_config_tests {
130 use super::*;
131 use std::path::Path;
132
133 #[test]
134 fn flat_mode_returns_root_dir() {
135 let cfg = OutputRootConfig::flat("/tmp/out");
136 assert_eq!(cfg.effective_dir(), Path::new("/tmp/out"));
137 assert!(!cfg.per_entity_subtree);
138 assert!(cfg.entity_code.is_none());
139 }
140
141 #[test]
142 fn per_entity_mode_routes_under_entities_code() {
143 let cfg = OutputRootConfig::per_entity("/tmp/out", "NESTLE_SA");
144 assert_eq!(
145 cfg.effective_dir(),
146 Path::new("/tmp/out/entities/NESTLE_SA")
147 );
148 assert!(cfg.per_entity_subtree);
149 assert_eq!(cfg.entity_code.as_deref(), Some("NESTLE_SA"));
150 }
151
152 #[test]
153 fn per_entity_true_without_code_falls_back_to_root() {
154 let cfg = OutputRootConfig {
158 root_dir: "/tmp/out".into(),
159 per_entity_subtree: true,
160 entity_code: None,
161 };
162 assert_eq!(cfg.effective_dir(), Path::new("/tmp/out"));
163 }
164
165 #[test]
166 fn default_is_flat_at_cwd() {
167 let cfg = OutputRootConfig::default();
168 assert_eq!(cfg.effective_dir(), Path::new("."));
169 assert!(!cfg.per_entity_subtree);
170 assert!(cfg.entity_code.is_none());
171 }
172
173 #[test]
174 fn root_path_always_returns_root_regardless_of_mode() {
175 let flat = OutputRootConfig::flat("/tmp/out");
179 assert_eq!(flat.root_path(), Path::new("/tmp/out"));
180
181 let per_entity = OutputRootConfig::per_entity("/tmp/out", "ENT_A");
182 assert_eq!(per_entity.root_path(), Path::new("/tmp/out"));
183 }
184}