"use server"; import { auth } from "@/auth"; import { db } from "@/lib/db"; import { createPoSchema } from "@/lib/validations/po"; import { notify } from "@/lib/notifier"; import { revalidatePath } from "next/cache"; function parseLineItems(formData: FormData) { const items = []; let i = 0; while (formData.has(`lineItems[${i}].name`)) { items.push({ name: formData.get(`lineItems[${i}].name`) as string, description: (formData.get(`lineItems[${i}].description`) as string) || undefined, quantity: Number(formData.get(`lineItems[${i}].quantity`)), unit: formData.get(`lineItems[${i}].unit`) as string, size: (formData.get(`lineItems[${i}].size`) as string) || undefined, unitPrice: Number(formData.get(`lineItems[${i}].unitPrice`)), gstRate: Number(formData.get(`lineItems[${i}].gstRate`) ?? 0.18), accountId: (formData.get(`lineItems[${i}].accountId`) as string) || undefined, }); i++; } return items; } export async function updatePo( poId: string, formData: FormData ): Promise<{ id: string } | { error: string }> { const session = await auth(); if (!session?.user) return { error: "Unauthorized" }; const po = await db.purchaseOrder.findUnique({ where: { id: poId } }); if (!po) return { error: "PO not found" }; if (!["DRAFT", "EDITS_REQUESTED"].includes(po.status)) { return { error: "This PO cannot be edited in its current state." }; } if (po.submitterId !== session.user.id && session.user.role !== "SUPERUSER") { return { error: "You can only edit your own purchase orders." }; } const intent = formData.get("intent") as "save" | "submit" | "resubmit"; const parsed = createPoSchema.safeParse({ title: formData.get("title"), vesselId: formData.get("vesselId"), accountId: formData.get("accountId"), projectCode: formData.get("projectCode") || undefined, dateRequired: formData.get("dateRequired") || undefined, vendorId: formData.get("vendorId") || undefined, piQuotationNo: formData.get("piQuotationNo") || undefined, piQuotationDate: formData.get("piQuotationDate") || undefined, requisitionNo: formData.get("requisitionNo") || undefined, requisitionDate: formData.get("requisitionDate") || undefined, placeOfDelivery: formData.get("placeOfDelivery") || undefined, tcDelivery: formData.get("tcDelivery") || undefined, tcDispatch: formData.get("tcDispatch") || undefined, tcInspection: formData.get("tcInspection") || undefined, tcTransitInsurance: formData.get("tcTransitInsurance") || undefined, tcPaymentTerms: formData.get("tcPaymentTerms") || undefined, tcOthers: formData.get("tcOthers") || undefined, lineItems: parseLineItems(formData), }); if (!parsed.success) { return { error: parsed.error.errors[0]?.message ?? "Validation failed" }; } const data = parsed.data; const total = data.lineItems.reduce( (sum, item) => sum + item.quantity * item.unitPrice * (1 + item.gstRate), 0 ); const isSubmit = intent === "submit" && po.status === "DRAFT"; const isResubmit = intent === "resubmit" && po.status === "EDITS_REQUESTED"; const shouldSubmit = isSubmit || isResubmit; // Before mutating, snapshot the current PO state so the manager can see // exactly what the submitter changed when they resubmit after edits requested. let resubmitSnapshot: { lineItems: Array<{ name: string; description: string | null; quantity: number; unit: string; size: string | null; unitPrice: number; gstRate: number; }>; fields: { title: string; vessel: string | null; vesselId: string; account: string; accountId: string; vendor: string | null; vendorId: string | null; projectCode: string | null; dateRequired: string | null; placeOfDelivery: string | null; }; } | null = null; if (isResubmit) { const currentPo = await db.purchaseOrder.findUnique({ where: { id: poId }, include: { lineItems: { orderBy: { sortOrder: "asc" } }, vessel: true, account: true, vendor: true, }, }); if (currentPo) { resubmitSnapshot = { lineItems: currentPo.lineItems.map((li) => ({ name: li.name, description: li.description, quantity: Number(li.quantity), unit: li.unit, size: li.size, unitPrice: Number(li.unitPrice), gstRate: Number(li.gstRate), })), fields: { title: currentPo.title, vessel: currentPo.vessel?.name ?? null, vesselId: currentPo.vesselId, account: `${currentPo.account.name} (${currentPo.account.code})`, accountId: currentPo.accountId, vendor: currentPo.vendor?.name ?? null, vendorId: currentPo.vendorId, projectCode: currentPo.projectCode, dateRequired: currentPo.dateRequired?.toISOString() ?? null, placeOfDelivery: currentPo.placeOfDelivery, }, }; } } await db.purchaseOrder.update({ where: { id: poId }, data: { title: data.title, vesselId: data.vesselId, accountId: data.accountId, vendorId: data.vendorId ?? null, projectCode: data.projectCode ?? null, dateRequired: data.dateRequired ? new Date(data.dateRequired) : null, piQuotationNo: data.piQuotationNo ?? null, piQuotationDate: data.piQuotationDate ? new Date(data.piQuotationDate) : null, requisitionNo: data.requisitionNo ?? null, requisitionDate: data.requisitionDate ? new Date(data.requisitionDate) : null, placeOfDelivery: data.placeOfDelivery ?? null, tcDelivery: data.tcDelivery ?? null, tcDispatch: data.tcDispatch ?? null, tcInspection: data.tcInspection ?? null, tcTransitInsurance: data.tcTransitInsurance ?? null, tcPaymentTerms: data.tcPaymentTerms ?? null, tcOthers: data.tcOthers ?? null, totalAmount: total, status: shouldSubmit ? "MGR_REVIEW" : "DRAFT", submittedAt: shouldSubmit ? new Date() : po.submittedAt, lineItems: { deleteMany: {}, create: data.lineItems.map((item, idx) => ({ name: item.name, description: item.description ?? null, quantity: item.quantity, unit: item.unit, size: item.size ?? null, unitPrice: item.unitPrice, totalPrice: item.quantity * item.unitPrice, gstRate: item.gstRate, sortOrder: idx, accountId: (item as { accountId?: string }).accountId ?? null, })), }, actions: { create: shouldSubmit ? { actionType: "SUBMITTED", actorId: session.user.id, ...(isResubmit && resubmitSnapshot ? { metadata: { editSnapshot: resubmitSnapshot } } : {}), } : undefined, }, }, }); if (shouldSubmit) { const [fullPo, managers] = await Promise.all([ db.purchaseOrder.findUnique({ where: { id: poId }, include: { submitter: true } }), db.user.findMany({ where: { role: "MANAGER", isActive: true } }), ]); if (fullPo) { await notify({ event: "PO_SUBMITTED", po: fullPo, recipients: [fullPo.submitter, ...managers] }); } } revalidatePath(`/po/${poId}`); revalidatePath("/dashboard"); return { id: poId }; }