import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import { updateDraft, validateCustomer, validateOrderItem, type ItemSnapshot, } from "../domain/salesOrder"; import type { Transaction } from "../generated/kysely-tailordb"; import { SalesOrderNotFoundError } from "../lib/errors.generated"; import type { ItemManagementQueries, BusinessPartnerQueries } from "../module"; import { createSalesItemRepository, type SalesItemRepository, } from "../repository/salesItemRepository"; import { createSalesOrderRepository, type SalesOrderRepository, } from "../repository/salesOrderRepository"; import type { SalesOrderLineInput } from "./createSalesOrder"; export interface SalesOrderHeaderPatch { customerAccountId?: string; shippingAddress?: string | null; billingAddress?: string | null; } export interface SalesOrderLinePatch { itemId?: string; description?: string | null; quantity?: string; unitPrice?: string; unitId?: string | null; } export interface SalesOrderLineEdit { lineId: string; linePatch: SalesOrderLinePatch; } export type UpdateSalesOrderInput = { id: string; headerPatch?: SalesOrderHeaderPatch; addLines?: SalesOrderLineInput[]; updateLines?: SalesOrderLineEdit[]; removeLineIds?: string[]; }; export async function run, LCF extends Record>( db: Transaction, input: Omit & { headerPatch?: SalesOrderHeaderPatch & Partial; addLines?: (SalesOrderLineInput & LCF)[]; updateLines?: { lineId: string; linePatch: SalesOrderLinePatch & Partial }[]; }, ctx: CommandContext, itemManagementQueries: Pick, businessPartnerQueries: Pick, repositoryFactory: (db: Transaction) => SalesOrderRepository = createSalesOrderRepository, salesItemRepositoryFactory: (db: Transaction) => SalesItemRepository = createSalesItemRepository, ) { const { id, headerPatch, addLines = [], updateLines = [], removeLineIds = [] } = input; const repository = repositoryFactory(db); const order = await repository.findById(id, { forUpdate: true }); if (!order) { return err(new SalesOrderNotFoundError(id)); } if (headerPatch?.customerAccountId !== undefined) { const { account } = ( await businessPartnerQueries.getCustomerAccount( db, { customerAccountId: headerPatch.customerAccountId }, ctx, ) ).value; const customer = validateCustomer( headerPatch.customerAccountId, order.header.companyId, account, ); if (!customer.ok) return customer; } // Items are validated only where they are (re)chosen. const itemIdsToValidate = new Set(); for (const line of addLines) { if (line.itemId) { itemIdsToValidate.add(line.itemId); } } for (const { linePatch } of updateLines) { if (linePatch.itemId !== undefined) { itemIdsToValidate.add(linePatch.itemId); } } const salesItemByItemId = await salesItemRepositoryFactory(db).findByItemIds([ ...itemIdsToValidate, ]); const itemSnapshotByItemId = new Map(); for (const itemId of itemIdsToValidate) { const { item } = (await itemManagementQueries.getItem(db, { id: itemId }, ctx)).value; const snapshot = validateOrderItem(itemId, order.header.companyId, { item, salesItem: salesItemByItemId.get(itemId) ?? null, }); if (!snapshot.ok) { return snapshot; } itemSnapshotByItemId.set(itemId, snapshot.value); } // Every itemId validated above is in the map, so this lookup never misses. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const itemSnapshotFor = (itemId: string) => itemSnapshotByItemId.get(itemId)!; const { customerAccountId, shippingAddress, billingAddress, ...headerCustomFields } = headerPatch ?? {}; const updated = updateDraft(order, { headerPatch: { customerAccountId, 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 { itemId, description, quantity, unitPrice, unitId, ...lineCustomFields } = linePatch; return { lineId, linePatch: { item: itemId === undefined ? undefined : itemSnapshotFor(itemId), description, quantity, unitPrice, unitId, customFields: lineCustomFields, }, }; }), removeLineIds, }); if (!updated.ok) { return updated; } await repository.save(updated.value); return ok({ salesOrderId: id }); }