use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use crate::aggregate::translation::classify::TranslationAccountType;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexedRestatement {
#[serde(with = "datasynth_core::serde_decimal")]
pub opening_index: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub closing_index: Decimal,
#[serde(with = "datasynth_core::serde_decimal")]
pub average_index: Decimal,
}
impl IndexedRestatement {
pub fn new(
opening_index: Decimal,
closing_index: Decimal,
average_index: Decimal,
) -> Result<Self, String> {
if opening_index <= Decimal::ZERO {
return Err(format!(
"IndexedRestatement::new: opening_index must be > 0, got {opening_index}"
));
}
if closing_index <= Decimal::ZERO {
return Err(format!(
"IndexedRestatement::new: closing_index must be > 0, got {closing_index}"
));
}
if average_index <= Decimal::ZERO {
return Err(format!(
"IndexedRestatement::new: average_index must be > 0, got {average_index}"
));
}
Ok(Self {
opening_index,
closing_index,
average_index,
})
}
pub fn non_monetary_factor(&self) -> Decimal {
self.closing_index / self.opening_index
}
pub fn pl_factor(&self) -> Decimal {
self.closing_index / self.average_index
}
pub fn factor_for(&self, account_type: TranslationAccountType) -> Decimal {
match account_type {
TranslationAccountType::BsMonetary => Decimal::ONE,
TranslationAccountType::BsNonMonetary | TranslationAccountType::Equity => {
self.non_monetary_factor()
}
TranslationAccountType::PlRevenue
| TranslationAccountType::PlExpense
| TranslationAccountType::PlOci => self.pl_factor(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn new_rejects_non_positive_indices() {
assert!(IndexedRestatement::new(dec!(0), dec!(110), dec!(105)).is_err());
assert!(IndexedRestatement::new(dec!(100), dec!(-1), dec!(105)).is_err());
assert!(IndexedRestatement::new(dec!(100), dec!(110), dec!(0)).is_err());
}
#[test]
fn factors_compute_from_indices() {
let ir = IndexedRestatement::new(dec!(100), dec!(200), dec!(150)).unwrap();
assert_eq!(ir.non_monetary_factor(), dec!(2));
assert_eq!(ir.pl_factor(), dec!(200) / dec!(150));
}
#[test]
fn factor_for_dispatches_by_account_type() {
let ir = IndexedRestatement::new(dec!(100), dec!(200), dec!(150)).unwrap();
assert_eq!(ir.factor_for(TranslationAccountType::BsMonetary), dec!(1));
assert_eq!(
ir.factor_for(TranslationAccountType::BsNonMonetary),
dec!(2)
);
assert_eq!(ir.factor_for(TranslationAccountType::Equity), dec!(2));
assert_eq!(
ir.factor_for(TranslationAccountType::PlRevenue),
dec!(200) / dec!(150)
);
assert_eq!(
ir.factor_for(TranslationAccountType::PlExpense),
dec!(200) / dec!(150)
);
assert_eq!(
ir.factor_for(TranslationAccountType::PlOci),
dec!(200) / dec!(150)
);
}
#[test]
fn unit_factors_when_all_indices_equal() {
let ir = IndexedRestatement::new(dec!(100), dec!(100), dec!(100)).unwrap();
for ty in [
TranslationAccountType::BsMonetary,
TranslationAccountType::BsNonMonetary,
TranslationAccountType::Equity,
TranslationAccountType::PlRevenue,
TranslationAccountType::PlExpense,
TranslationAccountType::PlOci,
] {
assert_eq!(ir.factor_for(ty), dec!(1), "{ty:?}");
}
}
#[test]
fn json_round_trip() {
let ir = IndexedRestatement::new(dec!(123.45), dec!(678.90), dec!(401.17)).unwrap();
let json = serde_json::to_string(&ir).unwrap();
let back: IndexedRestatement = serde_json::from_str(&json).unwrap();
assert_eq!(ir, back);
}
}