import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import { updateDraft, validateOrderItem, type ItemSnapshot } from "../domain/purchaseOrder"; import type { Transaction } from "../generated/kysely-tailordb"; import { PurchaseOrderNotFoundError, ReceivingSiteNotFoundError } from "../lib/errors.generated"; import type { ItemManagementQueries, OrganizationQueries } from "../module"; import { createPurchaseItemRepository, type PurchaseItemRepository, } from "../repository/purchaseItemRepository"; import { createPurchaseOrderRepository, type PurchaseOrderRepository, } from "../repository/purchaseOrderRepository"; import type { PurchaseOrderLineInput } from "./createPurchaseOrder"; export interface PurchaseOrderHeaderPatch { orderDate?: Date; receivingSiteId?: string | null; externalSupplierOrderReference?: string | null; } export interface PurchaseOrderLinePatch { itemId?: string; quantity?: string; unitPrice?: string; unitId?: string; receivingSiteId?: string | null; } export interface PurchaseOrderLineEdit { lineId: string; linePatch: PurchaseOrderLinePatch; } export type UpdatePurchaseOrderInput = { id: string; headerPatch?: PurchaseOrderHeaderPatch; addLines?: PurchaseOrderLineInput[]; updateLines?: PurchaseOrderLineEdit[]; removeLineIds?: string[]; }; export async function run, LCF extends Record>( db: Transaction, input: Omit & { headerPatch?: PurchaseOrderHeaderPatch & Partial; addLines?: (PurchaseOrderLineInput & LCF)[]; updateLines?: { lineId: string; linePatch: PurchaseOrderLinePatch & Partial }[]; }, ctx: CommandContext, itemManagementQueries: Pick, organizationQueries: Pick, repositoryFactory: (db: Transaction) => PurchaseOrderRepository = createPurchaseOrderRepository, purchaseItemRepositoryFactory: ( db: Transaction, ) => PurchaseItemRepository = createPurchaseItemRepository, ) { const { id, headerPatch, addLines = [], updateLines = [], removeLineIds = [] } = input; const { orderDate, receivingSiteId, externalSupplierOrderReference, ...headerCustomFields } = headerPatch ?? {}; const repository = repositoryFactory(db); const order = await repository.findById(id, { forUpdate: true }); if (!order) { return err(new PurchaseOrderNotFoundError(id)); } // Items are validated only where they are (re)chosen. const itemSnapshotByItemId = new Map(); const itemIdsToValidate = [ ...addLines.map((line) => line.itemId), ...updateLines.flatMap((edit) => edit.linePatch.itemId === undefined ? [] : [edit.linePatch.itemId], ), ]; const purchaseItemByItemId = await purchaseItemRepositoryFactory(db).findByItemIds(itemIdsToValidate); for (const itemId of itemIdsToValidate) { if (itemSnapshotByItemId.has(itemId)) { continue; } const { item } = (await itemManagementQueries.getItem(db, { id: itemId }, ctx)).value; const snapshot = validateOrderItem(itemId, { item, purchaseItem: purchaseItemByItemId.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 siteIdsToValidate = new Set(); if (receivingSiteId != null) { siteIdsToValidate.add(receivingSiteId); } for (const line of addLines) { if (line.receivingSiteId != null) { siteIdsToValidate.add(line.receivingSiteId); } } for (const { linePatch } of updateLines) { if (linePatch.receivingSiteId != null) { siteIdsToValidate.add(linePatch.receivingSiteId); } } for (const siteId of siteIdsToValidate) { const { site } = (await organizationQueries.getSite(db, { id: siteId }, ctx)).value; if (!site) { return err(new ReceivingSiteNotFoundError(siteId)); } } const updated = updateDraft(order, { headerPatch: { orderDate, receivingSiteId, externalSupplierOrderReference, customFields: headerCustomFields, }, addLines: addLines.map((line) => { const { itemId, quantity, unitPrice, unitId, receivingSiteId, ...customFields } = line; return { item: itemSnapshotFor(itemId), quantity, unitPrice, unitId, receivingSiteId, customFields, }; }), updateLines: updateLines.map(({ lineId, linePatch }) => { const { itemId, quantity, unitPrice, unitId, receivingSiteId, ...customFields } = linePatch; return { lineId, patch: { item: itemId === undefined ? undefined : itemSnapshotFor(itemId), quantity, unitPrice, unitId, receivingSiteId, customFields, }, }; }), removeLineIds, }); if (!updated.ok) { return updated; } await repository.save(updated.value); return ok({ purchaseOrderId: updated.value.id }); }