feat: 新增规格做法模板管理接口与数据模型
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 45s

This commit is contained in:
2026-02-20 20:08:34 +08:00
parent 1b3525862a
commit 392d9f03a1
27 changed files with 10161 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
using TakeoutSaaS.Domain.Products.Enums;
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Products.Entities;
/// <summary>
/// 门店规格做法模板。
/// </summary>
public sealed class ProductSpecTemplate : MultiTenantEntityBase
{
/// <summary>
/// 所属门店。
/// </summary>
public long StoreId { get; set; }
/// <summary>
/// 模板名称。
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 模板类型。
/// </summary>
public ProductSpecTemplateType TemplateType { get; set; } = ProductSpecTemplateType.Spec;
/// <summary>
/// 选择方式。
/// </summary>
public AttributeSelectionType SelectionType { get; set; } = AttributeSelectionType.Single;
/// <summary>
/// 是否必选。
/// </summary>
public bool IsRequired { get; set; } = true;
/// <summary>
/// 排序值。
/// </summary>
public int SortOrder { get; set; } = 100;
/// <summary>
/// 是否启用。
/// </summary>
public bool IsEnabled { get; set; } = true;
}

View File

@@ -0,0 +1,29 @@
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Products.Entities;
/// <summary>
/// 规格做法模板选项。
/// </summary>
public sealed class ProductSpecTemplateOption : MultiTenantEntityBase
{
/// <summary>
/// 模板 ID。
/// </summary>
public long TemplateId { get; set; }
/// <summary>
/// 选项名称。
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 附加价格。
/// </summary>
public decimal ExtraPrice { get; set; }
/// <summary>
/// 排序值。
/// </summary>
public int SortOrder { get; set; } = 100;
}

View File

@@ -0,0 +1,24 @@
using TakeoutSaaS.Shared.Abstractions.Entities;
namespace TakeoutSaaS.Domain.Products.Entities;
/// <summary>
/// 规格做法模板与商品关联。
/// </summary>
public sealed class ProductSpecTemplateProduct : MultiTenantEntityBase
{
/// <summary>
/// 所属门店。
/// </summary>
public long StoreId { get; set; }
/// <summary>
/// 模板 ID。
/// </summary>
public long TemplateId { get; set; }
/// <summary>
/// 商品 ID。
/// </summary>
public long ProductId { get; set; }
}

View File

@@ -0,0 +1,17 @@
namespace TakeoutSaaS.Domain.Products.Enums;
/// <summary>
/// 规格做法模板类型。
/// </summary>
public enum ProductSpecTemplateType
{
/// <summary>
/// 规格模板。
/// </summary>
Spec = 0,
/// <summary>
/// 做法模板。
/// </summary>
Method = 1
}

View File

@@ -43,6 +43,36 @@ public interface IProductRepository
/// </summary>
Task<Dictionary<long, int>> CountProductsByCategoryIdsAsync(long tenantId, long storeId, IReadOnlyCollection<long> categoryIds, CancellationToken cancellationToken = default);
/// <summary>
/// 按门店读取规格做法模板。
/// </summary>
Task<IReadOnlyList<ProductSpecTemplate>> GetSpecTemplatesByStoreAsync(long tenantId, long storeId, CancellationToken cancellationToken = default);
/// <summary>
/// 依据标识读取规格做法模板。
/// </summary>
Task<ProductSpecTemplate?> FindSpecTemplateByIdAsync(long templateId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 判断门店内模板名称是否已存在。
/// </summary>
Task<bool> ExistsSpecTemplateNameAsync(long tenantId, long storeId, string name, long? excludeTemplateId = null, CancellationToken cancellationToken = default);
/// <summary>
/// 按模板读取规格做法选项。
/// </summary>
Task<IReadOnlyList<ProductSpecTemplateOption>> GetSpecTemplateOptionsByTemplateIdsAsync(IReadOnlyCollection<long> templateIds, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 按模板读取模板关联商品。
/// </summary>
Task<IReadOnlyList<ProductSpecTemplateProduct>> GetSpecTemplateProductsByTemplateIdsAsync(IReadOnlyCollection<long> templateIds, long tenantId, long storeId, CancellationToken cancellationToken = default);
/// <summary>
/// 过滤门店内真实存在的商品 ID。
/// </summary>
Task<IReadOnlyList<long>> FilterExistingProductIdsAsync(long tenantId, long storeId, IReadOnlyCollection<long> productIds, CancellationToken cancellationToken = default);
/// <summary>
/// 查询商品选择器列表。
/// </summary>
@@ -160,6 +190,21 @@ public interface IProductRepository
/// <returns>异步任务。</returns>
Task AddCategoryAsync(ProductCategory category, CancellationToken cancellationToken = default);
/// <summary>
/// 新增规格做法模板。
/// </summary>
Task AddSpecTemplateAsync(ProductSpecTemplate template, CancellationToken cancellationToken = default);
/// <summary>
/// 新增规格做法模板选项。
/// </summary>
Task AddSpecTemplateOptionsAsync(IEnumerable<ProductSpecTemplateOption> options, CancellationToken cancellationToken = default);
/// <summary>
/// 新增规格做法模板关联商品。
/// </summary>
Task AddSpecTemplateProductsAsync(IEnumerable<ProductSpecTemplateProduct> relations, CancellationToken cancellationToken = default);
/// <summary>
/// 新增商品。
/// </summary>
@@ -242,6 +287,11 @@ public interface IProductRepository
/// <returns>异步任务。</returns>
Task UpdateCategoryAsync(ProductCategory category, CancellationToken cancellationToken = default);
/// <summary>
/// 更新规格做法模板。
/// </summary>
Task UpdateSpecTemplateAsync(ProductSpecTemplate template, CancellationToken cancellationToken = default);
/// <summary>
/// 删除分类。
/// </summary>
@@ -251,6 +301,11 @@ public interface IProductRepository
/// <returns>异步任务。</returns>
Task DeleteCategoryAsync(long categoryId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 删除规格做法模板。
/// </summary>
Task DeleteSpecTemplateAsync(long templateId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 删除商品下的 SKU。
/// </summary>
@@ -260,6 +315,16 @@ public interface IProductRepository
/// <returns>异步任务。</returns>
Task RemoveSkusAsync(long productId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 删除模板下的规格做法选项。
/// </summary>
Task RemoveSpecTemplateOptionsAsync(long templateId, long tenantId, CancellationToken cancellationToken = default);
/// <summary>
/// 删除模板关联商品。
/// </summary>
Task RemoveSpecTemplateProductsAsync(long templateId, long tenantId, long storeId, CancellationToken cancellationToken = default);
/// <summary>
/// 删除商品下的加料组及选项。
/// </summary>