impl/typescript.ts
3,265 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 Rational, multiplyRational, rational, rationalToInteger } from "./math_rational.ts"; ← from math.rational ^1.0.0 · built alongside by fune
import { type RoundingMode } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, assertSameCurrency, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { type CostVariances, type LabourActual, type MaterialActual, type Variance } from "./manufacturing_variance_types.ts";
function notNegative(name: string, value: Rational): Rational {
const r = rational(value.numerator, value.denominator);
if (r.numerator < 0) {
throw new RangeError(`${name} must not be negative`);
}
return r;
}
function costOf(name: string, value: Money, currency: Money): number {
assertSameCurrency(currency, value);
if (value.minor < 0) {
throw new RangeError(`${name} must not be negative, received ${value.minor}`);
}
return value.minor;
}
function variance(signed: number, currency: string): Variance {
return {
amount: money(Math.abs(signed), currency),
effect: signed > 0 ? "adverse" : signed < 0 ? "favourable" : "none",
};
}
/**
* Material price and usage, labour rate and efficiency variances, flexed to
* actual output. The four flexed costs are each rounded once and every
* variance is a difference of them, so the variances always sum exactly.
*/
export function costVariances(material: MaterialActual, labour: LabourActual, actualOutput: number, mode: RoundingMode): CostVariances {
if (!Number.isSafeInteger(actualOutput) || actualOutput < 0) {
throw new RangeError(`actualOutput must be a whole number, not negative, received ${actualOutput}`);
}
const unit = material.standardPrice;
const currency = unit.currency;
const sp = costOf("material standardPrice", material.standardPrice, unit);
const matActual = costOf("material actualCost", material.actualCost, unit);
const sr = costOf("labour standardRate", labour.standardRate, unit);
const labActual = costOf("labour actualCost", labour.actualCost, unit);
const sq = notNegative("material standardQuantity", material.standardQuantity);
const aq = notNegative("material actualQuantity", material.actualQuantity);
const sh = notNegative("labour standardHours", labour.standardHours);
const ah = notNegative("labour actualHours", labour.actualHours);
const output = rational(actualOutput, 1);
const actualAtStandard = rationalToInteger(multiplyRational(aq, rational(sp, 1)), mode);
const standardMaterial = rationalToInteger(multiplyRational(multiplyRational(sq, output), rational(sp, 1)), mode);
const hoursAtStandard = rationalToInteger(multiplyRational(ah, rational(sr, 1)), mode);
const standardLabour = rationalToInteger(multiplyRational(multiplyRational(sh, output), rational(sr, 1)), mode);
const standardCost = standardMaterial + standardLabour;
const actualCost = matActual + labActual;
return {
materialPrice: variance(matActual - actualAtStandard, currency),
materialUsage: variance(actualAtStandard - standardMaterial, currency),
labourRate: variance(labActual - hoursAtStandard, currency),
labourEfficiency: variance(hoursAtStandard - standardLabour, currency),
total: variance(actualCost - standardCost, currency),
standardCost: money(standardCost, currency),
actualCost: money(actualCost, currency),
};
}