professional.wip-valuation@1.0.0
impl/typescript.ts
1,805 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 { roundDiv } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { applyRate } from "./money_apply_rate.ts"; ← from money.apply-rate ^1.0.0 · built alongside by fune
import { type WipBasis, type WipEntry, type WipValuation } from "./professional_wip_valuation_types.ts";
/** Work in progress at cost or charge-out rates, less a write-down. */
export function wipValuation(entries: readonly WipEntry[], basis: WipBasis, writeDownBasisPoints: number, currency: string): WipValuation {
if (basis !== "cost" && basis !== "charge-out") {
throw new RangeError(`unknown WIP basis "${basis}": expected cost or charge-out`);
}
if (!Number.isInteger(writeDownBasisPoints) || writeDownBasisPoints < 0 || writeDownBasisPoints > 10000) {
throw new RangeError(`writeDownBasisPoints must be between 0 and 10000, received ${writeDownBasisPoints}`);
}
let minutes = 0;
let gross = 0;
for (const entry of entries) {
if (!Number.isInteger(entry.minutes) || entry.minutes < 0) {
throw new RangeError(`minutes must be a non-negative integer, received ${entry.minutes}`);
}
const rate: Money = basis === "cost" ? entry.costRate : entry.chargeRate;
if (rate.currency !== currency) {
throw new RangeError(`currency mismatch: ${currency} and ${rate.currency}`);
}
if (rate.minor < 0) {
throw new RangeError(`hourly rates must not be negative, received ${rate.minor}`);
}
minutes += entry.minutes;
// Per entry, as the time would be billed, so WIP reconciles with the bill.
gross += roundDiv(entry.minutes * rate.minor, 60, "half-up");
}
const grossMoney = money(gross, currency);
const writeDown = applyRate(grossMoney, writeDownBasisPoints, "half-up");
return { minutes, gross: grossMoney, writeDown, net: money(gross - writeDown.minor, currency) };
}