import Link from "next/link"; import { PoStatusBadge } from "@/components/po/po-status-badge"; import { LineItemsEditor } from "@/components/po/po-line-items-editor"; import { DiscardDraftButton } from "@/components/po/discard-draft-button"; import { SubmitDraftButton } from "@/components/po/submit-draft-button"; import { formatCurrency, formatDate, formatDateTime } from "@/lib/utils"; import { generateDownloadUrl } from "@/lib/storage"; import { TC_FIXED_LINE } from "@/lib/validations/po"; import type { LineItemInput } from "@/lib/validations/po"; import type { Role } from "@prisma/client"; type PoWithRelations = { id: string; poNumber: string; title: string; status: import("@prisma/client").POStatus; totalAmount: import("@prisma/client").Prisma.Decimal; currency: string; projectCode: string | null; dateRequired: Date | null; managerNote: string | null; paymentRef: string | null; paidAmount?: import("@prisma/client").Prisma.Decimal | null; 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; createdAt: Date; submittedAt: Date | null; approvedAt: Date | null; paidAt: Date | null; closedAt: Date | null; submitter: { id: string; name: string; email: string }; vessel: { id: string; name: string }; account: { id: string; name: string; code: string }; vendor: { id: string; name: string; vendorId: string | null; address?: string | null; gstin?: string | null; contactName?: string | null; contactMobile?: string | null; contactEmail?: string | null; } | null; lineItems: { id: string; name: string; description?: string | null; quantity: import("@prisma/client").Prisma.Decimal; unit: string; size?: string | null; unitPrice: import("@prisma/client").Prisma.Decimal; totalPrice: import("@prisma/client").Prisma.Decimal; gstRate?: import("@prisma/client").Prisma.Decimal | null; sortOrder: number; }[]; documents: { id: string; fileName: string; fileSize: number; storageKey: string; uploadedAt: Date }[]; actions: { id: string; actionType: string; note: string | null; metadata: import("@prisma/client").Prisma.JsonValue; createdAt: Date; actor: { name: string } }[]; }; interface Props { po: PoWithRelations; currentUserId: string; currentRole: Role; readOnly?: boolean; } const ACTION_LABELS: Record = { CREATED: "Created", SUBMITTED: "Submitted for review", APPROVED: "Approved", APPROVED_WITH_NOTE: "Approved with note", REJECTED: "Rejected", EDITS_REQUESTED: "Edits requested", VENDOR_ID_REQUESTED: "Vendor ID requested", VENDOR_ID_PROVIDED: "Vendor ID provided", PAYMENT_SENT: "Payment confirmed", PARTIAL_PAYMENT_CONFIRMED: "Partial payment confirmed", RECEIPT_CONFIRMED: "Receipt confirmed", PARTIAL_RECEIPT_CONFIRMED: "Partial receipt confirmed", CLOSED: "Closed", MANAGER_LINE_EDIT: "Manager amended line items", PRODUCT_PRICE_UPDATED: "Product prices updated", }; export async function PoDetail({ po, currentUserId, currentRole, readOnly = false }: Props) { const lineItemsForEditor = po.lineItems.map((li) => ({ name: li.name, description: li.description ?? undefined, quantity: Number(li.quantity), unit: li.unit, size: li.size ?? undefined, unitPrice: Number(li.unitPrice), gstRate: li.gstRate != null ? Number(li.gstRate) : 0.18, })); const managerEditAction = [...po.actions] .reverse() .find((a) => a.actionType === "MANAGER_LINE_EDIT"); const noteAction = [...po.actions] .reverse() .find((a) => ["EDITS_REQUESTED", "REJECTED", "APPROVED", "APPROVED_WITH_NOTE"].includes(a.actionType) && a.note ); const managerNoteAuthor = noteAction?.actor.name ?? null; // Resubmit snapshot: stored in the most recent SUBMITTED action's metadata // when the submitter resubmits after EDITS_REQUESTED. type ResubmitSnapshot = { lineItems: LineItemInput[]; 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; }; }; const resubmitAction = [...po.actions] .reverse() .find( (a) => a.actionType === "SUBMITTED" && !!(a.metadata as { editSnapshot?: unknown } | null)?.editSnapshot ); const resubmitSnapshot = resubmitAction ? (resubmitAction.metadata as { editSnapshot: ResubmitSnapshot }).editSnapshot : null; // Resubmit snapshot takes priority over manager line edit diff. const originalLineItems: LineItemInput[] | undefined = resubmitSnapshot?.lineItems ?? (managerEditAction ? (managerEditAction.metadata as { original: typeof lineItemsForEditor } | null)?.original : undefined); const lineItemsDiffLabel = resubmitSnapshot ? "Submitter updated these line items after edits were requested. Previous values shown with strikethrough." : "Line items were amended by manager. Current values shown; original values shown with strikethrough."; const downloadUrls = await Promise.all( po.documents.map((doc) => generateDownloadUrl(doc.storageKey)) ); const canConfirmReceipt = (po.status === "PAID_DELIVERED" || po.status === "PARTIALLY_CLOSED" || po.status === "PARTIALLY_PAID") && (po.submitter.id === currentUserId || currentRole === "SUPERUSER") && !readOnly; // Find the approver from actions const approvalAction = [...po.actions] .reverse() .find((a) => a.actionType === "APPROVED" || a.actionType === "APPROVED_WITH_NOTE"); return (
{/* Header */}
{po.poNumber}

{po.title}

{["DRAFT", "EDITS_REQUESTED"].includes(po.status) && (po.submitter.id === currentUserId || currentRole === "SUPERUSER") && !readOnly && ( Edit )} {po.status === "DRAFT" && (po.submitter.id === currentUserId || currentRole === "SUPERUSER") && !readOnly && ( )} {po.status === "DRAFT" && (po.submitter.id === currentUserId || ["MANAGER", "SUPERUSER"].includes(currentRole)) && !readOnly && ( )} {/* Export buttons — only available once the PO has been approved by a manager */} {["MGR_APPROVED", "SENT_FOR_PAYMENT", "PARTIALLY_PAID", "PAID_DELIVERED", "PARTIALLY_CLOSED", "CLOSED"].includes(po.status) && (<> Export PDF Export XLSX )}
{/* Manager note banner */} {po.managerNote && (

{managerNoteAuthor ? `Note from ${managerNoteAuthor}` : "Manager note"}

{po.managerNote}

)} {/* Submitter changes banner — shown to managers when PO is resubmitted after edits */} {resubmitSnapshot && po.status === "MGR_REVIEW" && (currentRole === "MANAGER" || currentRole === "SUPERUSER") && (() => { const snap = resubmitSnapshot.fields; const currentVessel = po.vessel?.name ?? null; const currentAccount = `${po.account.name} (${po.account.code})`; const currentVendor = po.vendor?.name ?? null; const currentDateRequired = po.dateRequired?.toISOString() ?? null; const fieldChanges: { label: string; before: string | null; after: string | null }[] = []; if (snap.title !== po.title) fieldChanges.push({ label: "Title", before: snap.title, after: po.title }); if (snap.vesselId !== po.vessel.id) fieldChanges.push({ label: "Cost Centre", before: snap.vessel, after: currentVessel }); if (snap.accountId !== po.account.id) fieldChanges.push({ label: "Account", before: snap.account, after: currentAccount }); if (snap.vendorId !== (po.vendor?.id ?? null)) fieldChanges.push({ label: "Vendor", before: snap.vendor ?? "None", after: currentVendor ?? "None" }); if (snap.projectCode !== po.projectCode) fieldChanges.push({ label: "Project Code", before: snap.projectCode ?? "—", after: po.projectCode ?? "—" }); if (snap.dateRequired !== currentDateRequired) fieldChanges.push({ label: "Date Required", before: snap.dateRequired ? formatDate(snap.dateRequired) : "—", after: po.dateRequired ? formatDate(po.dateRequired) : "—", }); if (snap.placeOfDelivery !== po.placeOfDelivery) fieldChanges.push({ label: "Place of Delivery", before: snap.placeOfDelivery ?? "—", after: po.placeOfDelivery ?? "—" }); if (fieldChanges.length === 0) return null; return (

Submitter updated the following fields after edits were requested

{fieldChanges.map(({ label, before, after }) => ( ))}
Field Before After
{label} {before} {after}
); })()} {/* Order Details */}

Order Details

Cost Centre
{po.vessel?.name ?? "—"}
Accounting Code
{po.account.name} ({po.account.code})
Requested By
{po.submitter.name}
{approvalAction && (
Approved By
{approvalAction.actor.name}
)} {po.projectCode &&
Project Code
{po.projectCode}
} {po.dateRequired &&
Delivery Date Required
{formatDate(po.dateRequired)}
} {po.piQuotationNo &&
PI / Quotation No.
{po.piQuotationNo}
} {po.piQuotationDate &&
PI / Quotation Date
{formatDate(po.piQuotationDate)}
} {po.requisitionNo &&
Requisition No.
{po.requisitionNo}
} {po.requisitionDate &&
Requisition Date
{formatDate(po.requisitionDate)}
} {po.paymentRef &&
Payment Ref
{po.paymentRef}
}
{po.placeOfDelivery && (
Place of Delivery
{po.placeOfDelivery}
)}
{/* Vendor */} {po.vendor ? (

Vendor

Name
{po.vendor.name}
Vendor ID
{po.vendor.vendorId ?? ( Not assigned )}
{po.vendor.gstin && (
GSTIN
{po.vendor.gstin}
)} {po.vendor.address &&
Address
{po.vendor.address}
} {(po.vendor.contactName || po.vendor.contactMobile || po.vendor.contactEmail) && (
Contact
{[po.vendor.contactName, po.vendor.contactMobile, po.vendor.contactEmail].filter(Boolean).join(" · ")}
)}
) : (

No vendor assigned to this PO.

)} {/* Line Items */}

Line Items

{/* Terms & Conditions */} {(po.tcDelivery || po.tcDispatch || po.tcInspection || po.tcTransitInsurance || po.tcPaymentTerms || po.tcOthers) && (

Terms & Conditions

  1. 1. {TC_FIXED_LINE}
  2. {([ { n: 2, label: "DELIVERY", value: po.tcDelivery }, { n: 3, label: "DISPATCH INSTRUCTIONS", value: po.tcDispatch }, { n: 4, label: "INSPECTION", value: po.tcInspection }, { n: 5, label: "TRANSIT INSURANCE", value: po.tcTransitInsurance }, { n: 6, label: "PAYMENT TERMS", value: po.tcPaymentTerms }, { n: 7, label: "OTHERS", value: po.tcOthers }, ] as const).filter(({ value }) => value).map(({ n, label, value }) => (
  3. {n}. {label}: {value}
  4. ))}
)} {/* Documents */} {po.documents.length > 0 && (

Attachments

    {po.documents.map((doc, i) => (
  • {doc.fileName} {(doc.fileSize / 1024).toFixed(0)} KB · {formatDate(doc.uploadedAt)}
  • ))}
)} {/* Confirm receipt CTA */} {canConfirmReceipt && (

{po.status === "PARTIALLY_CLOSED" ? "Partially received" : po.status === "PARTIALLY_PAID" ? "Advance payment received" : "Payment confirmed"}

{po.status === "PARTIALLY_CLOSED" ? "Some items are still outstanding. Confirm remaining deliveries." : po.status === "PARTIALLY_PAID" ? `Advance payment received (${formatCurrency(Number(po.paidAmount ?? 0), po.currency)} of ${formatCurrency(Number(po.totalAmount), po.currency)}). Items can be received now — PO closes when fully paid and delivered.` : "Please confirm that you have received all items."}

{po.status === "PARTIALLY_CLOSED" ? "Confirm Remaining" : "Confirm Receipt"}
)} {/* Audit trail */}

Activity

    {po.actions.map((action) => (
  1. {ACTION_LABELS[action.actionType] ?? action.actionType} by {action.actor.name} {formatDateTime(action.createdAt)}
    {action.note && (

    "{action.note}"

    )}
  2. ))}
); }