1use chrono::NaiveDate;
8use rust_decimal::Decimal;
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct FrameworkDifferenceRecord {
15 pub difference_id: Uuid,
17
18 pub company_code: String,
20
21 pub period_date: NaiveDate,
23
24 pub difference_area: DifferenceArea,
26
27 pub source_reference: String,
29
30 pub description: String,
32
33 #[serde(with = "rust_decimal::serde::str")]
35 pub us_gaap_amount: Decimal,
36
37 #[serde(with = "rust_decimal::serde::str")]
39 pub ifrs_amount: Decimal,
40
41 #[serde(with = "rust_decimal::serde::str")]
43 pub difference_amount: Decimal,
44
45 pub us_gaap_classification: String,
47
48 pub ifrs_classification: String,
50
51 pub explanation: String,
53
54 pub difference_type: DifferenceType,
56
57 pub financial_statement_impact: FinancialStatementImpact,
59}
60
61impl FrameworkDifferenceRecord {
62 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
94#[serde(rename_all = "snake_case")]
95pub enum DifferenceArea {
96 RevenueRecognition,
98 LeaseAccounting,
100 InventoryCosting,
102 DevelopmentCosts,
104 PropertyRevaluation,
106 Impairment,
108 ContingentLiabilities,
110 ShareBasedPayment,
112 FinancialInstruments,
114 Consolidation,
116 JointArrangements,
118 IncomeTaxes,
120 PresentationDisclosure,
122 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
149#[serde(rename_all = "snake_case")]
150pub enum DifferenceType {
151 #[default]
153 Temporary,
154 Permanent,
156 Classification,
158 Measurement,
160 Timing,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, Default)]
166pub struct FinancialStatementImpact {
167 #[serde(with = "rust_decimal::serde::str")]
169 pub assets_impact: Decimal,
170 #[serde(with = "rust_decimal::serde::str")]
172 pub liabilities_impact: Decimal,
173 #[serde(with = "rust_decimal::serde::str")]
175 pub equity_impact: Decimal,
176 #[serde(with = "rust_decimal::serde::str")]
178 pub revenue_impact: Decimal,
179 #[serde(with = "rust_decimal::serde::str")]
181 pub expense_impact: Decimal,
182 #[serde(with = "rust_decimal::serde::str")]
184 pub net_income_impact: Decimal,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct FrameworkReconciliation {
190 pub company_code: String,
192
193 pub period_date: NaiveDate,
195
196 #[serde(with = "rust_decimal::serde::str")]
198 pub us_gaap_net_income: Decimal,
199
200 #[serde(with = "rust_decimal::serde::str")]
202 pub ifrs_net_income: Decimal,
203
204 #[serde(with = "rust_decimal::serde::str")]
206 pub us_gaap_equity: Decimal,
207
208 #[serde(with = "rust_decimal::serde::str")]
210 pub ifrs_equity: Decimal,
211
212 #[serde(with = "rust_decimal::serde::str")]
214 pub us_gaap_assets: Decimal,
215
216 #[serde(with = "rust_decimal::serde::str")]
218 pub ifrs_assets: Decimal,
219
220 pub reconciling_items: Vec<ReconcilingItem>,
222}
223
224impl FrameworkReconciliation {
225 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct ReconcilingItem {
261 pub description: String,
263
264 pub difference_area: DifferenceArea,
266
267 #[serde(with = "rust_decimal::serde::str")]
269 pub net_income_impact: Decimal,
270
271 #[serde(with = "rust_decimal::serde::str")]
273 pub equity_impact: Decimal,
274
275 #[serde(with = "rust_decimal::serde::str")]
277 pub asset_impact: Decimal,
278
279 #[serde(with = "rust_decimal::serde::str")]
281 pub liability_impact: Decimal,
282
283 pub explanation: String,
285}
286
287impl ReconcilingItem {
288 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, 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), dec!(100000), );
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 recon.reconciling_items.push(ReconcilingItem::new(
338 "Development cost capitalization",
339 DifferenceArea::DevelopmentCosts,
340 dec!(100000), ));
342
343 recon.reconciling_items.push(ReconcilingItem::new(
344 "Impairment reversal",
345 DifferenceArea::Impairment,
346 dec!(50000), ));
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}