use chrono::NaiveDate;
use rust_decimal::prelude::*;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::framework::AccountingFramework;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Lease {
pub lease_id: Uuid,
pub company_code: String,
pub lessor_name: String,
pub description: String,
pub asset_class: LeaseAssetClass,
pub classification: LeaseClassification,
pub commencement_date: NaiveDate,
pub lease_term_months: u32,
pub noncancelable_term_months: u32,
pub renewal_option_months: Option<u32>,
pub termination_option_months: Option<u32>,
#[serde(with = "datasynth_core::serde_decimal")]
pub fixed_payment: Decimal,
pub payment_frequency: PaymentFrequency,
pub variable_payments: Vec<VariableLeasePayment>,
#[serde(with = "datasynth_core::serde_decimal")]
pub discount_rate: Decimal,
pub implicit_rate_determinable: bool,
#[serde(with = "datasynth_core::serde_decimal")]
pub fair_value_at_commencement: Decimal,
pub economic_life_months: u32,
pub rou_asset: ROUAsset,
pub lease_liability: LeaseLiability,
pub framework: AccountingFramework,
pub short_term_election: bool,
pub low_value_election: bool,
#[serde(default)]
pub transfers_ownership: bool,
#[serde(default)]
pub has_bargain_purchase_option: bool,
#[serde(default)]
pub is_specialized_asset: bool,
#[serde(default)]
#[serde(with = "datasynth_core::serde_decimal")]
pub initial_direct_costs: Decimal,
#[serde(default)]
#[serde(with = "datasynth_core::serde_decimal")]
pub prepaid_payments: Decimal,
#[serde(default)]
#[serde(with = "datasynth_core::serde_decimal")]
pub lease_incentives: Decimal,
pub fixed_asset_id: Option<Uuid>,
#[serde(default)]
pub journal_entry_ids: Vec<Uuid>,
}
impl Lease {
#[allow(clippy::too_many_arguments)]
pub fn new(
company_code: impl Into<String>,
lessor_name: impl Into<String>,
description: impl Into<String>,
asset_class: LeaseAssetClass,
commencement_date: NaiveDate,
lease_term_months: u32,
fixed_payment: Decimal,
payment_frequency: PaymentFrequency,
discount_rate: Decimal,
fair_value_at_commencement: Decimal,
economic_life_months: u32,
framework: AccountingFramework,
) -> Self {
let lease_id = Uuid::now_v7();
let rou_asset = ROUAsset {
lease_id,
initial_measurement: Decimal::ZERO,
accumulated_depreciation: Decimal::ZERO,
accumulated_impairment: Decimal::ZERO,
carrying_amount: Decimal::ZERO,
useful_life_months: lease_term_months,
depreciation_method: DepreciationMethod::StraightLine,
};
let lease_liability = LeaseLiability {
lease_id,
initial_measurement: Decimal::ZERO,
current_portion: Decimal::ZERO,
non_current_portion: Decimal::ZERO,
accumulated_interest: Decimal::ZERO,
amortization_schedule: Vec::new(),
};
let mut lease = Self {
lease_id,
company_code: company_code.into(),
lessor_name: lessor_name.into(),
description: description.into(),
asset_class,
classification: LeaseClassification::Operating, commencement_date,
lease_term_months,
noncancelable_term_months: lease_term_months,
renewal_option_months: None,
termination_option_months: None,
fixed_payment,
payment_frequency,
variable_payments: Vec::new(),
discount_rate,
implicit_rate_determinable: false,
fair_value_at_commencement,
economic_life_months,
rou_asset,
lease_liability,
framework,
short_term_election: false,
low_value_election: false,
transfers_ownership: false,
has_bargain_purchase_option: false,
is_specialized_asset: false,
initial_direct_costs: Decimal::ZERO,
prepaid_payments: Decimal::ZERO,
lease_incentives: Decimal::ZERO,
fixed_asset_id: None,
journal_entry_ids: Vec::new(),
};
lease.classify();
lease.calculate_initial_measurement();
lease
}
pub fn classify(&mut self) {
if self.short_term_election || self.low_value_election {
self.classification = LeaseClassification::Operating;
return;
}
match self.framework {
AccountingFramework::UsGaap => self.classify_us_gaap(),
AccountingFramework::Ifrs => self.classify_ifrs(),
AccountingFramework::DualReporting => {
self.classify_us_gaap();
}
AccountingFramework::FrenchGaap => self.classify_french_gaap(),
AccountingFramework::GermanGaap => self.classify_german_gaap(),
}
}
fn classify_french_gaap(&mut self) {
self.classify_ifrs();
}
fn classify_german_gaap(&mut self) {
let term_ratio = if self.economic_life_months > 0 {
self.lease_term_months as f64 / self.economic_life_months as f64
} else {
0.0
};
if term_ratio >= 0.90 || term_ratio <= 0.40 {
if term_ratio >= 0.90 {
self.classification = LeaseClassification::Finance;
} else {
self.classification = LeaseClassification::Operating;
}
} else {
self.classification = LeaseClassification::Operating;
}
}
fn classify_us_gaap(&mut self) {
if self.transfers_ownership {
self.classification = LeaseClassification::Finance;
return;
}
if self.has_bargain_purchase_option {
self.classification = LeaseClassification::Finance;
return;
}
let term_ratio =
Decimal::from(self.lease_term_months) / Decimal::from(self.economic_life_months);
if term_ratio >= Decimal::from_str_exact("0.75").expect("valid decimal literal") {
self.classification = LeaseClassification::Finance;
return;
}
let pv = self.calculate_present_value_of_payments();
let pv_ratio = if self.fair_value_at_commencement > Decimal::ZERO {
pv / self.fair_value_at_commencement
} else {
Decimal::ZERO
};
if pv_ratio >= Decimal::from_str_exact("0.90").expect("valid decimal literal") {
self.classification = LeaseClassification::Finance;
return;
}
if self.is_specialized_asset {
self.classification = LeaseClassification::Finance;
return;
}
self.classification = LeaseClassification::Operating;
}
fn classify_ifrs(&mut self) {
if self.transfers_ownership {
self.classification = LeaseClassification::Finance;
return;
}
if self.has_bargain_purchase_option {
self.classification = LeaseClassification::Finance;
return;
}
let term_ratio =
Decimal::from(self.lease_term_months) / Decimal::from(self.economic_life_months);
let pv = self.calculate_present_value_of_payments();
let pv_ratio = if self.fair_value_at_commencement > Decimal::ZERO {
pv / self.fair_value_at_commencement
} else {
Decimal::ZERO
};
if term_ratio >= Decimal::from_str_exact("0.75").expect("valid decimal literal")
|| pv_ratio >= Decimal::from_str_exact("0.90").expect("valid decimal literal")
{
self.classification = LeaseClassification::Finance;
return;
}
if self.is_specialized_asset {
self.classification = LeaseClassification::Finance;
return;
}
self.classification = LeaseClassification::Operating;
}
pub fn calculate_present_value_of_payments(&self) -> Decimal {
let mut pv = Decimal::ZERO;
let periods = self.total_payment_periods();
let periodic_rate = self.periodic_discount_rate();
for period in 1..=periods {
let discount_factor =
Decimal::ONE / (Decimal::ONE + periodic_rate).powd(Decimal::from(period as i64));
pv += self.fixed_payment * discount_factor;
}
pv
}
pub fn calculate_initial_measurement(&mut self) {
let pv = self.calculate_present_value_of_payments();
self.lease_liability.initial_measurement = pv;
let rou_initial =
pv + self.initial_direct_costs + self.prepaid_payments - self.lease_incentives;
self.rou_asset.initial_measurement = rou_initial.max(Decimal::ZERO);
self.rou_asset.carrying_amount = self.rou_asset.initial_measurement;
self.update_liability_classification();
self.generate_amortization_schedule();
}
fn update_liability_classification(&mut self) {
let payments_per_year = self.payments_per_year();
let annual_payments = self.fixed_payment * Decimal::from(payments_per_year);
let periodic_rate = self.periodic_discount_rate();
let total_liability = self.lease_liability.initial_measurement;
let first_year_interest =
total_liability * periodic_rate * Decimal::from(payments_per_year);
let first_year_principal = (annual_payments - first_year_interest).max(Decimal::ZERO);
self.lease_liability.current_portion = first_year_principal.min(total_liability);
self.lease_liability.non_current_portion =
total_liability - self.lease_liability.current_portion;
}
fn generate_amortization_schedule(&mut self) {
let periods = self.total_payment_periods();
let periodic_rate = self.periodic_discount_rate();
let mut balance = self.lease_liability.initial_measurement;
let mut cumulative_interest = Decimal::ZERO;
self.lease_liability.amortization_schedule.clear();
for period in 1..=periods {
let interest_expense = balance * periodic_rate;
let principal_payment = self.fixed_payment - interest_expense;
let new_balance = (balance - principal_payment).max(Decimal::ZERO);
cumulative_interest += interest_expense;
let entry = LeaseAmortizationEntry {
period_number: period,
period_date: self.period_date(period),
beginning_balance: balance,
payment_amount: self.fixed_payment,
interest_expense,
principal_payment,
ending_balance: new_balance,
};
self.lease_liability.amortization_schedule.push(entry);
balance = new_balance;
}
self.lease_liability.accumulated_interest = cumulative_interest;
}
fn payments_per_year(&self) -> u32 {
match self.payment_frequency {
PaymentFrequency::Monthly => 12,
PaymentFrequency::Quarterly => 4,
PaymentFrequency::SemiAnnual => 2,
PaymentFrequency::Annual => 1,
}
}
fn total_payment_periods(&self) -> u32 {
let periods_per_month = match self.payment_frequency {
PaymentFrequency::Monthly => 1,
PaymentFrequency::Quarterly => 3,
PaymentFrequency::SemiAnnual => 6,
PaymentFrequency::Annual => 12,
};
self.lease_term_months / periods_per_month
}
fn periodic_discount_rate(&self) -> Decimal {
self.discount_rate / Decimal::from(self.payments_per_year())
}
fn period_date(&self, period: u32) -> NaiveDate {
let months_offset = match self.payment_frequency {
PaymentFrequency::Monthly => period,
PaymentFrequency::Quarterly => period * 3,
PaymentFrequency::SemiAnnual => period * 6,
PaymentFrequency::Annual => period * 12,
};
self.commencement_date
.checked_add_months(chrono::Months::new(months_offset))
.unwrap_or(self.commencement_date)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum LeaseAssetClass {
#[default]
RealEstate,
Equipment,
Vehicles,
InformationTechnology,
FurnitureAndFixtures,
Other,
}
impl std::fmt::Display for LeaseAssetClass {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RealEstate => write!(f, "Real Estate"),
Self::Equipment => write!(f, "Equipment"),
Self::Vehicles => write!(f, "Vehicles"),
Self::InformationTechnology => write!(f, "Information Technology"),
Self::FurnitureAndFixtures => write!(f, "Furniture and Fixtures"),
Self::Other => write!(f, "Other"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum LeaseClassification {
Finance,
#[default]
Operating,
ShortTerm,
LowValue,
}
impl std::fmt::Display for LeaseClassification {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Finance => write!(f, "Finance Lease"),
Self::Operating => write!(f, "Operating Lease"),
Self::ShortTerm => write!(f, "Short-Term Lease"),
Self::LowValue => write!(f, "Low-Value Asset Lease"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum PaymentFrequency {
#[default]
Monthly,
Quarterly,
SemiAnnual,
Annual,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VariableLeasePayment {
pub payment_type: VariablePaymentType,
pub description: String,
pub calculation_basis: String,
#[serde(with = "datasynth_core::serde_decimal")]
pub estimated_annual_amount: Decimal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VariablePaymentType {
IndexBased,
RateBased,
UsageBased,
OperatingCosts,
PropertyTaxes,
Insurance,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ROUAsset {
pub lease_id: Uuid,
#[serde(with = "datasynth_core::serde_decimal")]
pub initial_measurement: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub accumulated_depreciation: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub accumulated_impairment: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub carrying_amount: Decimal,
pub useful_life_months: u32,
pub depreciation_method: DepreciationMethod,
}
impl ROUAsset {
pub fn monthly_depreciation(&self) -> Decimal {
if self.useful_life_months == 0 {
return Decimal::ZERO;
}
self.initial_measurement / Decimal::from(self.useful_life_months)
}
pub fn record_depreciation(&mut self, months: u32) {
let depreciation = self.monthly_depreciation() * Decimal::from(months);
self.accumulated_depreciation += depreciation;
self.carrying_amount =
self.initial_measurement - self.accumulated_depreciation - self.accumulated_impairment;
self.carrying_amount = self.carrying_amount.max(Decimal::ZERO);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum DepreciationMethod {
#[default]
StraightLine,
UnitsOfProduction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LeaseLiability {
pub lease_id: Uuid,
#[serde(with = "datasynth_core::serde_decimal")]
pub initial_measurement: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub current_portion: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub non_current_portion: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub accumulated_interest: Decimal,
pub amortization_schedule: Vec<LeaseAmortizationEntry>,
}
impl LeaseLiability {
pub fn total_balance(&self) -> Decimal {
self.current_portion + self.non_current_portion
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LeaseAmortizationEntry {
pub period_number: u32,
pub period_date: NaiveDate,
#[serde(with = "datasynth_core::serde_decimal")]
pub beginning_balance: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub payment_amount: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub interest_expense: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub principal_payment: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub ending_balance: Decimal,
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_lease_creation() {
let lease = Lease::new(
"1000",
"ABC Leasing",
"Office Space Lease",
LeaseAssetClass::RealEstate,
NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
60, dec!(10000),
PaymentFrequency::Monthly,
dec!(0.05), dec!(500000), 120, AccountingFramework::UsGaap,
);
assert_eq!(lease.lease_term_months, 60);
assert!(lease.lease_liability.initial_measurement > Decimal::ZERO);
}
#[test]
fn test_finance_lease_classification() {
let lease = Lease::new(
"1000",
"ABC Leasing",
"Equipment Lease",
LeaseAssetClass::Equipment,
NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
108, dec!(5000),
PaymentFrequency::Monthly,
dec!(0.05),
dec!(400000),
120, AccountingFramework::UsGaap,
);
assert_eq!(lease.classification, LeaseClassification::Finance);
}
#[test]
fn test_amortization_schedule() {
let lease = Lease::new(
"1000",
"ABC Leasing",
"Vehicle Lease",
LeaseAssetClass::Vehicles,
NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
36, dec!(500),
PaymentFrequency::Monthly,
dec!(0.06),
dec!(15000),
60, AccountingFramework::UsGaap,
);
assert_eq!(lease.lease_liability.amortization_schedule.len(), 36);
let first_entry = &lease.lease_liability.amortization_schedule[0];
let last_entry = &lease.lease_liability.amortization_schedule[35];
assert!(first_entry.interest_expense > last_entry.interest_expense);
assert!(last_entry.ending_balance < dec!(1));
}
#[test]
fn test_rou_asset_depreciation() {
let lease = Lease::new(
"1000",
"Lessor Co",
"Office Equipment",
LeaseAssetClass::Equipment,
NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
24,
dec!(1000),
PaymentFrequency::Monthly,
dec!(0.05),
dec!(20000),
60,
AccountingFramework::UsGaap,
);
let mut rou_asset = lease.rou_asset.clone();
let initial = rou_asset.carrying_amount;
let monthly_dep = rou_asset.monthly_depreciation();
rou_asset.record_depreciation(6);
assert_eq!(rou_asset.accumulated_depreciation, monthly_dep * dec!(6));
assert!(rou_asset.carrying_amount < initial);
}
fn create_base_operating_lease(framework: AccountingFramework) -> Lease {
Lease::new(
"2000",
"Test Lessor",
"Operating Lease Test",
LeaseAssetClass::Equipment,
NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
24, dec!(500), PaymentFrequency::Monthly,
dec!(0.05),
dec!(500000), 120, framework,
)
}
#[test]
fn test_classification_transfer_of_ownership() {
let mut lease = create_base_operating_lease(AccountingFramework::UsGaap);
assert_eq!(lease.classification, LeaseClassification::Operating);
lease.transfers_ownership = true;
lease.classify();
assert_eq!(
lease.classification,
LeaseClassification::Finance,
"Transfer of ownership (ASC 842 Test 1) should trigger finance lease"
);
}
#[test]
fn test_classification_bargain_purchase_option() {
let mut lease = create_base_operating_lease(AccountingFramework::UsGaap);
assert_eq!(lease.classification, LeaseClassification::Operating);
lease.has_bargain_purchase_option = true;
lease.classify();
assert_eq!(
lease.classification,
LeaseClassification::Finance,
"Bargain purchase option (ASC 842 Test 2) should trigger finance lease"
);
}
#[test]
fn test_classification_specialized_asset() {
let mut lease = create_base_operating_lease(AccountingFramework::UsGaap);
assert_eq!(lease.classification, LeaseClassification::Operating);
lease.is_specialized_asset = true;
lease.classify();
assert_eq!(
lease.classification,
LeaseClassification::Finance,
"Specialized asset (ASC 842 Test 5) should trigger finance lease"
);
}
#[test]
fn test_rou_asset_with_direct_costs_and_incentives() {
let mut lease = create_base_operating_lease(AccountingFramework::UsGaap);
let pv = lease.calculate_present_value_of_payments();
lease.initial_direct_costs = dec!(5000);
lease.prepaid_payments = dec!(2000);
lease.lease_incentives = dec!(1500);
lease.calculate_initial_measurement();
let expected_rou = pv + dec!(5000) + dec!(2000) - dec!(1500);
assert_eq!(
lease.rou_asset.initial_measurement, expected_rou,
"ROU asset should equal PV + direct costs + prepaid - incentives"
);
assert_eq!(lease.lease_liability.initial_measurement, pv);
}
#[test]
fn test_new_fields_default_values() {
let lease = create_base_operating_lease(AccountingFramework::UsGaap);
assert!(
!lease.transfers_ownership,
"transfers_ownership should default to false"
);
assert!(
!lease.has_bargain_purchase_option,
"has_bargain_purchase_option should default to false"
);
assert!(
!lease.is_specialized_asset,
"is_specialized_asset should default to false"
);
assert_eq!(
lease.initial_direct_costs,
Decimal::ZERO,
"initial_direct_costs should default to zero"
);
assert_eq!(
lease.prepaid_payments,
Decimal::ZERO,
"prepaid_payments should default to zero"
);
assert_eq!(
lease.lease_incentives,
Decimal::ZERO,
"lease_incentives should default to zero"
);
}
#[test]
fn test_operating_lease_none_of_criteria_met() {
let lease = create_base_operating_lease(AccountingFramework::UsGaap);
assert!(!lease.transfers_ownership);
assert!(!lease.has_bargain_purchase_option);
assert!(!lease.is_specialized_asset);
let term_ratio =
Decimal::from(lease.lease_term_months) / Decimal::from(lease.economic_life_months);
assert!(term_ratio < Decimal::from_str_exact("0.75").expect("valid decimal literal"));
let pv = lease.calculate_present_value_of_payments();
let pv_ratio = pv / lease.fair_value_at_commencement;
assert!(pv_ratio < Decimal::from_str_exact("0.90").expect("valid decimal literal"));
assert_eq!(
lease.classification,
LeaseClassification::Operating,
"Lease should be operating when no ASC 842 criteria are met"
);
}
#[test]
fn test_finance_lease_multiple_criteria() {
let mut lease = create_base_operating_lease(AccountingFramework::UsGaap);
lease.transfers_ownership = true;
lease.has_bargain_purchase_option = true;
lease.is_specialized_asset = true;
lease.classify();
assert_eq!(
lease.classification,
LeaseClassification::Finance,
"Lease with multiple finance criteria should be classified as finance"
);
}
#[test]
fn test_ifrs_classification_with_new_criteria() {
let mut lease = create_base_operating_lease(AccountingFramework::Ifrs);
assert_eq!(lease.classification, LeaseClassification::Operating);
lease.transfers_ownership = true;
lease.classify();
assert_eq!(
lease.classification,
LeaseClassification::Finance,
"IFRS: Transfer of ownership should trigger finance classification"
);
lease.transfers_ownership = false;
lease.has_bargain_purchase_option = true;
lease.classify();
assert_eq!(
lease.classification,
LeaseClassification::Finance,
"IFRS: Bargain purchase option should trigger finance classification"
);
lease.has_bargain_purchase_option = false;
lease.is_specialized_asset = true;
lease.classify();
assert_eq!(
lease.classification,
LeaseClassification::Finance,
"IFRS: Specialized asset should trigger finance classification"
);
}
#[test]
fn test_rou_asset_incentives_exceed_pv() {
let mut lease = create_base_operating_lease(AccountingFramework::UsGaap);
let pv = lease.calculate_present_value_of_payments();
lease.lease_incentives = pv + dec!(10000);
lease.calculate_initial_measurement();
assert_eq!(
lease.rou_asset.initial_measurement,
Decimal::ZERO,
"ROU asset should not go below zero when incentives exceed other components"
);
}
#[test]
fn test_german_gaap_lease_classification_operating() {
let lease = Lease::new(
"DE01",
"Siemens Leasing",
"Büroausstattung",
LeaseAssetClass::Equipment,
NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
60, dec!(800), PaymentFrequency::Monthly,
dec!(0.04),
dec!(200000), 120, AccountingFramework::GermanGaap,
);
assert_eq!(lease.classification, LeaseClassification::Operating);
}
#[test]
fn test_german_gaap_lease_classification_finance() {
let lease = Lease::new(
"DE01",
"Deutsche Leasing",
"Produktionsanlage",
LeaseAssetClass::Equipment,
NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
108, dec!(2000), PaymentFrequency::Monthly,
dec!(0.04),
dec!(300000), 120, AccountingFramework::GermanGaap,
);
assert_eq!(lease.classification, LeaseClassification::Finance);
}
#[test]
fn test_german_gaap_short_term_operating() {
let lease = Lease::new(
"DE01",
"Leasing GmbH",
"Firmenwagen",
LeaseAssetClass::Vehicles,
NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
36, dec!(500), PaymentFrequency::Monthly,
dec!(0.04),
dec!(50000), 120, AccountingFramework::GermanGaap,
);
assert_eq!(lease.classification, LeaseClassification::Operating);
assert!(AccountingFramework::GermanGaap.operating_leases_off_balance());
}
#[test]
fn test_serde_default_new_fields() {
let json = r#"{
"lease_id": "00000000-0000-0000-0000-000000000001",
"company_code": "1000",
"lessor_name": "Test",
"description": "Test Lease",
"asset_class": "equipment",
"classification": "operating",
"commencement_date": "2024-01-01",
"lease_term_months": 24,
"noncancelable_term_months": 24,
"renewal_option_months": null,
"termination_option_months": null,
"fixed_payment": "1000",
"payment_frequency": "monthly",
"variable_payments": [],
"discount_rate": "0.05",
"implicit_rate_determinable": false,
"fair_value_at_commencement": "50000",
"economic_life_months": 60,
"rou_asset": {
"lease_id": "00000000-0000-0000-0000-000000000001",
"initial_measurement": "1000",
"accumulated_depreciation": "0",
"accumulated_impairment": "0",
"carrying_amount": "1000",
"useful_life_months": 24,
"depreciation_method": "straight_line"
},
"lease_liability": {
"lease_id": "00000000-0000-0000-0000-000000000001",
"initial_measurement": "1000",
"current_portion": "500",
"non_current_portion": "500",
"accumulated_interest": "0",
"amortization_schedule": []
},
"framework": "us_gaap",
"short_term_election": false,
"low_value_election": false,
"fixed_asset_id": null,
"journal_entry_ids": []
}"#;
let lease: Lease =
serde_json::from_str(json).expect("Should deserialize without new fields");
assert!(!lease.transfers_ownership);
assert!(!lease.has_bargain_purchase_option);
assert!(!lease.is_specialized_asset);
assert_eq!(lease.initial_direct_costs, Decimal::ZERO);
assert_eq!(lease.prepaid_payments, Decimal::ZERO);
assert_eq!(lease.lease_incentives, Decimal::ZERO);
}
}