Functional Weave
Code in Rust

construction.valuation@1.0.0

impl/typescript.ts

2,616 bytes · the TypeScript 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.

import { type CisDeduction, cisDeduction } from "./construction_cis_deduction.ts";  ← from construction.cis-deduction ^1.0.0 · built alongside by fune
import { retention } from "./construction_retention.ts";  ← from construction.retention ^1.0.0 · built alongside by fune
import { addVat } from "./finance_tax_add_vat.ts";  ← from finance.tax.add-vat ^1.0.0 · built alongside by fune
import { addMoney, subtractMoney } from "./money_add.ts";  ← from money.add ^1.0.0 · built alongside by fune
import { money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { type Valuation, type ValuationInput } from "./construction_valuation_types.ts";

/**
 * 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.
 */
export function interimValuation(input: ValuationInput): Valuation {
  const grossValuation = addMoney(input.workDone, input.materialsOnSite);
  const held = retention(grossValuation, input.retentionBasisPoints, input.stage);
  const netValuation = subtractMoney(grossValuation, held.held);
  const amountDue = subtractMoney(netValuation, input.previouslyCertified);
  const currency = amountDue.currency;

  let vat = money(0, currency);
  let reverseChargeVat = money(0, currency);
  let vatBasisPoints = 0;
  if (input.vatCategory === null) {
    if (input.reverseCharge) {
      throw new RangeError("the reverse charge needs a VAT category: the supplier must be VAT registered");
    }
  } else {
    if (input.reverseCharge && input.vatCategory !== "STANDARD" && input.vatCategory !== "REDUCED") {
      throw new RangeError(`the reverse charge applies only to STANDARD or REDUCED supplies, received ${input.vatCategory}`);
    }
    const breakdown = addVat(amountDue, "GB", input.vatCategory, input.valuationDate);
    vatBasisPoints = breakdown.basisPoints;
    if (input.reverseCharge) reverseChargeVat = breakdown.tax;
    else vat = breakdown.tax;
  }

  let cis: CisDeduction | null = null;
  if (input.cisStatus !== null) {
    if (amountDue.minor < 0) {
      throw new RangeError(`the amount due is negative (${amountDue.minor}), so there is no payment to make a CIS deduction from`);
    }
    cis = cisDeduction(amountDue, input.cisMaterials, input.cisStatus, input.valuationDate, "down");
  }

  const payable = addMoney(amountDue, vat);
  return {
    grossValuation,
    retention: held,
    netValuation,
    previouslyCertified: input.previouslyCertified,
    amountDue,
    vat,
    vatBasisPoints,
    reverseChargeVat,
    cis,
    amountPayable: cis === null ? payable : subtractMoney(payable, cis.deduction),
  };
}