77 lines
3.1 KiB
C#
77 lines
3.1 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 库存释放处理器。
|
|
/// </summary>
|
|
public sealed class ReleaseInventoryCommandHandler(
|
|
IInventoryRepository inventoryRepository,
|
|
ITenantProvider tenantProvider,
|
|
ILogger<ReleaseInventoryCommandHandler> logger)
|
|
: IRequestHandler<ReleaseInventoryCommand, InventoryItemDto>
|
|
{
|
|
/// <inheritdoc />
|
|
public async Task<InventoryItemDto> 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);
|
|
}
|
|
}
|