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