fix(regular-store): deduct inventory and restore on cancel
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 19m38s
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 19m38s
- UserOrderService.SubmitShopBuyOrder: inject IInventoryService; validate available stock per item before any financial operation; call ConfirmSaleAsync and increment Product.SaleCount after order is persisted. - CancelOrderByAdminCommandHandler: inject IInventoryService; include FactorDetails in query; call ProcessReturnAsync for each item so stock is restored when an order is cancelled by admin. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+24
@@ -10,15 +10,18 @@ public class CancelOrderByAdminCommandHandler : IRequestHandler<CancelOrderByAdm
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
private readonly IInventoryService _inventoryService;
|
||||
private readonly ILogger<CancelOrderByAdminCommandHandler> _logger;
|
||||
|
||||
public CancelOrderByAdminCommandHandler(
|
||||
IApplicationDbContext context,
|
||||
ICurrentUserService currentUser,
|
||||
IInventoryService inventoryService,
|
||||
ILogger<CancelOrderByAdminCommandHandler> logger)
|
||||
{
|
||||
_context = context;
|
||||
_currentUser = currentUser;
|
||||
_inventoryService = inventoryService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -33,6 +36,7 @@ public class CancelOrderByAdminCommandHandler : IRequestHandler<CancelOrderByAdm
|
||||
var order = await _context.UserOrders
|
||||
.Include(x => x.User)
|
||||
.ThenInclude(x => x.UserWallets)
|
||||
.Include(x => x.FactorDetails)
|
||||
.FirstOrDefaultAsync(x => x.Id == request.OrderId && !x.IsDeleted, cancellationToken);
|
||||
|
||||
if (order == null)
|
||||
@@ -87,6 +91,26 @@ public class CancelOrderByAdminCommandHandler : IRequestHandler<CancelOrderByAdm
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// Restore inventory for each item (only if order was not yet delivered)
|
||||
if (order.FactorDetails != null && order.FactorDetails.Any())
|
||||
{
|
||||
foreach (var item in order.FactorDetails)
|
||||
{
|
||||
var restored = await _inventoryService.ProcessReturnAsync(
|
||||
item.ProductId, ProductType.RegularProduct,
|
||||
item.Count, order.Id,
|
||||
note: $"برگشت موجودی — لغو سفارش #{order.Id} توسط Admin",
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
if (!restored)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Failed to restore inventory for ProductId={ProductId} on cancel OrderId={OrderId}",
|
||||
item.ProductId, order.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Order cancelled by admin. OrderId: {OrderId}, Reason: {Reason}, Refunded: {Refunded}, Admin: {AdminId}",
|
||||
order.Id,
|
||||
|
||||
@@ -29,12 +29,14 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
private readonly ISender _sender;
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IInventoryService _inventoryService;
|
||||
|
||||
public UserOrderService(ISender sender, IApplicationDbContext context, ICurrentUserService currentUserService)
|
||||
public UserOrderService(ISender sender, IApplicationDbContext context, ICurrentUserService currentUserService, IInventoryService inventoryService)
|
||||
{
|
||||
_sender = sender;
|
||||
_context = context;
|
||||
_currentUserService = currentUserService;
|
||||
_inventoryService = inventoryService;
|
||||
}
|
||||
[RequiresPermission(PermissionNames.OrdersCreate)]
|
||||
public override async Task<CreateNewUserOrderResponse> CreateNewUserOrder(CreateNewUserOrderRequest request, ServerCallContext context)
|
||||
@@ -285,6 +287,18 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument,
|
||||
$"مبلغ نامعتبر است. محاسبه شده: {totalAmount}, دریافتی: {request.TotalAmount}"));
|
||||
}
|
||||
|
||||
// Validate stock availability for all cart items before any financial operation
|
||||
foreach (var cartItem in cartItems)
|
||||
{
|
||||
var available = await _inventoryService.GetAvailableQuantityAsync(
|
||||
cartItem.ProductId, ProductType.RegularProduct, ct: context.CancellationToken);
|
||||
if (available < cartItem.Count)
|
||||
{
|
||||
throw new RpcException(new Status(StatusCode.FailedPrecondition,
|
||||
$"موجودی «{cartItem.Product.Title}» کافی نیست. موجود: {available}، درخواستی: {cartItem.Count}"));
|
||||
}
|
||||
}
|
||||
|
||||
// Get user's wallet
|
||||
var wallet = await _context.UserWallets
|
||||
@@ -461,6 +475,18 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
|
||||
cartItem.IsDeleted = true;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
// Deduct inventory and update SaleCount for each purchased item
|
||||
foreach (var cartItem in cartItems)
|
||||
{
|
||||
await _inventoryService.ConfirmSaleAsync(
|
||||
cartItem.ProductId, ProductType.RegularProduct,
|
||||
cartItem.Count, order.Id, context.CancellationToken);
|
||||
|
||||
cartItem.Product.SaleCount += cartItem.Count;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(context.CancellationToken);
|
||||
|
||||
return new SubmitShopBuyOrderResponse
|
||||
|
||||
Reference in New Issue
Block a user