1use chrono::NaiveDate;
7use rust_decimal::Decimal;
8use rust_decimal_macros::dec;
9use std::collections::HashMap;
10
11use datasynth_core::models::balance::TrialBalance;
12use datasynth_core::models::{
13 FxRateTable, RateType, TranslatedAmount, TranslationAccountType, TranslationMethod,
14};
15
16#[derive(Debug, Clone)]
18pub struct CurrencyTranslatorConfig {
19 pub method: TranslationMethod,
21 pub group_currency: String,
23 pub account_type_map: HashMap<String, TranslationAccountType>,
25 pub historical_rate_accounts: Vec<String>,
27 pub retained_earnings_account: String,
29 pub cta_account: String,
31}
32
33impl Default for CurrencyTranslatorConfig {
34 fn default() -> Self {
35 let mut account_type_map = HashMap::new();
36 account_type_map.insert("1".to_string(), TranslationAccountType::Asset);
38 account_type_map.insert("2".to_string(), TranslationAccountType::Liability);
40 account_type_map.insert("3".to_string(), TranslationAccountType::Equity);
42 account_type_map.insert("4".to_string(), TranslationAccountType::Revenue);
44 account_type_map.insert("5".to_string(), TranslationAccountType::Expense);
46 account_type_map.insert("6".to_string(), TranslationAccountType::Expense);
47
48 Self {
49 method: TranslationMethod::CurrentRate,
50 group_currency: "USD".to_string(),
51 account_type_map,
52 historical_rate_accounts: vec![
53 "3100".to_string(), "3200".to_string(), ],
56 retained_earnings_account: "3300".to_string(),
57 cta_account: "3900".to_string(),
58 }
59 }
60}
61
62pub fn is_monetary(account_code: &str) -> bool {
84 if account_code.len() < 2 {
85 return true;
87 }
88
89 let prefix2 = &account_code[..2];
90
91 match prefix2 {
92 "10" => true,
94 "11" => true,
96 "12" => true,
98 "13" => true,
100 "14" => false,
102 "15" => false,
104 "16" => false,
106 "17" => false,
108 "18" => false,
110 "19" => false,
112 "20" | "21" | "22" | "23" | "24" | "25" | "26" | "27" | "28" | "29" => true,
114 "30" | "31" | "32" | "33" | "34" | "35" | "36" | "37" | "38" | "39" => false,
116 "40" | "41" | "42" | "43" | "44" | "45" | "46" | "47" | "48" | "49" => true,
118 "50" | "51" | "52" | "53" | "54" | "55" | "56" | "57" | "58" | "59" => true,
120 "60" | "61" | "62" | "63" | "64" | "65" | "66" | "67" | "68" | "69" => true,
121 _ => true,
123 }
124}
125
126pub struct CurrencyTranslator {
128 config: CurrencyTranslatorConfig,
129 historical_equity_rates: HashMap<String, Decimal>,
131}
132
133impl CurrencyTranslator {
134 pub fn new(config: CurrencyTranslatorConfig) -> Self {
136 Self {
137 config,
138 historical_equity_rates: HashMap::new(),
139 }
140 }
141
142 pub fn set_historical_equity_rates(&mut self, rates: HashMap<String, Decimal>) {
148 self.historical_equity_rates = rates;
149 }
150
151 pub fn translate_trial_balance(
153 &self,
154 trial_balance: &TrialBalance,
155 rate_table: &FxRateTable,
156 historical_rates: &HashMap<String, Decimal>,
157 ) -> TranslatedTrialBalance {
158 let local_currency = &trial_balance.currency;
159 let period_end = trial_balance.as_of_date;
160
161 let closing_rate = rate_table
163 .get_closing_rate(local_currency, &self.config.group_currency, period_end)
164 .map(|r| r.rate)
165 .unwrap_or(Decimal::ONE);
166
167 let average_rate = rate_table
168 .get_average_rate(local_currency, &self.config.group_currency, period_end)
169 .map(|r| r.rate)
170 .unwrap_or(closing_rate);
171
172 let mut translated_lines = Vec::new();
173 let mut total_local_debit = Decimal::ZERO;
174 let mut total_local_credit = Decimal::ZERO;
175 let mut total_group_debit = Decimal::ZERO;
176 let mut total_group_credit = Decimal::ZERO;
177
178 for line in &trial_balance.lines {
179 let account_type = self.determine_account_type(&line.account_code);
180 let rate = self.determine_rate(
181 &line.account_code,
182 &account_type,
183 closing_rate,
184 average_rate,
185 historical_rates,
186 );
187
188 let group_debit = (line.debit_balance * rate).round_dp(2);
189 let group_credit = (line.credit_balance * rate).round_dp(2);
190
191 translated_lines.push(TranslatedTrialBalanceLine {
192 account_code: line.account_code.clone(),
193 account_description: Some(line.account_description.clone()),
194 account_type: account_type.clone(),
195 local_debit: line.debit_balance,
196 local_credit: line.credit_balance,
197 rate_used: rate,
198 rate_type: self.rate_type_for_account(&account_type),
199 group_debit,
200 group_credit,
201 });
202
203 total_local_debit += line.debit_balance;
204 total_local_credit += line.credit_balance;
205 total_group_debit += group_debit;
206 total_group_credit += group_credit;
207 }
208
209 let cta_amount = total_group_debit - total_group_credit;
211
212 TranslatedTrialBalance {
213 company_code: trial_balance.company_code.clone(),
214 company_name: trial_balance.company_name.clone().unwrap_or_default(),
215 local_currency: local_currency.clone(),
216 group_currency: self.config.group_currency.clone(),
217 period_end_date: period_end,
218 fiscal_year: trial_balance.fiscal_year,
219 fiscal_period: trial_balance.fiscal_period as u8,
220 lines: translated_lines,
221 closing_rate,
222 average_rate,
223 total_local_debit,
224 total_local_credit,
225 total_group_debit,
226 total_group_credit,
227 cta_amount,
228 translation_method: self.config.method.clone(),
229 }
230 }
231
232 pub fn translate_amount(
238 &self,
239 amount: Decimal,
240 local_currency: &str,
241 account_code: &str,
242 account_type: &TranslationAccountType,
243 rate_table: &FxRateTable,
244 date: NaiveDate,
245 ) -> TranslatedAmount {
246 let closing_rate = rate_table
247 .get_closing_rate(local_currency, &self.config.group_currency, date)
248 .map(|r| r.rate)
249 .unwrap_or(Decimal::ONE);
250
251 let average_rate = rate_table
252 .get_average_rate(local_currency, &self.config.group_currency, date)
253 .map(|r| r.rate)
254 .unwrap_or(closing_rate);
255
256 let (rate, rate_type) = match &self.config.method {
257 TranslationMethod::CurrentRate => match account_type {
258 TranslationAccountType::Asset | TranslationAccountType::Liability => {
259 (closing_rate, RateType::Closing)
260 }
261 TranslationAccountType::Revenue | TranslationAccountType::Expense => {
262 (average_rate, RateType::Average)
263 }
264 TranslationAccountType::Equity
265 | TranslationAccountType::CommonStock
266 | TranslationAccountType::AdditionalPaidInCapital
267 | TranslationAccountType::RetainedEarnings => {
268 let hist_rate = self
269 .historical_equity_rates
270 .get(account_code)
271 .copied()
272 .unwrap_or(Decimal::ONE);
273 (hist_rate, RateType::Historical)
274 }
275 },
276 TranslationMethod::Temporal => match account_type {
277 TranslationAccountType::Revenue | TranslationAccountType::Expense => {
278 (average_rate, RateType::Average)
279 }
280 TranslationAccountType::CommonStock
281 | TranslationAccountType::AdditionalPaidInCapital
282 | TranslationAccountType::RetainedEarnings
283 | TranslationAccountType::Equity => {
284 let hist_rate = self
285 .historical_equity_rates
286 .get(account_code)
287 .copied()
288 .unwrap_or(Decimal::ONE);
289 (hist_rate, RateType::Historical)
290 }
291 TranslationAccountType::Asset | TranslationAccountType::Liability => {
292 if is_monetary(account_code) {
293 (closing_rate, RateType::Closing)
294 } else {
295 let hist_rate = self
296 .historical_equity_rates
297 .get(account_code)
298 .copied()
299 .unwrap_or(closing_rate);
300 (hist_rate, RateType::Historical)
301 }
302 }
303 },
304 TranslationMethod::MonetaryNonMonetary => match account_type {
305 TranslationAccountType::CommonStock
306 | TranslationAccountType::AdditionalPaidInCapital
307 | TranslationAccountType::RetainedEarnings
308 | TranslationAccountType::Equity => {
309 let hist_rate = self
310 .historical_equity_rates
311 .get(account_code)
312 .copied()
313 .unwrap_or(Decimal::ONE);
314 (hist_rate, RateType::Historical)
315 }
316 _ => {
317 if is_monetary(account_code) {
318 (closing_rate, RateType::Closing)
319 } else {
320 let hist_rate = self
321 .historical_equity_rates
322 .get(account_code)
323 .copied()
324 .unwrap_or(closing_rate);
325 (hist_rate, RateType::Historical)
326 }
327 }
328 },
329 };
330
331 TranslatedAmount {
332 local_amount: amount,
333 local_currency: local_currency.to_string(),
334 group_amount: (amount * rate).round_dp(2),
335 group_currency: self.config.group_currency.clone(),
336 rate_used: rate,
337 rate_type,
338 translation_date: date,
339 }
340 }
341
342 fn determine_account_type(&self, account_code: &str) -> TranslationAccountType {
344 if self
346 .config
347 .historical_rate_accounts
348 .contains(&account_code.to_string())
349 {
350 if account_code.starts_with("31") {
351 return TranslationAccountType::CommonStock;
352 } else if account_code.starts_with("32") {
353 return TranslationAccountType::AdditionalPaidInCapital;
354 }
355 }
356
357 if account_code == self.config.retained_earnings_account {
358 return TranslationAccountType::RetainedEarnings;
359 }
360
361 for (prefix, account_type) in &self.config.account_type_map {
363 if account_code.starts_with(prefix) {
364 return account_type.clone();
365 }
366 }
367
368 TranslationAccountType::Asset
370 }
371
372 fn lookup_historical_equity_rate(
377 &self,
378 account_code: &str,
379 historical_rates: &HashMap<String, Decimal>,
380 fallback: Decimal,
381 ) -> Decimal {
382 self.historical_equity_rates
383 .get(account_code)
384 .or_else(|| historical_rates.get(account_code))
385 .copied()
386 .unwrap_or(fallback)
387 }
388
389 fn determine_rate(
391 &self,
392 account_code: &str,
393 account_type: &TranslationAccountType,
394 closing_rate: Decimal,
395 average_rate: Decimal,
396 historical_rates: &HashMap<String, Decimal>,
397 ) -> Decimal {
398 match self.config.method {
399 TranslationMethod::CurrentRate => {
400 match account_type {
401 TranslationAccountType::Asset | TranslationAccountType::Liability => {
402 closing_rate
403 }
404 TranslationAccountType::Revenue | TranslationAccountType::Expense => {
405 average_rate
406 }
407 TranslationAccountType::CommonStock
408 | TranslationAccountType::AdditionalPaidInCapital => {
409 self.lookup_historical_equity_rate(
411 account_code,
412 historical_rates,
413 closing_rate,
414 )
415 }
416 TranslationAccountType::Equity | TranslationAccountType::RetainedEarnings => {
417 closing_rate
419 }
420 }
421 }
422 TranslationMethod::Temporal => {
423 match account_type {
426 TranslationAccountType::CommonStock
427 | TranslationAccountType::AdditionalPaidInCapital
428 | TranslationAccountType::RetainedEarnings => self
429 .lookup_historical_equity_rate(
430 account_code,
431 historical_rates,
432 closing_rate,
433 ),
434 TranslationAccountType::Equity => self.lookup_historical_equity_rate(
435 account_code,
436 historical_rates,
437 closing_rate,
438 ),
439 TranslationAccountType::Revenue | TranslationAccountType::Expense => {
440 average_rate
442 }
443 TranslationAccountType::Asset | TranslationAccountType::Liability => {
444 if is_monetary(account_code) {
446 closing_rate
447 } else {
448 historical_rates
451 .get(account_code)
452 .copied()
453 .unwrap_or(closing_rate)
454 }
455 }
456 }
457 }
458 TranslationMethod::MonetaryNonMonetary => {
459 match account_type {
463 TranslationAccountType::CommonStock
464 | TranslationAccountType::AdditionalPaidInCapital
465 | TranslationAccountType::RetainedEarnings => self
466 .lookup_historical_equity_rate(
467 account_code,
468 historical_rates,
469 closing_rate,
470 ),
471 TranslationAccountType::Equity => self.lookup_historical_equity_rate(
472 account_code,
473 historical_rates,
474 closing_rate,
475 ),
476 _ => {
477 if is_monetary(account_code) {
479 closing_rate
480 } else {
481 historical_rates
483 .get(account_code)
484 .copied()
485 .unwrap_or(closing_rate)
486 }
487 }
488 }
489 }
490 }
491 }
492
493 fn rate_type_for_account(&self, account_type: &TranslationAccountType) -> RateType {
495 match account_type {
496 TranslationAccountType::Asset | TranslationAccountType::Liability => RateType::Closing,
497 TranslationAccountType::Revenue | TranslationAccountType::Expense => RateType::Average,
498 TranslationAccountType::Equity
499 | TranslationAccountType::CommonStock
500 | TranslationAccountType::AdditionalPaidInCapital
501 | TranslationAccountType::RetainedEarnings => RateType::Historical,
502 }
503 }
504}
505
506#[derive(Debug, Clone)]
508pub struct TranslatedTrialBalance {
509 pub company_code: String,
511 pub company_name: String,
513 pub local_currency: String,
515 pub group_currency: String,
517 pub period_end_date: NaiveDate,
519 pub fiscal_year: i32,
521 pub fiscal_period: u8,
523 pub lines: Vec<TranslatedTrialBalanceLine>,
525 pub closing_rate: Decimal,
527 pub average_rate: Decimal,
529 pub total_local_debit: Decimal,
531 pub total_local_credit: Decimal,
533 pub total_group_debit: Decimal,
535 pub total_group_credit: Decimal,
537 pub cta_amount: Decimal,
539 pub translation_method: TranslationMethod,
541}
542
543impl TranslatedTrialBalance {
544 pub fn is_local_balanced(&self) -> bool {
546 (self.total_local_debit - self.total_local_credit).abs() < dec!(0.01)
547 }
548
549 pub fn is_group_balanced(&self) -> bool {
551 let balance = self.total_group_debit - self.total_group_credit - self.cta_amount;
552 balance.abs() < dec!(0.01)
553 }
554
555 pub fn local_net_assets(&self) -> Decimal {
557 let assets: Decimal = self
558 .lines
559 .iter()
560 .filter(|l| matches!(l.account_type, TranslationAccountType::Asset))
561 .map(|l| l.local_debit - l.local_credit)
562 .sum();
563
564 let liabilities: Decimal = self
565 .lines
566 .iter()
567 .filter(|l| matches!(l.account_type, TranslationAccountType::Liability))
568 .map(|l| l.local_credit - l.local_debit)
569 .sum();
570
571 assets - liabilities
572 }
573
574 pub fn group_net_assets(&self) -> Decimal {
576 let assets: Decimal = self
577 .lines
578 .iter()
579 .filter(|l| matches!(l.account_type, TranslationAccountType::Asset))
580 .map(|l| l.group_debit - l.group_credit)
581 .sum();
582
583 let liabilities: Decimal = self
584 .lines
585 .iter()
586 .filter(|l| matches!(l.account_type, TranslationAccountType::Liability))
587 .map(|l| l.group_credit - l.group_debit)
588 .sum();
589
590 assets - liabilities
591 }
592}
593
594#[derive(Debug, Clone)]
596pub struct TranslatedTrialBalanceLine {
597 pub account_code: String,
599 pub account_description: Option<String>,
601 pub account_type: TranslationAccountType,
603 pub local_debit: Decimal,
605 pub local_credit: Decimal,
607 pub rate_used: Decimal,
609 pub rate_type: RateType,
611 pub group_debit: Decimal,
613 pub group_credit: Decimal,
615}
616
617impl TranslatedTrialBalanceLine {
618 pub fn local_net(&self) -> Decimal {
620 self.local_debit - self.local_credit
621 }
622
623 pub fn group_net(&self) -> Decimal {
625 self.group_debit - self.group_credit
626 }
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632 use datasynth_core::models::balance::{
633 AccountCategory, AccountType, TrialBalanceLine, TrialBalanceType,
634 };
635 use datasynth_core::models::FxRate;
636
637 fn create_test_trial_balance() -> TrialBalance {
638 let mut tb = TrialBalance::new(
639 "TB-TEST-2024-12".to_string(),
640 "1200".to_string(),
641 NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
642 2024,
643 12,
644 "EUR".to_string(),
645 TrialBalanceType::PostClosing,
646 );
647 tb.company_name = Some("Test Subsidiary".to_string());
648
649 tb.add_line(TrialBalanceLine {
650 account_code: "1000".to_string(),
651 account_description: "Cash".to_string(),
652 category: AccountCategory::CurrentAssets,
653 account_type: AccountType::Asset,
654 opening_balance: Decimal::ZERO,
655 period_debits: dec!(100000),
656 period_credits: Decimal::ZERO,
657 closing_balance: dec!(100000),
658 debit_balance: dec!(100000),
659 credit_balance: Decimal::ZERO,
660 cost_center: None,
661 profit_center: None,
662 });
663
664 tb.add_line(TrialBalanceLine {
665 account_code: "2000".to_string(),
666 account_description: "Accounts Payable".to_string(),
667 category: AccountCategory::CurrentLiabilities,
668 account_type: AccountType::Liability,
669 opening_balance: Decimal::ZERO,
670 period_debits: Decimal::ZERO,
671 period_credits: dec!(50000),
672 closing_balance: dec!(50000),
673 debit_balance: Decimal::ZERO,
674 credit_balance: dec!(50000),
675 cost_center: None,
676 profit_center: None,
677 });
678
679 tb.add_line(TrialBalanceLine {
680 account_code: "4000".to_string(),
681 account_description: "Revenue".to_string(),
682 category: AccountCategory::Revenue,
683 account_type: AccountType::Revenue,
684 opening_balance: Decimal::ZERO,
685 period_debits: Decimal::ZERO,
686 period_credits: dec!(150000),
687 closing_balance: dec!(150000),
688 debit_balance: Decimal::ZERO,
689 credit_balance: dec!(150000),
690 cost_center: None,
691 profit_center: None,
692 });
693
694 tb.add_line(TrialBalanceLine {
695 account_code: "5000".to_string(),
696 account_description: "Expenses".to_string(),
697 category: AccountCategory::OperatingExpenses,
698 account_type: AccountType::Expense,
699 opening_balance: Decimal::ZERO,
700 period_debits: dec!(100000),
701 period_credits: Decimal::ZERO,
702 closing_balance: dec!(100000),
703 debit_balance: dec!(100000),
704 credit_balance: Decimal::ZERO,
705 cost_center: None,
706 profit_center: None,
707 });
708
709 tb
710 }
711
712 #[test]
713 fn test_translate_trial_balance() {
714 let translator = CurrencyTranslator::new(CurrencyTranslatorConfig::default());
715 let trial_balance = create_test_trial_balance();
716
717 let mut rate_table = FxRateTable::new("USD");
718 rate_table.add_rate(FxRate::new(
719 "EUR",
720 "USD",
721 RateType::Closing,
722 NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
723 dec!(1.10),
724 "TEST",
725 ));
726 rate_table.add_rate(FxRate::new(
727 "EUR",
728 "USD",
729 RateType::Average,
730 NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
731 dec!(1.08),
732 "TEST",
733 ));
734
735 let historical_rates = HashMap::new();
736 let translated =
737 translator.translate_trial_balance(&trial_balance, &rate_table, &historical_rates);
738
739 assert!(translated.is_local_balanced());
740 assert_eq!(translated.closing_rate, dec!(1.10));
741 assert_eq!(translated.average_rate, dec!(1.08));
742 }
743
744 #[test]
745 fn test_is_monetary() {
746 assert!(is_monetary("1000"));
748 assert!(is_monetary("1001"));
749 assert!(is_monetary("1099"));
750
751 assert!(is_monetary("1100"));
753 assert!(is_monetary("1150"));
754
755 assert!(is_monetary("1200"));
757
758 assert!(is_monetary("1300"));
760
761 assert!(!is_monetary("1400"));
763 assert!(!is_monetary("1450"));
764
765 assert!(!is_monetary("1500"));
767 assert!(!is_monetary("1550"));
768
769 assert!(!is_monetary("1600"));
771 assert!(!is_monetary("1650"));
772
773 assert!(!is_monetary("1700"));
775
776 assert!(!is_monetary("1800"));
778
779 assert!(!is_monetary("1900"));
781
782 assert!(is_monetary("2000"));
784 assert!(is_monetary("2100"));
785 assert!(is_monetary("2500"));
786 assert!(is_monetary("2900"));
787
788 assert!(!is_monetary("3000"));
790 assert!(!is_monetary("3100"));
791 assert!(!is_monetary("3200"));
792 assert!(!is_monetary("3900"));
793
794 assert!(is_monetary("4000"));
796 assert!(is_monetary("4500"));
797
798 assert!(is_monetary("5000"));
800 assert!(is_monetary("6000"));
801
802 assert!(is_monetary("1"));
804 assert!(is_monetary(""));
805 }
806
807 #[test]
808 fn test_historical_equity_rates() {
809 let mut translator = CurrencyTranslator::new(CurrencyTranslatorConfig::default());
810
811 assert!(translator.historical_equity_rates.is_empty());
813
814 let mut rates = HashMap::new();
816 rates.insert("3100".to_string(), dec!(1.05));
817 rates.insert("3200".to_string(), dec!(0.98));
818 translator.set_historical_equity_rates(rates);
819
820 assert_eq!(translator.historical_equity_rates.len(), 2);
821 assert_eq!(
822 translator.historical_equity_rates.get("3100"),
823 Some(&dec!(1.05))
824 );
825 assert_eq!(
826 translator.historical_equity_rates.get("3200"),
827 Some(&dec!(0.98))
828 );
829
830 let rate = translator.determine_rate(
832 "3100",
833 &TranslationAccountType::CommonStock,
834 dec!(1.10),
835 dec!(1.08),
836 &HashMap::new(),
837 );
838 assert_eq!(rate, dec!(1.05));
839 }
840
841 #[test]
842 fn test_temporal_method_monetary_vs_non_monetary() {
843 let config = CurrencyTranslatorConfig {
844 method: TranslationMethod::Temporal,
845 ..CurrencyTranslatorConfig::default()
846 };
847 let translator = CurrencyTranslator::new(config);
848
849 let closing_rate = dec!(1.10);
850 let average_rate = dec!(1.08);
851 let mut historical_rates = HashMap::new();
852 historical_rates.insert("1500".to_string(), dec!(1.02)); let rate = translator.determine_rate(
856 "1000",
857 &TranslationAccountType::Asset,
858 closing_rate,
859 average_rate,
860 &historical_rates,
861 );
862 assert_eq!(rate, closing_rate);
863
864 let rate = translator.determine_rate(
866 "1100",
867 &TranslationAccountType::Asset,
868 closing_rate,
869 average_rate,
870 &historical_rates,
871 );
872 assert_eq!(rate, closing_rate);
873
874 let rate = translator.determine_rate(
876 "1500",
877 &TranslationAccountType::Asset,
878 closing_rate,
879 average_rate,
880 &historical_rates,
881 );
882 assert_eq!(rate, dec!(1.02));
883
884 let rate = translator.determine_rate(
886 "1600",
887 &TranslationAccountType::Asset,
888 closing_rate,
889 average_rate,
890 &historical_rates,
891 );
892 assert_eq!(rate, closing_rate);
893
894 let rate = translator.determine_rate(
896 "2000",
897 &TranslationAccountType::Liability,
898 closing_rate,
899 average_rate,
900 &historical_rates,
901 );
902 assert_eq!(rate, closing_rate);
903
904 let rate = translator.determine_rate(
906 "4000",
907 &TranslationAccountType::Revenue,
908 closing_rate,
909 average_rate,
910 &historical_rates,
911 );
912 assert_eq!(rate, average_rate);
913
914 let rate = translator.determine_rate(
916 "5000",
917 &TranslationAccountType::Expense,
918 closing_rate,
919 average_rate,
920 &historical_rates,
921 );
922 assert_eq!(rate, average_rate);
923
924 let mut translator_with_equity = CurrencyTranslator::new(CurrencyTranslatorConfig {
926 method: TranslationMethod::Temporal,
927 ..CurrencyTranslatorConfig::default()
928 });
929 let mut equity_rates = HashMap::new();
930 equity_rates.insert("3100".to_string(), dec!(0.95));
931 translator_with_equity.set_historical_equity_rates(equity_rates);
932
933 let rate = translator_with_equity.determine_rate(
934 "3100",
935 &TranslationAccountType::CommonStock,
936 closing_rate,
937 average_rate,
938 &historical_rates,
939 );
940 assert_eq!(rate, dec!(0.95));
941 }
942
943 #[test]
944 fn test_monetary_non_monetary_method() {
945 let config = CurrencyTranslatorConfig {
946 method: TranslationMethod::MonetaryNonMonetary,
947 ..CurrencyTranslatorConfig::default()
948 };
949 let mut translator = CurrencyTranslator::new(config);
950
951 let closing_rate = dec!(1.10);
952 let average_rate = dec!(1.08);
953 let mut historical_rates = HashMap::new();
954 historical_rates.insert("1500".to_string(), dec!(1.02));
955 historical_rates.insert("1600".to_string(), dec!(0.99));
956
957 let mut equity_rates = HashMap::new();
959 equity_rates.insert("3100".to_string(), dec!(1.05));
960 equity_rates.insert("3300".to_string(), dec!(1.03));
961 translator.set_historical_equity_rates(equity_rates);
962
963 let rate = translator.determine_rate(
965 "1000",
966 &TranslationAccountType::Asset,
967 closing_rate,
968 average_rate,
969 &historical_rates,
970 );
971 assert_eq!(rate, closing_rate);
972
973 let rate = translator.determine_rate(
975 "1100",
976 &TranslationAccountType::Asset,
977 closing_rate,
978 average_rate,
979 &historical_rates,
980 );
981 assert_eq!(rate, closing_rate);
982
983 let rate = translator.determine_rate(
985 "1500",
986 &TranslationAccountType::Asset,
987 closing_rate,
988 average_rate,
989 &historical_rates,
990 );
991 assert_eq!(rate, dec!(1.02));
992
993 let rate = translator.determine_rate(
995 "1600",
996 &TranslationAccountType::Asset,
997 closing_rate,
998 average_rate,
999 &historical_rates,
1000 );
1001 assert_eq!(rate, dec!(0.99));
1002
1003 let rate = translator.determine_rate(
1005 "2000",
1006 &TranslationAccountType::Liability,
1007 closing_rate,
1008 average_rate,
1009 &historical_rates,
1010 );
1011 assert_eq!(rate, closing_rate);
1012
1013 let rate = translator.determine_rate(
1015 "3100",
1016 &TranslationAccountType::CommonStock,
1017 closing_rate,
1018 average_rate,
1019 &historical_rates,
1020 );
1021 assert_eq!(rate, dec!(1.05));
1022
1023 let rate = translator.determine_rate(
1025 "3300",
1026 &TranslationAccountType::RetainedEarnings,
1027 closing_rate,
1028 average_rate,
1029 &historical_rates,
1030 );
1031 assert_eq!(rate, dec!(1.03));
1032
1033 let rate = translator.determine_rate(
1035 "4000",
1036 &TranslationAccountType::Revenue,
1037 closing_rate,
1038 average_rate,
1039 &historical_rates,
1040 );
1041 assert_eq!(rate, closing_rate);
1042
1043 let rate = translator.determine_rate(
1045 "5000",
1046 &TranslationAccountType::Expense,
1047 closing_rate,
1048 average_rate,
1049 &historical_rates,
1050 );
1051 assert_eq!(rate, closing_rate);
1052 }
1053}