diff --git a/artifacts/api-server/src/routes/service-orders.ts b/artifacts/api-server/src/routes/service-orders.ts index c3ec4381..5393983e 100644 --- a/artifacts/api-server/src/routes/service-orders.ts +++ b/artifacts/api-server/src/routes/service-orders.ts @@ -384,8 +384,12 @@ router.patch( return; } else if (next === "cancelled") { // Check terminal state first so the receiver sees the right message - // regardless of permission gating order. - if (existing.status === "cancelled" || existing.status === "completed") { + // regardless of permission gating order. Admin may still cancel a + // completed/cancelled order (e.g. corrective action), so they bypass. + if ( + !admin && + (existing.status === "cancelled" || existing.status === "completed") + ) { res.status(409).json({ error: "order_unavailable" }); return; } diff --git a/artifacts/api-server/tests/service-orders.test.mjs b/artifacts/api-server/tests/service-orders.test.mjs index 994d96b8..aabb202d 100644 --- a/artifacts/api-server/tests/service-orders.test.mjs +++ b/artifacts/api-server/tests/service-orders.test.mjs @@ -245,11 +245,11 @@ test("status transitions matrix: receiver flows + owner/admin cancel rules", asy assert.equal(res.status, 200); assert.equal((await res.json()).status, "completed"); - // Cannot transition completed → anything (non-admin) + // Cannot transition completed → anything (non-admin); now 409 order_unavailable per Task #80 res = await call(cr, "PATCH", `/orders/${order.id}/status`, { status: "preparing", }); - assert.equal(res.status, 400); + assert.equal(res.status, 409); // Admin CAN cancel a completed order res = await call(ca, "PATCH", `/orders/${order.id}/status`, {