import { ok } from "@tailor-platform/erp-kit/core"; import { Decimal } from "decimal.js"; import type { Selectable, Transaction } from "../../../generated/kysely-tailordb"; // Average costing never restates history: the consumed share of an invoice // price variance is expensed to the consumed price variance account, and only the // on-hand share folds into the moving average. The split is by quantity alone, // so unlike FIFO no consumption classification is needed. export async function restateAverageLayer( db: Transaction, args: { layer: Selectable<"CostLayer">; delta: Decimal }, ) { const { layer, delta } = args; const received = new Decimal(layer.quantity); const consumed = received.minus(layer.remainingQuantity); const consumedShare = delta.mul(consumed).div(received); const onHandShare = delta.minus(consumedShare); if (!onHandShare.isZero()) { // On-hand quantity from a costed receipt guarantees the moving-average pool exists. const current = await db .selectFrom("AverageCost") .selectAll() .where("itemId", "=", layer.itemId) .where("companyId", "=", layer.companyId) .orderBy("sequence", "desc") .limit(1) .executeTakeFirstOrThrow(); // Value moves without quantity: the pool quantity is unchanged and the // average shifts by the on-hand share over the pool. const quantityBefore = new Decimal(current.quantityAfter); const unitCostBefore = new Decimal(current.unitCostAfter); const unitCostAfter = quantityBefore.mul(unitCostBefore).plus(onHandShare).div(quantityBefore); await db .insertInto("AverageCost") .values({ itemId: layer.itemId, companyId: layer.companyId, sequence: current.sequence + 1, eventType: "COST_ADJUSTMENT", quantityBefore: quantityBefore.toString(), unitCostBefore: unitCostBefore.toString(), quantity: "0", unitCost: null, quantityAfter: quantityBefore.toString(), unitCostAfter: unitCostAfter.toString(), }) .execute(); } const contributions = [ ...(onHandShare.isZero() ? [] : [{ role: "INVENTORY" as const, amount: onHandShare }]), ...(consumedShare.isZero() ? [] : [{ role: "CONSUMED_PRICE_VARIANCE" as const, amount: consumedShare }]), ]; return ok(contributions); }