feat: 商品模块移除租户上下文依赖
This commit is contained in:
@@ -4,24 +4,40 @@ using TakeoutSaaS.Application.App.Products.Commands;
|
|||||||
using TakeoutSaaS.Application.App.Products.Dto;
|
using TakeoutSaaS.Application.App.Products.Dto;
|
||||||
using TakeoutSaaS.Domain.Products.Entities;
|
using TakeoutSaaS.Domain.Products.Entities;
|
||||||
using TakeoutSaaS.Domain.Products.Repositories;
|
using TakeoutSaaS.Domain.Products.Repositories;
|
||||||
|
using TakeoutSaaS.Domain.Stores.Repositories;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建商品命令处理器。
|
/// 创建商品命令处理器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class CreateProductCommandHandler(IProductRepository productRepository, ILogger<CreateProductCommandHandler> logger)
|
public sealed class CreateProductCommandHandler(
|
||||||
|
IProductRepository productRepository,
|
||||||
|
IStoreRepository storeRepository,
|
||||||
|
ILogger<CreateProductCommandHandler> logger)
|
||||||
: IRequestHandler<CreateProductCommand, ProductDto>
|
: IRequestHandler<CreateProductCommand, ProductDto>
|
||||||
{
|
{
|
||||||
private readonly IProductRepository _productRepository = productRepository;
|
private readonly IProductRepository _productRepository = productRepository;
|
||||||
|
private readonly IStoreRepository _storeRepository = storeRepository;
|
||||||
private readonly ILogger<CreateProductCommandHandler> _logger = logger;
|
private readonly ILogger<CreateProductCommandHandler> _logger = logger;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<ProductDto> Handle(CreateProductCommand request, CancellationToken cancellationToken)
|
public async Task<ProductDto> Handle(CreateProductCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 构建实体
|
// 1. 校验门店存在并解析租户
|
||||||
|
var store = await _storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
|
||||||
|
if (store is null)
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
|
||||||
|
}
|
||||||
|
var tenantId = store.TenantId;
|
||||||
|
|
||||||
|
// 2. (空行后) 构建实体并写入租户
|
||||||
var product = new Product
|
var product = new Product
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
StoreId = request.StoreId,
|
StoreId = request.StoreId,
|
||||||
CategoryId = request.CategoryId,
|
CategoryId = request.CategoryId,
|
||||||
SpuCode = request.SpuCode.Trim(),
|
SpuCode = request.SpuCode.Trim(),
|
||||||
@@ -42,12 +58,12 @@ public sealed class CreateProductCommandHandler(IProductRepository productReposi
|
|||||||
IsFeatured = request.IsFeatured
|
IsFeatured = request.IsFeatured
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. 持久化
|
// 3. (空行后) 持久化
|
||||||
await _productRepository.AddProductAsync(product, cancellationToken);
|
await _productRepository.AddProductAsync(product, cancellationToken);
|
||||||
await _productRepository.SaveChangesAsync(cancellationToken);
|
await _productRepository.SaveChangesAsync(cancellationToken);
|
||||||
_logger.LogInformation("创建商品 {ProductId} - {ProductName}", product.Id, product.Name);
|
_logger.LogInformation("创建商品 {ProductId} - {ProductName}", product.Id, product.Name);
|
||||||
|
|
||||||
// 3. 返回 DTO
|
// 4. (空行后) 返回 DTO
|
||||||
return MapToDto(product);
|
return MapToDto(product);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using MediatR;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using TakeoutSaaS.Application.App.Products.Commands;
|
using TakeoutSaaS.Application.App.Products.Commands;
|
||||||
using TakeoutSaaS.Domain.Products.Repositories;
|
using TakeoutSaaS.Domain.Products.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||||
|
|
||||||
@@ -11,27 +10,24 @@ namespace TakeoutSaaS.Application.App.Products.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class DeleteProductCommandHandler(
|
public sealed class DeleteProductCommandHandler(
|
||||||
IProductRepository productRepository,
|
IProductRepository productRepository,
|
||||||
ITenantProvider tenantProvider,
|
|
||||||
ILogger<DeleteProductCommandHandler> logger)
|
ILogger<DeleteProductCommandHandler> logger)
|
||||||
: IRequestHandler<DeleteProductCommand, bool>
|
: IRequestHandler<DeleteProductCommand, bool>
|
||||||
{
|
{
|
||||||
private readonly IProductRepository _productRepository = productRepository;
|
private readonly IProductRepository _productRepository = productRepository;
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
|
||||||
private readonly ILogger<DeleteProductCommandHandler> _logger = logger;
|
private readonly ILogger<DeleteProductCommandHandler> _logger = logger;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<bool> Handle(DeleteProductCommand request, CancellationToken cancellationToken)
|
public async Task<bool> Handle(DeleteProductCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验存在性
|
// 1. 校验存在性(跨租户)
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
var existing = await _productRepository.FindByIdAsync(request.ProductId, cancellationToken);
|
||||||
var existing = await _productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 删除
|
// 2. (空行后) 删除
|
||||||
await _productRepository.DeleteProductAsync(request.ProductId, tenantId, cancellationToken);
|
await _productRepository.DeleteProductAsync(request.ProductId, existing.TenantId, cancellationToken);
|
||||||
await _productRepository.SaveChangesAsync(cancellationToken);
|
await _productRepository.SaveChangesAsync(cancellationToken);
|
||||||
_logger.LogInformation("删除商品 {ProductId}", request.ProductId);
|
_logger.LogInformation("删除商品 {ProductId}", request.ProductId);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ using TakeoutSaaS.Application.App.Products.Queries;
|
|||||||
using TakeoutSaaS.Domain.Products.Entities;
|
using TakeoutSaaS.Domain.Products.Entities;
|
||||||
using TakeoutSaaS.Domain.Products.Enums;
|
using TakeoutSaaS.Domain.Products.Enums;
|
||||||
using TakeoutSaaS.Domain.Products.Repositories;
|
using TakeoutSaaS.Domain.Products.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
using TakeoutSaaS.Domain.Stores.Repositories;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||||
|
|
||||||
@@ -18,15 +20,20 @@ namespace TakeoutSaaS.Application.App.Products.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class GetStoreMenuQueryHandler(
|
public sealed class GetStoreMenuQueryHandler(
|
||||||
IProductRepository productRepository,
|
IProductRepository productRepository,
|
||||||
ITenantProvider tenantProvider,
|
IStoreRepository storeRepository,
|
||||||
ILogger<GetStoreMenuQueryHandler> logger)
|
ILogger<GetStoreMenuQueryHandler> logger)
|
||||||
: IRequestHandler<GetStoreMenuQuery, StoreMenuDto>
|
: IRequestHandler<GetStoreMenuQuery, StoreMenuDto>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<StoreMenuDto> Handle(GetStoreMenuQuery request, CancellationToken cancellationToken)
|
public async Task<StoreMenuDto> Handle(GetStoreMenuQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 准备上下文
|
// 1. 校验门店存在并解析租户
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
|
||||||
|
if (store is null)
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
|
||||||
|
}
|
||||||
|
var tenantId = store.TenantId;
|
||||||
var updatedAfterUtc = request.UpdatedAfter?.ToUniversalTime();
|
var updatedAfterUtc = request.UpdatedAfter?.ToUniversalTime();
|
||||||
// 2. 获取分类
|
// 2. 获取分类
|
||||||
var categories = await productRepository.GetCategoriesByStoreAsync(tenantId, request.StoreId, true, cancellationToken);
|
var categories = await productRepository.GetCategoriesByStoreAsync(tenantId, request.StoreId, true, cancellationToken);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using TakeoutSaaS.Domain.Products.Enums;
|
|||||||
using TakeoutSaaS.Domain.Products.Repositories;
|
using TakeoutSaaS.Domain.Products.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||||
|
|
||||||
@@ -16,29 +15,28 @@ namespace TakeoutSaaS.Application.App.Products.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PublishProductCommandHandler(
|
public sealed class PublishProductCommandHandler(
|
||||||
IProductRepository productRepository,
|
IProductRepository productRepository,
|
||||||
ITenantProvider tenantProvider,
|
|
||||||
ILogger<PublishProductCommandHandler> logger)
|
ILogger<PublishProductCommandHandler> logger)
|
||||||
: IRequestHandler<PublishProductCommand, ProductDto?>
|
: IRequestHandler<PublishProductCommand, ProductDto?>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<ProductDto?> Handle(PublishProductCommand request, CancellationToken cancellationToken)
|
public async Task<ProductDto?> Handle(PublishProductCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 读取商品
|
// 1. 读取商品(跨租户)
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var product = await productRepository.FindByIdAsync(request.ProductId, cancellationToken);
|
||||||
var product = await productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
|
||||||
if (product is null)
|
if (product is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
var tenantId = product.TenantId;
|
||||||
|
|
||||||
// 2. 校验 SKU 可售
|
// 2. (空行后) 校验 SKU 可售
|
||||||
var skus = await productRepository.GetSkusAsync(product.Id, tenantId, cancellationToken);
|
var skus = await productRepository.GetSkusAsync(product.Id, tenantId, cancellationToken);
|
||||||
if (skus.Count == 0)
|
if (skus.Count == 0)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.Conflict, "请先配置可售 SKU 后再上架");
|
throw new BusinessException(ErrorCodes.Conflict, "请先配置可售 SKU 后再上架");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 上架
|
// 3. (空行后) 上架
|
||||||
product.Status = ProductStatus.OnSale;
|
product.Status = ProductStatus.OnSale;
|
||||||
await productRepository.UpdateProductAsync(product, cancellationToken);
|
await productRepository.UpdateProductAsync(product, cancellationToken);
|
||||||
await productRepository.SaveChangesAsync(cancellationToken);
|
await productRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Products.Entities;
|
|||||||
using TakeoutSaaS.Domain.Products.Repositories;
|
using TakeoutSaaS.Domain.Products.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||||
|
|
||||||
@@ -15,33 +14,33 @@ namespace TakeoutSaaS.Application.App.Products.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ReplaceProductAddonsCommandHandler(
|
public sealed class ReplaceProductAddonsCommandHandler(
|
||||||
IProductRepository productRepository,
|
IProductRepository productRepository,
|
||||||
ITenantProvider tenantProvider,
|
|
||||||
ILogger<ReplaceProductAddonsCommandHandler> logger)
|
ILogger<ReplaceProductAddonsCommandHandler> logger)
|
||||||
: IRequestHandler<ReplaceProductAddonsCommand, IReadOnlyList<ProductAddonGroupDto>>
|
: IRequestHandler<ReplaceProductAddonsCommand, IReadOnlyList<ProductAddonGroupDto>>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<ProductAddonGroupDto>> Handle(ReplaceProductAddonsCommand request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<ProductAddonGroupDto>> Handle(ReplaceProductAddonsCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验商品
|
// 1. 校验商品(跨租户)
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var product = await productRepository.FindByIdAsync(request.ProductId, cancellationToken);
|
||||||
var product = await productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
|
||||||
if (product is null)
|
if (product is null)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.NotFound, "商品不存在");
|
throw new BusinessException(ErrorCodes.NotFound, "商品不存在");
|
||||||
}
|
}
|
||||||
|
var tenantId = product.TenantId;
|
||||||
|
|
||||||
// 2. 校验组名唯一
|
// 2. (空行后) 校验组名唯一
|
||||||
var names = request.AddonGroups.Select(x => x.Name.Trim()).ToList();
|
var names = request.AddonGroups.Select(x => x.Name.Trim()).ToList();
|
||||||
if (names.Count != names.Distinct(StringComparer.OrdinalIgnoreCase).Count())
|
if (names.Count != names.Distinct(StringComparer.OrdinalIgnoreCase).Count())
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.Conflict, "加料组名称重复");
|
throw new BusinessException(ErrorCodes.Conflict, "加料组名称重复");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 替换
|
// 3. (空行后) 替换
|
||||||
await productRepository.RemoveAddonGroupsAsync(request.ProductId, tenantId, cancellationToken);
|
await productRepository.RemoveAddonGroupsAsync(request.ProductId, tenantId, cancellationToken);
|
||||||
// 重新插入组
|
// 重新插入组
|
||||||
var groupEntities = request.AddonGroups.Select(g => new ProductAddonGroup
|
var groupEntities = request.AddonGroups.Select(g => new ProductAddonGroup
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
ProductId = request.ProductId,
|
ProductId = request.ProductId,
|
||||||
Name = g.Name.Trim(),
|
Name = g.Name.Trim(),
|
||||||
MinSelect = g.MinSelect,
|
MinSelect = g.MinSelect,
|
||||||
@@ -57,6 +56,7 @@ public sealed class ReplaceProductAddonsCommandHandler(
|
|||||||
var optionEntities = request.AddonGroups
|
var optionEntities = request.AddonGroups
|
||||||
.SelectMany(dto => dto.Options.Select(o => new ProductAddonOption
|
.SelectMany(dto => dto.Options.Select(o => new ProductAddonOption
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
AddonGroupId = groupIdLookup[dto],
|
AddonGroupId = groupIdLookup[dto],
|
||||||
Name = o.Name.Trim(),
|
Name = o.Name.Trim(),
|
||||||
ExtraPrice = o.ExtraPrice,
|
ExtraPrice = o.ExtraPrice,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Products.Entities;
|
|||||||
using TakeoutSaaS.Domain.Products.Repositories;
|
using TakeoutSaaS.Domain.Products.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||||
|
|
||||||
@@ -15,33 +14,33 @@ namespace TakeoutSaaS.Application.App.Products.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ReplaceProductAttributesCommandHandler(
|
public sealed class ReplaceProductAttributesCommandHandler(
|
||||||
IProductRepository productRepository,
|
IProductRepository productRepository,
|
||||||
ITenantProvider tenantProvider,
|
|
||||||
ILogger<ReplaceProductAttributesCommandHandler> logger)
|
ILogger<ReplaceProductAttributesCommandHandler> logger)
|
||||||
: IRequestHandler<ReplaceProductAttributesCommand, IReadOnlyList<ProductAttributeGroupDto>>
|
: IRequestHandler<ReplaceProductAttributesCommand, IReadOnlyList<ProductAttributeGroupDto>>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<ProductAttributeGroupDto>> Handle(ReplaceProductAttributesCommand request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<ProductAttributeGroupDto>> Handle(ReplaceProductAttributesCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验商品
|
// 1. 校验商品(跨租户)
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var product = await productRepository.FindByIdAsync(request.ProductId, cancellationToken);
|
||||||
var product = await productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
|
||||||
if (product is null)
|
if (product is null)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.NotFound, "商品不存在");
|
throw new BusinessException(ErrorCodes.NotFound, "商品不存在");
|
||||||
}
|
}
|
||||||
|
var tenantId = product.TenantId;
|
||||||
|
|
||||||
// 2. 组名唯一
|
// 2. (空行后) 组名唯一
|
||||||
var groupNames = request.AttributeGroups.Select(x => x.Name.Trim()).ToList();
|
var groupNames = request.AttributeGroups.Select(x => x.Name.Trim()).ToList();
|
||||||
if (groupNames.Count != groupNames.Distinct(StringComparer.OrdinalIgnoreCase).Count())
|
if (groupNames.Count != groupNames.Distinct(StringComparer.OrdinalIgnoreCase).Count())
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.Conflict, "规格组名称重复");
|
throw new BusinessException(ErrorCodes.Conflict, "规格组名称重复");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 替换
|
// 3. (空行后) 替换
|
||||||
await productRepository.RemoveAttributeGroupsAsync(request.ProductId, tenantId, cancellationToken);
|
await productRepository.RemoveAttributeGroupsAsync(request.ProductId, tenantId, cancellationToken);
|
||||||
|
|
||||||
var groupEntities = request.AttributeGroups.Select(g => new ProductAttributeGroup
|
var groupEntities = request.AttributeGroups.Select(g => new ProductAttributeGroup
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
ProductId = request.ProductId,
|
ProductId = request.ProductId,
|
||||||
Name = g.Name.Trim(),
|
Name = g.Name.Trim(),
|
||||||
SelectionType = (Domain.Products.Enums.AttributeSelectionType)g.SelectionType,
|
SelectionType = (Domain.Products.Enums.AttributeSelectionType)g.SelectionType,
|
||||||
@@ -59,6 +58,7 @@ public sealed class ReplaceProductAttributesCommandHandler(
|
|||||||
var optionEntities = request.AttributeGroups
|
var optionEntities = request.AttributeGroups
|
||||||
.SelectMany(dto => dto.Options.Select(o => new ProductAttributeOption
|
.SelectMany(dto => dto.Options.Select(o => new ProductAttributeOption
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
AttributeGroupId = groupIdLookup[dto],
|
AttributeGroupId = groupIdLookup[dto],
|
||||||
Name = o.Name.Trim(),
|
Name = o.Name.Trim(),
|
||||||
SortOrder = o.SortOrder
|
SortOrder = o.SortOrder
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Products.Entities;
|
|||||||
using TakeoutSaaS.Domain.Products.Repositories;
|
using TakeoutSaaS.Domain.Products.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||||
|
|
||||||
@@ -15,26 +14,26 @@ namespace TakeoutSaaS.Application.App.Products.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ReplaceProductMediaCommandHandler(
|
public sealed class ReplaceProductMediaCommandHandler(
|
||||||
IProductRepository productRepository,
|
IProductRepository productRepository,
|
||||||
ITenantProvider tenantProvider,
|
|
||||||
ILogger<ReplaceProductMediaCommandHandler> logger)
|
ILogger<ReplaceProductMediaCommandHandler> logger)
|
||||||
: IRequestHandler<ReplaceProductMediaCommand, IReadOnlyList<ProductMediaAssetDto>>
|
: IRequestHandler<ReplaceProductMediaCommand, IReadOnlyList<ProductMediaAssetDto>>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<ProductMediaAssetDto>> Handle(ReplaceProductMediaCommand request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<ProductMediaAssetDto>> Handle(ReplaceProductMediaCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验商品
|
// 1. 校验商品(跨租户)
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var product = await productRepository.FindByIdAsync(request.ProductId, cancellationToken);
|
||||||
var product = await productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
|
||||||
if (product is null)
|
if (product is null)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.NotFound, "商品不存在");
|
throw new BusinessException(ErrorCodes.NotFound, "商品不存在");
|
||||||
}
|
}
|
||||||
|
var tenantId = product.TenantId;
|
||||||
|
|
||||||
// 2. 替换
|
// 2. (空行后) 替换
|
||||||
await productRepository.RemoveMediaAssetsAsync(request.ProductId, tenantId, cancellationToken);
|
await productRepository.RemoveMediaAssetsAsync(request.ProductId, tenantId, cancellationToken);
|
||||||
|
|
||||||
var assets = request.MediaAssets.Select(a => new ProductMediaAsset
|
var assets = request.MediaAssets.Select(a => new ProductMediaAsset
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
ProductId = request.ProductId,
|
ProductId = request.ProductId,
|
||||||
MediaType = a.MediaType,
|
MediaType = a.MediaType,
|
||||||
Url = a.Url.Trim(),
|
Url = a.Url.Trim(),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Products.Entities;
|
|||||||
using TakeoutSaaS.Domain.Products.Repositories;
|
using TakeoutSaaS.Domain.Products.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||||
|
|
||||||
@@ -15,26 +14,26 @@ namespace TakeoutSaaS.Application.App.Products.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ReplaceProductPricingRulesCommandHandler(
|
public sealed class ReplaceProductPricingRulesCommandHandler(
|
||||||
IProductRepository productRepository,
|
IProductRepository productRepository,
|
||||||
ITenantProvider tenantProvider,
|
|
||||||
ILogger<ReplaceProductPricingRulesCommandHandler> logger)
|
ILogger<ReplaceProductPricingRulesCommandHandler> logger)
|
||||||
: IRequestHandler<ReplaceProductPricingRulesCommand, IReadOnlyList<ProductPricingRuleDto>>
|
: IRequestHandler<ReplaceProductPricingRulesCommand, IReadOnlyList<ProductPricingRuleDto>>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<ProductPricingRuleDto>> Handle(ReplaceProductPricingRulesCommand request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<ProductPricingRuleDto>> Handle(ReplaceProductPricingRulesCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验商品
|
// 1. 校验商品(跨租户)
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var product = await productRepository.FindByIdAsync(request.ProductId, cancellationToken);
|
||||||
var product = await productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
|
||||||
if (product is null)
|
if (product is null)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.NotFound, "商品不存在");
|
throw new BusinessException(ErrorCodes.NotFound, "商品不存在");
|
||||||
}
|
}
|
||||||
|
var tenantId = product.TenantId;
|
||||||
|
|
||||||
// 2. 替换
|
// 2. (空行后) 替换
|
||||||
await productRepository.RemovePricingRulesAsync(request.ProductId, tenantId, cancellationToken);
|
await productRepository.RemovePricingRulesAsync(request.ProductId, tenantId, cancellationToken);
|
||||||
|
|
||||||
var rules = request.PricingRules.Select(r => new ProductPricingRule
|
var rules = request.PricingRules.Select(r => new ProductPricingRule
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
ProductId = request.ProductId,
|
ProductId = request.ProductId,
|
||||||
RuleType = r.RuleType,
|
RuleType = r.RuleType,
|
||||||
ConditionsJson = r.ConditionsJson.Trim(),
|
ConditionsJson = r.ConditionsJson.Trim(),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using TakeoutSaaS.Domain.Products.Entities;
|
|||||||
using TakeoutSaaS.Domain.Products.Repositories;
|
using TakeoutSaaS.Domain.Products.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Constants;
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||||
|
|
||||||
@@ -15,32 +14,32 @@ namespace TakeoutSaaS.Application.App.Products.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class ReplaceProductSkusCommandHandler(
|
public sealed class ReplaceProductSkusCommandHandler(
|
||||||
IProductRepository productRepository,
|
IProductRepository productRepository,
|
||||||
ITenantProvider tenantProvider,
|
|
||||||
ILogger<ReplaceProductSkusCommandHandler> logger)
|
ILogger<ReplaceProductSkusCommandHandler> logger)
|
||||||
: IRequestHandler<ReplaceProductSkusCommand, IReadOnlyList<ProductSkuDto>>
|
: IRequestHandler<ReplaceProductSkusCommand, IReadOnlyList<ProductSkuDto>>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<ProductSkuDto>> Handle(ReplaceProductSkusCommand request, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<ProductSkuDto>> Handle(ReplaceProductSkusCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 校验商品存在
|
// 1. 校验商品存在(跨租户)
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var product = await productRepository.FindByIdAsync(request.ProductId, cancellationToken);
|
||||||
var product = await productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
|
||||||
if (product is null)
|
if (product is null)
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.NotFound, "商品不存在");
|
throw new BusinessException(ErrorCodes.NotFound, "商品不存在");
|
||||||
}
|
}
|
||||||
|
var tenantId = product.TenantId;
|
||||||
|
|
||||||
// 2. 校验 SKU 唯一性
|
// 2. (空行后) 校验 SKU 唯一性
|
||||||
var codes = request.Skus.Select(x => x.SkuCode.Trim()).ToList();
|
var codes = request.Skus.Select(x => x.SkuCode.Trim()).ToList();
|
||||||
if (codes.Count != codes.Distinct(StringComparer.OrdinalIgnoreCase).Count())
|
if (codes.Count != codes.Distinct(StringComparer.OrdinalIgnoreCase).Count())
|
||||||
{
|
{
|
||||||
throw new BusinessException(ErrorCodes.Conflict, "SKU 编码重复");
|
throw new BusinessException(ErrorCodes.Conflict, "SKU 编码重复");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 替换
|
// 3. (空行后) 替换
|
||||||
await productRepository.RemoveSkusAsync(request.ProductId, tenantId, cancellationToken);
|
await productRepository.RemoveSkusAsync(request.ProductId, tenantId, cancellationToken);
|
||||||
var entities = request.Skus.Select(x => new ProductSku
|
var entities = request.Skus.Select(x => new ProductSku
|
||||||
{
|
{
|
||||||
|
TenantId = tenantId,
|
||||||
ProductId = request.ProductId,
|
ProductId = request.ProductId,
|
||||||
SkuCode = x.SkuCode.Trim(),
|
SkuCode = x.SkuCode.Trim(),
|
||||||
Barcode = x.Barcode?.Trim(),
|
Barcode = x.Barcode?.Trim(),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using TakeoutSaaS.Application.App.Products.Commands;
|
|||||||
using TakeoutSaaS.Application.App.Products.Dto;
|
using TakeoutSaaS.Application.App.Products.Dto;
|
||||||
using TakeoutSaaS.Domain.Products.Enums;
|
using TakeoutSaaS.Domain.Products.Enums;
|
||||||
using TakeoutSaaS.Domain.Products.Repositories;
|
using TakeoutSaaS.Domain.Products.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||||
|
|
||||||
@@ -13,22 +12,20 @@ namespace TakeoutSaaS.Application.App.Products.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class UnpublishProductCommandHandler(
|
public sealed class UnpublishProductCommandHandler(
|
||||||
IProductRepository productRepository,
|
IProductRepository productRepository,
|
||||||
ITenantProvider tenantProvider,
|
|
||||||
ILogger<UnpublishProductCommandHandler> logger)
|
ILogger<UnpublishProductCommandHandler> logger)
|
||||||
: IRequestHandler<UnpublishProductCommand, ProductDto?>
|
: IRequestHandler<UnpublishProductCommand, ProductDto?>
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<ProductDto?> Handle(UnpublishProductCommand request, CancellationToken cancellationToken)
|
public async Task<ProductDto?> Handle(UnpublishProductCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 读取商品
|
// 1. 读取商品(跨租户)
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
var product = await productRepository.FindByIdAsync(request.ProductId, cancellationToken);
|
||||||
var product = await productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
|
||||||
if (product is null)
|
if (product is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 下架
|
// 2. (空行后) 下架
|
||||||
product.Status = ProductStatus.OffShelf;
|
product.Status = ProductStatus.OffShelf;
|
||||||
await productRepository.UpdateProductAsync(product, cancellationToken);
|
await productRepository.UpdateProductAsync(product, cancellationToken);
|
||||||
await productRepository.SaveChangesAsync(cancellationToken);
|
await productRepository.SaveChangesAsync(cancellationToken);
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ using TakeoutSaaS.Application.App.Products.Commands;
|
|||||||
using TakeoutSaaS.Application.App.Products.Dto;
|
using TakeoutSaaS.Application.App.Products.Dto;
|
||||||
using TakeoutSaaS.Domain.Products.Entities;
|
using TakeoutSaaS.Domain.Products.Entities;
|
||||||
using TakeoutSaaS.Domain.Products.Repositories;
|
using TakeoutSaaS.Domain.Products.Repositories;
|
||||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
using TakeoutSaaS.Domain.Stores.Repositories;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Constants;
|
||||||
|
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||||
|
|
||||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||||
|
|
||||||
@@ -13,26 +15,37 @@ namespace TakeoutSaaS.Application.App.Products.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class UpdateProductCommandHandler(
|
public sealed class UpdateProductCommandHandler(
|
||||||
IProductRepository productRepository,
|
IProductRepository productRepository,
|
||||||
ITenantProvider tenantProvider,
|
IStoreRepository storeRepository,
|
||||||
ILogger<UpdateProductCommandHandler> logger)
|
ILogger<UpdateProductCommandHandler> logger)
|
||||||
: IRequestHandler<UpdateProductCommand, ProductDto?>
|
: IRequestHandler<UpdateProductCommand, ProductDto?>
|
||||||
{
|
{
|
||||||
private readonly IProductRepository _productRepository = productRepository;
|
private readonly IProductRepository _productRepository = productRepository;
|
||||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
private readonly IStoreRepository _storeRepository = storeRepository;
|
||||||
private readonly ILogger<UpdateProductCommandHandler> _logger = logger;
|
private readonly ILogger<UpdateProductCommandHandler> _logger = logger;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<ProductDto?> Handle(UpdateProductCommand request, CancellationToken cancellationToken)
|
public async Task<ProductDto?> Handle(UpdateProductCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// 1. 读取商品
|
// 1. 读取商品(跨租户)
|
||||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
var existing = await _productRepository.FindByIdAsync(request.ProductId, cancellationToken);
|
||||||
var existing = await _productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
|
||||||
if (existing == null)
|
if (existing == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
var tenantId = existing.TenantId;
|
||||||
|
|
||||||
// 2. 更新字段
|
// 2. (空行后) 校验门店存在且属于同租户
|
||||||
|
var store = await _storeRepository.FindByIdAsync(request.StoreId, tenantId: null, cancellationToken);
|
||||||
|
if (store is null)
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
|
||||||
|
}
|
||||||
|
if (store.TenantId != tenantId)
|
||||||
|
{
|
||||||
|
throw new BusinessException(ErrorCodes.ValidationFailed, "门店与商品不属于同一租户");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. (空行后) 更新字段
|
||||||
existing.StoreId = request.StoreId;
|
existing.StoreId = request.StoreId;
|
||||||
existing.CategoryId = request.CategoryId;
|
existing.CategoryId = request.CategoryId;
|
||||||
existing.SpuCode = request.SpuCode.Trim();
|
existing.SpuCode = request.SpuCode.Trim();
|
||||||
@@ -52,12 +65,12 @@ public sealed class UpdateProductCommandHandler(
|
|||||||
existing.EnableDelivery = request.EnableDelivery;
|
existing.EnableDelivery = request.EnableDelivery;
|
||||||
existing.IsFeatured = request.IsFeatured;
|
existing.IsFeatured = request.IsFeatured;
|
||||||
|
|
||||||
// 3. 持久化
|
// 4. (空行后) 持久化
|
||||||
await _productRepository.UpdateProductAsync(existing, cancellationToken);
|
await _productRepository.UpdateProductAsync(existing, cancellationToken);
|
||||||
await _productRepository.SaveChangesAsync(cancellationToken);
|
await _productRepository.SaveChangesAsync(cancellationToken);
|
||||||
_logger.LogInformation("更新商品 {ProductId} - {ProductName}", existing.Id, existing.Name);
|
_logger.LogInformation("更新商品 {ProductId} - {ProductName}", existing.Id, existing.Name);
|
||||||
|
|
||||||
// 4. 返回 DTO
|
// 5. (空行后) 返回 DTO
|
||||||
return MapToDto(existing);
|
return MapToDto(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user