use std::collections::{HashMap, BTreeMap};
use std::ops::Bound;
use chrono::{Datelike, Duration};
use crate::currency::Cash;
use crate::taxes::IncomeType;
use crate::time;
use crate::types::{Date, Decimal};
#[derive(Clone)]
pub struct Country {
pub currency: &'static str,
default_tax_rate: Decimal,
tax_rates: HashMap<IncomeType, BTreeMap<i32, Decimal>>,
tax_precision: u32,
}
impl Country {
fn new(
currency: &'static str, mut default_tax_rate: Decimal,
mut tax_rates: HashMap<IncomeType, BTreeMap<i32, Decimal>>, tax_precision: u32,
) -> Country {
default_tax_rate /= dec!(100);
for tax_rates in tax_rates.values_mut() {
for tax_rate in tax_rates.values_mut() {
*tax_rate /= dec!(100);
}
}
Country {currency, default_tax_rate, tax_rates, tax_precision}
}
pub fn cash(&self, amount: Decimal) -> Cash {
Cash::new(self.currency, amount)
}
pub fn round_tax(&self, tax: Cash) -> Cash {
assert_eq!(tax.currency, self.currency);
tax.round().round_to(self.tax_precision)
}
pub fn tax_to_pay(
&self, income_type: IncomeType, year: i32, income: Cash, paid_tax: Option<Cash>,
) -> Cash {
assert_eq!(income.currency, self.currency);
let income = income.round();
if income.is_negative() || income.is_zero() {
return Cash::zero(self.currency);
}
let tax_to_pay = self.round_tax(income * self.tax_rate(income_type, year));
if let Some(paid_tax) = paid_tax {
assert!(!paid_tax.is_negative());
assert_eq!(paid_tax.currency, tax_to_pay.currency);
let tax_deduction = self.round_tax(paid_tax);
if tax_deduction.amount < tax_to_pay.amount {
tax_to_pay - tax_deduction
} else {
Cash::zero(self.currency)
}
} else {
tax_to_pay
}
}
pub fn deduce_income(&self, income_type: IncomeType, year: i32, result_income: Cash) -> Cash {
assert_eq!(result_income.currency, self.currency);
(result_income / (dec!(1) - self.tax_rate(income_type, year))).round()
}
fn tax_rate(&self, income_type: IncomeType, year: i32) -> Decimal {
self.tax_rates.get(&income_type).and_then(|tax_rates| {
tax_rates
.range((Bound::Unbounded, Bound::Included(year)))
.map(|entry| *entry.1)
.last()
}).unwrap_or(self.default_tax_rate)
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum Jurisdiction {
Russia,
Usa,
}
impl Jurisdiction {
pub fn name(self) -> &'static str {
match self {
Jurisdiction::Russia => "Russia",
Jurisdiction::Usa => "USA",
}
}
pub fn code(self) -> &'static str {
match self {
Jurisdiction::Russia => "RU",
Jurisdiction::Usa => "US",
}
}
}
pub fn russia(
trading_tax_rates: &BTreeMap<i32, Decimal>, dividends_tax_rates: &BTreeMap<i32, Decimal>,
interest_tax_rates: &BTreeMap<i32, Decimal>,
) -> Country {
Country::new("RUB", dec!(13), hashmap!{
IncomeType::Trading => trading_tax_rates.clone(),
IncomeType::Dividends => dividends_tax_rates.clone(),
IncomeType::Interest => interest_tax_rates.clone(),
}, 0)
}
pub fn us() -> Country {
Country::new("USD", dec!(0), hashmap!{
IncomeType::Dividends => btreemap!{0 => dec!(10)},
}, 2)
}
pub fn is_valid_execution_date(conclusion: Date, execution: Date) -> bool {
let expected_execution = conclusion + Duration::days(2);
conclusion <= execution && get_russian_stock_exchange_min_last_working_day(execution) <= expected_execution
}
pub fn get_russian_central_bank_min_last_working_day(today: Date) -> Date {
if today.month() == 1 && today.day() < 12 {
std::cmp::max(
today - Duration::days(10),
Date::from_ymd(today.year() - 1, 12, 30),
)
} else if today.year() == 2020 && today.month() == 4 && today.day() <= 6 {
Date::from_ymd(2020, 3, 28)
} else {
today - Duration::days(5)
}
}
pub fn get_russian_stock_exchange_min_last_working_day(today: Date) -> Date {
if today.month() == 1 && today.day() < 4 {
today - Duration::days(4)
} else {
today - Duration::days(3)
}
}
pub fn nearest_possible_account_close_date() -> Date {
let execution_date = time::today_trade_execution_date();
let mut close_date = execution_date;
while get_russian_stock_exchange_min_last_working_day(close_date) < execution_date {
close_date += Duration::days(1);
}
close_date
}