import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core"; import { createDraft, validateOrderItem, validateSupplier, type ItemSnapshot, } from "../domain/purchaseOrder"; import type { Transaction } from "../generated/kysely-tailordb"; import { ReceivingSiteNotFoundError } from "../lib/errors.generated"; import type { BusinessPartnerQueries, ItemManagementQueries, OrganizationQueries } from "../module"; import { createPurchaseItemRepository, type PurchaseItemRepository, } from "../repository/purchaseItemRepository"; import { createPurchaseOrderRepository, type PurchaseOrderRepository, } from "../repository/purchaseOrderRepository"; export interface PurchaseOrderLineInput { itemId: string; quantity: string; unitPrice: string; unitId: string; receivingSiteId?: string; } export interface PurchaseOrderHeaderInput { companyId: string; supplierAccountId: string; currencyId: string; orderDate: Date; receivingSiteId?: string; externalSupplierOrderReference?: string; } export interface CreatePurchaseOrderInput { header: PurchaseOrderHeaderInput; lines: PurchaseOrderLineInput[]; } export async function run, LCF extends Record>( db: Transaction, input: { header: PurchaseOrderHeaderInput & CF; lines: (PurchaseOrderLineInput & LCF)[]; }, ctx: CommandContext, supplierAccountQueries: Pick, itemManagementQueries: Pick, organizationQueries: Pick, repositoryFactory: (db: Transaction) => PurchaseOrderRepository = createPurchaseOrderRepository, purchaseItemRepositoryFactory: ( db: Transaction, ) => PurchaseItemRepository = createPurchaseItemRepository, ) { const { header, lines } = input; const { companyId, supplierAccountId, currencyId, orderDate, receivingSiteId, externalSupplierOrderReference, ...headerCustomFields } = header; const { company } = (await organizationQueries.getCompany(db, { id: companyId }, ctx)).value; const { account } = ( await supplierAccountQueries.getSupplierAccount(db, { supplierAccountId }, ctx) ).value; const supplierSnapshot = validateSupplier(supplierAccountId, companyId, account); if (!supplierSnapshot.ok) { return supplierSnapshot; } const purchaseItemByItemId = await purchaseItemRepositoryFactory(db).findByItemIds( lines.map((line) => line.itemId), ); const itemSnapshotByItemId = new Map(); for (const line of lines) { if (itemSnapshotByItemId.has(line.itemId)) { continue; } const { item } = (await itemManagementQueries.getItem(db, { id: line.itemId }, ctx)).value; const snapshot = validateOrderItem(line.itemId, { item, purchaseItem: purchaseItemByItemId.get(line.itemId) ?? null, }); if (!snapshot.ok) { return snapshot; } itemSnapshotByItemId.set(line.itemId, snapshot.value); } // Every item 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 lines) { if (line.receivingSiteId != null) { siteIdsToValidate.add(line.receivingSiteId); } } for (const siteId of siteIdsToValidate) { const { site } = (await organizationQueries.getSite(db, { id: siteId }, ctx)).value; if (!site) { return err(new ReceivingSiteNotFoundError(siteId)); } } const order = createDraft( { companyId, supplier: supplierSnapshot.value, currencyId, orderDate, receivingSiteId, externalSupplierOrderReference, customFields: headerCustomFields, lines: lines.map((line) => { const { itemId, quantity, unitPrice, unitId, receivingSiteId: lineReceivingSiteId, ...lineCustomFields } = line; return { item: itemSnapshotFor(itemId), quantity, unitPrice, unitId, receivingSiteId: lineReceivingSiteId, customFields: lineCustomFields, }; }), }, { company }, ); if (!order.ok) { return order; } await repositoryFactory(db).save(order.value); return ok({ purchaseOrderId: order.value.id }); }