fix(orders): block cancelling an order once the kitchen has started it
CI/CD / CI · API (dotnet build + test) (push) Successful in 52s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 29s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m8s
CI/CD / CI · Admin Web (tsc) (push) Successful in 39s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 49s
CI/CD / Deploy · all services (push) Successful in 1m40s

Anti-fraud / integrity: a cashier could fire an order to the kitchen, take cash
without recording a payment, then cancel (soft-delete) the unpaid order to erase
it. CancelOrderAsync now only allows cancelling a still-Pending order; once the
kitchen has acted on it (Confirmed/Preparing/Ready) it returns ORDER_IN_PREPARATION
and a started order can no longer be removed — it must be completed (and refunded
through the audited refund flow if needed). Delivered → ORDER_NOT_OPEN; paid →
ORDER_HAS_PAYMENTS (unchanged). Orders are never hard-deleted and every cancel is
already audited with the actor. Applies to all roles, independent of permissions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-25 10:41:30 +03:30
parent b162335b48
commit 27ca80fd54
2 changed files with 12 additions and 1 deletions
+10 -1
View File
@@ -995,9 +995,18 @@ public class OrderService : IOrderService
if (order.Status == OrderStatus.Cancelled)
return new OrderServiceResult<OrderDto>(false, null, "ORDER_ALREADY_CANCELLED");
if (!OpenForPaymentStatuses.Contains(order.Status))
if (order.Status == OrderStatus.Delivered)
return new OrderServiceResult<OrderDto>(false, null, "ORDER_NOT_OPEN");
// Integrity / anti-fraud: once the kitchen has acted on the order
// (Confirmed / Preparing / Ready) the food has been produced, so the order
// can no longer be cancelled/deleted — otherwise a cashier could fire an
// order, take cash without recording a payment, then erase it. Only a
// not-yet-started (Pending) order may be cancelled; a started one must be
// completed (and refunded via the audited refund flow if needed).
if (order.Status != OrderStatus.Pending)
return new OrderServiceResult<OrderDto>(false, null, "ORDER_IN_PREPARATION");
// A paid order must be refunded through the payment flow first — cancelling it
// here would silently strip the recorded money. Block and surface the reason.
if (order.Payments.Any(p => p.DeletedAt == null))