investments 4.16.1

Helps you with managing your investments
Documentation
use std::any::Any;

use crate::core::{EmptyResult, GenericResult};
use crate::currency;
use crate::types::{Date, Decimal};

use super::countries::CountryCode;
use super::encoding::TaxStatementType;
use super::parser::{TaxStatementReader, TaxStatementWriter};
use super::record::Record;
use super::types::Integer;

#[derive(Debug)]
pub struct ForeignIncome {
    pub incomes: Vec<CurrencyIncome>,
}

impl ForeignIncome {
    pub const RECORD_NAME: &'static str = "@DeclForeign";

    pub fn read(reader: &mut TaxStatementReader) -> GenericResult<ForeignIncome> {
        let number: usize = reader.read_value()?;
        let mut incomes = Vec::with_capacity(number);

        for index in 0..number {
            incomes.push(CurrencyIncome::read(reader, index)?);
        }

        Ok(ForeignIncome {incomes: incomes})
    }
}

impl Record for ForeignIncome {
    fn name(&self) -> &str {
        ForeignIncome::RECORD_NAME
    }

    fn as_mut_any(&mut self) -> &mut dyn Any {
        self
    }

    fn write(&self, writer: &mut TaxStatementWriter) -> EmptyResult {
        writer.write_data(ForeignIncome::RECORD_NAME)?;
        writer.write_value(&self.incomes.len())?;

        for (index, income) in self.incomes.iter().enumerate() {
            income.write(writer, index)?;
        }

        Ok(())
    }

}

tax_statement_array_record!(CurrencyIncome {
    type_: IncomeType,
    description: String,

    source_from: CountryCode,
    received_in: CountryCode,

    date: Date,
    tax_payment_date: Date,
    currency: CurrencyInfo,

    amount: Decimal,
    local_amount: Decimal,

    paid_tax: Decimal,
    local_paid_tax: Decimal,
    deduction: DeductionInfo,

    controlled_foreign_company: ControlledForeignCompanyInfo,
}, index_length=4);

tax_statement_inner_record!(CurrencyInfo {
    automatic_convertion: bool,
    code: String,

    income_date_rate: Decimal,
    income_date_units: Integer,

    tax_payment_date_rate: Decimal,
    tax_payment_date_units: Integer,

    name: String,
});

impl CurrencyInfo {
    pub fn new(currency: &str, precise_currency_rate: Decimal) -> GenericResult<CurrencyInfo> {
        let (currency_code, currency_name, currency_rate_units) = match currency {
            "AUD" => ("036", "Австралийский доллар", 100),
            "EUR" => ("978", "Евро", 100),
            "GBP" => ("826", "Фунт стерлингов", 100),
            "HKD" => ("344", "Гонконгский доллар", 1000),
            "RUB" => ("643", "Российский рубль", 1000),
            "USD" => ("840", "Доллар США", 100),
            _ => return Err!("{} currency is not supported yet", currency),
        };
        let currency_rate = currency::round(precise_currency_rate * Decimal::from(currency_rate_units));

        Ok(CurrencyInfo {
            automatic_convertion: true,
            code: currency_code.to_owned(),

            income_date_rate: currency_rate,
            income_date_units: currency_rate_units,

            tax_payment_date_rate: currency_rate,
            tax_payment_date_units: currency_rate_units,

            name: currency_name.to_owned(),
        })
    }
}

tax_statement_inner_record!(DeductionInfo {
    code: Integer,
    amount: Decimal,
});

impl DeductionInfo {
    pub fn new_none() -> DeductionInfo {
        DeductionInfo {
            code: 0,
            amount: dec!(0),
        }
    }
}

tax_statement_inner_record!(ControlledForeignCompanyInfo {
    unknown1: Integer,
    unknown2: Integer,
    profit_calculation_method: Integer,
    number: String,
    paid_tax: Integer,
});

impl ControlledForeignCompanyInfo {
    pub fn new_none() -> ControlledForeignCompanyInfo {
        ControlledForeignCompanyInfo {
            unknown1: 0,
            unknown2: 0,
            profit_calculation_method: 0,
            number: String::new(),
            paid_tax: 0,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IncomeType {
    Dividend,
    Interest,
    Stock,
    Other(GenericIncomeType),
}

impl IncomeType {
    fn to_generic(&self) -> GenericIncomeType {
        let (category, code, name) = match self {
            IncomeType::Dividend => (0, 1010, "Дивиденды"),
            IncomeType::Stock => (0, 1530, "(01)Доходы от реализации ЦБ (обращ-ся на орг. рынке ЦБ)"),
            IncomeType::Interest => (0, 6013, "Доходы в виде процентов, полученных от источников за пределами Российской Федерации, в отношении которых применяется налоговая ставка, предусмотренная пунктом 1 статьи 224 Кодекса"),
            IncomeType::Other(other) => return other.clone(),
        };
        GenericIncomeType {category, code, name: name.to_owned()}
    }
}

impl TaxStatementType for IncomeType {
    fn read(reader: &mut TaxStatementReader) -> GenericResult<IncomeType> {
        let generic = GenericIncomeType::read(reader)?;

        for income_type in [IncomeType::Dividend, IncomeType::Interest, IncomeType::Stock] {
            if income_type.to_generic() == generic {
                return Ok(income_type);
            }
        }

        Ok(IncomeType::Other(generic))
    }

    fn write(&self, writer: &mut TaxStatementWriter) -> EmptyResult {
        self.to_generic().write(writer)
    }
}

tax_statement_inner_record!(GenericIncomeType {
    category: Integer,
    code: Integer,
    name: String,
});