Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { type Money, money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { sumMoney } from "./money_sum.ts"; ← from money.sum ^1.0.0 · built alongside by fune
import { subtractMoney } from "./money_add.ts"; ← from money.add ^1.0.0 · built alongside by fune
import { roundDiv } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { type GrossMargin, type MarginBasis, type MarginLine } from "./agri_gross_margin_types.ts";
const MAX_MINOR = 900000000000;
/**
* Gross margin: enterprise output less its variable costs, per hectare or head.
*
* Each per-unit figure is its own total divided once, half up. Taking output
* per head less costs per head instead can be a penny out: 3 head with 10000
* output and 5000 costs is 3333 - 1667 = 1666 that way, but 5000 / 3 = 1667.
*/
export function grossMargin(
outputs: readonly MarginLine[],
variableCosts: readonly MarginLine[],
per: MarginBasis,
units: number,
currency: string,
): GrossMargin {
let divisor: number;
let scale: number;
if (per === "hectare") {
if (typeof units !== "number" || !Number.isFinite(units) || units <= 0 || units > 10000000) {
throw new RangeError(`units must be a finite number of hectares greater than 0 and at most 10000000, received ${units}`);
}
divisor = Math.round(units * 10000);
if (divisor < 1) throw new RangeError(`units must be at least 0.0001 hectares, received ${units}`);
scale = 10000;
} else if (per === "head") {
if (typeof units !== "number" || !Number.isInteger(units) || units < 1 || units > 10000000) {
throw new RangeError(`units must be a whole number of head from 1 to 10000000, received ${units}`);
}
divisor = units;
scale = 1;
} else {
throw new RangeError(`unknown basis "${per}": use hectare or head`);
}
const output = sumMoney(outputs.map((line) => line.amount), currency);
const costs = sumMoney(variableCosts.map((line) => line.amount), currency);
const margin = subtractMoney(output, costs);
for (const m of [output, costs, margin]) {
if (Math.abs(m.minor) > MAX_MINOR) {
throw new RangeError("totals must be within 900000000000 minor units either side of zero");
}
}
const perUnit = (m: Money): Money => money(roundDiv(m.minor * scale, divisor, "half-up"), currency);
return {
output,
variableCosts: costs,
grossMargin: margin,
outputPerUnit: perUnit(output),
variableCostsPerUnit: perUnit(costs),
grossMarginPerUnit: perUnit(margin),
marginBasisPoints: output.minor === 0 ? null : roundDiv(margin.minor * 10000, output.minor, "half-up"),
};
}