use super::funejson::Value; use super::construction_cis_deduction::{cis_deduction, cis_deduction_to_value, CisDeduction}; use super::construction_retention::{retention, retention_to_value}; use super::finance_tax_add_vat::add_vat; use super::money_add::{add_money, subtract_money}; use super::money_amount::{money, money_from_value, money_to_value}; /// An interim valuation, from the cumulative value of the work to what is paid. /// /// The order is the whole point: retention is taken on the cumulative gross, /// previous certificates come off the net, VAT goes on the amount due, and the /// CIS deduction comes off the labour in the amount due, never off the VAT. /// Each step is its own capability; this one only fixes the order. /// /// # Panics /// Panics on mixed currencies, a reverse charge without a standard or reduced /// VAT category, a negative amount due under CIS, and whatever the retention, /// VAT and CIS capabilities refuse. pub fn interim_valuation(input: &ValuationInput) -> Valuation { let gross_valuation = add_money(&input.work_done, &input.materials_on_site); let held = retention(&gross_valuation, input.retention_basis_points, &input.stage); let net_valuation = subtract_money(&gross_valuation, &held.held); let amount_due = subtract_money(&net_valuation, &input.previously_certified); let currency = amount_due.currency.clone(); let mut vat = money(0, ¤cy); let mut reverse_charge_vat = money(0, ¤cy); let mut vat_basis_points = 0; match &input.vat_category { None => { if input.reverse_charge { panic!("the reverse charge needs a VAT category: the supplier must be VAT registered"); } } Some(category) => { if input.reverse_charge && category != "STANDARD" && category != "REDUCED" { panic!( "the reverse charge applies only to STANDARD or REDUCED supplies, received {}", category ); } let breakdown = add_vat(&amount_due, "GB", category, &input.valuation_date); vat_basis_points = breakdown.basis_points; if input.reverse_charge { reverse_charge_vat = breakdown.tax; } else { vat = breakdown.tax; } } } let cis: Option = match &input.cis_status { None => None, Some(status) => { if amount_due.minor < 0 { panic!( "the amount due is negative ({}), so there is no payment to make a CIS deduction from", amount_due.minor ); } Some(cis_deduction( &amount_due, &input.cis_materials, status, &input.valuation_date, "down", )) } }; let payable = add_money(&amount_due, &vat); let amount_payable = match &cis { None => payable, Some(c) => subtract_money(&payable, &c.deduction), }; Valuation { gross_valuation, retention: held, net_valuation, previously_certified: input.previously_certified.clone(), amount_due, vat, vat_basis_points, reverse_charge_vat, cis, amount_payable, } } fn optional_string(v: &Value) -> Option { if v.is_null() { None } else { Some(v.as_str().to_string()) } } pub fn valuation_input_from_value(v: &Value) -> ValuationInput { ValuationInput { work_done: money_from_value(v.get("workDone")), materials_on_site: money_from_value(v.get("materialsOnSite")), retention_basis_points: v.get("retentionBasisPoints").as_i64(), stage: v.get("stage").as_str().to_string(), previously_certified: money_from_value(v.get("previouslyCertified")), cis_status: optional_string(v.get("cisStatus")), cis_materials: money_from_value(v.get("cisMaterials")), vat_category: optional_string(v.get("vatCategory")), reverse_charge: v.get("reverseCharge").as_bool(), valuation_date: v.get("valuationDate").as_str().to_string(), } } pub fn valuation_to_value(v: &Valuation) -> Value { Value::obj(vec![ ("grossValuation", money_to_value(&v.gross_valuation)), ("retention", retention_to_value(&v.retention)), ("netValuation", money_to_value(&v.net_valuation)), ("previouslyCertified", money_to_value(&v.previously_certified)), ("amountDue", money_to_value(&v.amount_due)), ("vat", money_to_value(&v.vat)), ("vatBasisPoints", Value::Int(v.vat_basis_points)), ("reverseChargeVat", money_to_value(&v.reverse_charge_vat)), ( "cis", match &v.cis { None => Value::Null, Some(c) => cis_deduction_to_value(c), }, ), ("amountPayable", money_to_value(&v.amount_payable)), ]) } pub fn fune_vector(args: &[Value]) -> Value { valuation_to_value(&interim_valuation(&valuation_input_from_value(&args[0]))) }