feat: 完善库存锁定幂等与批次扣减策略
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using TakeoutSaaS.Shared.Abstractions.Entities;
|
||||
|
||||
namespace TakeoutSaaS.Domain.Inventory.Entities;
|
||||
@@ -41,4 +43,10 @@ public sealed class InventoryBatch : MultiTenantEntityBase
|
||||
/// 剩余数量。
|
||||
/// </summary>
|
||||
public int RemainingQuantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 并发控制字段。
|
||||
/// </summary>
|
||||
[Timestamp]
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using TakeoutSaaS.Shared.Abstractions.Entities;
|
||||
using TakeoutSaaS.Domain.Inventory.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Domain.Inventory.Entities;
|
||||
|
||||
@@ -46,4 +49,50 @@ public sealed class InventoryItem : MultiTenantEntityBase
|
||||
/// 过期日期。
|
||||
/// </summary>
|
||||
public DateTime? ExpireDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否预售商品。
|
||||
/// </summary>
|
||||
public bool IsPresale { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 预售开始时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime? PresaleStartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 预售结束时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime? PresaleEndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 预售名额(上限)。
|
||||
/// </summary>
|
||||
public int? PresaleCapacity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前预售已锁定数量。
|
||||
/// </summary>
|
||||
public int PresaleLocked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 单品限购(覆盖商品级 MaxQuantityPerOrder)。
|
||||
/// </summary>
|
||||
public int? MaxQuantityPerOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否标记售罄。
|
||||
/// </summary>
|
||||
public bool IsSoldOut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 批次扣减策略。
|
||||
/// </summary>
|
||||
public InventoryBatchConsumeStrategy BatchConsumeStrategy { get; set; } = InventoryBatchConsumeStrategy.Fifo;
|
||||
|
||||
/// <summary>
|
||||
/// 并发控制字段。
|
||||
/// </summary>
|
||||
[Timestamp]
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using TakeoutSaaS.Domain.Inventory.Enums;
|
||||
using TakeoutSaaS.Shared.Abstractions.Entities;
|
||||
|
||||
namespace TakeoutSaaS.Domain.Inventory.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 库存锁定记录。
|
||||
/// </summary>
|
||||
public sealed class InventoryLockRecord : MultiTenantEntityBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SKU ID。
|
||||
/// </summary>
|
||||
public long ProductSkuId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 锁定数量。
|
||||
/// </summary>
|
||||
public int Quantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否预售锁定。
|
||||
/// </summary>
|
||||
public bool IsPresale { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 幂等键。
|
||||
/// </summary>
|
||||
public string IdempotencyKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 过期时间(UTC)。
|
||||
/// </summary>
|
||||
public DateTime? ExpiresAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 锁定状态。
|
||||
/// </summary>
|
||||
public InventoryLockStatus Status { get; set; } = InventoryLockStatus.Locked;
|
||||
|
||||
/// <summary>
|
||||
/// 并发控制字段。
|
||||
/// </summary>
|
||||
[Timestamp]
|
||||
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace TakeoutSaaS.Domain.Inventory.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// 批次扣减策略。
|
||||
/// </summary>
|
||||
public enum InventoryBatchConsumeStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 先进先出。
|
||||
/// </summary>
|
||||
Fifo = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 先到期先出。
|
||||
/// </summary>
|
||||
Fefo = 1
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace TakeoutSaaS.Domain.Inventory.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// 库存锁定状态。
|
||||
/// </summary>
|
||||
public enum InventoryLockStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 已锁定。
|
||||
/// </summary>
|
||||
Locked = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 已释放。
|
||||
/// </summary>
|
||||
Released = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 已扣减。
|
||||
/// </summary>
|
||||
Deducted = 2
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TakeoutSaaS.Domain.Inventory.Entities;
|
||||
using TakeoutSaaS.Domain.Inventory.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Domain.Inventory.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 库存仓储契约。
|
||||
/// </summary>
|
||||
public interface IInventoryRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 依据标识查询库存。
|
||||
/// </summary>
|
||||
Task<InventoryItem?> FindByIdAsync(long inventoryItemId, long tenantId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 按门店与 SKU 查询库存(只读)。
|
||||
/// </summary>
|
||||
Task<InventoryItem?> FindBySkuAsync(long tenantId, long storeId, long productSkuId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 按门店与 SKU 查询库存(跟踪用于更新)。
|
||||
/// </summary>
|
||||
Task<InventoryItem?> GetForUpdateAsync(long tenantId, long storeId, long productSkuId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增库存记录。
|
||||
/// </summary>
|
||||
Task AddItemAsync(InventoryItem item, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 更新库存记录。
|
||||
/// </summary>
|
||||
Task UpdateItemAsync(InventoryItem item, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增库存调整记录。
|
||||
/// </summary>
|
||||
Task AddAdjustmentAsync(InventoryAdjustment adjustment, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增锁定记录。
|
||||
/// </summary>
|
||||
Task AddLockAsync(InventoryLockRecord lockRecord, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 按幂等键查询锁记录。
|
||||
/// </summary>
|
||||
Task<InventoryLockRecord?> FindLockByKeyAsync(long tenantId, string idempotencyKey, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 更新锁状态。
|
||||
/// </summary>
|
||||
Task MarkLockStatusAsync(InventoryLockRecord lockRecord, InventoryLockStatus status, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 查询过期锁定。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<InventoryLockRecord>> FindExpiredLocksAsync(long tenantId, DateTime utcNow, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 查询批次列表。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<InventoryBatch>> GetBatchesAsync(long tenantId, long storeId, long productSkuId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 批次扣减读取(带排序策略)。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<InventoryBatch>> GetBatchesForConsumeAsync(long tenantId, long storeId, long productSkuId, InventoryBatchConsumeStrategy strategy, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 查询批次(跟踪用于更新)。
|
||||
/// </summary>
|
||||
Task<InventoryBatch?> GetBatchForUpdateAsync(long tenantId, long storeId, long productSkuId, string batchNumber, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 新增批次。
|
||||
/// </summary>
|
||||
Task AddBatchAsync(InventoryBatch batch, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 更新批次。
|
||||
/// </summary>
|
||||
Task UpdateBatchAsync(InventoryBatch batch, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 持久化变更。
|
||||
/// </summary>
|
||||
Task SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
Reference in New Issue
Block a user