fix(export): gate PDF/XLSX on manager-approved status; drop submitter-name fallback
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
340a3dcce0
commit
8322f33880
2 changed files with 31 additions and 18 deletions
|
|
@ -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) {
|
|||
<div class="sig-box">
|
||||
${signatureBase64
|
||||
? `<img src="data:${signatureMime};base64,${signatureBase64}" alt="Signature" style="max-height:48px;max-width:180px;object-fit:contain;display:block;margin:0 auto 4px;" />`
|
||||
: `<div class="sig-name">${approvedBy || po.submitter.name}</div>`
|
||||
: `<div class="sig-name">${approvedBy}</div>`
|
||||
}
|
||||
<div>
|
||||
<div class="sig-sub" style="font-weight:bold">${approvedBy || po.submitter.name}</div>
|
||||
<div class="sig-sub" style="font-weight:bold">${approvedBy}</div>
|
||||
<div class="sig-sub">Authorized Signatory & Stamp</div>
|
||||
<div class="sig-sub">For, Pelagia Marine Services Pvt. Ltd.</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -183,20 +183,23 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals
|
|||
!readOnly && (
|
||||
<DiscardDraftButton poId={po.id} />
|
||||
)}
|
||||
<a
|
||||
href={`/api/po/${po.id}/export?format=pdf`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50"
|
||||
>
|
||||
Export PDF
|
||||
</a>
|
||||
<a
|
||||
href={`/api/po/${po.id}/export?format=xlsx`}
|
||||
className="rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50"
|
||||
>
|
||||
Export XLSX
|
||||
</a>
|
||||
{/* 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) && (<>
|
||||
<a
|
||||
href={`/api/po/${po.id}/export?format=pdf`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50"
|
||||
>
|
||||
Export PDF
|
||||
</a>
|
||||
<a
|
||||
href={`/api/po/${po.id}/export?format=xlsx`}
|
||||
className="rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50"
|
||||
>
|
||||
Export XLSX
|
||||
</a>
|
||||
</>)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue