use chrono::NaiveDate;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrameworkDifferenceRecord {
pub difference_id: Uuid,
pub company_code: String,
pub period_date: NaiveDate,
pub difference_area: DifferenceArea,
pub source_reference: String,
pub description: String,
#[serde(with = "datasynth_core::serde_decimal")]
pub us_gaap_amount: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub ifrs_amount: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub difference_amount: Decimal,
pub us_gaap_classification: String,
pub ifrs_classification: String,
pub explanation: String,
pub difference_type: DifferenceType,
pub financial_statement_impact: FinancialStatementImpact,
}
impl FrameworkDifferenceRecord {
pub fn new(
company_code: impl Into<String>,
period_date: NaiveDate,
difference_area: DifferenceArea,
source_reference: impl Into<String>,
description: impl Into<String>,
us_gaap_amount: Decimal,
ifrs_amount: Decimal,
) -> Self {
let difference_amount = ifrs_amount - us_gaap_amount;
Self {
difference_id: Uuid::now_v7(),
company_code: company_code.into(),
period_date,
difference_area,
source_reference: source_reference.into(),
description: description.into(),
us_gaap_amount,
ifrs_amount,
difference_amount,
us_gaap_classification: String::new(),
ifrs_classification: String::new(),
explanation: String::new(),
difference_type: DifferenceType::Temporary,
financial_statement_impact: FinancialStatementImpact::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DifferenceArea {
RevenueRecognition,
LeaseAccounting,
InventoryCosting,
DevelopmentCosts,
PropertyRevaluation,
Impairment,
ContingentLiabilities,
ShareBasedPayment,
FinancialInstruments,
Consolidation,
JointArrangements,
IncomeTaxes,
PresentationDisclosure,
Other,
}
impl std::fmt::Display for DifferenceArea {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RevenueRecognition => write!(f, "Revenue Recognition"),
Self::LeaseAccounting => write!(f, "Lease Accounting"),
Self::InventoryCosting => write!(f, "Inventory Costing"),
Self::DevelopmentCosts => write!(f, "Development Costs"),
Self::PropertyRevaluation => write!(f, "Property Revaluation"),
Self::Impairment => write!(f, "Impairment"),
Self::ContingentLiabilities => write!(f, "Contingent Liabilities"),
Self::ShareBasedPayment => write!(f, "Share-Based Payment"),
Self::FinancialInstruments => write!(f, "Financial Instruments"),
Self::Consolidation => write!(f, "Consolidation"),
Self::JointArrangements => write!(f, "Joint Arrangements"),
Self::IncomeTaxes => write!(f, "Income Taxes"),
Self::PresentationDisclosure => write!(f, "Presentation & Disclosure"),
Self::Other => write!(f, "Other"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum DifferenceType {
#[default]
Temporary,
Permanent,
Classification,
Measurement,
Timing,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FinancialStatementImpact {
#[serde(with = "datasynth_core::serde_decimal")]
pub assets_impact: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub liabilities_impact: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub equity_impact: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub revenue_impact: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub expense_impact: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub net_income_impact: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrameworkReconciliation {
pub company_code: String,
pub period_date: NaiveDate,
#[serde(with = "datasynth_core::serde_decimal")]
pub us_gaap_net_income: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub ifrs_net_income: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub us_gaap_equity: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub ifrs_equity: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub us_gaap_assets: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub ifrs_assets: Decimal,
pub reconciling_items: Vec<ReconcilingItem>,
}
impl FrameworkReconciliation {
pub fn new(company_code: impl Into<String>, period_date: NaiveDate) -> Self {
Self {
company_code: company_code.into(),
period_date,
us_gaap_net_income: Decimal::ZERO,
ifrs_net_income: Decimal::ZERO,
us_gaap_equity: Decimal::ZERO,
ifrs_equity: Decimal::ZERO,
us_gaap_assets: Decimal::ZERO,
ifrs_assets: Decimal::ZERO,
reconciling_items: Vec::new(),
}
}
pub fn calculate_totals(&mut self) {
let mut income_adjustment = Decimal::ZERO;
let mut equity_adjustment = Decimal::ZERO;
let mut asset_adjustment = Decimal::ZERO;
for item in &self.reconciling_items {
income_adjustment += item.net_income_impact;
equity_adjustment += item.equity_impact;
asset_adjustment += item.asset_impact;
}
self.ifrs_net_income = self.us_gaap_net_income + income_adjustment;
self.ifrs_equity = self.us_gaap_equity + equity_adjustment;
self.ifrs_assets = self.us_gaap_assets + asset_adjustment;
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReconcilingItem {
pub description: String,
pub difference_area: DifferenceArea,
#[serde(with = "datasynth_core::serde_decimal")]
pub net_income_impact: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub equity_impact: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub asset_impact: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub liability_impact: Decimal,
pub explanation: String,
}
impl ReconcilingItem {
pub fn new(
description: impl Into<String>,
difference_area: DifferenceArea,
net_income_impact: Decimal,
) -> Self {
Self {
description: description.into(),
difference_area,
net_income_impact,
equity_impact: net_income_impact,
asset_impact: Decimal::ZERO,
liability_impact: Decimal::ZERO,
explanation: String::new(),
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_framework_difference_record() {
let record = FrameworkDifferenceRecord::new(
"1000",
NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
DifferenceArea::DevelopmentCosts,
"RD001",
"Software development costs",
dec!(0), dec!(100000), );
assert_eq!(record.difference_amount, dec!(100000));
assert_eq!(record.difference_area, DifferenceArea::DevelopmentCosts);
}
#[test]
fn test_framework_reconciliation() {
let mut recon =
FrameworkReconciliation::new("1000", NaiveDate::from_ymd_opt(2024, 12, 31).unwrap());
recon.us_gaap_net_income = dec!(1000000);
recon.us_gaap_equity = dec!(5000000);
recon.us_gaap_assets = dec!(10000000);
recon.reconciling_items.push(ReconcilingItem::new(
"Development cost capitalization",
DifferenceArea::DevelopmentCosts,
dec!(100000), ));
recon.reconciling_items.push(ReconcilingItem::new(
"Impairment reversal",
DifferenceArea::Impairment,
dec!(50000), ));
recon.calculate_totals();
assert_eq!(recon.ifrs_net_income, dec!(1150000));
assert_eq!(recon.ifrs_equity, dec!(5150000));
}
}