import { db } from "@/lib/db"; /** * Product catalogue sync — registers a PO's line items as reusable `Product`s * (the `/catalogue/items` catalogue) and keeps last/per-vendor prices fresh: * - line items with no `productId` are matched to an existing product by name, * or a brand-new product is created, and the line item is linked back; * - `lastPrice`/`lastVendorId` and the per-vendor price are upserted. * * Called at **approval** (so approved items are immediately reusable in further * POs) and again at **payment** (to refresh prices on the final figures). The * function is idempotent — re-running matches the same product by name/id. */ function nameToCode(name: string): string { const slug = name.toUpperCase() .replace(/[^A-Z0-9]+/g, "-") .replace(/^-|-$/g, "") .substring(0, 20); return `${slug}-${Date.now().toString(36).toUpperCase().slice(-5)}`; } export async function syncProductCatalog( poId: string, lineItems: { id: string; name: string; unitPrice: { toNumber(): number } | number; productId: string | null }[], vendorId: string | null, actorId: string ) { const updatedProductIds: string[] = []; for (const li of lineItems) { const unitPrice = typeof li.unitPrice === "number" ? li.unitPrice : li.unitPrice.toNumber(); let productId = li.productId; let priceChanged = false; if (!productId) { // Try to find an existing product by name (case-insensitive) const existing = await db.product.findFirst({ where: { name: { equals: li.name, mode: "insensitive" }, isActive: true }, select: { id: true, lastPrice: true }, }); if (existing) { productId = existing.id; priceChanged = Number(existing.lastPrice ?? 0) !== unitPrice; } else { // Create a new product — first-time registration, not a price update const code = nameToCode(li.name); try { const created = await db.product.create({ data: { code, name: li.name, lastPrice: unitPrice, lastVendorId: vendorId }, }); productId = created.id; } catch { // Code collision (extremely unlikely) — add extra entropy const created = await db.product.create({ data: { code: `${code}-${Math.random().toString(36).slice(2, 5).toUpperCase()}`, name: li.name, lastPrice: unitPrice, lastVendorId: vendorId, }, }); productId = created.id; } } // Link the line item to the product for future reference await db.pOLineItem.update({ where: { id: li.id }, data: { productId } }); } else { const current = await db.product.findUnique({ where: { id: productId }, select: { lastPrice: true }, }); priceChanged = !current || Number(current.lastPrice ?? 0) !== unitPrice; } // Always update lastPrice / lastVendorId on the product await db.product.update({ where: { id: productId }, data: { lastPrice: unitPrice, lastVendorId: vendorId ?? undefined }, }); // Upsert per-vendor price if PO has a vendor if (vendorId) { await db.productVendorPrice.upsert({ where: { productId_vendorId: { productId, vendorId } }, update: { price: unitPrice }, create: { productId, vendorId, price: unitPrice }, }); } if (priceChanged) updatedProductIds.push(productId); } if (updatedProductIds.length > 0) { await db.pOAction.create({ data: { actionType: "PRODUCT_PRICE_UPDATED", actorId, poId, metadata: { updatedProductIds }, }, }); } }