from typing import Optional from .construction_cis_deduction import CisDeduction, cis_deduction from .construction_retention import retention from .construction_valuation_types import Valuation, ValuationInput from .finance_tax_add_vat import add_vat from .money_add import add_money, subtract_money from .money_amount import money def interim_valuation(input: ValuationInput) -> Valuation: """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. """ gross_valuation = add_money(input.work_done, input.materials_on_site) held = retention(gross_valuation, input.retention_basis_points, input.stage) net_valuation = subtract_money(gross_valuation, held.held) amount_due = subtract_money(net_valuation, input.previously_certified) currency = amount_due.currency vat = money(0, currency) reverse_charge_vat = money(0, currency) vat_basis_points = 0 if input.vat_category is None: if input.reverse_charge: raise ValueError("the reverse charge needs a VAT category: the supplier must be VAT registered") else: if input.reverse_charge and input.vat_category not in ("STANDARD", "REDUCED"): raise ValueError( "the reverse charge applies only to STANDARD or REDUCED supplies, received %s" % (input.vat_category,) ) breakdown = add_vat(amount_due, "GB", input.vat_category, input.valuation_date) vat_basis_points = breakdown.basis_points if input.reverse_charge: reverse_charge_vat = breakdown.tax else: vat = breakdown.tax cis: Optional[CisDeduction] = None if input.cis_status is not None: if amount_due.minor < 0: raise ValueError( "the amount due is negative (%d), so there is no payment to make a CIS deduction from" % (amount_due.minor,) ) cis = cis_deduction(amount_due, input.cis_materials, input.cis_status, input.valuation_date, "down") payable = add_money(amount_due, vat) return Valuation( gross_valuation=gross_valuation, retention=held, net_valuation=net_valuation, previously_certified=input.previously_certified, amount_due=amount_due, vat=vat, vat_basis_points=vat_basis_points, reverse_charge_vat=reverse_charge_vat, cis=cis, amount_payable=payable if cis is None else subtract_money(payable, cis.deduction), )