diff --git a/src/Meezi.API/Controllers/OrdersController.cs b/src/Meezi.API/Controllers/OrdersController.cs index dc642a3..c738df9 100644 --- a/src/Meezi.API/Controllers/OrdersController.cs +++ b/src/Meezi.API/Controllers/OrdersController.cs @@ -376,6 +376,8 @@ public class OrdersController : CafeApiControllerBase false, null, new ApiError(code, "Order is already cancelled.", field))), "ORDER_HAS_PAYMENTS" => Conflict(new ApiResponse( false, null, new ApiError(code, "Refund the recorded payments before cancelling this order.", field))), + "ORDER_IN_PREPARATION" => Conflict(new ApiResponse( + false, null, new ApiError(code, "This order has already been sent to the kitchen and cannot be cancelled.", field))), "ITEM_NOT_FOUND" => NotFound(new ApiResponse( false, null, new ApiError(code, "Line item not found.", field))), "ITEM_ALREADY_VOIDED" => BadRequest(new ApiResponse( diff --git a/src/Meezi.API/Services/OrderService.cs b/src/Meezi.API/Services/OrderService.cs index b996a83..4283204 100644 --- a/src/Meezi.API/Services/OrderService.cs +++ b/src/Meezi.API/Services/OrderService.cs @@ -995,9 +995,18 @@ public class OrderService : IOrderService if (order.Status == OrderStatus.Cancelled) return new OrderServiceResult(false, null, "ORDER_ALREADY_CANCELLED"); - if (!OpenForPaymentStatuses.Contains(order.Status)) + if (order.Status == OrderStatus.Delivered) return new OrderServiceResult(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(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))