From 8322f33880b2693bfe768313a645cb9e41ad0027 Mon Sep 17 00:00:00 2001 From: Hardik Date: Sat, 16 May 2026 20:50:26 +0530 Subject: [PATCH] fix(export): gate PDF/XLSX on manager-approved status; drop submitter-name fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Export API returns 403 for any PO not yet approved (DRAFT, SUBMITTED, MGR_REVIEW, EDITS_REQUESTED, VENDOR_ID_PENDING, REJECTED) — only MGR_APPROVED, SENT_FOR_PAYMENT, PAID_DELIVERED, PARTIALLY_CLOSED and CLOSED are exportable. - The submitter's name is no longer used as a signatory fallback; since export is blocked until after manager approval an approver always exists. - Export PDF / Export XLSX buttons are hidden in po-detail for pre-approval statuses, so users never encounter the 403 through normal UI flows. Co-Authored-By: Claude Sonnet 4.6 --- .../app/api/po/[id]/export/route.ts | 18 ++++++++--- .../components/po/po-detail.tsx | 31 ++++++++++--------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/App/pelagia-portal/app/api/po/[id]/export/route.ts b/App/pelagia-portal/app/api/po/[id]/export/route.ts index d5ca79f..d86800e 100644 --- a/App/pelagia-portal/app/api/po/[id]/export/route.ts +++ b/App/pelagia-portal/app/api/po/[id]/export/route.ts @@ -47,6 +47,16 @@ export async function GET(request: NextRequest, { params }: Props) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } + // Exports are only available for approved POs — manager approval is a prerequisite for a valid PO document. + // The submitter's signature is never embedded; only the approving manager's signature is used. + const EXPORTABLE_STATUSES = ["MGR_APPROVED", "SENT_FOR_PAYMENT", "PAID_DELIVERED", "PARTIALLY_CLOSED", "CLOSED"]; + if (!EXPORTABLE_STATUSES.includes(po.status)) { + return NextResponse.json( + { error: "Export is only available for approved purchase orders." }, + { status: 403 } + ); + } + const format = request.nextUrl.searchParams.get("format") ?? "pdf"; // ── Computed data ───────────────────────────────────────────────────────── @@ -386,10 +396,10 @@ export async function GET(request: NextRequest, { params }: Props) { }); sc(SIG_ROW, 1, "", { border: { top: thin(), left: thin(), right: thin() } }); } else { - sc(SIG_ROW, 1, approvedBy || po.submitter.name, { font: fBold, border: { top: thin(), left: thin(), right: thin() }, align: alignC }); + sc(SIG_ROW, 1, approvedBy, { font: fBold, border: { top: thin(), left: thin(), right: thin() }, align: alignC }); } ws.mergeCells(`A${SIG_ROW}:D${SIG_ROW}`); - sc(SIG_ROW + 1, 1, approvedBy || po.submitter.name, { font: fBold, border: { left: thin(), right: thin() }, align: alignC }); + sc(SIG_ROW + 1, 1, approvedBy, { font: fBold, border: { left: thin(), right: thin() }, align: alignC }); ws.mergeCells(`A${SIG_ROW + 1}:D${SIG_ROW + 1}`); sc(SIG_ROW + 2, 1, "Authorized Signatory & Stamp", { font: fSmall, border: { left: thin(), right: thin() }, align: alignC }); ws.mergeCells(`A${SIG_ROW + 2}:D${SIG_ROW + 2}`); @@ -675,10 +685,10 @@ export async function GET(request: NextRequest, { params }: Props) {
${signatureBase64 ? `Signature` - : `
${approvedBy || po.submitter.name}
` + : `
${approvedBy}
` }
-
${approvedBy || po.submitter.name}
+
${approvedBy}
Authorized Signatory & Stamp
For, Pelagia Marine Services Pvt. Ltd.
diff --git a/App/pelagia-portal/components/po/po-detail.tsx b/App/pelagia-portal/components/po/po-detail.tsx index 265089f..575380e 100644 --- a/App/pelagia-portal/components/po/po-detail.tsx +++ b/App/pelagia-portal/components/po/po-detail.tsx @@ -183,20 +183,23 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals !readOnly && ( )} - - Export PDF - - - Export XLSX - + {/* Export buttons — only available once the PO has been approved by a manager */} + {["MGR_APPROVED", "SENT_FOR_PAYMENT", "PAID_DELIVERED", "PARTIALLY_CLOSED", "CLOSED"].includes(po.status) && (<> + + Export PDF + + + Export XLSX + + )}