use chrono::NaiveDate;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::collections::HashMap;
use tracing::warn;
use datasynth_core::accounts::AccountCategory;
use datasynth_core::models::balance::TrialBalance;
use datasynth_core::models::{
FxRateTable, RateType, TranslatedAmount, TranslationAccountType, TranslationMethod,
};
use datasynth_core::FrameworkAccounts;
#[derive(Debug, Clone)]
pub struct CurrencyTranslatorConfig {
pub method: TranslationMethod,
pub group_currency: String,
pub account_type_map: HashMap<String, TranslationAccountType>,
pub historical_rate_accounts: Vec<String>,
pub retained_earnings_account: String,
pub cta_account: String,
}
impl Default for CurrencyTranslatorConfig {
fn default() -> Self {
let mut account_type_map = HashMap::new();
account_type_map.insert("1".to_string(), TranslationAccountType::Asset);
account_type_map.insert("2".to_string(), TranslationAccountType::Liability);
account_type_map.insert("3".to_string(), TranslationAccountType::Equity);
account_type_map.insert("4".to_string(), TranslationAccountType::Revenue);
account_type_map.insert("5".to_string(), TranslationAccountType::Expense);
account_type_map.insert("6".to_string(), TranslationAccountType::Expense);
Self {
method: TranslationMethod::CurrentRate,
group_currency: "USD".to_string(),
account_type_map,
historical_rate_accounts: vec![
"3100".to_string(), "3200".to_string(), ],
retained_earnings_account: "3300".to_string(),
cta_account: "3900".to_string(),
}
}
}
pub fn is_monetary(account_code: &str, framework_accounts: &FrameworkAccounts) -> bool {
let category = framework_accounts.classify(account_code);
match category {
AccountCategory::Liability => true,
AccountCategory::Equity => false,
AccountCategory::Revenue
| AccountCategory::Cogs
| AccountCategory::OperatingExpense
| AccountCategory::OtherIncomeExpense
| AccountCategory::Tax => true,
AccountCategory::Asset => is_monetary_asset(account_code),
AccountCategory::Suspense | AccountCategory::Unknown => {
warn!(
account_code,
?category,
"is_monetary: unrecognized account category, defaulting to monetary"
);
true
}
}
}
fn is_monetary_asset(account_code: &str) -> bool {
if account_code.len() < 2 {
return true;
}
let prefix2 = &account_code[..2];
match prefix2 {
"10" => true,
"11" => true,
"12" => true,
"13" => true,
"14" => false,
"15" => false,
"16" => false,
"17" => false,
"18" => false,
"19" => false,
_ => true,
}
}
pub struct CurrencyTranslator {
config: CurrencyTranslatorConfig,
historical_equity_rates: HashMap<String, Decimal>,
framework_accounts: FrameworkAccounts,
}
impl CurrencyTranslator {
pub fn new_with_framework(config: CurrencyTranslatorConfig, framework: &str) -> Self {
Self {
config,
historical_equity_rates: HashMap::new(),
framework_accounts: FrameworkAccounts::for_framework(framework),
}
}
pub fn new(config: CurrencyTranslatorConfig) -> Self {
Self::new_with_framework(config, "us_gaap")
}
pub fn set_historical_equity_rates(&mut self, rates: HashMap<String, Decimal>) {
self.historical_equity_rates = rates;
}
pub fn translate_trial_balance(
&self,
trial_balance: &TrialBalance,
rate_table: &FxRateTable,
historical_rates: &HashMap<String, Decimal>,
) -> TranslatedTrialBalance {
let local_currency = &trial_balance.currency;
let period_end = trial_balance.as_of_date;
let closing_rate = rate_table
.get_closing_rate(local_currency, &self.config.group_currency, period_end)
.map(|r| r.rate)
.unwrap_or(Decimal::ONE);
let average_rate = rate_table
.get_average_rate(local_currency, &self.config.group_currency, period_end)
.map(|r| r.rate)
.unwrap_or(closing_rate);
let mut translated_lines = Vec::new();
let mut total_local_debit = Decimal::ZERO;
let mut total_local_credit = Decimal::ZERO;
let mut total_group_debit = Decimal::ZERO;
let mut total_group_credit = Decimal::ZERO;
for line in &trial_balance.lines {
let account_type = self.determine_account_type(&line.account_code);
let rate = self.determine_rate(
&line.account_code,
&account_type,
closing_rate,
average_rate,
historical_rates,
);
let group_debit = (line.debit_balance * rate).round_dp(2);
let group_credit = (line.credit_balance * rate).round_dp(2);
translated_lines.push(TranslatedTrialBalanceLine {
account_code: line.account_code.clone(),
account_description: Some(line.account_description.clone()),
account_type: account_type.clone(),
local_debit: line.debit_balance,
local_credit: line.credit_balance,
rate_used: rate,
rate_type: self.rate_type_for_account(&account_type),
group_debit,
group_credit,
});
total_local_debit += line.debit_balance;
total_local_credit += line.credit_balance;
total_group_debit += group_debit;
total_group_credit += group_credit;
}
let cta_amount = total_group_debit - total_group_credit;
TranslatedTrialBalance {
company_code: trial_balance.company_code.clone(),
company_name: trial_balance.company_name.clone().unwrap_or_default(),
local_currency: local_currency.clone(),
group_currency: self.config.group_currency.clone(),
period_end_date: period_end,
fiscal_year: trial_balance.fiscal_year,
fiscal_period: trial_balance.fiscal_period as u8,
lines: translated_lines,
closing_rate,
average_rate,
total_local_debit,
total_local_credit,
total_group_debit,
total_group_credit,
cta_amount,
translation_method: self.config.method.clone(),
}
}
pub fn translate_amount(
&self,
amount: Decimal,
local_currency: &str,
account_code: &str,
account_type: &TranslationAccountType,
rate_table: &FxRateTable,
date: NaiveDate,
) -> TranslatedAmount {
let closing_rate = rate_table
.get_closing_rate(local_currency, &self.config.group_currency, date)
.map(|r| r.rate)
.unwrap_or(Decimal::ONE);
let average_rate = rate_table
.get_average_rate(local_currency, &self.config.group_currency, date)
.map(|r| r.rate)
.unwrap_or(closing_rate);
let (rate, rate_type) = match &self.config.method {
TranslationMethod::CurrentRate => match account_type {
TranslationAccountType::Asset | TranslationAccountType::Liability => {
(closing_rate, RateType::Closing)
}
TranslationAccountType::Revenue | TranslationAccountType::Expense => {
(average_rate, RateType::Average)
}
TranslationAccountType::Equity
| TranslationAccountType::CommonStock
| TranslationAccountType::AdditionalPaidInCapital
| TranslationAccountType::RetainedEarnings => {
let hist_rate = self
.historical_equity_rates
.get(account_code)
.copied()
.unwrap_or(Decimal::ONE);
(hist_rate, RateType::Historical)
}
},
TranslationMethod::Temporal => match account_type {
TranslationAccountType::Revenue | TranslationAccountType::Expense => {
(average_rate, RateType::Average)
}
TranslationAccountType::CommonStock
| TranslationAccountType::AdditionalPaidInCapital
| TranslationAccountType::RetainedEarnings
| TranslationAccountType::Equity => {
let hist_rate = self
.historical_equity_rates
.get(account_code)
.copied()
.unwrap_or(Decimal::ONE);
(hist_rate, RateType::Historical)
}
TranslationAccountType::Asset | TranslationAccountType::Liability => {
if is_monetary(account_code, &self.framework_accounts) {
(closing_rate, RateType::Closing)
} else {
let hist_rate = self
.historical_equity_rates
.get(account_code)
.copied()
.unwrap_or(closing_rate);
(hist_rate, RateType::Historical)
}
}
},
TranslationMethod::MonetaryNonMonetary => match account_type {
TranslationAccountType::CommonStock
| TranslationAccountType::AdditionalPaidInCapital
| TranslationAccountType::RetainedEarnings
| TranslationAccountType::Equity => {
let hist_rate = self
.historical_equity_rates
.get(account_code)
.copied()
.unwrap_or(Decimal::ONE);
(hist_rate, RateType::Historical)
}
_ => {
if is_monetary(account_code, &self.framework_accounts) {
(closing_rate, RateType::Closing)
} else {
let hist_rate = self
.historical_equity_rates
.get(account_code)
.copied()
.unwrap_or(closing_rate);
(hist_rate, RateType::Historical)
}
}
},
};
TranslatedAmount {
local_amount: amount,
local_currency: local_currency.to_string(),
group_amount: (amount * rate).round_dp(2),
group_currency: self.config.group_currency.clone(),
rate_used: rate,
rate_type,
translation_date: date,
}
}
fn determine_account_type(&self, account_code: &str) -> TranslationAccountType {
if self
.config
.historical_rate_accounts
.contains(&account_code.to_string())
{
if account_code.starts_with("31") {
return TranslationAccountType::CommonStock;
} else if account_code.starts_with("32") {
return TranslationAccountType::AdditionalPaidInCapital;
}
}
if account_code == self.config.retained_earnings_account {
return TranslationAccountType::RetainedEarnings;
}
for (prefix, account_type) in &self.config.account_type_map {
if account_code.starts_with(prefix) {
return account_type.clone();
}
}
TranslationAccountType::Asset
}
fn lookup_historical_equity_rate(
&self,
account_code: &str,
historical_rates: &HashMap<String, Decimal>,
fallback: Decimal,
) -> Decimal {
self.historical_equity_rates
.get(account_code)
.or_else(|| historical_rates.get(account_code))
.copied()
.unwrap_or(fallback)
}
fn determine_rate(
&self,
account_code: &str,
account_type: &TranslationAccountType,
closing_rate: Decimal,
average_rate: Decimal,
historical_rates: &HashMap<String, Decimal>,
) -> Decimal {
match self.config.method {
TranslationMethod::CurrentRate => {
match account_type {
TranslationAccountType::Asset | TranslationAccountType::Liability => {
closing_rate
}
TranslationAccountType::Revenue | TranslationAccountType::Expense => {
average_rate
}
TranslationAccountType::CommonStock
| TranslationAccountType::AdditionalPaidInCapital => {
self.lookup_historical_equity_rate(
account_code,
historical_rates,
closing_rate,
)
}
TranslationAccountType::Equity | TranslationAccountType::RetainedEarnings => {
closing_rate
}
}
}
TranslationMethod::Temporal => {
match account_type {
TranslationAccountType::CommonStock
| TranslationAccountType::AdditionalPaidInCapital
| TranslationAccountType::RetainedEarnings => self
.lookup_historical_equity_rate(
account_code,
historical_rates,
closing_rate,
),
TranslationAccountType::Equity => self.lookup_historical_equity_rate(
account_code,
historical_rates,
closing_rate,
),
TranslationAccountType::Revenue | TranslationAccountType::Expense => {
average_rate
}
TranslationAccountType::Asset | TranslationAccountType::Liability => {
if is_monetary(account_code, &self.framework_accounts) {
closing_rate
} else {
historical_rates
.get(account_code)
.copied()
.unwrap_or(closing_rate)
}
}
}
}
TranslationMethod::MonetaryNonMonetary => {
match account_type {
TranslationAccountType::CommonStock
| TranslationAccountType::AdditionalPaidInCapital
| TranslationAccountType::RetainedEarnings => self
.lookup_historical_equity_rate(
account_code,
historical_rates,
closing_rate,
),
TranslationAccountType::Equity => self.lookup_historical_equity_rate(
account_code,
historical_rates,
closing_rate,
),
_ => {
if is_monetary(account_code, &self.framework_accounts) {
closing_rate
} else {
historical_rates
.get(account_code)
.copied()
.unwrap_or(closing_rate)
}
}
}
}
}
}
fn rate_type_for_account(&self, account_type: &TranslationAccountType) -> RateType {
match account_type {
TranslationAccountType::Asset | TranslationAccountType::Liability => RateType::Closing,
TranslationAccountType::Revenue | TranslationAccountType::Expense => RateType::Average,
TranslationAccountType::Equity
| TranslationAccountType::CommonStock
| TranslationAccountType::AdditionalPaidInCapital
| TranslationAccountType::RetainedEarnings => RateType::Historical,
}
}
}
#[derive(Debug, Clone)]
pub struct TranslatedTrialBalance {
pub company_code: String,
pub company_name: String,
pub local_currency: String,
pub group_currency: String,
pub period_end_date: NaiveDate,
pub fiscal_year: i32,
pub fiscal_period: u8,
pub lines: Vec<TranslatedTrialBalanceLine>,
pub closing_rate: Decimal,
pub average_rate: Decimal,
pub total_local_debit: Decimal,
pub total_local_credit: Decimal,
pub total_group_debit: Decimal,
pub total_group_credit: Decimal,
pub cta_amount: Decimal,
pub translation_method: TranslationMethod,
}
impl TranslatedTrialBalance {
pub fn is_local_balanced(&self) -> bool {
(self.total_local_debit - self.total_local_credit).abs() < dec!(0.01)
}
pub fn is_group_balanced(&self) -> bool {
let balance = self.total_group_debit - self.total_group_credit - self.cta_amount;
balance.abs() < dec!(0.01)
}
pub fn local_net_assets(&self) -> Decimal {
let assets: Decimal = self
.lines
.iter()
.filter(|l| matches!(l.account_type, TranslationAccountType::Asset))
.map(|l| l.local_debit - l.local_credit)
.sum();
let liabilities: Decimal = self
.lines
.iter()
.filter(|l| matches!(l.account_type, TranslationAccountType::Liability))
.map(|l| l.local_credit - l.local_debit)
.sum();
assets - liabilities
}
pub fn group_net_assets(&self) -> Decimal {
let assets: Decimal = self
.lines
.iter()
.filter(|l| matches!(l.account_type, TranslationAccountType::Asset))
.map(|l| l.group_debit - l.group_credit)
.sum();
let liabilities: Decimal = self
.lines
.iter()
.filter(|l| matches!(l.account_type, TranslationAccountType::Liability))
.map(|l| l.group_credit - l.group_debit)
.sum();
assets - liabilities
}
}
#[derive(Debug, Clone)]
pub struct TranslatedTrialBalanceLine {
pub account_code: String,
pub account_description: Option<String>,
pub account_type: TranslationAccountType,
pub local_debit: Decimal,
pub local_credit: Decimal,
pub rate_used: Decimal,
pub rate_type: RateType,
pub group_debit: Decimal,
pub group_credit: Decimal,
}
impl TranslatedTrialBalanceLine {
pub fn local_net(&self) -> Decimal {
self.local_debit - self.local_credit
}
pub fn group_net(&self) -> Decimal {
self.group_debit - self.group_credit
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use datasynth_core::models::balance::{
AccountCategory, AccountType, TrialBalanceLine, TrialBalanceType,
};
use datasynth_core::models::FxRate;
fn create_test_trial_balance() -> TrialBalance {
let mut tb = TrialBalance::new(
"TB-TEST-2024-12".to_string(),
"1200".to_string(),
NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
2024,
12,
"EUR".to_string(),
TrialBalanceType::PostClosing,
);
tb.company_name = Some("Test Subsidiary".to_string());
tb.add_line(TrialBalanceLine {
account_code: "1000".to_string(),
account_description: "Cash".to_string(),
category: AccountCategory::CurrentAssets,
account_type: AccountType::Asset,
opening_balance: Decimal::ZERO,
period_debits: dec!(100000),
period_credits: Decimal::ZERO,
closing_balance: dec!(100000),
debit_balance: dec!(100000),
credit_balance: Decimal::ZERO,
cost_center: None,
profit_center: None,
});
tb.add_line(TrialBalanceLine {
account_code: "2000".to_string(),
account_description: "Accounts Payable".to_string(),
category: AccountCategory::CurrentLiabilities,
account_type: AccountType::Liability,
opening_balance: Decimal::ZERO,
period_debits: Decimal::ZERO,
period_credits: dec!(50000),
closing_balance: dec!(50000),
debit_balance: Decimal::ZERO,
credit_balance: dec!(50000),
cost_center: None,
profit_center: None,
});
tb.add_line(TrialBalanceLine {
account_code: "4000".to_string(),
account_description: "Revenue".to_string(),
category: AccountCategory::Revenue,
account_type: AccountType::Revenue,
opening_balance: Decimal::ZERO,
period_debits: Decimal::ZERO,
period_credits: dec!(150000),
closing_balance: dec!(150000),
debit_balance: Decimal::ZERO,
credit_balance: dec!(150000),
cost_center: None,
profit_center: None,
});
tb.add_line(TrialBalanceLine {
account_code: "5000".to_string(),
account_description: "Expenses".to_string(),
category: AccountCategory::OperatingExpenses,
account_type: AccountType::Expense,
opening_balance: Decimal::ZERO,
period_debits: dec!(100000),
period_credits: Decimal::ZERO,
closing_balance: dec!(100000),
debit_balance: dec!(100000),
credit_balance: Decimal::ZERO,
cost_center: None,
profit_center: None,
});
tb
}
#[test]
fn test_translate_trial_balance() {
let translator = CurrencyTranslator::new(CurrencyTranslatorConfig::default());
let trial_balance = create_test_trial_balance();
let mut rate_table = FxRateTable::new("USD");
rate_table.add_rate(FxRate::new(
"EUR",
"USD",
RateType::Closing,
NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
dec!(1.10),
"TEST",
));
rate_table.add_rate(FxRate::new(
"EUR",
"USD",
RateType::Average,
NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
dec!(1.08),
"TEST",
));
let historical_rates = HashMap::new();
let translated =
translator.translate_trial_balance(&trial_balance, &rate_table, &historical_rates);
assert!(translated.is_local_balanced());
assert_eq!(translated.closing_rate, dec!(1.10));
assert_eq!(translated.average_rate, dec!(1.08));
}
#[test]
fn test_is_monetary_us_gaap() {
let fa = FrameworkAccounts::us_gaap();
assert!(is_monetary("1000", &fa));
assert!(is_monetary("1001", &fa));
assert!(is_monetary("1099", &fa));
assert!(is_monetary("1100", &fa));
assert!(is_monetary("1150", &fa));
assert!(is_monetary("1200", &fa));
assert!(is_monetary("1300", &fa));
assert!(!is_monetary("1400", &fa));
assert!(!is_monetary("1450", &fa));
assert!(!is_monetary("1500", &fa));
assert!(!is_monetary("1550", &fa));
assert!(!is_monetary("1600", &fa));
assert!(!is_monetary("1650", &fa));
assert!(!is_monetary("1700", &fa));
assert!(!is_monetary("1800", &fa));
assert!(!is_monetary("1900", &fa));
assert!(is_monetary("2000", &fa));
assert!(is_monetary("2100", &fa));
assert!(is_monetary("2500", &fa));
assert!(is_monetary("2900", &fa));
assert!(!is_monetary("3000", &fa));
assert!(!is_monetary("3100", &fa));
assert!(!is_monetary("3200", &fa));
assert!(!is_monetary("3900", &fa));
assert!(is_monetary("4000", &fa));
assert!(is_monetary("4500", &fa));
assert!(is_monetary("5000", &fa));
assert!(is_monetary("6000", &fa));
assert!(is_monetary("1", &fa));
}
#[test]
fn test_is_monetary_french_gaap() {
let fa = FrameworkAccounts::french_gaap();
assert!(is_monetary("512000", &fa));
assert!(is_monetary("411000", &fa));
assert!(is_monetary("210000", &fa));
assert!(is_monetary("401000", &fa));
assert!(!is_monetary("101000", &fa));
assert!(is_monetary("701000", &fa));
assert!(is_monetary("603000", &fa));
}
#[test]
fn test_historical_equity_rates() {
let mut translator = CurrencyTranslator::new(CurrencyTranslatorConfig::default());
assert!(translator.historical_equity_rates.is_empty());
let mut rates = HashMap::new();
rates.insert("3100".to_string(), dec!(1.05));
rates.insert("3200".to_string(), dec!(0.98));
translator.set_historical_equity_rates(rates);
assert_eq!(translator.historical_equity_rates.len(), 2);
assert_eq!(
translator.historical_equity_rates.get("3100"),
Some(&dec!(1.05))
);
assert_eq!(
translator.historical_equity_rates.get("3200"),
Some(&dec!(0.98))
);
let rate = translator.determine_rate(
"3100",
&TranslationAccountType::CommonStock,
dec!(1.10),
dec!(1.08),
&HashMap::new(),
);
assert_eq!(rate, dec!(1.05));
}
#[test]
fn test_temporal_method_monetary_vs_non_monetary() {
let config = CurrencyTranslatorConfig {
method: TranslationMethod::Temporal,
..CurrencyTranslatorConfig::default()
};
let translator = CurrencyTranslator::new(config);
let closing_rate = dec!(1.10);
let average_rate = dec!(1.08);
let mut historical_rates = HashMap::new();
historical_rates.insert("1500".to_string(), dec!(1.02));
let rate = translator.determine_rate(
"1000",
&TranslationAccountType::Asset,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, closing_rate);
let rate = translator.determine_rate(
"1100",
&TranslationAccountType::Asset,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, closing_rate);
let rate = translator.determine_rate(
"1500",
&TranslationAccountType::Asset,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, dec!(1.02));
let rate = translator.determine_rate(
"1600",
&TranslationAccountType::Asset,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, closing_rate);
let rate = translator.determine_rate(
"2000",
&TranslationAccountType::Liability,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, closing_rate);
let rate = translator.determine_rate(
"4000",
&TranslationAccountType::Revenue,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, average_rate);
let rate = translator.determine_rate(
"5000",
&TranslationAccountType::Expense,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, average_rate);
let mut translator_with_equity = CurrencyTranslator::new(CurrencyTranslatorConfig {
method: TranslationMethod::Temporal,
..CurrencyTranslatorConfig::default()
});
let mut equity_rates = HashMap::new();
equity_rates.insert("3100".to_string(), dec!(0.95));
translator_with_equity.set_historical_equity_rates(equity_rates);
let rate = translator_with_equity.determine_rate(
"3100",
&TranslationAccountType::CommonStock,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, dec!(0.95));
}
#[test]
fn test_monetary_non_monetary_method() {
let config = CurrencyTranslatorConfig {
method: TranslationMethod::MonetaryNonMonetary,
..CurrencyTranslatorConfig::default()
};
let mut translator = CurrencyTranslator::new(config);
let closing_rate = dec!(1.10);
let average_rate = dec!(1.08);
let mut historical_rates = HashMap::new();
historical_rates.insert("1500".to_string(), dec!(1.02));
historical_rates.insert("1600".to_string(), dec!(0.99));
let mut equity_rates = HashMap::new();
equity_rates.insert("3100".to_string(), dec!(1.05));
equity_rates.insert("3300".to_string(), dec!(1.03));
translator.set_historical_equity_rates(equity_rates);
let rate = translator.determine_rate(
"1000",
&TranslationAccountType::Asset,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, closing_rate);
let rate = translator.determine_rate(
"1100",
&TranslationAccountType::Asset,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, closing_rate);
let rate = translator.determine_rate(
"1500",
&TranslationAccountType::Asset,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, dec!(1.02));
let rate = translator.determine_rate(
"1600",
&TranslationAccountType::Asset,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, dec!(0.99));
let rate = translator.determine_rate(
"2000",
&TranslationAccountType::Liability,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, closing_rate);
let rate = translator.determine_rate(
"3100",
&TranslationAccountType::CommonStock,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, dec!(1.05));
let rate = translator.determine_rate(
"3300",
&TranslationAccountType::RetainedEarnings,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, dec!(1.03));
let rate = translator.determine_rate(
"4000",
&TranslationAccountType::Revenue,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, closing_rate);
let rate = translator.determine_rate(
"5000",
&TranslationAccountType::Expense,
closing_rate,
average_rate,
&historical_rates,
);
assert_eq!(rate, closing_rate);
}
}