Files
TakeoutSaaS.TenantApi/src/Application/TakeoutSaaS.Application/App/Inventory/Handlers/AdjustInventoryCommandHandler.cs

86 lines
3.3 KiB
C#

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;
/// <summary>
/// 库存调整处理器。
/// </summary>
public sealed class AdjustInventoryCommandHandler(
IInventoryRepository inventoryRepository,
ITenantProvider tenantProvider,
ILogger<AdjustInventoryCommandHandler> logger)
: IRequestHandler<AdjustInventoryCommand, InventoryItemDto>
{
/// <inheritdoc />
public async Task<InventoryItemDto> 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;
}
}