chore: more reporting

This commit is contained in:
Nathnael
2026-08-19 10:14:46 +00:00
parent 54f52f077c
commit e964a9b8f4
20 changed files with 1707 additions and 25 deletions

View File

@@ -0,0 +1,102 @@
import {
BULK_FREIGHT_CHARGES,
PAYMENT_CLASSES,
PAYMENT_CLASS_EXPR,
PERIOD_FILTER,
REVENUE_CATEGORIES,
REVENUE_CATEGORY_EXPR,
periodExpr,
} from './revenue-classification';
/**
* `invoice_lines.charge_type` is an unconstrained varchar written by eight
* unrelated code paths. Nothing at the type level stops someone adding a ninth
* spelling, whose revenue would then land silently in the ELSE arm.
*
* This list is every value the codebase writes today. When it grows, these
* tests are what fail — which is the whole trade the const-map design makes.
*/
const KNOWN_CHARGE_TYPES = [
// booking base freight (rate_type codes)
'CONTAINER_IMPORT', 'CONTAINER_EXPORT', 'CONTAINER_20FT', 'CONTAINER_40FT',
'BULK_IMPORT', 'BULK_EXPORT', 'INTERCITY_BULK', 'INTERCITY_CONTAINER', 'FREIGHT',
// surcharges
'FUEL_SURCHARGE', 'LASHING', 'OVERWEIGHT_PER_TON', 'HAZARD_SURCHARGE',
'REEFER_SURCHARGE', 'PIL_EXTRA_FEE', 'RETURN_SURCHARGE', 'RETURN_SURCHARGE_20FT',
'RETURN_SURCHARGE_40FT', 'CONTAINER_WITH_RETURN', 'ADJUSTMENT', 'RATE_ADJUSTMENT',
// customs
'CUSTOMS_CLEARANCE', 'CUSTOMS_CLEARANCE_20FT', 'CUSTOMS_CLEARANCE_40FT',
// mile legs
'FIRST_MILE', 'LAST_MILE', 'DELIVERY', 'LAST_MILE_ADVANCE',
// warehouse fees
'CONTAINER_DEMURRAGE', 'BULK_DEMURRAGE', 'DEMURRAGE', 'STORAGE_FEE',
'HANDLING_FEE', 'DOUBLE_HANDLING', 'TRUCK_DETENTION',
// other producers
'CANCELLATION_FEE', 'SHIPPING_LINE_SERVICE',
];
/**
* Does the expression name this charge type — either as a literal or through
* one of its `LIKE 'PREFIX%'` arms?
*
* Deliberately a substring check, not a SQL parser: a parser would be more
* fragile than the expression it is guarding. This catches the failure that
* actually happens (a new charge type nobody added to the map) and nothing
* pretends it verifies the branch order.
*/
function isNamed(expr: string, chargeType: string): boolean {
if (expr.includes(`'${chargeType}'`)) return true;
return [...expr.matchAll(/LIKE '([^']*)%'/g)].some(([, prefix]) =>
chargeType.startsWith(prefix),
);
}
describe('revenue classification', () => {
it('names every charge type the codebase writes in the payment-class map', () => {
const unmapped = KNOWN_CHARGE_TYPES.filter((c) => !isNamed(PAYMENT_CLASS_EXPR, c));
expect(unmapped).toEqual([]);
});
it('names every ancillary charge type in the revenue-category map', () => {
// Bulk freight lines carry no category of their own — the CASE falls
// through to the booking's cargo type and trade direction for those.
const cargoDerived = new Set(BULK_FREIGHT_CHARGES);
const unmapped = KNOWN_CHARGE_TYPES.filter(
(c) => !cargoDerived.has(c) && !isNamed(REVENUE_CATEGORY_EXPR, c),
);
expect(unmapped).toEqual([]);
});
it('emits only categories that are offered as filter options', () => {
const declared = new Set(REVENUE_CATEGORIES.map((c) => c.value));
const emitted = [...REVENUE_CATEGORY_EXPR.matchAll(/THEN '([A-Z_]+)'/g)].map((m) => m[1]);
expect(emitted.length).toBeGreaterThan(0);
expect(emitted.filter((c) => !declared.has(c))).toEqual([]);
expect(declared.has('UNCLASSIFIED')).toBe(true);
});
it('emits only payment classes that are offered as filter options', () => {
const declared = new Set(PAYMENT_CLASSES.map((c) => c.value));
const emitted = [...PAYMENT_CLASS_EXPR.matchAll(/THEN '([A-Z_]+)'/g)].map((m) => m[1]);
expect(emitted.filter((c) => !declared.has(c))).toEqual([]);
expect(declared.has('ADDITIONAL')).toBe(true);
});
it('falls back to a whitelisted period unit instead of interpolating input', () => {
expect(periodExpr({ period: 'quarter' })).toContain("date_trunc('quarter'");
expect(periodExpr({ period: 'year' })).toContain("date_trunc('year'");
// Anything unrecognised — including an injection attempt — becomes 'month'.
expect(periodExpr({ period: "day'); DROP TABLE freight.invoices; --" })).toContain(
"date_trunc('month'",
);
expect(periodExpr({})).toContain("date_trunc('month'");
});
it('offers exactly the period units the expression understands', () => {
const offered = (PERIOD_FILTER.options ?? []).map((o) => o.value);
expect(offered.length).toBe(5);
for (const unit of offered) {
expect(periodExpr({ period: unit })).toContain(`date_trunc('${unit}'`);
}
});
});