Implement comprehensive notification system for LittleShop
- Add admin PWA push notifications for order management - Integrate TeleBot customer messaging service - Add push notification endpoints and VAPID key support - Implement order status notifications throughout workflow - Add notification UI components in admin panel - Create TeleBotMessagingService for customer updates - Add WebPush configuration to appsettings - Fix compilation issues (BotStatus, BotContacts DbSet) - Add comprehensive testing documentation Features: - Real-time admin notifications for new orders and status changes - Customer order progress updates via TeleBot - Graceful failure handling for notification services - Test endpoints for notification system validation 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,12 +11,16 @@ public class OrderService : IOrderService
|
||||
private readonly LittleShopContext _context;
|
||||
private readonly ILogger<OrderService> _logger;
|
||||
private readonly ICustomerService _customerService;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly ITeleBotMessagingService _teleBotMessagingService;
|
||||
|
||||
public OrderService(LittleShopContext context, ILogger<OrderService> logger, ICustomerService customerService)
|
||||
public OrderService(LittleShopContext context, ILogger<OrderService> logger, ICustomerService customerService, IPushNotificationService pushNotificationService, ITeleBotMessagingService teleBotMessagingService)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_customerService = customerService;
|
||||
_pushNotificationService = pushNotificationService;
|
||||
_teleBotMessagingService = teleBotMessagingService;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OrderDto>> GetAllOrdersAsync()
|
||||
@@ -179,15 +183,18 @@ public class OrderService : IOrderService
|
||||
|
||||
if (customerId.HasValue)
|
||||
{
|
||||
_logger.LogInformation("Created order {OrderId} for customer {CustomerId} with total {Total}",
|
||||
_logger.LogInformation("Created order {OrderId} for customer {CustomerId} with total {Total}",
|
||||
order.Id, customerId.Value, totalAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Created order {OrderId} for identity {Identity} with total {Total}",
|
||||
_logger.LogInformation("Created order {OrderId} for identity {Identity} with total {Total}",
|
||||
order.Id, identityReference, totalAmount);
|
||||
}
|
||||
|
||||
// Send notification about new order to admin users
|
||||
await SendNewOrderNotification(order);
|
||||
|
||||
// Reload order with includes
|
||||
var createdOrder = await GetOrderByIdAsync(order.Id);
|
||||
return createdOrder!;
|
||||
@@ -201,11 +208,14 @@ public class OrderService : IOrderService
|
||||
|
||||
public async Task<bool> UpdateOrderStatusAsync(Guid id, UpdateOrderStatusDto updateOrderStatusDto)
|
||||
{
|
||||
var order = await _context.Orders.FindAsync(id);
|
||||
var order = await _context.Orders
|
||||
.Include(o => o.Customer)
|
||||
.FirstOrDefaultAsync(o => o.Id == id);
|
||||
if (order == null) return false;
|
||||
|
||||
var previousStatus = order.Status;
|
||||
order.Status = updateOrderStatusDto.Status;
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(updateOrderStatusDto.TrackingNumber))
|
||||
{
|
||||
order.TrackingNumber = updateOrderStatusDto.TrackingNumber;
|
||||
@@ -225,7 +235,10 @@ public class OrderService : IOrderService
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Updated order {OrderId} status to {Status}", id, updateOrderStatusDto.Status);
|
||||
_logger.LogInformation("Updated order {OrderId} status from {PreviousStatus} to {NewStatus}", id, previousStatus, updateOrderStatusDto.Status);
|
||||
|
||||
// Send push notifications for status changes
|
||||
await SendOrderStatusNotification(order, previousStatus, updateOrderStatusDto.Status);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -336,10 +349,13 @@ public class OrderService : IOrderService
|
||||
// Enhanced workflow methods
|
||||
public async Task<bool> AcceptOrderAsync(Guid id, string userName, AcceptOrderDto acceptDto)
|
||||
{
|
||||
var order = await _context.Orders.FindAsync(id);
|
||||
var order = await _context.Orders
|
||||
.Include(o => o.Customer)
|
||||
.FirstOrDefaultAsync(o => o.Id == id);
|
||||
if (order == null || order.Status != OrderStatus.PaymentReceived)
|
||||
return false;
|
||||
|
||||
var previousStatus = order.Status;
|
||||
order.Status = OrderStatus.Accepted;
|
||||
order.AcceptedAt = DateTime.UtcNow;
|
||||
order.AcceptedByUser = userName;
|
||||
@@ -349,15 +365,22 @@ public class OrderService : IOrderService
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("Order {OrderId} accepted by {User}", id, userName);
|
||||
|
||||
// Send push notifications
|
||||
await SendOrderStatusNotification(order, previousStatus, OrderStatus.Accepted);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> StartPackingAsync(Guid id, string userName, StartPackingDto packingDto)
|
||||
{
|
||||
var order = await _context.Orders.FindAsync(id);
|
||||
var order = await _context.Orders
|
||||
.Include(o => o.Customer)
|
||||
.FirstOrDefaultAsync(o => o.Id == id);
|
||||
if (order == null || order.Status != OrderStatus.Accepted)
|
||||
return false;
|
||||
|
||||
var previousStatus = order.Status;
|
||||
order.Status = OrderStatus.Packing;
|
||||
order.PackingStartedAt = DateTime.UtcNow;
|
||||
order.PackedByUser = userName;
|
||||
@@ -367,12 +390,18 @@ public class OrderService : IOrderService
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("Order {OrderId} packing started by {User}", id, userName);
|
||||
|
||||
// Send push notifications
|
||||
await SendOrderStatusNotification(order, previousStatus, OrderStatus.Packing);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> DispatchOrderAsync(Guid id, string userName, DispatchOrderDto dispatchDto)
|
||||
{
|
||||
var order = await _context.Orders.FindAsync(id);
|
||||
var order = await _context.Orders
|
||||
.Include(o => o.Customer)
|
||||
.FirstOrDefaultAsync(o => o.Id == id);
|
||||
if (order == null || order.Status != OrderStatus.Packing)
|
||||
return false;
|
||||
|
||||
@@ -398,6 +427,10 @@ public class OrderService : IOrderService
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("Order {OrderId} dispatched by {User} with tracking {TrackingNumber}", id, userName, dispatchDto.TrackingNumber);
|
||||
|
||||
// Send push notifications
|
||||
await SendOrderStatusNotification(order, OrderStatus.Packing, OrderStatus.Dispatched);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -490,4 +523,81 @@ public class OrderService : IOrderService
|
||||
{
|
||||
return await GetOrdersByStatusAsync(OrderStatus.OnHold);
|
||||
}
|
||||
|
||||
private async Task SendNewOrderNotification(Order order)
|
||||
{
|
||||
try
|
||||
{
|
||||
var title = "🛒 New Order Received";
|
||||
var body = $"Order #{order.Id.ToString()[..8]} created for £{order.TotalAmount:F2}. Awaiting payment.";
|
||||
|
||||
// Send notification to all admin users about new order
|
||||
await _pushNotificationService.SendOrderNotificationAsync(order.Id, title, body);
|
||||
|
||||
_logger.LogInformation("Sent new order notification for order {OrderId}", order.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to send new order notification for order {OrderId}", order.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendOrderStatusNotification(Order order, OrderStatus previousStatus, OrderStatus newStatus)
|
||||
{
|
||||
try
|
||||
{
|
||||
var title = GetOrderStatusNotificationTitle(newStatus);
|
||||
var body = GetOrderStatusNotificationBody(order, previousStatus, newStatus);
|
||||
|
||||
// Send notification to admin users about order status change
|
||||
await _pushNotificationService.SendOrderNotificationAsync(order.Id, title, body);
|
||||
|
||||
// Send TeleBot message to customer (if customer exists)
|
||||
if (order.Customer != null)
|
||||
{
|
||||
await _teleBotMessagingService.SendOrderStatusUpdateAsync(order.Id, newStatus);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Sent order status notifications for order {OrderId}: {Status} (Admin + Customer)", order.Id, newStatus);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to send order status notification for order {OrderId}", order.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetOrderStatusNotificationTitle(OrderStatus status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
OrderStatus.PaymentReceived => "💰 Payment Confirmed",
|
||||
OrderStatus.Accepted => "✅ Order Accepted",
|
||||
OrderStatus.Packing => "📦 Being Packed",
|
||||
OrderStatus.Dispatched => "🚚 Order Dispatched",
|
||||
OrderStatus.Delivered => "🎉 Order Delivered",
|
||||
OrderStatus.OnHold => "⏸️ Order On Hold",
|
||||
OrderStatus.Cancelled => "❌ Order Cancelled",
|
||||
OrderStatus.Refunded => "💸 Order Refunded",
|
||||
_ => "📋 Order Updated"
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetOrderStatusNotificationBody(Order order, OrderStatus previousStatus, OrderStatus newStatus)
|
||||
{
|
||||
var orderId = order.Id.ToString()[..8];
|
||||
var amount = order.TotalAmount.ToString("F2");
|
||||
|
||||
return newStatus switch
|
||||
{
|
||||
OrderStatus.PaymentReceived => $"Order #{orderId} payment confirmed (£{amount}). Ready for acceptance.",
|
||||
OrderStatus.Accepted => $"Order #{orderId} has been accepted and is ready for packing.",
|
||||
OrderStatus.Packing => $"Order #{orderId} is being packed. Will be dispatched soon.",
|
||||
OrderStatus.Dispatched => $"Order #{orderId} dispatched with tracking: {order.TrackingNumber ?? "TBA"}",
|
||||
OrderStatus.Delivered => $"Order #{orderId} has been delivered successfully.",
|
||||
OrderStatus.OnHold => $"Order #{orderId} has been put on hold: {order.OnHoldReason}",
|
||||
OrderStatus.Cancelled => $"Order #{orderId} has been cancelled.",
|
||||
OrderStatus.Refunded => $"Order #{orderId} has been refunded (£{amount}).",
|
||||
_ => $"Order #{orderId} status updated from {previousStatus} to {newStatus}."
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user