import { err, ok } from "@tailor-platform/erp-kit/core"; import { Decimal } from "decimal.js"; import type { Transaction } from "../../../generated/kysely-tailordb"; import { AverageCostNotFoundError, UnitCostRequiredError } from "../../../lib/errors.generated"; import { pushNonZero, type CostingFact, type CostingRole } from "../journalEntry"; export async function priceAverageReceipt( db: Transaction, args: { itemId: string; companyId: string; quantity: Decimal; unitCost?: Decimal; counterpartRole: CostingRole; }, ) { const { itemId, companyId, quantity, unitCost, counterpartRole } = args; if (!unitCost) { return err(new UnitCostRequiredError(itemId)); } const current = await db .selectFrom("AverageCost") .selectAll() .where("itemId", "=", itemId) .where("companyId", "=", companyId) .orderBy("sequence", "desc") .limit(1) .executeTakeFirst(); const quantityBefore = new Decimal(current?.quantityAfter ?? 0); const unitCostBefore = new Decimal(current?.unitCostAfter ?? 0); const quantityAfter = quantityBefore.plus(quantity); const unitCostAfter = quantityBefore .mul(unitCostBefore) .plus(quantity.mul(unitCost)) .div(quantityAfter); await db .insertInto("AverageCost") .values({ itemId, companyId, sequence: (current?.sequence ?? 0) + 1, eventType: "RECEIPT", quantityBefore: quantityBefore.toString(), unitCostBefore: unitCostBefore.toString(), quantity: quantity.toString(), unitCost: unitCost.toString(), quantityAfter: quantityAfter.toString(), unitCostAfter: unitCostAfter.toString(), }) .execute(); const actualValue = unitCost.mul(quantity); const facts: CostingFact[] = []; pushNonZero(facts, { role: "INVENTORY", dcIndicator: "DR", amount: actualValue.toString(), }); pushNonZero(facts, { role: counterpartRole, dcIndicator: "CR", amount: actualValue.toString(), }); return ok(facts); } export async function priceAverageIssue( db: Transaction, args: { itemId: string; companyId: string; quantity: Decimal; counterpartRole: CostingRole; }, ) { const { itemId, companyId, quantity, counterpartRole } = args; const current = await db .selectFrom("AverageCost") .selectAll() .where("itemId", "=", itemId) .where("companyId", "=", companyId) .orderBy("sequence", "desc") .limit(1) .executeTakeFirst(); if (!current) { return err(new AverageCostNotFoundError(itemId)); } const quantityBefore = new Decimal(current.quantityAfter); const average = new Decimal(current.unitCostAfter); const issueValue = average.mul(quantity); await db .insertInto("AverageCost") .values({ itemId, companyId, sequence: current.sequence + 1, eventType: "ISSUE", quantityBefore: quantityBefore.toString(), unitCostBefore: average.toString(), quantity: quantity.neg().toString(), unitCost: average.toString(), quantityAfter: quantityBefore.minus(quantity).toString(), unitCostAfter: average.toString(), }) .execute(); const facts: CostingFact[] = []; pushNonZero(facts, { role: counterpartRole, dcIndicator: "DR", amount: issueValue.toString(), }); pushNonZero(facts, { role: "INVENTORY", dcIndicator: "CR", amount: issueValue.toString(), }); return ok(facts); }