diff --git a/App/pelagia-portal/app/(portal)/po/[id]/actions.ts b/App/pelagia-portal/app/(portal)/po/[id]/actions.ts index 0295202..219d0f9 100644 --- a/App/pelagia-portal/app/(portal)/po/[id]/actions.ts +++ b/App/pelagia-portal/app/(portal)/po/[id]/actions.ts @@ -47,3 +47,33 @@ export async function provideVendorId({ revalidatePath(`/po/${poId}`); return { ok: true }; } + +export async function discardDraftPo( + poId: string +): Promise<{ ok: true } | { error: string }> { + const session = await auth(); + if (!session?.user) return { error: "Unauthorized" }; + + const po = await db.purchaseOrder.findUnique({ + where: { id: poId }, + select: { id: true, status: true, submitterId: true }, + }); + if (!po) return { error: "PO not found" }; + if (po.status !== "DRAFT") return { error: "Only DRAFT purchase orders can be discarded." }; + + const { role, id: userId } = session.user; + const canDiscard = + po.submitterId === userId || ["MANAGER", "SUPERUSER"].includes(role); + if (!canDiscard) return { error: "You do not have permission to discard this PO." }; + + // POAction has no cascade — delete child records before the PO + await db.$transaction([ + db.pOAction.deleteMany({ where: { poId } }), + db.notification.deleteMany({ where: { poId } }), + db.purchaseOrder.delete({ where: { id: poId } }), + ]); + + revalidatePath("/my-orders"); + revalidatePath("/dashboard"); + return { ok: true }; +} diff --git a/App/pelagia-portal/components/po/discard-draft-button.tsx b/App/pelagia-portal/components/po/discard-draft-button.tsx new file mode 100644 index 0000000..6b23037 --- /dev/null +++ b/App/pelagia-portal/components/po/discard-draft-button.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { discardDraftPo } from "@/app/(portal)/po/[id]/actions"; + +export function DiscardDraftButton({ poId }: { poId: string }) { + const router = useRouter(); + const [pending, setPending] = useState(false); + const [error, setError] = useState(""); + + async function handleDiscard() { + if (!confirm("Permanently discard this draft? This cannot be undone.")) return; + setPending(true); + const result = await discardDraftPo(poId); + if ("error" in result) { + setError(result.error); + setPending(false); + } else { + router.push("/my-orders"); + } + } + + return ( + + + {error && ( + + {error} + + )} + + ); +} diff --git a/App/pelagia-portal/components/po/po-detail.tsx b/App/pelagia-portal/components/po/po-detail.tsx index 71ec1eb..1c6447f 100644 --- a/App/pelagia-portal/components/po/po-detail.tsx +++ b/App/pelagia-portal/components/po/po-detail.tsx @@ -1,6 +1,7 @@ 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 { formatCurrency, formatDate, formatDateTime } from "@/lib/utils"; import { generateDownloadUrl } from "@/lib/storage"; import { TC_FIXED_LINE } from "@/lib/validations/po"; @@ -137,6 +138,11 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals Edit )} + {po.status === "DRAFT" && + (po.submitter.id === currentUserId || ["MANAGER", "SUPERUSER"].includes(currentRole)) && + !readOnly && ( + + )}