datasynth_generators/period_close/cash_flow_enhancer.rs
1//! Cash flow enhancer — supplementary CashFlowItem entries from v2.3 data.
2//!
3//! The existing [`FinancialStatementGenerator`] produces a basic indirect-method
4//! cash flow statement. This module generates **additional** items (from
5//! manufacturing, treasury, tax, and dividend modules) that Task 10 merges into
6//! the financial statement's `cash_flow_items` vector.
7
8use datasynth_core::models::{CashFlowCategory, CashFlowItem};
9use rust_decimal::Decimal;
10
11/// Source data collected from upstream v2.3 generators.
12#[derive(Debug, Clone)]
13pub struct CashFlowSourceData {
14 /// Total depreciation / amortisation for the period (from depreciation runs).
15 pub depreciation_total: Decimal,
16 /// Net movement in provisions for the period (from provision generator).
17 pub provision_movements_net: Decimal,
18 /// Change in accounts-receivable balance (positive = AR increased).
19 pub delta_ar: Decimal,
20 /// Change in accounts-payable balance (positive = AP increased).
21 pub delta_ap: Decimal,
22 /// Change in inventory balance (positive = inventory increased).
23 pub delta_inventory: Decimal,
24 /// Capital expenditure paid in the period (non-negative).
25 pub capex: Decimal,
26 /// Proceeds from new debt issued in the period.
27 pub debt_issuance: Decimal,
28 /// Principal repaid on debt in the period (non-negative).
29 pub debt_repayment: Decimal,
30 /// Interest paid in cash during the period (non-negative).
31 pub interest_paid: Decimal,
32 /// Income tax paid in cash during the period (non-negative).
33 pub tax_paid: Decimal,
34 /// Dividends paid to shareholders in the period (non-negative).
35 pub dividends_paid: Decimal,
36 /// Accounting framework: `"US_GAAP"` or `"IFRS"`.
37 ///
38 /// Under US GAAP, interest paid is an Operating cash flow.
39 /// Under IFRS, interest paid is a Financing cash flow.
40 pub framework: String,
41}
42
43/// Generates supplementary cash flow items from v2.3 source data.
44pub struct CashFlowEnhancer;
45
46impl CashFlowEnhancer {
47 /// Build the supplementary [`CashFlowItem`] list.
48 ///
49 /// Items whose computed amount is zero are silently omitted so that the
50 /// merged statement is not cluttered with empty lines.
51 pub fn generate(data: &CashFlowSourceData) -> Vec<CashFlowItem> {
52 let is_ifrs = data.framework.eq_ignore_ascii_case("IFRS");
53 let mut items: Vec<CashFlowItem> = Vec::new();
54 let mut sort = 100u32; // start above the values the FS generator uses
55
56 // ------------------------------------------------------------------
57 // Operating activities — adjustments to net income
58 // ------------------------------------------------------------------
59
60 // Depreciation add-back (non-cash charge reversed back)
61 if !data.depreciation_total.is_zero() {
62 items.push(CashFlowItem {
63 item_code: "CF-DEP".to_string(),
64 label: "Depreciation and Amortisation".to_string(),
65 category: CashFlowCategory::Operating,
66 amount: data.depreciation_total,
67 amount_prior: None,
68 sort_order: sort,
69 is_total: false,
70 });
71 sort += 10;
72 }
73
74 // Provision movements (e.g., warranties, bad-debt allowances)
75 if !data.provision_movements_net.is_zero() {
76 items.push(CashFlowItem {
77 item_code: "CF-PROV".to_string(),
78 label: "Movement in Provisions".to_string(),
79 category: CashFlowCategory::Operating,
80 amount: data.provision_movements_net,
81 amount_prior: None,
82 sort_order: sort,
83 is_total: false,
84 });
85 sort += 10;
86 }
87
88 // ΔAR: increase in AR → cash has been tied up → outflow (negative)
89 let dar = -data.delta_ar;
90 if !dar.is_zero() {
91 items.push(CashFlowItem {
92 item_code: "CF-DAR".to_string(),
93 label: "Change in Trade Receivables".to_string(),
94 category: CashFlowCategory::Operating,
95 amount: dar,
96 amount_prior: None,
97 sort_order: sort,
98 is_total: false,
99 });
100 sort += 10;
101 }
102
103 // ΔAP: increase in AP → supplier financing received → inflow (positive)
104 if !data.delta_ap.is_zero() {
105 items.push(CashFlowItem {
106 item_code: "CF-DAP".to_string(),
107 label: "Change in Trade Payables".to_string(),
108 category: CashFlowCategory::Operating,
109 amount: data.delta_ap,
110 amount_prior: None,
111 sort_order: sort,
112 is_total: false,
113 });
114 sort += 10;
115 }
116
117 // ΔInventory: increase in inventory → cash consumed → outflow (negative)
118 let dinv = -data.delta_inventory;
119 if !dinv.is_zero() {
120 items.push(CashFlowItem {
121 item_code: "CF-DINV".to_string(),
122 label: "Change in Inventories".to_string(),
123 category: CashFlowCategory::Operating,
124 amount: dinv,
125 amount_prior: None,
126 sort_order: sort,
127 is_total: false,
128 });
129 sort += 10;
130 }
131
132 // Interest paid — Operating under US GAAP only
133 if !is_ifrs && !data.interest_paid.is_zero() {
134 items.push(CashFlowItem {
135 item_code: "CF-INT".to_string(),
136 label: "Interest Paid".to_string(),
137 category: CashFlowCategory::Operating,
138 amount: -data.interest_paid.abs(),
139 amount_prior: None,
140 sort_order: sort,
141 is_total: false,
142 });
143 sort += 10;
144 }
145
146 // Tax paid (always Operating under both frameworks)
147 if !data.tax_paid.is_zero() {
148 items.push(CashFlowItem {
149 item_code: "CF-TAX".to_string(),
150 label: "Income Tax Paid".to_string(),
151 category: CashFlowCategory::Operating,
152 amount: -data.tax_paid.abs(),
153 amount_prior: None,
154 sort_order: sort,
155 is_total: false,
156 });
157 sort += 10;
158 }
159
160 // ------------------------------------------------------------------
161 // Investing activities
162 // ------------------------------------------------------------------
163
164 if !data.capex.is_zero() {
165 items.push(CashFlowItem {
166 item_code: "CF-CAPEX".to_string(),
167 label: "Purchase of Property, Plant and Equipment".to_string(),
168 category: CashFlowCategory::Investing,
169 amount: -data.capex.abs(),
170 amount_prior: None,
171 sort_order: sort,
172 is_total: false,
173 });
174 sort += 10;
175 }
176
177 // ------------------------------------------------------------------
178 // Financing activities
179 // ------------------------------------------------------------------
180
181 if !data.debt_issuance.is_zero() {
182 items.push(CashFlowItem {
183 item_code: "CF-DEBT-IN".to_string(),
184 label: "Proceeds from Borrowings".to_string(),
185 category: CashFlowCategory::Financing,
186 amount: data.debt_issuance,
187 amount_prior: None,
188 sort_order: sort,
189 is_total: false,
190 });
191 sort += 10;
192 }
193
194 if !data.debt_repayment.is_zero() {
195 items.push(CashFlowItem {
196 item_code: "CF-DEBT-OUT".to_string(),
197 label: "Repayment of Borrowings".to_string(),
198 category: CashFlowCategory::Financing,
199 amount: -data.debt_repayment.abs(),
200 amount_prior: None,
201 sort_order: sort,
202 is_total: false,
203 });
204 sort += 10;
205 }
206
207 // Interest paid — Financing under IFRS only
208 if is_ifrs && !data.interest_paid.is_zero() {
209 items.push(CashFlowItem {
210 item_code: "CF-INT-FIN".to_string(),
211 label: "Interest Paid".to_string(),
212 category: CashFlowCategory::Financing,
213 amount: -data.interest_paid.abs(),
214 amount_prior: None,
215 sort_order: sort,
216 is_total: false,
217 });
218 sort += 10;
219 }
220
221 if !data.dividends_paid.is_zero() {
222 items.push(CashFlowItem {
223 item_code: "CF-DIV".to_string(),
224 label: "Dividends Paid".to_string(),
225 category: CashFlowCategory::Financing,
226 amount: -data.dividends_paid.abs(),
227 amount_prior: None,
228 sort_order: sort,
229 is_total: false,
230 });
231 }
232
233 items
234 }
235}