first commit
CI/CD / CI · Admin API (dotnet build) (push) Successful in 41s
CI/CD / CI · Admin Web (tsc) (push) Failing after 5s
CI/CD / CI · Website (tsc) (push) Failing after 4s
CI/CD / CI · Koja (tsc) (push) Failing after 5s
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m13s
CI/CD / CI · Dashboard (tsc) (push) Failing after 2m32s
CI/CD / Deploy · all services (push) Has been skipped
CI/CD / CI · Admin API (dotnet build) (push) Successful in 41s
CI/CD / CI · Admin Web (tsc) (push) Failing after 5s
CI/CD / CI · Website (tsc) (push) Failing after 4s
CI/CD / CI · Koja (tsc) (push) Failing after 5s
CI/CD / CI · API (dotnet build + test) (push) Successful in 1m13s
CI/CD / CI · Dashboard (tsc) (push) Failing after 2m32s
CI/CD / Deploy · all services (push) Has been skipped
This commit is contained in:
@@ -55,6 +55,12 @@ public interface IOrderService
|
||||
string targetTableId,
|
||||
CancellationToken cancellationToken = default);
|
||||
Task<OrderDto?> UpdateStatusAsync(string cafeId, string orderId, OrderStatus status, CancellationToken cancellationToken = default);
|
||||
Task<OrderServiceResult<OrderDto>> CancelOrderAsync(
|
||||
string cafeId,
|
||||
string orderId,
|
||||
string? reason,
|
||||
string? cancelledByEmployeeId,
|
||||
CancellationToken cancellationToken = default);
|
||||
Task<OrderServiceResult<IReadOnlyList<PaymentDto>>> RecordPaymentsAsync(
|
||||
string cafeId,
|
||||
string orderId,
|
||||
@@ -957,6 +963,53 @@ public class OrderService : IOrderService
|
||||
return await GetOrderAsync(cafeId, orderId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<OrderServiceResult<OrderDto>> CancelOrderAsync(
|
||||
string cafeId,
|
||||
string orderId,
|
||||
string? reason,
|
||||
string? cancelledByEmployeeId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var order = await _db.Orders
|
||||
.Include(o => o.Payments)
|
||||
.FirstOrDefaultAsync(o => o.Id == orderId && o.CafeId == cafeId, cancellationToken);
|
||||
|
||||
if (order is null)
|
||||
return new OrderServiceResult<OrderDto>(false, null, "ORDER_NOT_FOUND");
|
||||
|
||||
if (order.Status == OrderStatus.Cancelled)
|
||||
return new OrderServiceResult<OrderDto>(false, null, "ORDER_ALREADY_CANCELLED");
|
||||
|
||||
if (!OpenForPaymentStatuses.Contains(order.Status))
|
||||
return new OrderServiceResult<OrderDto>(false, null, "ORDER_NOT_OPEN");
|
||||
|
||||
// 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))
|
||||
return new OrderServiceResult<OrderDto>(false, null, "ORDER_HAS_PAYMENTS");
|
||||
|
||||
order.Status = OrderStatus.Cancelled;
|
||||
order.StatusUpdatedAt = DateTime.UtcNow;
|
||||
order.CancelledAt = DateTime.UtcNow;
|
||||
order.CancelReason = string.IsNullOrWhiteSpace(reason) ? null : reason.Trim();
|
||||
order.CancelledByEmployeeId = cancelledByEmployeeId;
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
await _kdsNotifier.NotifyOrderStatusChangedAsync(cafeId, orderId, OrderStatus.Cancelled, cancellationToken);
|
||||
if (!string.IsNullOrEmpty(order.TableId))
|
||||
await _kdsNotifier.NotifyTableStatusChangedAsync(cafeId, cancellationToken);
|
||||
|
||||
await _deliverySync.SyncInternalStatusAsync(cafeId, orderId, OrderStatus.Cancelled, cancellationToken);
|
||||
|
||||
var loaded = await LoadOrderAsync(cafeId, orderId, cancellationToken);
|
||||
if (loaded is not null)
|
||||
await _orderNotifications.NotifyOrderStatusChangedAsync(loaded, cancellationToken);
|
||||
|
||||
return loaded is null
|
||||
? new OrderServiceResult<OrderDto>(false, null, "ORDER_NOT_FOUND")
|
||||
: new OrderServiceResult<OrderDto>(true, MapOrder(loaded));
|
||||
}
|
||||
|
||||
public async Task<OrderServiceResult<IReadOnlyList<PaymentDto>>> RecordPaymentsAsync(
|
||||
string cafeId,
|
||||
string orderId,
|
||||
|
||||
Reference in New Issue
Block a user