using MediatR; using Microsoft.Extensions.Logging; using TakeoutSaaS.Application.App.Inventory.Commands; using TakeoutSaaS.Application.App.Inventory.Dto; using TakeoutSaaS.Domain.Inventory.Repositories; using TakeoutSaaS.Shared.Abstractions.Constants; using TakeoutSaaS.Shared.Abstractions.Exceptions; using TakeoutSaaS.Shared.Abstractions.Tenancy; namespace TakeoutSaaS.Application.App.Inventory.Handlers; /// /// 库存释放处理器。 /// public sealed class ReleaseInventoryCommandHandler( IInventoryRepository inventoryRepository, ITenantProvider tenantProvider, ILogger logger) : IRequestHandler { /// public async Task Handle(ReleaseInventoryCommand request, CancellationToken cancellationToken) { // 1. 读取库存 var tenantId = tenantProvider.GetCurrentTenantId(); var item = await inventoryRepository.GetForUpdateAsync(tenantId, request.StoreId, request.ProductSkuId, cancellationToken); if (item is null) { throw new BusinessException(ErrorCodes.NotFound, "库存不存在"); } // 1.1 幂等处理:若提供键且锁记录不存在,直接视为已释放 if (!string.IsNullOrWhiteSpace(request.IdempotencyKey)) { var lockRecord = await inventoryRepository.FindLockByKeyAsync(tenantId, request.IdempotencyKey, cancellationToken); if (lockRecord is not null) { if (lockRecord.Status != Domain.Inventory.Enums.InventoryLockStatus.Locked) { return InventoryMapping.ToDto(item); } // 将数量同步为锁记录数,避免重复释放不一致 request = request with { Quantity = lockRecord.Quantity }; await inventoryRepository.MarkLockStatusAsync(lockRecord, Domain.Inventory.Enums.InventoryLockStatus.Released, cancellationToken); } } // 2. 计算释放 var isPresale = request.IsPresaleOrder || item.IsPresale; if (isPresale) { if (item.PresaleLocked < request.Quantity) { throw new BusinessException(ErrorCodes.Conflict, "预售锁定不足"); } item.PresaleLocked -= request.Quantity; } else { if (item.QuantityReserved < request.Quantity) { throw new BusinessException(ErrorCodes.Conflict, "锁定库存不足"); } item.QuantityReserved -= request.Quantity; } item.IsSoldOut = item.QuantityOnHand - item.QuantityReserved - item.PresaleLocked <= (item.SafetyStock ?? 0); await inventoryRepository.UpdateItemAsync(item, cancellationToken); await inventoryRepository.SaveChangesAsync(cancellationToken); logger.LogInformation("释放库存 门店 {StoreId} SKU {ProductSkuId} 数量 {Quantity}", request.StoreId, request.ProductSkuId, request.Quantity); return InventoryMapping.ToDto(item); } }