Merge branch 'kub-stage' into production
Build and Deploy to Production / build-and-deploy (push) Successful in 19m44s

This commit is contained in:
masoodafar-web
2026-05-18 00:12:13 +03:30
3 changed files with 55 additions and 5 deletions
@@ -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",
ct: 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,
@@ -64,7 +64,7 @@ public static class SystemConstants
/// مقدار ۱٬۰۰۰٬۰۰۰ ریال = ۱۰۰ هزار تومان
/// (Q24: چون قیمت محصولات متفاوت است، موجودی دقیقاً صفر نمی‌شود)
/// </summary>
public const long MagicWalletEntryThreshold = 1_000_000;
public const long MagicWalletEntryThreshold = 100_000;
/// <summary>
/// مقدار پیش‌فرض ضریب جادویی وقتی پکیج یافت نشود (بازگشت به پایه)
@@ -74,12 +74,12 @@ public static class SystemConstants
/// <summary>
/// مقدار پیش‌فرض حداکثر واریز جادویی وقتی پکیج یافت نشود (ریال)
/// </summary>
public const long MagicWalletDefaultMaxDeposit = 1_000_000_000;
public const long MagicWalletDefaultMaxDeposit = 100_000_000;
/// <summary>
/// مقدار پیش‌فرض حداکثر اعتبار جادویی وقتی پکیج یافت نشود (ریال)
/// </summary>
public const long MagicWalletDefaultMaxCredit = 2_500_000_000;
public const long MagicWalletDefaultMaxCredit = 250_000_000;
#endregion
@@ -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
@@ -338,7 +352,7 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
_context.UserWalletHistories.Add(walletLog);
// ═══ Magic Wallet: Entry / Exit Trigger ═══
// Q24: آستانه <= 1,000,000 ریال به جای == 0 (چون قیمت محصولات متفاوت است)
// Q24: آستانه <= 1,00,000 تومان به جای == 0 (چون قیمت محصولات متفاوت است)
if (wallet.Balance <= SystemConstants.MagicWalletEntryThreshold)
{
var user = await _context.Users
@@ -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