import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import { amendConfirmed, validateOrderItem, type ItemSnapshot } from "../domain/salesOrder"; import type { Transaction } from "../generated/kysely-tailordb"; import { SalesOrderNotFoundError } from "../lib/errors.generated"; import type { ItemManagementQueries } from "../module"; import { createSalesItemRepository, type SalesItemRepository, } from "../repository/salesItemRepository"; import { createSalesOrderRepository, type SalesOrderRepository, } from "../repository/salesOrderRepository"; import type { SalesOrderLineInput } from "./createSalesOrder"; export interface AmendSalesOrderHeaderPatch { orderDate?: Date; shippingAddress?: string | null; billingAddress?: string | null; } export interface AmendSalesOrderLinePatch { quantity?: string; unitPrice?: string; description?: string | null; } export interface AmendSalesOrderLineEdit { lineId: string; linePatch: AmendSalesOrderLinePatch; } export interface AmendConfirmedSalesOrderInput { id: string; reason?: string; headerPatch?: AmendSalesOrderHeaderPatch; addLines?: SalesOrderLineInput[]; updateLines?: AmendSalesOrderLineEdit[]; removeLineIds?: string[]; } export async function run< CF extends Record = Record, LCF extends Record = Record, >( db: Transaction, input: Omit & { headerPatch?: AmendSalesOrderHeaderPatch & Partial; addLines?: (SalesOrderLineInput & LCF)[]; updateLines?: { lineId: string; linePatch: AmendSalesOrderLinePatch & Partial }[]; }, ctx: CommandContext, itemManagementQueries: Pick, repositoryFactory: (db: Transaction) => SalesOrderRepository = createSalesOrderRepository, salesItemRepositoryFactory: (db: Transaction) => SalesItemRepository = createSalesItemRepository, ) { const { id, reason, headerPatch, addLines = [], updateLines = [], removeLineIds = [] } = input; const { orderDate, shippingAddress, billingAddress, ...headerCustomFields } = headerPatch ?? {}; const repository = repositoryFactory(db); const order = await repository.findById(id, { forUpdate: true }); if (!order) { return err(new SalesOrderNotFoundError(id)); } // Items are validated only where they are chosen: added lines. const salesItemByItemId = await salesItemRepositoryFactory(db).findByItemIds( addLines.map((line) => line.itemId), ); const itemSnapshotByItemId = new Map(); for (const line of addLines) { if (itemSnapshotByItemId.has(line.itemId)) { continue; } const { item } = (await itemManagementQueries.getItem(db, { id: line.itemId }, ctx)).value; const snapshot = validateOrderItem(line.itemId, order.header.companyId, { item, salesItem: salesItemByItemId.get(line.itemId) ?? null, }); if (!snapshot.ok) { return snapshot; } itemSnapshotByItemId.set(line.itemId, snapshot.value); } // Every added itemId was validated above, so this lookup never misses. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const itemSnapshotFor = (itemId: string) => itemSnapshotByItemId.get(itemId)!; const amended = amendConfirmed(order, { headerPatch: { orderDate, shippingAddress, billingAddress, customFields: headerCustomFields, }, addLines: addLines.map((line) => { const { itemId, description, quantity, unitPrice, unitId, ...lineCustomFields } = line; return { item: itemSnapshotFor(itemId), description, quantity, unitPrice, unitId, customFields: lineCustomFields, }; }), updateLines: updateLines.map(({ lineId, linePatch }) => { const { quantity, unitPrice, description, ...lineCustomFields } = linePatch; return { lineId, patch: { quantity, unitPrice, description, customFields: lineCustomFields, }, }; }), removeLineIds, reason, amendedByUserId: ctx.actorId, }); if (!amended.ok) { return amended; } await repository.save(amended.value.order); return ok({ salesOrderId: id, revisionId: amended.value.revisionId }); }