import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import { Decimal } from "decimal.js"; import { accountReceivableDocumentLifecycle } from "../db/accountReceivableDocument.lifecycle.generated"; import type { Transaction } from "../generated/kysely-tailordb"; import { syncSalesOrderBillingStatusFromAccountReceivable } from "../internal/salesBillingFeedback"; import { DocumentNotFoundError, DueScheduleInvalidError, InvalidDocumentStatusError, LineTotalMismatchError, MinimumLinesNotMetError, } from "../lib/errors.generated"; import type { SalesCommands } from "../module"; export async function run( db: Transaction, input: { id: string }, ctx: CommandContext, salesCommands: Pick, ) { const document = await db .selectFrom("AccountReceivableDocument") .selectAll() .where("id", "=", input.id) .forUpdate() .executeTakeFirst(); if (!document) return err(new DocumentNotFoundError(input.id)); const nextStatus = accountReceivableDocumentLifecycle.tryTransition(document.status, "register"); if (!nextStatus) return err(new InvalidDocumentStatusError(input.id)); const lines = await db .selectFrom("AccountReceivableDocumentLine") .selectAll() .where("accountReceivableDocumentId", "=", input.id) .execute(); if (lines.length === 0) return err(new MinimumLinesNotMetError(input.id)); const lineTotal = lines.reduce((sum, line) => sum.plus(line.grossAmount), new Decimal(0)); if (!lineTotal.eq(document.totalAmount)) return err(new LineTotalMismatchError(input.id)); const distributions = await db .selectFrom("AccountReceivableDistributionLine as distribution") .innerJoin( "AccountReceivableDocumentLine as line", "line.id", "distribution.accountReceivableDocumentLineId", ) .selectAll("distribution") .where("line.accountReceivableDocumentId", "=", input.id) .execute(); const distributionTotal = distributions.reduce( (sum, distribution) => sum.plus(distribution.amount), new Decimal(0), ); if (!distributionTotal.eq(document.totalAmount)) { return err(new LineTotalMismatchError(input.id)); } const dueSchedule = await db .selectFrom("AccountReceivableDueScheduleLine") .selectAll() .where("accountReceivableDocumentId", "=", input.id) .execute(); const dueTotal = dueSchedule.reduce((sum, line) => sum.plus(line.amount), new Decimal(0)); if ( dueSchedule.length === 0 || !dueTotal.eq(document.totalAmount) || dueSchedule.some((line) => new Decimal(line.amount).lte(0)) ) { return err(new DueScheduleInvalidError(input.id)); } const registeredDocument = await db .updateTable("AccountReceivableDocument") .set({ status: nextStatus, registeredAt: new Date() }) .where("id", "=", input.id) .returningAll() .executeTakeFirstOrThrow(); const syncResult = await syncSalesOrderBillingStatusFromAccountReceivable( db, { document: registeredDocument, lines }, ctx, salesCommands, ); if (!syncResult.ok) return syncResult; return ok({ accountReceivableDocument: registeredDocument }); }