ef15fd6247
Full backend implementation: - Multi-tenant cafe/restaurant management (menus, orders, tables, staff) - POS order flow with ZarinPal and Snappfood payment integration - OTP authentication via Kavenegar SMS - QR digital menu with public discover/finder endpoints - Customer loyalty, coupons, CRM - PostgreSQL via EF Core, Redis for caching/sessions - Background jobs, webhook handlers - Full migration history Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
152 lines
4.8 KiB
C#
152 lines
4.8 KiB
C#
using Meezi.API.Models.Orders;
|
|
using Meezi.Core.Entities;
|
|
using Meezi.Core.Enums;
|
|
using Meezi.Core.Interfaces;
|
|
using Meezi.Infrastructure.Data;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Meezi.API.Services.Delivery;
|
|
|
|
public interface IDeliveryStatusSyncService
|
|
{
|
|
Task<bool> SyncInternalStatusAsync(
|
|
string cafeId,
|
|
string orderId,
|
|
OrderStatus newStatus,
|
|
CancellationToken ct = default);
|
|
|
|
Task<bool> ApplyPlatformStatusAsync(
|
|
DeliveryPlatform platform,
|
|
string externalOrderId,
|
|
string platformStatus,
|
|
CancellationToken ct = default);
|
|
}
|
|
|
|
public class DeliveryStatusSyncService : IDeliveryStatusSyncService
|
|
{
|
|
private readonly AppDbContext _db;
|
|
private readonly IKdsNotifier _kds;
|
|
private readonly ISnappfoodClient _snappfood;
|
|
private readonly ITap30Client _tap30;
|
|
private readonly IInventoryService _inventory;
|
|
private readonly ILogger<DeliveryStatusSyncService> _logger;
|
|
|
|
public DeliveryStatusSyncService(
|
|
AppDbContext db,
|
|
IKdsNotifier kds,
|
|
ISnappfoodClient snappfood,
|
|
ITap30Client tap30,
|
|
IInventoryService inventory,
|
|
ILogger<DeliveryStatusSyncService> logger)
|
|
{
|
|
_db = db;
|
|
_kds = kds;
|
|
_snappfood = snappfood;
|
|
_tap30 = tap30;
|
|
_inventory = inventory;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<bool> SyncInternalStatusAsync(
|
|
string cafeId,
|
|
string orderId,
|
|
OrderStatus newStatus,
|
|
CancellationToken ct = default)
|
|
{
|
|
var order = await _db.Orders
|
|
.FirstOrDefaultAsync(o => o.Id == orderId && o.CafeId == cafeId, ct);
|
|
|
|
if (order?.DeliveryPlatform is null || string.IsNullOrEmpty(order.ExternalOrderId))
|
|
return false;
|
|
|
|
var platformStatus = MapToPlatformStatus(newStatus);
|
|
await NotifyPlatformAsync(order.DeliveryPlatform.Value, order.ExternalOrderId, platformStatus, ct);
|
|
|
|
if (newStatus == OrderStatus.Delivered && !string.IsNullOrEmpty(order.SnappfoodOrderId))
|
|
await _snappfood.NotifyOrderDeliveredAsync(order.SnappfoodOrderId, ct);
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> ApplyPlatformStatusAsync(
|
|
DeliveryPlatform platform,
|
|
string externalOrderId,
|
|
string platformStatus,
|
|
CancellationToken ct = default)
|
|
{
|
|
var order = await _db.Orders
|
|
.Include(o => o.Items)
|
|
.ThenInclude(i => i.MenuItem)
|
|
.FirstOrDefaultAsync(
|
|
o => o.DeliveryPlatform == platform && o.ExternalOrderId == externalOrderId,
|
|
ct);
|
|
|
|
if (order is null)
|
|
return false;
|
|
|
|
var mapped = MapFromPlatformStatus(platformStatus);
|
|
if (order.Status == mapped)
|
|
return true;
|
|
|
|
if (mapped == OrderStatus.Cancelled)
|
|
await RollbackInventoryPlaceholderAsync(order, ct);
|
|
|
|
order.Status = mapped;
|
|
await _db.SaveChangesAsync(ct);
|
|
|
|
await _kds.NotifyOrderStatusChangedAsync(order.CafeId, order.Id, mapped, ct);
|
|
if (!string.IsNullOrEmpty(order.TableId))
|
|
await _kds.NotifyTableStatusChangedAsync(order.CafeId, ct);
|
|
|
|
return true;
|
|
}
|
|
|
|
private async Task NotifyPlatformAsync(
|
|
DeliveryPlatform platform,
|
|
string externalOrderId,
|
|
string status,
|
|
CancellationToken ct)
|
|
{
|
|
switch (platform)
|
|
{
|
|
case DeliveryPlatform.Snappfood:
|
|
await _snappfood.NotifyOrderStatusAsync(externalOrderId, status, ct);
|
|
break;
|
|
case DeliveryPlatform.Tap30:
|
|
await _tap30.NotifyOrderStatusAsync(externalOrderId, status, ct);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private Task RollbackInventoryPlaceholderAsync(Order order, CancellationToken ct)
|
|
{
|
|
_logger.LogInformation(
|
|
"Delivery order {OrderId} cancelled — inventory rollback pending BOM linkage",
|
|
order.Id);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private static string MapToPlatformStatus(OrderStatus status) => status switch
|
|
{
|
|
OrderStatus.Pending => "pending",
|
|
OrderStatus.Confirmed => "confirmed",
|
|
OrderStatus.Preparing => "preparing",
|
|
OrderStatus.Ready => "ready",
|
|
OrderStatus.Delivered => "delivered",
|
|
OrderStatus.Cancelled => "cancelled",
|
|
_ => "confirmed"
|
|
};
|
|
|
|
private static OrderStatus MapFromPlatformStatus(string platformStatus) =>
|
|
platformStatus.Trim().ToLowerInvariant() switch
|
|
{
|
|
"pending" => OrderStatus.Pending,
|
|
"confirmed" => OrderStatus.Confirmed,
|
|
"preparing" or "in_progress" => OrderStatus.Preparing,
|
|
"ready" => OrderStatus.Ready,
|
|
"delivered" or "completed" => OrderStatus.Delivered,
|
|
"cancelled" or "canceled" => OrderStatus.Cancelled,
|
|
_ => OrderStatus.Confirmed
|
|
};
|
|
}
|