feat: 管理端核心实体CRUD补齐
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Products.Dto;
|
||||
using TakeoutSaaS.Domain.Products.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Products.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 创建商品命令。
|
||||
/// </summary>
|
||||
public sealed class CreateProductCommand : IRequest<ProductDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类 ID。
|
||||
/// </summary>
|
||||
public long CategoryId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品编码。
|
||||
/// </summary>
|
||||
public string SpuCode { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 名称。
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 副标题。
|
||||
/// </summary>
|
||||
public string? Subtitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 单位。
|
||||
/// </summary>
|
||||
public string? Unit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 现价。
|
||||
/// </summary>
|
||||
public decimal Price { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原价。
|
||||
/// </summary>
|
||||
public decimal? OriginalPrice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 库存数量。
|
||||
/// </summary>
|
||||
public int? StockQuantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每单限购。
|
||||
/// </summary>
|
||||
public int? MaxQuantityPerOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态。
|
||||
/// </summary>
|
||||
public ProductStatus Status { get; set; } = ProductStatus.Draft;
|
||||
|
||||
/// <summary>
|
||||
/// 主图。
|
||||
/// </summary>
|
||||
public string? CoverImage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图集。
|
||||
/// </summary>
|
||||
public string? GalleryImages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述。
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支持堂食。
|
||||
/// </summary>
|
||||
public bool EnableDineIn { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 支持自提。
|
||||
/// </summary>
|
||||
public bool EnablePickup { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 支持配送。
|
||||
/// </summary>
|
||||
public bool EnableDelivery { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否推荐。
|
||||
/// </summary>
|
||||
public bool IsFeatured { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using MediatR;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Products.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 删除商品命令。
|
||||
/// </summary>
|
||||
public sealed class DeleteProductCommand : IRequest<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品 ID。
|
||||
/// </summary>
|
||||
public long ProductId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Products.Dto;
|
||||
using TakeoutSaaS.Domain.Products.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Products.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 更新商品命令。
|
||||
/// </summary>
|
||||
public sealed class UpdateProductCommand : IRequest<ProductDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品 ID。
|
||||
/// </summary>
|
||||
public long ProductId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类 ID。
|
||||
/// </summary>
|
||||
public long CategoryId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品编码。
|
||||
/// </summary>
|
||||
public string SpuCode { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 名称。
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 副标题。
|
||||
/// </summary>
|
||||
public string? Subtitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 单位。
|
||||
/// </summary>
|
||||
public string? Unit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 现价。
|
||||
/// </summary>
|
||||
public decimal Price { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原价。
|
||||
/// </summary>
|
||||
public decimal? OriginalPrice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 库存数量。
|
||||
/// </summary>
|
||||
public int? StockQuantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 每单限购。
|
||||
/// </summary>
|
||||
public int? MaxQuantityPerOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态。
|
||||
/// </summary>
|
||||
public ProductStatus Status { get; set; } = ProductStatus.Draft;
|
||||
|
||||
/// <summary>
|
||||
/// 主图。
|
||||
/// </summary>
|
||||
public string? CoverImage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图集。
|
||||
/// </summary>
|
||||
public string? GalleryImages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述。
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支持堂食。
|
||||
/// </summary>
|
||||
public bool EnableDineIn { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 支持自提。
|
||||
/// </summary>
|
||||
public bool EnablePickup { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 支持配送。
|
||||
/// </summary>
|
||||
public bool EnableDelivery { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否推荐。
|
||||
/// </summary>
|
||||
public bool IsFeatured { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using TakeoutSaaS.Domain.Products.Enums;
|
||||
using TakeoutSaaS.Shared.Abstractions.Serialization;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Products.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 商品 DTO。
|
||||
/// </summary>
|
||||
public sealed class ProductDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品 ID。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户 ID。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类 ID。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long CategoryId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// SPU 编码。
|
||||
/// </summary>
|
||||
public string SpuCode { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 副标题。
|
||||
/// </summary>
|
||||
public string? Subtitle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 单位。
|
||||
/// </summary>
|
||||
public string? Unit { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 现价。
|
||||
/// </summary>
|
||||
public decimal Price { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 原价。
|
||||
/// </summary>
|
||||
public decimal? OriginalPrice { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 库存数量。
|
||||
/// </summary>
|
||||
public int? StockQuantity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 每单限购。
|
||||
/// </summary>
|
||||
public int? MaxQuantityPerOrder { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态。
|
||||
/// </summary>
|
||||
public ProductStatus Status { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 主图。
|
||||
/// </summary>
|
||||
public string? CoverImage { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 图集。
|
||||
/// </summary>
|
||||
public string? GalleryImages { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 描述。
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支持堂食。
|
||||
/// </summary>
|
||||
public bool EnableDineIn { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支持自提。
|
||||
/// </summary>
|
||||
public bool EnablePickup { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支持配送。
|
||||
/// </summary>
|
||||
public bool EnableDelivery { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否推荐。
|
||||
/// </summary>
|
||||
public bool IsFeatured { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Application.App.Products.Commands;
|
||||
using TakeoutSaaS.Application.App.Products.Dto;
|
||||
using TakeoutSaaS.Domain.Products.Entities;
|
||||
using TakeoutSaaS.Domain.Products.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 创建商品命令处理器。
|
||||
/// </summary>
|
||||
public sealed class CreateProductCommandHandler(IProductRepository productRepository, ILogger<CreateProductCommandHandler> logger)
|
||||
: IRequestHandler<CreateProductCommand, ProductDto>
|
||||
{
|
||||
private readonly IProductRepository _productRepository = productRepository;
|
||||
private readonly ILogger<CreateProductCommandHandler> _logger = logger;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ProductDto> Handle(CreateProductCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 构建实体
|
||||
var product = new Product
|
||||
{
|
||||
StoreId = request.StoreId,
|
||||
CategoryId = request.CategoryId,
|
||||
SpuCode = request.SpuCode.Trim(),
|
||||
Name = request.Name.Trim(),
|
||||
Subtitle = request.Subtitle?.Trim(),
|
||||
Unit = request.Unit?.Trim(),
|
||||
Price = request.Price,
|
||||
OriginalPrice = request.OriginalPrice,
|
||||
StockQuantity = request.StockQuantity,
|
||||
MaxQuantityPerOrder = request.MaxQuantityPerOrder,
|
||||
Status = request.Status,
|
||||
CoverImage = request.CoverImage?.Trim(),
|
||||
GalleryImages = request.GalleryImages?.Trim(),
|
||||
Description = request.Description?.Trim(),
|
||||
EnableDineIn = request.EnableDineIn,
|
||||
EnablePickup = request.EnablePickup,
|
||||
EnableDelivery = request.EnableDelivery,
|
||||
IsFeatured = request.IsFeatured
|
||||
};
|
||||
|
||||
// 2. 持久化
|
||||
await _productRepository.AddProductAsync(product, cancellationToken);
|
||||
await _productRepository.SaveChangesAsync(cancellationToken);
|
||||
_logger.LogInformation("创建商品 {ProductId} - {ProductName}", product.Id, product.Name);
|
||||
|
||||
// 3. 返回 DTO
|
||||
return MapToDto(product);
|
||||
}
|
||||
|
||||
private static ProductDto MapToDto(Product product) => new()
|
||||
{
|
||||
Id = product.Id,
|
||||
TenantId = product.TenantId,
|
||||
StoreId = product.StoreId,
|
||||
CategoryId = product.CategoryId,
|
||||
SpuCode = product.SpuCode,
|
||||
Name = product.Name,
|
||||
Subtitle = product.Subtitle,
|
||||
Unit = product.Unit,
|
||||
Price = product.Price,
|
||||
OriginalPrice = product.OriginalPrice,
|
||||
StockQuantity = product.StockQuantity,
|
||||
MaxQuantityPerOrder = product.MaxQuantityPerOrder,
|
||||
Status = product.Status,
|
||||
CoverImage = product.CoverImage,
|
||||
GalleryImages = product.GalleryImages,
|
||||
Description = product.Description,
|
||||
EnableDineIn = product.EnableDineIn,
|
||||
EnablePickup = product.EnablePickup,
|
||||
EnableDelivery = product.EnableDelivery,
|
||||
IsFeatured = product.IsFeatured
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Application.App.Products.Commands;
|
||||
using TakeoutSaaS.Domain.Products.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 删除商品命令处理器。
|
||||
/// </summary>
|
||||
public sealed class DeleteProductCommandHandler(
|
||||
IProductRepository productRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ILogger<DeleteProductCommandHandler> logger)
|
||||
: IRequestHandler<DeleteProductCommand, bool>
|
||||
{
|
||||
private readonly IProductRepository _productRepository = productRepository;
|
||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
||||
private readonly ILogger<DeleteProductCommandHandler> _logger = logger;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> Handle(DeleteProductCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 校验存在性
|
||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
||||
var existing = await _productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
||||
if (existing == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 删除
|
||||
await _productRepository.DeleteProductAsync(request.ProductId, tenantId, cancellationToken);
|
||||
await _productRepository.SaveChangesAsync(cancellationToken);
|
||||
_logger.LogInformation("删除商品 {ProductId}", request.ProductId);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Products.Dto;
|
||||
using TakeoutSaaS.Application.App.Products.Queries;
|
||||
using TakeoutSaaS.Domain.Products.Entities;
|
||||
using TakeoutSaaS.Domain.Products.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 商品详情查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetProductByIdQueryHandler(
|
||||
IProductRepository productRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<GetProductByIdQuery, ProductDto?>
|
||||
{
|
||||
private readonly IProductRepository _productRepository = productRepository;
|
||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ProductDto?> Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
||||
var product = await _productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
||||
return product == null ? null : MapToDto(product);
|
||||
}
|
||||
|
||||
private static ProductDto MapToDto(Product product) => new()
|
||||
{
|
||||
Id = product.Id,
|
||||
TenantId = product.TenantId,
|
||||
StoreId = product.StoreId,
|
||||
CategoryId = product.CategoryId,
|
||||
SpuCode = product.SpuCode,
|
||||
Name = product.Name,
|
||||
Subtitle = product.Subtitle,
|
||||
Unit = product.Unit,
|
||||
Price = product.Price,
|
||||
OriginalPrice = product.OriginalPrice,
|
||||
StockQuantity = product.StockQuantity,
|
||||
MaxQuantityPerOrder = product.MaxQuantityPerOrder,
|
||||
Status = product.Status,
|
||||
CoverImage = product.CoverImage,
|
||||
GalleryImages = product.GalleryImages,
|
||||
Description = product.Description,
|
||||
EnableDineIn = product.EnableDineIn,
|
||||
EnablePickup = product.EnablePickup,
|
||||
EnableDelivery = product.EnableDelivery,
|
||||
IsFeatured = product.IsFeatured
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Products.Dto;
|
||||
using TakeoutSaaS.Application.App.Products.Queries;
|
||||
using TakeoutSaaS.Domain.Products.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 商品列表查询处理器。
|
||||
/// </summary>
|
||||
public sealed class SearchProductsQueryHandler(
|
||||
IProductRepository productRepository,
|
||||
ITenantProvider tenantProvider)
|
||||
: IRequestHandler<SearchProductsQuery, IReadOnlyList<ProductDto>>
|
||||
{
|
||||
private readonly IProductRepository _productRepository = productRepository;
|
||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<ProductDto>> Handle(SearchProductsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
||||
var products = await _productRepository.SearchAsync(tenantId, request.CategoryId, request.Status, cancellationToken);
|
||||
|
||||
if (request.StoreId.HasValue)
|
||||
{
|
||||
products = products.Where(x => x.StoreId == request.StoreId.Value).ToList();
|
||||
}
|
||||
|
||||
return products.Select(MapToDto).ToList();
|
||||
}
|
||||
|
||||
private static ProductDto MapToDto(Domain.Products.Entities.Product product) => new()
|
||||
{
|
||||
Id = product.Id,
|
||||
TenantId = product.TenantId,
|
||||
StoreId = product.StoreId,
|
||||
CategoryId = product.CategoryId,
|
||||
SpuCode = product.SpuCode,
|
||||
Name = product.Name,
|
||||
Subtitle = product.Subtitle,
|
||||
Unit = product.Unit,
|
||||
Price = product.Price,
|
||||
OriginalPrice = product.OriginalPrice,
|
||||
StockQuantity = product.StockQuantity,
|
||||
MaxQuantityPerOrder = product.MaxQuantityPerOrder,
|
||||
Status = product.Status,
|
||||
CoverImage = product.CoverImage,
|
||||
GalleryImages = product.GalleryImages,
|
||||
Description = product.Description,
|
||||
EnableDineIn = product.EnableDineIn,
|
||||
EnablePickup = product.EnablePickup,
|
||||
EnableDelivery = product.EnableDelivery,
|
||||
IsFeatured = product.IsFeatured
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Application.App.Products.Commands;
|
||||
using TakeoutSaaS.Application.App.Products.Dto;
|
||||
using TakeoutSaaS.Domain.Products.Entities;
|
||||
using TakeoutSaaS.Domain.Products.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Tenancy;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Products.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 更新商品命令处理器。
|
||||
/// </summary>
|
||||
public sealed class UpdateProductCommandHandler(
|
||||
IProductRepository productRepository,
|
||||
ITenantProvider tenantProvider,
|
||||
ILogger<UpdateProductCommandHandler> logger)
|
||||
: IRequestHandler<UpdateProductCommand, ProductDto?>
|
||||
{
|
||||
private readonly IProductRepository _productRepository = productRepository;
|
||||
private readonly ITenantProvider _tenantProvider = tenantProvider;
|
||||
private readonly ILogger<UpdateProductCommandHandler> _logger = logger;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ProductDto?> Handle(UpdateProductCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 读取商品
|
||||
var tenantId = _tenantProvider.GetCurrentTenantId();
|
||||
var existing = await _productRepository.FindByIdAsync(request.ProductId, tenantId, cancellationToken);
|
||||
if (existing == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 更新字段
|
||||
existing.StoreId = request.StoreId;
|
||||
existing.CategoryId = request.CategoryId;
|
||||
existing.SpuCode = request.SpuCode.Trim();
|
||||
existing.Name = request.Name.Trim();
|
||||
existing.Subtitle = request.Subtitle?.Trim();
|
||||
existing.Unit = request.Unit?.Trim();
|
||||
existing.Price = request.Price;
|
||||
existing.OriginalPrice = request.OriginalPrice;
|
||||
existing.StockQuantity = request.StockQuantity;
|
||||
existing.MaxQuantityPerOrder = request.MaxQuantityPerOrder;
|
||||
existing.Status = request.Status;
|
||||
existing.CoverImage = request.CoverImage?.Trim();
|
||||
existing.GalleryImages = request.GalleryImages?.Trim();
|
||||
existing.Description = request.Description?.Trim();
|
||||
existing.EnableDineIn = request.EnableDineIn;
|
||||
existing.EnablePickup = request.EnablePickup;
|
||||
existing.EnableDelivery = request.EnableDelivery;
|
||||
existing.IsFeatured = request.IsFeatured;
|
||||
|
||||
// 3. 持久化
|
||||
await _productRepository.UpdateProductAsync(existing, cancellationToken);
|
||||
await _productRepository.SaveChangesAsync(cancellationToken);
|
||||
_logger.LogInformation("更新商品 {ProductId} - {ProductName}", existing.Id, existing.Name);
|
||||
|
||||
// 4. 返回 DTO
|
||||
return MapToDto(existing);
|
||||
}
|
||||
|
||||
private static ProductDto MapToDto(Product product) => new()
|
||||
{
|
||||
Id = product.Id,
|
||||
TenantId = product.TenantId,
|
||||
StoreId = product.StoreId,
|
||||
CategoryId = product.CategoryId,
|
||||
SpuCode = product.SpuCode,
|
||||
Name = product.Name,
|
||||
Subtitle = product.Subtitle,
|
||||
Unit = product.Unit,
|
||||
Price = product.Price,
|
||||
OriginalPrice = product.OriginalPrice,
|
||||
StockQuantity = product.StockQuantity,
|
||||
MaxQuantityPerOrder = product.MaxQuantityPerOrder,
|
||||
Status = product.Status,
|
||||
CoverImage = product.CoverImage,
|
||||
GalleryImages = product.GalleryImages,
|
||||
Description = product.Description,
|
||||
EnableDineIn = product.EnableDineIn,
|
||||
EnablePickup = product.EnablePickup,
|
||||
EnableDelivery = product.EnableDelivery,
|
||||
IsFeatured = product.IsFeatured
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Products.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Products.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 获取商品详情查询。
|
||||
/// </summary>
|
||||
public sealed class GetProductByIdQuery : IRequest<ProductDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 商品 ID。
|
||||
/// </summary>
|
||||
public long ProductId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Products.Dto;
|
||||
using TakeoutSaaS.Domain.Products.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Products.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 商品列表查询。
|
||||
/// </summary>
|
||||
public sealed class SearchProductsQuery : IRequest<IReadOnlyList<ProductDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID(可选)。
|
||||
/// </summary>
|
||||
public long? StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类 ID(可选)。
|
||||
/// </summary>
|
||||
public long? CategoryId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态过滤。
|
||||
/// </summary>
|
||||
public ProductStatus? Status { get; init; }
|
||||
}
|
||||
Reference in New Issue
Block a user