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 && (
+
+ )}