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)]
630#[allow(clippy::unwrap_used)]
631mod tests {
632 use super::*;
633 use datasynth_core::models::balance::{
634 AccountCategory, AccountType, TrialBalanceLine, TrialBalanceType,
635 };
636 use datasynth_core::models::FxRate;
637
638 fn create_test_trial_balance() -> TrialBalance {
639 let mut tb = TrialBalance::new(
640 "TB-TEST-2024-12".to_string(),
641 "1200".to_string(),
642 NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
643 2024,
644 12,
645 "EUR".to_string(),
646 TrialBalanceType::PostClosing,
647 );
648 tb.company_name = Some("Test Subsidiary".to_string());
649
650 tb.add_line(TrialBalanceLine {
651 account_code: "1000".to_string(),
652 account_description: "Cash".to_string(),
653 category: AccountCategory::CurrentAssets,
654 account_type: AccountType::Asset,
655 opening_balance: Decimal::ZERO,
656 period_debits: dec!(100000),
657 period_credits: Decimal::ZERO,
658 closing_balance: dec!(100000),
659 debit_balance: dec!(100000),
660 credit_balance: Decimal::ZERO,
661 cost_center: None,
662 profit_center: None,
663 });
664
665 tb.add_line(TrialBalanceLine {
666 account_code: "2000".to_string(),
667 account_description: "Accounts Payable".to_string(),
668 category: AccountCategory::CurrentLiabilities,
669 account_type: AccountType::Liability,
670 opening_balance: Decimal::ZERO,
671 period_debits: Decimal::ZERO,
672 period_credits: dec!(50000),
673 closing_balance: dec!(50000),
674 debit_balance: Decimal::ZERO,
675 credit_balance: dec!(50000),
676 cost_center: None,
677 profit_center: None,
678 });
679
680 tb.add_line(TrialBalanceLine {
681 account_code: "4000".to_string(),
682 account_description: "Revenue".to_string(),
683 category: AccountCategory::Revenue,
684 account_type: AccountType::Revenue,
685 opening_balance: Decimal::ZERO,
686 period_debits: Decimal::ZERO,
687 period_credits: dec!(150000),
688 closing_balance: dec!(150000),
689 debit_balance: Decimal::ZERO,
690 credit_balance: dec!(150000),
691 cost_center: None,
692 profit_center: None,
693 });
694
695 tb.add_line(TrialBalanceLine {
696 account_code: "5000".to_string(),
697 account_description: "Expenses".to_string(),
698 category: AccountCategory::OperatingExpenses,
699 account_type: AccountType::Expense,
700 opening_balance: Decimal::ZERO,
701 period_debits: dec!(100000),
702 period_credits: Decimal::ZERO,
703 closing_balance: dec!(100000),
704 debit_balance: dec!(100000),
705 credit_balance: Decimal::ZERO,
706 cost_center: None,
707 profit_center: None,
708 });
709
710 tb
711 }
712
713 #[test]
714 fn test_translate_trial_balance() {
715 let translator = CurrencyTranslator::new(CurrencyTranslatorConfig::default());
716 let trial_balance = create_test_trial_balance();
717
718 let mut rate_table = FxRateTable::new("USD");
719 rate_table.add_rate(FxRate::new(
720 "EUR",
721 "USD",
722 RateType::Closing,
723 NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
724 dec!(1.10),
725 "TEST",
726 ));
727 rate_table.add_rate(FxRate::new(
728 "EUR",
729 "USD",
730 RateType::Average,
731 NaiveDate::from_ymd_opt(2024, 12, 31).unwrap(),
732 dec!(1.08),
733 "TEST",
734 ));
735
736 let historical_rates = HashMap::new();
737 let translated =
738 translator.translate_trial_balance(&trial_balance, &rate_table, &historical_rates);
739
740 assert!(translated.is_local_balanced());
741 assert_eq!(translated.closing_rate, dec!(1.10));
742 assert_eq!(translated.average_rate, dec!(1.08));
743 }
744
745 #[test]
746 fn test_is_monetary() {
747 assert!(is_monetary("1000"));
749 assert!(is_monetary("1001"));
750 assert!(is_monetary("1099"));
751
752 assert!(is_monetary("1100"));
754 assert!(is_monetary("1150"));
755
756 assert!(is_monetary("1200"));
758
759 assert!(is_monetary("1300"));
761
762 assert!(!is_monetary("1400"));
764 assert!(!is_monetary("1450"));
765
766 assert!(!is_monetary("1500"));
768 assert!(!is_monetary("1550"));
769
770 assert!(!is_monetary("1600"));
772 assert!(!is_monetary("1650"));
773
774 assert!(!is_monetary("1700"));
776
777 assert!(!is_monetary("1800"));
779
780 assert!(!is_monetary("1900"));
782
783 assert!(is_monetary("2000"));
785 assert!(is_monetary("2100"));
786 assert!(is_monetary("2500"));
787 assert!(is_monetary("2900"));
788
789 assert!(!is_monetary("3000"));
791 assert!(!is_monetary("3100"));
792 assert!(!is_monetary("3200"));
793 assert!(!is_monetary("3900"));
794
795 assert!(is_monetary("4000"));
797 assert!(is_monetary("4500"));
798
799 assert!(is_monetary("5000"));
801 assert!(is_monetary("6000"));
802
803 assert!(is_monetary("1"));
805 assert!(is_monetary(""));
806 }
807
808 #[test]
809 fn test_historical_equity_rates() {
810 let mut translator = CurrencyTranslator::new(CurrencyTranslatorConfig::default());
811
812 assert!(translator.historical_equity_rates.is_empty());
814
815 let mut rates = HashMap::new();
817 rates.insert("3100".to_string(), dec!(1.05));
818 rates.insert("3200".to_string(), dec!(0.98));
819 translator.set_historical_equity_rates(rates);
820
821 assert_eq!(translator.historical_equity_rates.len(), 2);
822 assert_eq!(
823 translator.historical_equity_rates.get("3100"),
824 Some(&dec!(1.05))
825 );
826 assert_eq!(
827 translator.historical_equity_rates.get("3200"),
828 Some(&dec!(0.98))
829 );
830
831 let rate = translator.determine_rate(
833 "3100",
834 &TranslationAccountType::CommonStock,
835 dec!(1.10),
836 dec!(1.08),
837 &HashMap::new(),
838 );
839 assert_eq!(rate, dec!(1.05));
840 }
841
842 #[test]
843 fn test_temporal_method_monetary_vs_non_monetary() {
844 let config = CurrencyTranslatorConfig {
845 method: TranslationMethod::Temporal,
846 ..CurrencyTranslatorConfig::default()
847 };
848 let translator = CurrencyTranslator::new(config);
849
850 let closing_rate = dec!(1.10);
851 let average_rate = dec!(1.08);
852 let mut historical_rates = HashMap::new();
853 historical_rates.insert("1500".to_string(), dec!(1.02)); let rate = translator.determine_rate(
857 "1000",
858 &TranslationAccountType::Asset,
859 closing_rate,
860 average_rate,
861 &historical_rates,
862 );
863 assert_eq!(rate, closing_rate);
864
865 let rate = translator.determine_rate(
867 "1100",
868 &TranslationAccountType::Asset,
869 closing_rate,
870 average_rate,
871 &historical_rates,
872 );
873 assert_eq!(rate, closing_rate);
874
875 let rate = translator.determine_rate(
877 "1500",
878 &TranslationAccountType::Asset,
879 closing_rate,
880 average_rate,
881 &historical_rates,
882 );
883 assert_eq!(rate, dec!(1.02));
884
885 let rate = translator.determine_rate(
887 "1600",
888 &TranslationAccountType::Asset,
889 closing_rate,
890 average_rate,
891 &historical_rates,
892 );
893 assert_eq!(rate, closing_rate);
894
895 let rate = translator.determine_rate(
897 "2000",
898 &TranslationAccountType::Liability,
899 closing_rate,
900 average_rate,
901 &historical_rates,
902 );
903 assert_eq!(rate, closing_rate);
904
905 let rate = translator.determine_rate(
907 "4000",
908 &TranslationAccountType::Revenue,
909 closing_rate,
910 average_rate,
911 &historical_rates,
912 );
913 assert_eq!(rate, average_rate);
914
915 let rate = translator.determine_rate(
917 "5000",
918 &TranslationAccountType::Expense,
919 closing_rate,
920 average_rate,
921 &historical_rates,
922 );
923 assert_eq!(rate, average_rate);
924
925 let mut translator_with_equity = CurrencyTranslator::new(CurrencyTranslatorConfig {
927 method: TranslationMethod::Temporal,
928 ..CurrencyTranslatorConfig::default()
929 });
930 let mut equity_rates = HashMap::new();
931 equity_rates.insert("3100".to_string(), dec!(0.95));
932 translator_with_equity.set_historical_equity_rates(equity_rates);
933
934 let rate = translator_with_equity.determine_rate(
935 "3100",
936 &TranslationAccountType::CommonStock,
937 closing_rate,
938 average_rate,
939 &historical_rates,
940 );
941 assert_eq!(rate, dec!(0.95));
942 }
943
944 #[test]
945 fn test_monetary_non_monetary_method() {
946 let config = CurrencyTranslatorConfig {
947 method: TranslationMethod::MonetaryNonMonetary,
948 ..CurrencyTranslatorConfig::default()
949 };
950 let mut translator = CurrencyTranslator::new(config);
951
952 let closing_rate = dec!(1.10);
953 let average_rate = dec!(1.08);
954 let mut historical_rates = HashMap::new();
955 historical_rates.insert("1500".to_string(), dec!(1.02));
956 historical_rates.insert("1600".to_string(), dec!(0.99));
957
958 let mut equity_rates = HashMap::new();
960 equity_rates.insert("3100".to_string(), dec!(1.05));
961 equity_rates.insert("3300".to_string(), dec!(1.03));
962 translator.set_historical_equity_rates(equity_rates);
963
964 let rate = translator.determine_rate(
966 "1000",
967 &TranslationAccountType::Asset,
968 closing_rate,
969 average_rate,
970 &historical_rates,
971 );
972 assert_eq!(rate, closing_rate);
973
974 let rate = translator.determine_rate(
976 "1100",
977 &TranslationAccountType::Asset,
978 closing_rate,
979 average_rate,
980 &historical_rates,
981 );
982 assert_eq!(rate, closing_rate);
983
984 let rate = translator.determine_rate(
986 "1500",
987 &TranslationAccountType::Asset,
988 closing_rate,
989 average_rate,
990 &historical_rates,
991 );
992 assert_eq!(rate, dec!(1.02));
993
994 let rate = translator.determine_rate(
996 "1600",
997 &TranslationAccountType::Asset,
998 closing_rate,
999 average_rate,
1000 &historical_rates,
1001 );
1002 assert_eq!(rate, dec!(0.99));
1003
1004 let rate = translator.determine_rate(
1006 "2000",
1007 &TranslationAccountType::Liability,
1008 closing_rate,
1009 average_rate,
1010 &historical_rates,
1011 );
1012 assert_eq!(rate, closing_rate);
1013
1014 let rate = translator.determine_rate(
1016 "3100",
1017 &TranslationAccountType::CommonStock,
1018 closing_rate,
1019 average_rate,
1020 &historical_rates,
1021 );
1022 assert_eq!(rate, dec!(1.05));
1023
1024 let rate = translator.determine_rate(
1026 "3300",
1027 &TranslationAccountType::RetainedEarnings,
1028 closing_rate,
1029 average_rate,
1030 &historical_rates,
1031 );
1032 assert_eq!(rate, dec!(1.03));
1033
1034 let rate = translator.determine_rate(
1036 "4000",
1037 &TranslationAccountType::Revenue,
1038 closing_rate,
1039 average_rate,
1040 &historical_rates,
1041 );
1042 assert_eq!(rate, closing_rate);
1043
1044 let rate = translator.determine_rate(
1046 "5000",
1047 &TranslationAccountType::Expense,
1048 closing_rate,
1049 average_rate,
1050 &historical_rates,
1051 );
1052 assert_eq!(rate, closing_rate);
1053 }
1054}