Functional Weave
Code in Python

construction.valuation@1.0.0

impl/python.py

2,781 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import Optional

from .construction_cis_deduction import CisDeduction, cis_deduction  ← from construction.cis-deduction ^1.0.0 · built alongside by fune
from .construction_retention import retention  ← from construction.retention ^1.0.0 · built alongside by fune
from .construction_valuation_types import Valuation, ValuationInput
from .finance_tax_add_vat import add_vat  ← from finance.tax.add-vat ^1.0.0 · built alongside by fune
from .money_add import add_money, subtract_money  ← from money.add ^1.0.0 · built alongside by fune
from .money_amount import money  ← from money.amount ^1.0.0 · built alongside by fune


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),
    )