[Issue]: Allow cancelling of POs and optional supersede by another PO #53
Labels
No labels
bug
claude-failed
claude-pr
claude-queue
claude-working
feature
interactive
portal
triaged
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: shad0w/pelagia-portal#53
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Raised by
Kaushal Pal Singh (kps@pelagiamarine.com, MANAGER) — via portal Report Issue button
Description
When a PO gets cancelled it's financials should get subtracted from the trackers and the graphs. Allow POs to be cancelled by managers only. Ensure sufficient warning is given to the manager while cancelling (modal that has them type "cancel" to confirm).
Optionally allow cancelled POs to be superseded by other POs. The cancelled PO will link to the one it is superseded by
Priority
P2 — Medium
Context
/po/cmqmltsf3006e8bro27eg5hwwClaude triage
Triage — Issue #53: Allow cancelling of POs and optional supersede by another PO
Type: Feature (new capability — adds a terminal cancelled state plus supersede linkage)
Routing: interactive
Priority: P2 — Medium
Interpretation
Two related requests:
CANCELLEDstate. A cancelled PO'sfinancials must be excluded from all spend trackers and graphs. The action is gated to
MANAGER (and likely SUPERUSER). The UI must show a strong confirmation modal that forces the
manager to type the word
cancelbefore the action fires.replaces it (
supersededByIdself-relation). This is described as optional/secondary.Action items
A. Data model (Prisma migration — disqualifier on its own)
CANCELLEDto thePOStatusenum (prisma/schema.prisma:20).PurchaseOrderfor supersede:supersededById String?+supersededBy/supersededByOfrelation pair, pluscancelledAt DateTime?and likely acancellationReason String?.CANCELLED(and optionallySUPERSEDED_LINKED) to theActionTypeenum for the audit trail.pelagia_test(prod mirror) per CLAUDE.md.B. State machine (
lib/po-state-machine.ts)cancelPOActionand decide which source states it is valid from (e.g. anynon-terminal pre-payment state? after approval? after payment? — see open questions). Restrict
allowedRolesto["MANAGER", "SUPERUSER"].C. Permissions (
lib/permissions.ts) — disqualifier (auth/permissions)cancel_popermission granted to MANAGER (+ SUPERUSER), and enforce it in the newserver action.
D. Server action + UI
cancelPo(and supersede-link) server action underapp/(portal)/po/[id]/actions.ts,calling
requirePermission/canPerformAction, writing aPOActionaudit row, andrevalidatePathfor/po/[id],/dashboard,/history,/my-orders.cancelto enable thebutton. Wire a "Cancel PO" button into
components/po/po-detail.tsx(manager-only, gated bycurrent status).
both POs in
po-detail.tsx.E. Financial exclusion (money/aggregation — disqualifier) — must be exhaustive
A cancelled PO must drop out of every spend/count aggregation. Known sites to audit:
app/(portal)/dashboard/page.tsx— Manager "Total Approved Spend", "Approved This Month",vessel breakdown, 12-month series; all keyed off
POST_APPROVAL_STATUSES(
lib/utils.ts:83). Simplest path: keepCANCELLEDout of that constant — but verify everyconsumer.
app/(portal)/admin/vessels/[id]/page.tsx:43,admin/sites/[id]/page.tsx,admin/vendors/[id]/page.tsx— per-entity spend totals filtering onCLOSED/PAID_DELIVERED.app/api/reports/export/route.tsandapp/api/po/[id]/export/route.ts— report/exportstatus filters.
app/(portal)/history/history-filters.tsx,my-orders/page.tsx,payments/*— listing andcounts.
F. Exhaustive enum maps (build will fail without these)
Record<POStatus, …>maps must get aCANCELLEDentry ortscbreaks:PO_STATUS_LABELS,PO_STATUS_VARIANTSinlib/utils.ts(add adanger/grey badge).PO_STATUS_LABELSlookup is covered.G. Side-effects to reverse on cancel
ItemInventory(
app/(portal)/approvals/[id]/actions.ts:61). Cancelling an already-approved PO with asiteIdshould reverse the increment — needs an explicit decision.cancel should be blocked once paid (open question).
Files / areas involved
prisma/schema.prisma(enum + self-relation + fields → migration)lib/po-state-machine.ts,lib/permissions.ts,lib/utils.tsapp/(portal)/po/[id]/actions.ts,components/po/po-detail.tsx, new modal component,components/po/po-status-badge.tsxlib/notifier.ts(cancellation email event)Open questions
sent-for-payment? Is a paid/closed PO cancellable at all?
flow? Does linking enforce same vessel/account/vendor? Can the link be added later?
Routing rationale
Routing rationale: interactive — this is a large multi-file feature that requires a Prisma
migration (new enum value + self-referential supersede relation), permissions/auth changes
(manager-only
cancel_po), and money/aggregation changes across many trackers, plusunderspecified supersede semantics and side-effect reversal — all explicit disqualifiers for an
unattended claude-queue run.
Routing:
interactive| Type:feature