From 2ca1861226b86a7a03192e6dced26098a72d3b66 Mon Sep 17 00:00:00 2001 From: Hardik Date: Wed, 6 May 2026 00:38:14 +0530 Subject: [PATCH] feat(approvals): manager can edit all PO fields during review Replaces the line-items-only editor with a full ManagerEditPoForm that covers every field: title, vessel, account, vendor, project code, delivery date, PI/Quotation No+Date, Requisition No+Date, place of delivery, all structured T&C fields, and line items (with GST rate). Edit toggle is amber-styled to distinguish manager changes from submitter input. On save, a complete snapshot of original values is written to the audit trail (MANAGER_LINE_EDIT action with metadata.original). managerEditPo server action: validates manager permission, checks status == MGR_REVIEW, recalculates totalAmount as grand total including GST, and persists all updated fields. Approval detail page now fetches vessels/accounts/vendors to populate the edit form dropdowns. --- .../approvals/[id]/manager-edit-po-form.tsx | 278 ++++++++++++++++++ .../approvals/[id]/manager-po-edit-actions.ts | 155 ++++++++++ .../app/(portal)/approvals/[id]/page.tsx | 53 ++-- 3 files changed, 461 insertions(+), 25 deletions(-) create mode 100644 App/pelagia-portal/app/(portal)/approvals/[id]/manager-edit-po-form.tsx create mode 100644 App/pelagia-portal/app/(portal)/approvals/[id]/manager-po-edit-actions.ts diff --git a/App/pelagia-portal/app/(portal)/approvals/[id]/manager-edit-po-form.tsx b/App/pelagia-portal/app/(portal)/approvals/[id]/manager-edit-po-form.tsx new file mode 100644 index 0000000..4ac5de6 --- /dev/null +++ b/App/pelagia-portal/app/(portal)/approvals/[id]/manager-edit-po-form.tsx @@ -0,0 +1,278 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { managerEditPo } from "./manager-po-edit-actions"; +import { LineItemsEditor } from "@/components/po/po-line-items-editor"; +import { TC_DEFAULTS, TC_FIXED_LINE } from "@/lib/validations/po"; +import type { LineItemInput } from "@/lib/validations/po"; +import type { Vessel, Account, Vendor, PurchaseOrder, POLineItem } from "@prisma/client"; + +type PoFull = PurchaseOrder & { + lineItems: POLineItem[]; + vessel: { id: string; name: string }; + account: { id: string; name: string; code: string }; + vendor: { id: string; name: string; vendorId: string | null } | null; +}; + +interface Props { + po: PoFull; + vessels: Vessel[]; + accounts: Account[]; + vendors: Vendor[]; +} + +const INPUT = + "w-full rounded-lg border border-amber-300 bg-amber-50 px-3 py-2 text-sm focus:border-amber-500 focus:outline-none focus:ring-2 focus:ring-amber-400/30 placeholder:text-neutral-400"; +const LABEL = "block text-xs font-semibold text-amber-800 mb-1"; + +export function ManagerEditPoForm({ po, vessels, accounts, vendors }: Props) { + const router = useRouter(); + const [editing, setEditing] = useState(false); + const [pending, setPending] = useState(false); + const [error, setError] = useState(""); + const [saved, setSaved] = useState(false); + + const extPo = po as typeof po & { + piQuotationNo?: string | null; piQuotationDate?: Date | null; + requisitionNo?: string | null; requisitionDate?: Date | null; + placeOfDelivery?: string | null; + tcDelivery?: string | null; tcDispatch?: string | null; + tcInspection?: string | null; tcTransitInsurance?: string | null; + tcPaymentTerms?: string | null; tcOthers?: string | null; + }; + + const [lineItems, setLineItems] = useState( + po.lineItems.map((li) => ({ + description: li.description, + quantity: Number(li.quantity), + unit: li.unit, + size: li.size ?? undefined, + unitPrice: Number(li.unitPrice), + gstRate: Number((li as POLineItem & { gstRate?: unknown }).gstRate ?? 0.18), + })) + ); + + function isoDate(d: Date | null | undefined) { + if (!d) return ""; + return new Date(d).toISOString().split("T")[0]; + } + + async function handleSave() { + setPending(true); + setError(""); + const form = document.getElementById("mgr-edit-po-form") as HTMLFormElement; + const data = new FormData(form); + lineItems.forEach((item, i) => { + data.set(`lineItems[${i}].description`, item.description); + data.set(`lineItems[${i}].quantity`, String(item.quantity)); + data.set(`lineItems[${i}].unit`, item.unit); + data.set(`lineItems[${i}].size`, item.size ?? ""); + data.set(`lineItems[${i}].unitPrice`, String(item.unitPrice)); + data.set(`lineItems[${i}].gstRate`, String(item.gstRate ?? 0.18)); + }); + + const result = await managerEditPo(po.id, data); + if ("error" in result) { + setError(result.error); + setPending(false); + } else { + setSaved(true); + setEditing(false); + setPending(false); + router.refresh(); + } + } + + if (!editing) { + return ( +
+ {saved && ( +

+ PO updated. Original values are preserved in the activity trail. +

+ )} + +

+ Edit any field on this PO. Original values will be saved to the audit trail. +

+
+ ); + } + + return ( +
+ {/* Header */} +
+
+

Manager — Editing PO

+

All changes will be saved to the audit trail with the original values.

+
+ +
+ +
e.preventDefault()}> + + {/* Order Information */} +
+

Order Information

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + {/* Quotation Reference */} +
+

Quotation Reference

+
+
+ + +
+
+ + +
+
+
+ + {/* Requisition */} +
+

Requisition

+
+
+ + +
+
+ + +
+
+
+ + {/* Delivery */} +
+

Delivery

+ +