feat: 完善库存锁定幂等与批次扣减策略
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user