diff --git a/src/commands/db/insert.rs b/src/commands/db/insert.rs new file mode 100644 index 0000000..84f87ea --- /dev/null +++ b/src/commands/db/insert.rs @@ -0,0 +1,62 @@ +extern crate clap; +extern crate rust_decimal; + +use clap::Parser; +use rust_decimal::Decimal; +use rust_decimal_macros::dec; + +use crate::{ + config::Config, + db::{self, TaxDb}, +}; + +#[derive(Parser, Debug)] +pub struct InsertArgs { + #[clap( + short, + long, + help = "Decimal income value in BAM (will be rounded to 2 decimals) after deduction" + )] + deduced_income: Option, + #[clap( + short, + long, + help = "Decimal income value in BAM (will be rounded to 2 decimals)" + )] + income: Option, + #[clap( + long, + help = "Tax deduction percentage (20 default, 30 for income from authored work). Applied only when income is used and not deduced income", + default_value_t = dec!(20) + )] + deduction_percentage: Decimal, + #[clap(long, help = "Invoice date (YYYY-MM-DD)")] + invoice_date: String, +} + +pub fn handle_command(config: Config, args: &InsertArgs) { + let income = match &args.income { + Some(inc) => { + let deduction_factor: Decimal = + dec!(1) - (args.deduction_percentage.round_dp(2) * dec!(0.01)); + inc * deduction_factor + } + None => match &args.deduced_income { + Some(deduced_income) => deduced_income.clone(), + None => panic!("Provide either --income or --deduced_income!"), + }, + }; + // TODO: extract tax calculations into a common module + let health_insurance = income * dec!(0.04); + let tax_base = income - health_insurance; + let tax_amount: Decimal = tax_base * dec!(0.10); + let mut tax_db: TaxDb = db::parse_db_with_default(config.db_location.as_str()); + tax_db.add_ams_info( + db::AmsInfo { + income_total: income, + tax_paid: tax_amount, + }, + args.invoice_date.clone(), + ); + tax_db.write_to_file(config.db_location.as_str()); +} diff --git a/src/commands/db/load.rs b/src/commands/db/load.rs new file mode 100644 index 0000000..b9098a9 --- /dev/null +++ b/src/commands/db/load.rs @@ -0,0 +1,42 @@ +extern crate clap; + +use clap::Parser; + +use crate::{ + config::Config, + db::{self, TaxDb}, + forms::amsform::{self, FormField}, +}; + +#[derive(Parser, Debug)] +pub struct LoadArgs { + #[clap(short, long)] + file: String, +} + +pub fn handle_command(config: Config, args: &LoadArgs) { + let form = amsform::load_ams_form(args.file.clone()); + + let total_paid = form.get_number_field_value(FormField::TaxToPayTotal); + let income = form.get_number_field_value(FormField::TaxBaseTotal) + + form.get_number_field_value(FormField::HealthInsuranceTotal); + + let invoice_date = [ + form.get_text_field_value(FormField::PaymentDateYear), + "-".to_string(), + form.get_text_field_value(FormField::PaymentDateMonth), + "-".to_string(), + form.get_text_field_value(FormField::PaymentDateDay), + ] + .concat(); + + let mut tax_db: TaxDb = db::parse_db_with_default(config.db_location.as_str()); + tax_db.add_ams_info( + db::AmsInfo { + income_total: income, + tax_paid: total_paid, + }, + invoice_date, + ); + tax_db.write_to_file(config.db_location.as_str()); +} diff --git a/src/commands/db/mod.rs b/src/commands/db/mod.rs new file mode 100644 index 0000000..afc381d --- /dev/null +++ b/src/commands/db/mod.rs @@ -0,0 +1,30 @@ +extern crate clap; +extern crate rust_decimal; + +mod insert; +mod load; + +use crate::config::Config; +use clap::{AppSettings, Parser, Subcommand}; + +use self::{insert::InsertArgs, load::LoadArgs}; + +#[derive(Parser, Debug)] +#[clap(setting(AppSettings::SubcommandRequiredElseHelp))] +pub struct DbArgs { + #[clap(subcommand)] + command: DbCommands, +} + +#[derive(Subcommand, Debug)] +enum DbCommands { + Load(LoadArgs), + Insert(InsertArgs), +} + +pub fn handle_command(config: Config, args: &DbArgs) { + match &args.command { + DbCommands::Load(load_args) => load::handle_command(config, load_args), + DbCommands::Insert(insert_args) => insert::handle_command(config, insert_args), + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 1ef4aa4..9aff9f3 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,4 @@ pub mod ams; +pub mod db; pub mod gpd; pub mod taxbreakdown; diff --git a/src/forms/amsform.rs b/src/forms/amsform.rs index 5c7c99a..4ac4fd4 100644 --- a/src/forms/amsform.rs +++ b/src/forms/amsform.rs @@ -219,6 +219,29 @@ impl AmsForm { .filter(|(k, _)| !k.is_empty()) .collect() } + + pub fn get_number_field_value(&self, field: FormField) -> Decimal { + Decimal::from_str_radix(self.get_text_field_value(field).as_str(), 10).unwrap() + } + + pub fn get_text_field_value(&self, field: FormField) -> String { + println!( + "Loading text field {} of {}", + field as usize, + self.pdf_form.len() + ); + match self.pdf_form.get_state(field as usize) { + pdf_forms::FieldState::Text { + text, + readonly, + required, + } => { + println!("Loaded text: {}", text); + text + } + _ => panic!("Unsupported field type!"), + } + } } pub fn load_ams_form(input_file: String) -> AmsForm { diff --git a/src/main.rs b/src/main.rs index 46f432f..2ffe0e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod format; mod forms; use clap::{AppSettings, Parser, Subcommand}; use commands::ams::{self, AmsArgs}; +use commands::db::DbArgs; use commands::gpd::{self, GpdArgs}; use commands::taxbreakdown::{self, TaxBreakdownArgs}; use config::Config; @@ -32,6 +33,7 @@ struct CliArgs { enum Commands { Ams(AmsArgs), Gpd(GpdArgs), + Db(DbArgs), TaxBreakdown(TaxBreakdownArgs), } @@ -43,6 +45,7 @@ fn main() { match &args.command { Commands::Ams(ams_args) => ams::handle_command(config, ams_args), Commands::Gpd(gpd_args) => gpd::handle_command(config, gpd_args), + Commands::Db(db_args) => commands::db::handle_command(config, db_args), Commands::TaxBreakdown(tax_breakdown_args) => { taxbreakdown::handle_command(config, tax_breakdown_args) }