use super::funejson::Value; use super::dates_add_days::add_days; use super::dates_add_months::add_months; use super::finance_tax_vat_flat_rate_data::{FLAT_RATES, FLAT_RATES_HISTORY}; use super::money_amount::{money, money_from_value, money_to_value, Money}; use super::money_apply_rate::apply_rate; const LIMITED_COST: &str = "limited-cost-business"; fn rate_on(sector: &str, on_date: &str) -> i64 { let rows: Vec<_> = FLAT_RATES.iter().filter(|r| r.sector == sector).collect(); for row in &rows { if on_date >= row.valid_from && row.valid_to.map_or(true, |to| on_date <= to) { return row.basis_points; } } if FLAT_RATES_HISTORY != "full" { if let Some(earliest) = rows.iter().map(|r| r.valid_from).min() { if on_date < earliest { panic!( "no flat rate for {} on {}: this build was installed with history={}, so it only carries rates from {}. Reinstall with history=full for older periods.", sector, on_date, FLAT_RATES_HISTORY, earliest ); } } } panic!("no flat rate for {} on {}", sector, on_date); } /// VAT due under the Flat Rate Scheme on a slice of VAT-inclusive turnover. /// /// The rate is the one in force on the supply date; the first-year reduction /// runs to the day before the first anniversary of registration. /// /// # Panics /// Panics on non-GBP turnover, an unknown sector, a date without a rate, or a /// supply before the registration date. pub fn flat_rate_vat( turnover: &Money, sector: &str, limited_cost_business: bool, registration_date: Option<&str>, supply_date: &str, ) -> FlatRateVat { if turnover.currency != "GBP" { panic!("the flat rate scheme is for GBP turnover, received {}", turnover.currency); } add_days(supply_date, 0); if sector == LIMITED_COST || !FLAT_RATES.iter().any(|r| r.sector == sector) { panic!("unknown flat rate sector \"{}\"", sector); } let base = rate_on(if limited_cost_business { LIMITED_COST } else { sector }, supply_date); let mut reduced = false; if let Some(registered) = registration_date { if supply_date < add_days(registered, 0).as_str() { panic!( "supply date {} is before the VAT registration date {}", supply_date, registered ); } reduced = supply_date < add_months(registered, 12).as_str(); } let basis_points = if reduced { (base - 100).max(0) } else { base }; FlatRateVat { sector: sector.to_string(), limited_cost_business, first_year_reduction: reduced, basis_points, vat_due: money(apply_rate(turnover, basis_points, "half-up").minor, "GBP"), } } pub fn flat_rate_vat_to_value(v: &FlatRateVat) -> Value { Value::obj(vec![ ("sector", Value::str(&v.sector)), ("limitedCostBusiness", Value::Bool(v.limited_cost_business)), ("firstYearReduction", Value::Bool(v.first_year_reduction)), ("basisPoints", Value::Int(v.basis_points)), ("vatDue", money_to_value(&v.vat_due)), ]) } pub fn fune_vector(args: &[Value]) -> Value { let registration = if args[3].is_null() { None } else { Some(args[3].as_str()) }; flat_rate_vat_to_value(&flat_rate_vat( &money_from_value(&args[0]), args[1].as_str(), args[2].as_bool(), registration, args[4].as_str(), )) }