Skip to main content

datasynth_standards/accounting/
differences.rs

1//! Framework Differences Tracking for Dual Reporting.
2//!
3//! Provides structures for tracking and reporting differences between
4//! US GAAP and IFRS accounting treatments for the same transactions.
5//! Used when generating synthetic data for dual-reporting scenarios.
6
7use chrono::NaiveDate;
8use rust_decimal::Decimal;
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12/// Framework difference record for dual reporting.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct FrameworkDifferenceRecord {
15    /// Unique difference identifier.
16    pub difference_id: Uuid,
17
18    /// Company code.
19    pub company_code: String,
20
21    /// Reporting period end date.
22    pub period_date: NaiveDate,
23
24    /// Area of accounting difference.
25    pub difference_area: DifferenceArea,
26
27    /// Reference to source transaction/item.
28    pub source_reference: String,
29
30    /// Description of the item.
31    pub description: String,
32
33    /// US GAAP amount.
34    #[serde(with = "rust_decimal::serde::str")]
35    pub us_gaap_amount: Decimal,
36
37    /// IFRS amount.
38    #[serde(with = "rust_decimal::serde::str")]
39    pub ifrs_amount: Decimal,
40
41    /// Difference (IFRS - US GAAP).
42    #[serde(with = "rust_decimal::serde::str")]
43    pub difference_amount: Decimal,
44
45    /// US GAAP account classification.
46    pub us_gaap_classification: String,
47
48    /// IFRS account classification.
49    pub ifrs_classification: String,
50
51    /// Explanation of the difference.
52    pub explanation: String,
53
54    /// Whether this is a permanent or temporary difference.
55    pub difference_type: DifferenceType,
56
57    /// Impact on financial statements.
58    pub financial_statement_impact: FinancialStatementImpact,
59}
60
61impl FrameworkDifferenceRecord {
62    /// Create a new framework difference record.
63    pub fn new(
64        company_code: impl Into<String>,
65        period_date: NaiveDate,
66        difference_area: DifferenceArea,
67        source_reference: impl Into<String>,
68        description: impl Into<String>,
69        us_gaap_amount: Decimal,
70        ifrs_amount: Decimal,
71    ) -> Self {
72        let difference_amount = ifrs_amount - us_gaap_amount;
73        Self {
74            difference_id: Uuid::now_v7(),
75            company_code: company_code.into(),
76            period_date,
77            difference_area,
78            source_reference: source_reference.into(),
79            description: description.into(),
80            us_gaap_amount,
81            ifrs_amount,
82            difference_amount,
83            us_gaap_classification: String::new(),
84            ifrs_classification: String::new(),
85            explanation: String::new(),
86            difference_type: DifferenceType::Temporary,
87            financial_statement_impact: FinancialStatementImpact::default(),
88        }
89    }
90}
91
92/// Area of accounting where framework differences occur.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
94#[serde(rename_all = "snake_case")]
95pub enum DifferenceArea {
96    /// Revenue recognition differences.
97    RevenueRecognition,
98    /// Lease classification and measurement.
99    LeaseAccounting,
100    /// Inventory costing method (LIFO).
101    InventoryCosting,
102    /// Development cost capitalization.
103    DevelopmentCosts,
104    /// PPE revaluation.
105    PropertyRevaluation,
106    /// Impairment and reversal.
107    Impairment,
108    /// Contingent liabilities threshold.
109    ContingentLiabilities,
110    /// Share-based payment measurement.
111    ShareBasedPayment,
112    /// Financial instrument classification.
113    FinancialInstruments,
114    /// Consolidation scope.
115    Consolidation,
116    /// Joint arrangement classification.
117    JointArrangements,
118    /// Income taxes.
119    IncomeTaxes,
120    /// Presentation and disclosure.
121    PresentationDisclosure,
122    /// Other differences.
123    Other,
124}
125
126impl std::fmt::Display for DifferenceArea {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        match self {
129            Self::RevenueRecognition => write!(f, "Revenue Recognition"),
130            Self::LeaseAccounting => write!(f, "Lease Accounting"),
131            Self::InventoryCosting => write!(f, "Inventory Costing"),
132            Self::DevelopmentCosts => write!(f, "Development Costs"),
133            Self::PropertyRevaluation => write!(f, "Property Revaluation"),
134            Self::Impairment => write!(f, "Impairment"),
135            Self::ContingentLiabilities => write!(f, "Contingent Liabilities"),
136            Self::ShareBasedPayment => write!(f, "Share-Based Payment"),
137            Self::FinancialInstruments => write!(f, "Financial Instruments"),
138            Self::Consolidation => write!(f, "Consolidation"),
139            Self::JointArrangements => write!(f, "Joint Arrangements"),
140            Self::IncomeTaxes => write!(f, "Income Taxes"),
141            Self::PresentationDisclosure => write!(f, "Presentation & Disclosure"),
142            Self::Other => write!(f, "Other"),
143        }
144    }
145}
146
147/// Type of accounting difference.
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
149#[serde(rename_all = "snake_case")]
150pub enum DifferenceType {
151    /// Temporary difference that will reverse over time.
152    #[default]
153    Temporary,
154    /// Permanent difference that will not reverse.
155    Permanent,
156    /// Classification difference (same total, different line items).
157    Classification,
158    /// Measurement difference (different amounts).
159    Measurement,
160    /// Timing difference (same amount, different period).
161    Timing,
162}
163
164/// Impact on specific financial statements.
165#[derive(Debug, Clone, Serialize, Deserialize, Default)]
166pub struct FinancialStatementImpact {
167    /// Impact on balance sheet assets.
168    #[serde(with = "rust_decimal::serde::str")]
169    pub assets_impact: Decimal,
170    /// Impact on balance sheet liabilities.
171    #[serde(with = "rust_decimal::serde::str")]
172    pub liabilities_impact: Decimal,
173    /// Impact on equity.
174    #[serde(with = "rust_decimal::serde::str")]
175    pub equity_impact: Decimal,
176    /// Impact on revenue.
177    #[serde(with = "rust_decimal::serde::str")]
178    pub revenue_impact: Decimal,
179    /// Impact on expenses.
180    #[serde(with = "rust_decimal::serde::str")]
181    pub expense_impact: Decimal,
182    /// Impact on net income.
183    #[serde(with = "rust_decimal::serde::str")]
184    pub net_income_impact: Decimal,
185}
186
187/// Reconciliation summary between frameworks.
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct FrameworkReconciliation {
190    /// Company code.
191    pub company_code: String,
192
193    /// Period end date.
194    pub period_date: NaiveDate,
195
196    /// US GAAP net income.
197    #[serde(with = "rust_decimal::serde::str")]
198    pub us_gaap_net_income: Decimal,
199
200    /// IFRS net income.
201    #[serde(with = "rust_decimal::serde::str")]
202    pub ifrs_net_income: Decimal,
203
204    /// US GAAP total equity.
205    #[serde(with = "rust_decimal::serde::str")]
206    pub us_gaap_equity: Decimal,
207
208    /// IFRS total equity.
209    #[serde(with = "rust_decimal::serde::str")]
210    pub ifrs_equity: Decimal,
211
212    /// US GAAP total assets.
213    #[serde(with = "rust_decimal::serde::str")]
214    pub us_gaap_assets: Decimal,
215
216    /// IFRS total assets.
217    #[serde(with = "rust_decimal::serde::str")]
218    pub ifrs_assets: Decimal,
219
220    /// Reconciling items.
221    pub reconciling_items: Vec<ReconcilingItem>,
222}
223
224impl FrameworkReconciliation {
225    /// Create a new reconciliation.
226    pub fn new(company_code: impl Into<String>, period_date: NaiveDate) -> Self {
227        Self {
228            company_code: company_code.into(),
229            period_date,
230            us_gaap_net_income: Decimal::ZERO,
231            ifrs_net_income: Decimal::ZERO,
232            us_gaap_equity: Decimal::ZERO,
233            ifrs_equity: Decimal::ZERO,
234            us_gaap_assets: Decimal::ZERO,
235            ifrs_assets: Decimal::ZERO,
236            reconciling_items: Vec::new(),
237        }
238    }
239
240    /// Calculate totals from reconciling items.
241    pub fn calculate_totals(&mut self) {
242        let mut income_adjustment = Decimal::ZERO;
243        let mut equity_adjustment = Decimal::ZERO;
244        let mut asset_adjustment = Decimal::ZERO;
245
246        for item in &self.reconciling_items {
247            income_adjustment += item.net_income_impact;
248            equity_adjustment += item.equity_impact;
249            asset_adjustment += item.asset_impact;
250        }
251
252        self.ifrs_net_income = self.us_gaap_net_income + income_adjustment;
253        self.ifrs_equity = self.us_gaap_equity + equity_adjustment;
254        self.ifrs_assets = self.us_gaap_assets + asset_adjustment;
255    }
256}
257
258/// Individual reconciling item.
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct ReconcilingItem {
261    /// Description of the adjustment.
262    pub description: String,
263
264    /// Difference area.
265    pub difference_area: DifferenceArea,
266
267    /// Impact on net income.
268    #[serde(with = "rust_decimal::serde::str")]
269    pub net_income_impact: Decimal,
270
271    /// Impact on equity.
272    #[serde(with = "rust_decimal::serde::str")]
273    pub equity_impact: Decimal,
274
275    /// Impact on assets.
276    #[serde(with = "rust_decimal::serde::str")]
277    pub asset_impact: Decimal,
278
279    /// Impact on liabilities.
280    #[serde(with = "rust_decimal::serde::str")]
281    pub liability_impact: Decimal,
282
283    /// Detailed explanation.
284    pub explanation: String,
285}
286
287impl ReconcilingItem {
288    /// Create a new reconciling item.
289    pub fn new(
290        description: impl Into<String>,
291        difference_area: DifferenceArea,
292        net_income_impact: Decimal,
293    ) -> Self {
294        Self {
295            description: description.into(),
296            difference_area,
297            net_income_impact,
298            equity_impact: net_income_impact, // Simplified: income flows to equity
299            asset_impact: Decimal::ZERO,
300            liability_impact: Decimal::ZERO,
301            explanation: String::new(),
302        }
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309    use rust_decimal_macros::dec;
310
311    #[test]
312    fn test_framework_difference_record() {
313        let record = FrameworkDifferenceRecord::new(
314            "1000",
315            NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
316            DifferenceArea::DevelopmentCosts,
317            "RD001",
318            "Software development costs",
319            dec!(0),      // US GAAP: expensed
320            dec!(100000), // IFRS: capitalized
321        );
322
323        assert_eq!(record.difference_amount, dec!(100000));
324        assert_eq!(record.difference_area, DifferenceArea::DevelopmentCosts);
325    }
326
327    #[test]
328    fn test_framework_reconciliation() {
329        let mut recon =
330            FrameworkReconciliation::new("1000", NaiveDate::from_ymd_opt(2024, 12, 31).unwrap());
331
332        recon.us_gaap_net_income = dec!(1000000);
333        recon.us_gaap_equity = dec!(5000000);
334        recon.us_gaap_assets = dec!(10000000);
335
336        // Add reconciling items
337        recon.reconciling_items.push(ReconcilingItem::new(
338            "Development cost capitalization",
339            DifferenceArea::DevelopmentCosts,
340            dec!(100000), // Higher income under IFRS
341        ));
342
343        recon.reconciling_items.push(ReconcilingItem::new(
344            "Impairment reversal",
345            DifferenceArea::Impairment,
346            dec!(50000), // Reversal permitted under IFRS
347        ));
348
349        recon.calculate_totals();
350
351        assert_eq!(recon.ifrs_net_income, dec!(1150000));
352        assert_eq!(recon.ifrs_equity, dec!(5150000));
353    }
354}