Files
Cibello-app/packages/inventory-engine/src/balance.ts
T
2026-08-05 19:21:11 +07:00

65 lines
2.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { InventoryTransactionType, Unit } from "@app/shared-types";
export interface TransactionLike {
type: InventoryTransactionType;
quantityDelta: number;
unit: Unit;
}
/** Transaktionstyper som ska vara negativa (uttag). */
const OUTFLOW_TYPES: ReadonlySet<InventoryTransactionType> = new Set([
"consume",
"discard",
"cook_use",
"leftover_consumed",
]);
/** Transaktionstyper som ska vara positiva (inflöde). */
const INFLOW_TYPES: ReadonlySet<InventoryTransactionType> = new Set([
"purchase",
"leftover_created",
]);
export interface BalanceResult {
balance: number;
valid: boolean;
errors: string[];
}
/**
* Beräkna saldo ur transaktionshistorik (spec §8: transaktioner är sanningen).
* Saldo tillåts aldrig bli negativt i så fall flaggas historiken som
* inkonsistent så att en correction kan föreslås, i stället för tyst clamp.
*/
export function computeBalance(transactions: TransactionLike[]): BalanceResult {
let balance = 0;
const errors: string[] = [];
for (const [i, tx] of transactions.entries()) {
if (OUTFLOW_TYPES.has(tx.type) && tx.quantityDelta > 0) {
errors.push(`Transaktion ${i} (${tx.type}) borde vara negativ men är +${tx.quantityDelta}`);
}
if (INFLOW_TYPES.has(tx.type) && tx.quantityDelta < 0) {
errors.push(`Transaktion ${i} (${tx.type}) borde vara positiv men är ${tx.quantityDelta}`);
}
balance += tx.quantityDelta;
if (balance < -1e-9) {
errors.push(
`Saldo blev negativt (${balance.toFixed(3)}) efter transaktion ${i} (${tx.type})`,
);
balance = 0;
}
}
return { balance: round3(balance), valid: errors.length === 0, errors };
}
/** Normalisera ett uttag: rätt tecken oavsett hur anroparen skickade mängden. */
export function normalizeDelta(type: InventoryTransactionType, quantity: number): number {
const magnitude = Math.abs(quantity);
if (OUTFLOW_TYPES.has(type)) return -magnitude;
if (INFLOW_TYPES.has(type)) return magnitude;
return quantity; // adjust/correction/move/freeze/thaw får vara valfritt tecken
}
function round3(v: number): number {
return Math.round(v * 1000) / 1000;
}