86 lines
3.3 KiB
C#
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;
|
|
}
|
|
}
|