using MediatR; using Microsoft.Extensions.Logging; using TakeoutSaaS.Application.App.Inventory.Commands; using TakeoutSaaS.Application.App.Inventory.Dto; using TakeoutSaaS.Domain.Inventory.Entities; 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 AdjustInventoryCommandHandler( IInventoryRepository inventoryRepository, ITenantProvider tenantProvider, ILogger logger) : IRequestHandler { /// public async Task Handle(AdjustInventoryCommand request, CancellationToken cancellationToken) { // 1. 读取库存 var tenantId = tenantProvider.GetCurrentTenantId(); var item = await inventoryRepository.GetForUpdateAsync(tenantId, request.StoreId, request.ProductSkuId, cancellationToken); // 2. 初始化或校验存在性 if (item is null) { if (request.QuantityDelta < 0) { throw new BusinessException(ErrorCodes.NotFound, "库存不存在,无法扣减"); } // 初始化库存记录 item = new InventoryItem { TenantId = tenantId, StoreId = request.StoreId, ProductSkuId = request.ProductSkuId, QuantityOnHand = request.QuantityDelta, QuantityReserved = 0, SafetyStock = request.SafetyStock, IsSoldOut = false }; await inventoryRepository.AddItemAsync(item, cancellationToken); } // 3. 应用调整 var newQuantity = item.QuantityOnHand + request.QuantityDelta; if (newQuantity < 0) { throw new BusinessException(ErrorCodes.Conflict, "库存不足,无法扣减"); } item.QuantityOnHand = newQuantity; item.SafetyStock = request.SafetyStock ?? item.SafetyStock; item.IsSoldOut = request.IsSoldOut ?? IsSoldOut(item); // 4. 写入调整记录 var adjustment = new InventoryAdjustment { TenantId = tenantId, InventoryItemId = item.Id, AdjustmentType = request.AdjustmentType, Quantity = request.QuantityDelta, Reason = request.Reason, OperatorId = null, OccurredAt = DateTime.UtcNow }; await inventoryRepository.AddAdjustmentAsync(adjustment, cancellationToken); await inventoryRepository.SaveChangesAsync(cancellationToken); logger.LogInformation("调整库存 SKU {ProductSkuId} 门店 {StoreId} 变更 {Delta}", request.ProductSkuId, request.StoreId, request.QuantityDelta); return InventoryMapping.ToDto(item); } // 辅助:售罄判定 private static bool IsSoldOut(InventoryItem item) { var available = item.QuantityOnHand - item.QuantityReserved - item.PresaleLocked; var safety = item.SafetyStock ?? 0; return available <= safety || item.QuantityOnHand <= 0; } }