1#![cfg_attr(not(test), 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)]
125mod test_helpers;
126
127#[cfg(test)]
128mod output_root_config_tests {
129 use super::*;
130 use std::path::Path;
131
132 #[test]
133 fn flat_mode_returns_root_dir() {
134 let cfg = OutputRootConfig::flat("/tmp/out");
135 assert_eq!(cfg.effective_dir(), Path::new("/tmp/out"));
136 assert!(!cfg.per_entity_subtree);
137 assert!(cfg.entity_code.is_none());
138 }
139
140 #[test]
141 fn per_entity_mode_routes_under_entities_code() {
142 let cfg = OutputRootConfig::per_entity("/tmp/out", "ACME_SA");
143 assert_eq!(cfg.effective_dir(), Path::new("/tmp/out/entities/ACME_SA"));
144 assert!(cfg.per_entity_subtree);
145 assert_eq!(cfg.entity_code.as_deref(), Some("ACME_SA"));
146 }
147
148 #[test]
149 fn per_entity_true_without_code_falls_back_to_root() {
150 let cfg = OutputRootConfig {
154 root_dir: "/tmp/out".into(),
155 per_entity_subtree: true,
156 entity_code: None,
157 };
158 assert_eq!(cfg.effective_dir(), Path::new("/tmp/out"));
159 }
160
161 #[test]
162 fn default_is_flat_at_cwd() {
163 let cfg = OutputRootConfig::default();
164 assert_eq!(cfg.effective_dir(), Path::new("."));
165 assert!(!cfg.per_entity_subtree);
166 assert!(cfg.entity_code.is_none());
167 }
168
169 #[test]
170 fn root_path_always_returns_root_regardless_of_mode() {
171 let flat = OutputRootConfig::flat("/tmp/out");
175 assert_eq!(flat.root_path(), Path::new("/tmp/out"));
176
177 let per_entity = OutputRootConfig::per_entity("/tmp/out", "ENT_A");
178 assert_eq!(per_entity.root_path(), Path::new("/tmp/out"));
179 }
180}