use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum AccountingFramework {
#[default]
UsGaap,
Ifrs,
DualReporting,
FrenchGaap,
GermanGaap,
}
impl AccountingFramework {
pub fn revenue_standard(&self) -> &'static str {
match self {
Self::UsGaap => "ASC 606",
Self::Ifrs => "IFRS 15",
Self::DualReporting => "ASC 606 / IFRS 15",
Self::FrenchGaap => "PCG / ANC (IFRS 15 aligned)",
Self::GermanGaap => "HGB §277 / BilRUG",
}
}
pub fn lease_standard(&self) -> &'static str {
match self {
Self::UsGaap => "ASC 842",
Self::Ifrs => "IFRS 16",
Self::DualReporting => "ASC 842 / IFRS 16",
Self::FrenchGaap => "PCG / ANC (IFRS 16 aligned)",
Self::GermanGaap => "HGB / BMF-Leasingerlasse",
}
}
pub fn fair_value_standard(&self) -> &'static str {
match self {
Self::UsGaap => "ASC 820",
Self::Ifrs => "IFRS 13",
Self::DualReporting => "ASC 820 / IFRS 13",
Self::FrenchGaap => "PCG / ANC (IFRS 13 aligned)",
Self::GermanGaap => "HGB §253 / IDW RS HFA 10",
}
}
pub fn impairment_standard(&self) -> &'static str {
match self {
Self::UsGaap => "ASC 360",
Self::Ifrs => "IAS 36",
Self::DualReporting => "ASC 360 / IAS 36",
Self::FrenchGaap => "PCG / ANC (IAS 36 aligned)",
Self::GermanGaap => "HGB §253(3)-(5)",
}
}
pub fn allows_lifo(&self) -> bool {
matches!(self, Self::UsGaap)
}
pub fn requires_development_capitalization(&self) -> bool {
matches!(self, Self::Ifrs | Self::DualReporting | Self::FrenchGaap)
}
pub fn allows_ppe_revaluation(&self) -> bool {
matches!(self, Self::Ifrs | Self::DualReporting)
}
pub fn allows_impairment_reversal(&self) -> bool {
matches!(
self,
Self::Ifrs | Self::DualReporting | Self::FrenchGaap | Self::GermanGaap
)
}
pub fn uses_brightline_lease_tests(&self) -> bool {
matches!(self, Self::UsGaap)
}
pub fn requires_pending_loss_provisions(&self) -> bool {
matches!(self, Self::GermanGaap)
}
pub fn allows_low_value_asset_expensing(&self) -> bool {
matches!(self, Self::GermanGaap)
}
pub fn operating_leases_off_balance(&self) -> bool {
matches!(self, Self::GermanGaap)
}
}
impl std::fmt::Display for AccountingFramework {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UsGaap => write!(f, "US GAAP"),
Self::Ifrs => write!(f, "IFRS"),
Self::DualReporting => write!(f, "Dual Reporting (US GAAP & IFRS)"),
Self::FrenchGaap => write!(f, "French GAAP (PCG)"),
Self::GermanGaap => write!(f, "German GAAP (HGB)"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrameworkSettings {
pub framework: AccountingFramework,
#[serde(default)]
pub use_lifo_inventory: bool,
#[serde(default)]
pub capitalize_development_costs: bool,
#[serde(default)]
pub use_ppe_revaluation: bool,
#[serde(default)]
pub allow_impairment_reversal: bool,
#[serde(default = "default_lease_term_threshold")]
pub lease_term_threshold: f64,
#[serde(default = "default_lease_pv_threshold")]
pub lease_pv_threshold: f64,
#[serde(default = "default_incremental_borrowing_rate")]
pub default_incremental_borrowing_rate: f64,
#[serde(default = "default_variable_consideration_constraint")]
pub variable_consideration_constraint: f64,
}
fn default_lease_term_threshold() -> f64 {
0.75
}
fn default_lease_pv_threshold() -> f64 {
0.90
}
fn default_incremental_borrowing_rate() -> f64 {
0.05
}
fn default_variable_consideration_constraint() -> f64 {
0.80
}
impl Default for FrameworkSettings {
fn default() -> Self {
Self {
framework: AccountingFramework::default(),
use_lifo_inventory: false,
capitalize_development_costs: false,
use_ppe_revaluation: false,
allow_impairment_reversal: false,
lease_term_threshold: default_lease_term_threshold(),
lease_pv_threshold: default_lease_pv_threshold(),
default_incremental_borrowing_rate: default_incremental_borrowing_rate(),
variable_consideration_constraint: default_variable_consideration_constraint(),
}
}
}
impl FrameworkSettings {
pub fn us_gaap() -> Self {
Self {
framework: AccountingFramework::UsGaap,
use_lifo_inventory: false, capitalize_development_costs: false,
use_ppe_revaluation: false,
allow_impairment_reversal: false,
..Default::default()
}
}
pub fn ifrs() -> Self {
Self {
framework: AccountingFramework::Ifrs,
use_lifo_inventory: false, capitalize_development_costs: true, use_ppe_revaluation: false, allow_impairment_reversal: true, ..Default::default()
}
}
pub fn dual_reporting() -> Self {
Self {
framework: AccountingFramework::DualReporting,
use_lifo_inventory: false,
capitalize_development_costs: true,
use_ppe_revaluation: false,
allow_impairment_reversal: true,
..Default::default()
}
}
pub fn french_gaap() -> Self {
Self {
framework: AccountingFramework::FrenchGaap,
use_lifo_inventory: false, capitalize_development_costs: true, use_ppe_revaluation: false, allow_impairment_reversal: true, ..Default::default()
}
}
pub fn german_gaap() -> Self {
Self {
framework: AccountingFramework::GermanGaap,
use_lifo_inventory: false, capitalize_development_costs: false, use_ppe_revaluation: false, allow_impairment_reversal: true, ..Default::default()
}
}
pub fn validate(&self) -> Result<(), FrameworkValidationError> {
if self.use_lifo_inventory
&& matches!(
self.framework,
AccountingFramework::Ifrs
| AccountingFramework::FrenchGaap
| AccountingFramework::GermanGaap
)
{
return Err(FrameworkValidationError::LifoNotPermittedUnderIfrs);
}
if self.use_ppe_revaluation && self.framework == AccountingFramework::UsGaap {
return Err(FrameworkValidationError::RevaluationNotPermittedUnderUsGaap);
}
if self.allow_impairment_reversal && self.framework == AccountingFramework::UsGaap {
return Err(FrameworkValidationError::ImpairmentReversalNotPermittedUnderUsGaap);
}
if !(0.0..=1.0).contains(&self.lease_term_threshold) {
return Err(FrameworkValidationError::InvalidThreshold(
"lease_term_threshold".to_string(),
));
}
if !(0.0..=1.0).contains(&self.lease_pv_threshold) {
return Err(FrameworkValidationError::InvalidThreshold(
"lease_pv_threshold".to_string(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum FrameworkValidationError {
#[error("LIFO inventory costing is not permitted under IFRS or French GAAP")]
LifoNotPermittedUnderIfrs,
#[error("PPE revaluation above cost is not permitted under US GAAP")]
RevaluationNotPermittedUnderUsGaap,
#[error("Reversal of impairment losses is not permitted under US GAAP")]
ImpairmentReversalNotPermittedUnderUsGaap,
#[error("Invalid threshold value for {0}: must be between 0.0 and 1.0")]
InvalidThreshold(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrameworkDifference {
pub area: String,
pub us_gaap_treatment: String,
pub ifrs_treatment: String,
pub typically_material: bool,
pub us_gaap_reference: String,
pub ifrs_reference: String,
}
impl FrameworkDifference {
pub fn common_differences() -> Vec<Self> {
vec![
Self {
area: "Inventory Costing".to_string(),
us_gaap_treatment: "LIFO, FIFO, and weighted average permitted".to_string(),
ifrs_treatment: "LIFO prohibited; FIFO and weighted average permitted".to_string(),
typically_material: true,
us_gaap_reference: "ASC 330".to_string(),
ifrs_reference: "IAS 2".to_string(),
},
Self {
area: "Development Costs".to_string(),
us_gaap_treatment: "Generally expensed as incurred".to_string(),
ifrs_treatment: "Capitalized when specified criteria are met".to_string(),
typically_material: true,
us_gaap_reference: "ASC 730".to_string(),
ifrs_reference: "IAS 38".to_string(),
},
Self {
area: "Property, Plant & Equipment".to_string(),
us_gaap_treatment: "Cost model only; no revaluation above cost".to_string(),
ifrs_treatment: "Cost model or revaluation model permitted".to_string(),
typically_material: true,
us_gaap_reference: "ASC 360".to_string(),
ifrs_reference: "IAS 16".to_string(),
},
Self {
area: "Impairment Reversal".to_string(),
us_gaap_treatment: "Not permitted for most assets".to_string(),
ifrs_treatment: "Permitted except for goodwill".to_string(),
typically_material: true,
us_gaap_reference: "ASC 360".to_string(),
ifrs_reference: "IAS 36".to_string(),
},
Self {
area: "Lease Classification".to_string(),
us_gaap_treatment: "Bright-line tests (75% term, 90% PV)".to_string(),
ifrs_treatment: "Principles-based; transfer of risks and rewards".to_string(),
typically_material: false,
us_gaap_reference: "ASC 842".to_string(),
ifrs_reference: "IFRS 16".to_string(),
},
Self {
area: "Contingent Liabilities".to_string(),
us_gaap_treatment: "Recognized when probable (>75%) and estimable".to_string(),
ifrs_treatment: "Recognized when probable (>50%) and estimable".to_string(),
typically_material: true,
us_gaap_reference: "ASC 450".to_string(),
ifrs_reference: "IAS 37".to_string(),
},
]
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_framework_defaults() {
let framework = AccountingFramework::default();
assert_eq!(framework, AccountingFramework::UsGaap);
}
#[test]
fn test_framework_standards() {
assert_eq!(AccountingFramework::UsGaap.revenue_standard(), "ASC 606");
assert_eq!(AccountingFramework::Ifrs.revenue_standard(), "IFRS 15");
assert_eq!(AccountingFramework::UsGaap.lease_standard(), "ASC 842");
assert_eq!(AccountingFramework::Ifrs.lease_standard(), "IFRS 16");
assert!(AccountingFramework::FrenchGaap
.revenue_standard()
.contains("PCG"));
}
#[test]
fn test_framework_features() {
assert!(AccountingFramework::UsGaap.allows_lifo());
assert!(!AccountingFramework::Ifrs.allows_lifo());
assert!(!AccountingFramework::FrenchGaap.allows_lifo());
assert!(!AccountingFramework::UsGaap.allows_ppe_revaluation());
assert!(AccountingFramework::Ifrs.allows_ppe_revaluation());
assert!(!AccountingFramework::UsGaap.allows_impairment_reversal());
assert!(AccountingFramework::Ifrs.allows_impairment_reversal());
assert!(AccountingFramework::FrenchGaap.allows_impairment_reversal());
}
#[test]
fn test_french_gaap_settings() {
let settings = FrameworkSettings::french_gaap();
assert!(settings.validate().is_ok());
assert_eq!(settings.framework, AccountingFramework::FrenchGaap);
}
#[test]
fn test_german_gaap_standards() {
let fw = AccountingFramework::GermanGaap;
assert_eq!(fw.revenue_standard(), "HGB §277 / BilRUG");
assert_eq!(fw.lease_standard(), "HGB / BMF-Leasingerlasse");
assert_eq!(fw.fair_value_standard(), "HGB §253 / IDW RS HFA 10");
assert_eq!(fw.impairment_standard(), "HGB §253(3)-(5)");
}
#[test]
fn test_german_gaap_features() {
let fw = AccountingFramework::GermanGaap;
assert!(!fw.allows_lifo(), "LIFO prohibited under HGB since BilMoG");
assert!(
!fw.allows_ppe_revaluation(),
"Strict Anschaffungskostenprinzip"
);
assert!(fw.allows_impairment_reversal(), "Mandatory per §253(5)");
assert!(!fw.uses_brightline_lease_tests(), "BMF uses 40-90% test");
assert!(fw.requires_pending_loss_provisions(), "§249(1) HGB");
assert!(fw.allows_low_value_asset_expensing(), "GWG ≤ 800 EUR");
assert!(fw.operating_leases_off_balance(), "BMF-Leasingerlasse");
assert!(
!fw.requires_development_capitalization(),
"§248(2) optional"
);
}
#[test]
fn test_german_gaap_settings() {
let settings = FrameworkSettings::german_gaap();
assert!(settings.validate().is_ok());
assert_eq!(settings.framework, AccountingFramework::GermanGaap);
assert!(!settings.use_lifo_inventory);
assert!(!settings.capitalize_development_costs);
assert!(!settings.use_ppe_revaluation);
assert!(settings.allow_impairment_reversal);
}
#[test]
fn test_german_gaap_lifo_validation_fails() {
let mut settings = FrameworkSettings::german_gaap();
settings.use_lifo_inventory = true;
assert!(matches!(
settings.validate(),
Err(FrameworkValidationError::LifoNotPermittedUnderIfrs)
));
}
#[test]
fn test_german_gaap_serde_roundtrip() {
let framework = AccountingFramework::GermanGaap;
let json = serde_json::to_string(&framework).unwrap();
assert_eq!(json, "\"german_gaap\"");
let deserialized: AccountingFramework = serde_json::from_str(&json).unwrap();
assert_eq!(framework, deserialized);
}
#[test]
fn test_settings_validation_us_gaap() {
let settings = FrameworkSettings::us_gaap();
assert!(settings.validate().is_ok());
}
#[test]
fn test_settings_validation_ifrs() {
let settings = FrameworkSettings::ifrs();
assert!(settings.validate().is_ok());
}
#[test]
fn test_settings_validation_lifo_under_ifrs() {
let mut settings = FrameworkSettings::ifrs();
settings.use_lifo_inventory = true;
assert!(matches!(
settings.validate(),
Err(FrameworkValidationError::LifoNotPermittedUnderIfrs)
));
}
#[test]
fn test_settings_validation_revaluation_under_us_gaap() {
let mut settings = FrameworkSettings::us_gaap();
settings.use_ppe_revaluation = true;
assert!(matches!(
settings.validate(),
Err(FrameworkValidationError::RevaluationNotPermittedUnderUsGaap)
));
}
#[test]
fn test_common_differences() {
let differences = FrameworkDifference::common_differences();
assert!(!differences.is_empty());
assert!(differences.iter().any(|d| d.area == "Inventory Costing"));
}
#[test]
fn test_serde_roundtrip() {
let framework = AccountingFramework::Ifrs;
let json = serde_json::to_string(&framework).unwrap();
let deserialized: AccountingFramework = serde_json::from_str(&json).unwrap();
assert_eq!(framework, deserialized);
let settings = FrameworkSettings::ifrs();
let json = serde_json::to_string(&settings).unwrap();
let deserialized: FrameworkSettings = serde_json::from_str(&json).unwrap();
assert_eq!(settings.framework, deserialized.framework);
}
}