diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/DeliveriesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/DeliveriesController.cs index a36e2e2..fbc2277 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/DeliveriesController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/DeliveriesController.cs @@ -49,12 +49,23 @@ public sealed class DeliveriesController : BaseApiController [HttpGet] [PermissionAuthorize("delivery:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List([FromQuery] long? orderId, [FromQuery] DeliveryStatus? status, CancellationToken cancellationToken) + public async Task>> List( + [FromQuery] long? orderId, + [FromQuery] DeliveryStatus? status, + [FromQuery] int page = 1, + [FromQuery] int pageSize = 20, + [FromQuery] string? sortBy = null, + [FromQuery] bool sortDesc = true, + CancellationToken cancellationToken = default) { var result = await _mediator.Send(new SearchDeliveryOrdersQuery { OrderId = orderId, - Status = status + Status = status, + Page = page, + PageSize = pageSize, + SortBy = sortBy, + SortDescending = sortDesc }, cancellationToken); return ApiResponse>.Ok(result); diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs index c24ec8d..545af0d 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/MerchantsController.cs @@ -49,9 +49,22 @@ public sealed class MerchantsController : BaseApiController [HttpGet] [PermissionAuthorize("merchant:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List([FromQuery] MerchantStatus? status, CancellationToken cancellationToken) + public async Task>> List( + [FromQuery] MerchantStatus? status, + [FromQuery] int page = 1, + [FromQuery] int pageSize = 20, + [FromQuery] string? sortBy = null, + [FromQuery] bool sortDesc = true, + CancellationToken cancellationToken = default) { - var result = await _mediator.Send(new SearchMerchantsQuery { Status = status }, cancellationToken); + var result = await _mediator.Send(new SearchMerchantsQuery + { + Status = status, + Page = page, + PageSize = pageSize, + SortBy = sortBy, + SortDescending = sortDesc + }, cancellationToken); return ApiResponse>.Ok(result); } diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs index f66b159..b14a915 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/OrdersController.cs @@ -55,14 +55,22 @@ public sealed class OrdersController : BaseApiController [FromQuery] OrderStatus? status, [FromQuery] PaymentStatus? paymentStatus, [FromQuery] string? orderNo, - CancellationToken cancellationToken) + [FromQuery] int page = 1, + [FromQuery] int pageSize = 20, + [FromQuery] string? sortBy = null, + [FromQuery] bool sortDesc = true, + CancellationToken cancellationToken = default) { var result = await _mediator.Send(new SearchOrdersQuery { StoreId = storeId, Status = status, PaymentStatus = paymentStatus, - OrderNo = orderNo + OrderNo = orderNo, + Page = page, + PageSize = pageSize, + SortBy = sortBy, + SortDescending = sortDesc }, cancellationToken); return ApiResponse>.Ok(result); diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/PaymentsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/PaymentsController.cs index 93ca700..5f91315 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/PaymentsController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/PaymentsController.cs @@ -49,12 +49,23 @@ public sealed class PaymentsController : BaseApiController [HttpGet] [PermissionAuthorize("payment:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List([FromQuery] long? orderId, [FromQuery] PaymentStatus? status, CancellationToken cancellationToken) + public async Task>> List( + [FromQuery] long? orderId, + [FromQuery] PaymentStatus? status, + [FromQuery] int page = 1, + [FromQuery] int pageSize = 20, + [FromQuery] string? sortBy = null, + [FromQuery] bool sortDesc = true, + CancellationToken cancellationToken = default) { var result = await _mediator.Send(new SearchPaymentsQuery { OrderId = orderId, - Status = status + Status = status, + Page = page, + PageSize = pageSize, + SortBy = sortBy, + SortDescending = sortDesc }, cancellationToken); return ApiResponse>.Ok(result); diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs index 4c8d42c..949cf0b 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/ProductsController.cs @@ -49,13 +49,25 @@ public sealed class ProductsController : BaseApiController [HttpGet] [PermissionAuthorize("product:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List([FromQuery] long? storeId, [FromQuery] long? categoryId, [FromQuery] ProductStatus? status, CancellationToken cancellationToken) + public async Task>> List( + [FromQuery] long? storeId, + [FromQuery] long? categoryId, + [FromQuery] ProductStatus? status, + [FromQuery] int page = 1, + [FromQuery] int pageSize = 20, + [FromQuery] string? sortBy = null, + [FromQuery] bool sortDesc = true, + CancellationToken cancellationToken = default) { var result = await _mediator.Send(new SearchProductsQuery { StoreId = storeId, CategoryId = categoryId, - Status = status + Status = status, + Page = page, + PageSize = pageSize, + SortBy = sortBy, + SortDescending = sortDesc }, cancellationToken); return ApiResponse>.Ok(result); diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs index e485df6..5680396 100644 --- a/src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs +++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/StoresController.cs @@ -49,12 +49,23 @@ public sealed class StoresController : BaseApiController [HttpGet] [PermissionAuthorize("store:read")] [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] - public async Task>> List([FromQuery] long? merchantId, [FromQuery] StoreStatus? status, CancellationToken cancellationToken) + public async Task>> List( + [FromQuery] long? merchantId, + [FromQuery] StoreStatus? status, + [FromQuery] int page = 1, + [FromQuery] int pageSize = 20, + [FromQuery] string? sortBy = null, + [FromQuery] bool sortDesc = true, + CancellationToken cancellationToken = default) { var result = await _mediator.Send(new SearchStoresQuery { MerchantId = merchantId, - Status = status + Status = status, + Page = page, + PageSize = pageSize, + SortBy = sortBy, + SortDescending = sortDesc }, cancellationToken); return ApiResponse>.Ok(result); diff --git a/src/Application/TakeoutSaaS.Application/App/Common/Behaviors/ValidationBehavior.cs b/src/Application/TakeoutSaaS.Application/App/Common/Behaviors/ValidationBehavior.cs new file mode 100644 index 0000000..177ebd9 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Common/Behaviors/ValidationBehavior.cs @@ -0,0 +1,35 @@ +using FluentValidation; +using MediatR; + +namespace TakeoutSaaS.Application.App.Common.Behaviors; + +/// +/// MediatR 请求验证行为,统一触发 FluentValidation。 +/// +/// 请求类型。 +/// 响应类型。 +public sealed class ValidationBehavior(IEnumerable> validators) : IPipelineBehavior + where TRequest : notnull, IRequest +{ + private readonly IEnumerable> _validators = validators; + + /// + /// 执行验证并在通过时继续后续处理。 + /// + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + if (_validators.Any()) + { + var context = new ValidationContext(request); + var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken))); + var failures = validationResults.SelectMany(r => r.Errors).Where(f => f is not null).ToList(); + + if (failures.Count > 0) + { + throw new ValidationException(failures); + } + } + + return await next(); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Deliveries/Dto/DeliveryOrderDto.cs b/src/Application/TakeoutSaaS.Application/App/Deliveries/Dto/DeliveryOrderDto.cs index 21fc36c..641d67a 100644 --- a/src/Application/TakeoutSaaS.Application/App/Deliveries/Dto/DeliveryOrderDto.cs +++ b/src/Application/TakeoutSaaS.Application/App/Deliveries/Dto/DeliveryOrderDto.cs @@ -81,4 +81,9 @@ public sealed class DeliveryOrderDto /// 事件列表。 /// public IReadOnlyList Events { get; init; } = Array.Empty(); + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/CreateDeliveryOrderCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/CreateDeliveryOrderCommandHandler.cs index 8e775e9..1a67240 100644 --- a/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/CreateDeliveryOrderCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/CreateDeliveryOrderCommandHandler.cs @@ -56,6 +56,7 @@ public sealed class CreateDeliveryOrderCommandHandler(IDeliveryRepository delive PickedUpAt = deliveryOrder.PickedUpAt, DeliveredAt = deliveryOrder.DeliveredAt, FailureReason = deliveryOrder.FailureReason, + CreatedAt = deliveryOrder.CreatedAt, Events = events.Select(x => new DeliveryEventDto { Id = x.Id, diff --git a/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/GetDeliveryOrderByIdQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/GetDeliveryOrderByIdQueryHandler.cs index 8d047de..24e425f 100644 --- a/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/GetDeliveryOrderByIdQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/GetDeliveryOrderByIdQueryHandler.cs @@ -47,6 +47,7 @@ public sealed class GetDeliveryOrderByIdQueryHandler( PickedUpAt = deliveryOrder.PickedUpAt, DeliveredAt = deliveryOrder.DeliveredAt, FailureReason = deliveryOrder.FailureReason, + CreatedAt = deliveryOrder.CreatedAt, Events = events.Select(x => new DeliveryEventDto { Id = x.Id, diff --git a/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/SearchDeliveryOrdersQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/SearchDeliveryOrdersQueryHandler.cs index 5dea6a9..f0b3750 100644 --- a/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/SearchDeliveryOrdersQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/SearchDeliveryOrdersQueryHandler.cs @@ -23,7 +23,13 @@ public sealed class SearchDeliveryOrdersQueryHandler( var tenantId = _tenantProvider.GetCurrentTenantId(); var orders = await _deliveryRepository.SearchAsync(tenantId, request.Status, request.OrderId, cancellationToken); - return orders.Select(order => new DeliveryOrderDto + var sorted = ApplySorting(orders, request.SortBy, request.SortDescending); + var paged = sorted + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .ToList(); + + return paged.Select(order => new DeliveryOrderDto { Id = order.Id, TenantId = order.TenantId, @@ -37,7 +43,21 @@ public sealed class SearchDeliveryOrdersQueryHandler( DispatchedAt = order.DispatchedAt, PickedUpAt = order.PickedUpAt, DeliveredAt = order.DeliveredAt, - FailureReason = order.FailureReason + FailureReason = order.FailureReason, + CreatedAt = order.CreatedAt }).ToList(); } + + private static IOrderedEnumerable ApplySorting( + IReadOnlyCollection orders, + string? sortBy, + bool sortDescending) + { + return sortBy?.ToLowerInvariant() switch + { + "status" => sortDescending ? orders.OrderByDescending(x => x.Status) : orders.OrderBy(x => x.Status), + "provider" => sortDescending ? orders.OrderByDescending(x => x.Provider) : orders.OrderBy(x => x.Provider), + _ => sortDescending ? orders.OrderByDescending(x => x.CreatedAt) : orders.OrderBy(x => x.CreatedAt) + }; + } } diff --git a/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/UpdateDeliveryOrderCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/UpdateDeliveryOrderCommandHandler.cs index 424e2a9..c8df929 100644 --- a/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/UpdateDeliveryOrderCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Deliveries/Handlers/UpdateDeliveryOrderCommandHandler.cs @@ -66,6 +66,7 @@ public sealed class UpdateDeliveryOrderCommandHandler( PickedUpAt = deliveryOrder.PickedUpAt, DeliveredAt = deliveryOrder.DeliveredAt, FailureReason = deliveryOrder.FailureReason, + CreatedAt = deliveryOrder.CreatedAt, Events = events.Select(x => new DeliveryEventDto { Id = x.Id, diff --git a/src/Application/TakeoutSaaS.Application/App/Deliveries/Queries/SearchDeliveryOrdersQuery.cs b/src/Application/TakeoutSaaS.Application/App/Deliveries/Queries/SearchDeliveryOrdersQuery.cs index 53d439c..7175ac3 100644 --- a/src/Application/TakeoutSaaS.Application/App/Deliveries/Queries/SearchDeliveryOrdersQuery.cs +++ b/src/Application/TakeoutSaaS.Application/App/Deliveries/Queries/SearchDeliveryOrdersQuery.cs @@ -18,4 +18,24 @@ public sealed class SearchDeliveryOrdersQuery : IRequest public DeliveryStatus? Status { get; init; } + + /// + /// 页码。 + /// + public int Page { get; init; } = 1; + + /// + /// 每页条数。 + /// + public int PageSize { get; init; } = 20; + + /// + /// 排序字段(createdAt/status/provider)。 + /// + public string? SortBy { get; init; } + + /// + /// 是否倒序。 + /// + public bool SortDescending { get; init; } = true; } diff --git a/src/Application/TakeoutSaaS.Application/App/Deliveries/Validators/CreateDeliveryOrderCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Deliveries/Validators/CreateDeliveryOrderCommandValidator.cs new file mode 100644 index 0000000..765e47f --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Deliveries/Validators/CreateDeliveryOrderCommandValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Deliveries.Commands; + +namespace TakeoutSaaS.Application.App.Deliveries.Validators; + +/// +/// 创建配送单命令验证器。 +/// +public sealed class CreateDeliveryOrderCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public CreateDeliveryOrderCommandValidator() + { + RuleFor(x => x.OrderId).GreaterThan(0); + RuleFor(x => x.ProviderOrderId).MaximumLength(64); + RuleFor(x => x.CourierName).MaximumLength(64); + RuleFor(x => x.CourierPhone).MaximumLength(32); + RuleFor(x => x.FailureReason).MaximumLength(256); + RuleFor(x => x.DeliveryFee).GreaterThanOrEqualTo(0).When(x => x.DeliveryFee.HasValue); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Deliveries/Validators/SearchDeliveryOrdersQueryValidator.cs b/src/Application/TakeoutSaaS.Application/App/Deliveries/Validators/SearchDeliveryOrdersQueryValidator.cs new file mode 100644 index 0000000..2119152 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Deliveries/Validators/SearchDeliveryOrdersQueryValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Deliveries.Queries; + +namespace TakeoutSaaS.Application.App.Deliveries.Validators; + +/// +/// 配送单列表查询验证器。 +/// +public sealed class SearchDeliveryOrdersQueryValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public SearchDeliveryOrdersQueryValidator() + { + RuleFor(x => x.Page).GreaterThan(0); + RuleFor(x => x.PageSize).InclusiveBetween(1, 200); + RuleFor(x => x.SortBy).MaximumLength(64); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Deliveries/Validators/UpdateDeliveryOrderCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Deliveries/Validators/UpdateDeliveryOrderCommandValidator.cs new file mode 100644 index 0000000..e2cbc19 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Deliveries/Validators/UpdateDeliveryOrderCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Deliveries.Commands; + +namespace TakeoutSaaS.Application.App.Deliveries.Validators; + +/// +/// 更新配送单命令验证器。 +/// +public sealed class UpdateDeliveryOrderCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public UpdateDeliveryOrderCommandValidator() + { + RuleFor(x => x.DeliveryOrderId).GreaterThan(0); + RuleFor(x => x.OrderId).GreaterThan(0); + RuleFor(x => x.ProviderOrderId).MaximumLength(64); + RuleFor(x => x.CourierName).MaximumLength(64); + RuleFor(x => x.CourierPhone).MaximumLength(32); + RuleFor(x => x.FailureReason).MaximumLength(256); + RuleFor(x => x.DeliveryFee).GreaterThanOrEqualTo(0).When(x => x.DeliveryFee.HasValue); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Extensions/AppApplicationServiceCollectionExtensions.cs b/src/Application/TakeoutSaaS.Application/App/Extensions/AppApplicationServiceCollectionExtensions.cs index 61d259f..62284b1 100644 --- a/src/Application/TakeoutSaaS.Application/App/Extensions/AppApplicationServiceCollectionExtensions.cs +++ b/src/Application/TakeoutSaaS.Application/App/Extensions/AppApplicationServiceCollectionExtensions.cs @@ -1,6 +1,8 @@ using System.Reflection; +using FluentValidation; using MediatR; using Microsoft.Extensions.DependencyInjection; +using TakeoutSaaS.Application.App.Common.Behaviors; namespace TakeoutSaaS.Application.App.Extensions; @@ -17,6 +19,8 @@ public static class AppApplicationServiceCollectionExtensions public static IServiceCollection AddAppApplication(this IServiceCollection services) { services.AddMediatR(Assembly.GetExecutingAssembly()); + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); return services; } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantDto.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantDto.cs index d1552c8..6124ec7 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantDto.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Dto/MerchantDto.cs @@ -60,4 +60,9 @@ public sealed class MerchantDto /// 入驻时间。 /// public DateTime? JoinedAt { get; init; } + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/CreateMerchantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/CreateMerchantCommandHandler.cs index 4b32e70..70c0982 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/CreateMerchantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/CreateMerchantCommandHandler.cs @@ -49,6 +49,7 @@ public sealed class CreateMerchantCommandHandler(IMerchantRepository merchantRep ContactPhone = merchant.ContactPhone, ContactEmail = merchant.ContactEmail, Status = merchant.Status, - JoinedAt = merchant.JoinedAt + JoinedAt = merchant.JoinedAt, + CreatedAt = merchant.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantByIdQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantByIdQueryHandler.cs index f689743..3c2313f 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantByIdQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/GetMerchantByIdQueryHandler.cs @@ -36,7 +36,8 @@ public sealed class GetMerchantByIdQueryHandler(IMerchantRepository merchantRepo ContactPhone = merchant.ContactPhone, ContactEmail = merchant.ContactEmail, Status = merchant.Status, - JoinedAt = merchant.JoinedAt + JoinedAt = merchant.JoinedAt, + CreatedAt = merchant.CreatedAt }; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/SearchMerchantsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/SearchMerchantsQueryHandler.cs index bfd2f9b..71d18e3 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/SearchMerchantsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/SearchMerchantsQueryHandler.cs @@ -23,20 +23,44 @@ public sealed class SearchMerchantsQueryHandler( var tenantId = _tenantProvider.GetCurrentTenantId(); var merchants = await _merchantRepository.SearchAsync(tenantId, request.Status, cancellationToken); - return merchants - .Select(merchant => new MerchantDto - { - Id = merchant.Id, - TenantId = merchant.TenantId, - BrandName = merchant.BrandName, - BrandAlias = merchant.BrandAlias, - LogoUrl = merchant.LogoUrl, - Category = merchant.Category, - ContactPhone = merchant.ContactPhone, - ContactEmail = merchant.ContactEmail, - Status = merchant.Status, - JoinedAt = merchant.JoinedAt - }) + var sorted = ApplySorting(merchants, request.SortBy, request.SortDescending); + var paged = sorted + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) .ToList(); + + return paged.Select(merchant => new MerchantDto + { + Id = merchant.Id, + TenantId = merchant.TenantId, + BrandName = merchant.BrandName, + BrandAlias = merchant.BrandAlias, + LogoUrl = merchant.LogoUrl, + Category = merchant.Category, + ContactPhone = merchant.ContactPhone, + ContactEmail = merchant.ContactEmail, + Status = merchant.Status, + JoinedAt = merchant.JoinedAt, + CreatedAt = merchant.CreatedAt + }).ToList(); + } + + private static IOrderedEnumerable ApplySorting( + IReadOnlyCollection merchants, + string? sortBy, + bool sortDescending) + { + return sortBy?.ToLowerInvariant() switch + { + "brandname" => sortDescending + ? merchants.OrderByDescending(x => x.BrandName) + : merchants.OrderBy(x => x.BrandName), + "status" => sortDescending + ? merchants.OrderByDescending(x => x.Status) + : merchants.OrderBy(x => x.Status), + _ => sortDescending + ? merchants.OrderByDescending(x => x.CreatedAt) + : merchants.OrderBy(x => x.CreatedAt) + }; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs index d825b72..38753c9 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs @@ -60,6 +60,7 @@ public sealed class UpdateMerchantCommandHandler( ContactPhone = merchant.ContactPhone, ContactEmail = merchant.ContactEmail, Status = merchant.Status, - JoinedAt = merchant.JoinedAt + JoinedAt = merchant.JoinedAt, + CreatedAt = merchant.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/SearchMerchantsQuery.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/SearchMerchantsQuery.cs index 3ec7561..b8a8b1a 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/SearchMerchantsQuery.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Queries/SearchMerchantsQuery.cs @@ -13,4 +13,24 @@ public sealed class SearchMerchantsQuery : IRequest> /// 按状态过滤。 /// public MerchantStatus? Status { get; init; } + + /// + /// 页码(从 1 开始)。 + /// + public int Page { get; init; } = 1; + + /// + /// 每页条数。 + /// + public int PageSize { get; init; } = 20; + + /// + /// 排序字段(brandName/status/createdAt)。 + /// + public string? SortBy { get; init; } + + /// + /// 是否倒序。 + /// + public bool SortDescending { get; init; } = true; } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/CreateMerchantCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/CreateMerchantCommandValidator.cs new file mode 100644 index 0000000..bd930d7 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/CreateMerchantCommandValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Merchants.Commands; + +namespace TakeoutSaaS.Application.App.Merchants.Validators; + +/// +/// 创建商户命令验证器。 +/// +public sealed class CreateMerchantCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public CreateMerchantCommandValidator() + { + RuleFor(x => x.BrandName).NotEmpty().MaximumLength(128); + RuleFor(x => x.BrandAlias).MaximumLength(64); + RuleFor(x => x.LogoUrl).MaximumLength(256); + RuleFor(x => x.Category).MaximumLength(64); + RuleFor(x => x.ContactPhone).NotEmpty().MaximumLength(32); + RuleFor(x => x.ContactEmail).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.ContactEmail)); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/SearchMerchantsQueryValidator.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/SearchMerchantsQueryValidator.cs new file mode 100644 index 0000000..e14707f --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/SearchMerchantsQueryValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Merchants.Queries; + +namespace TakeoutSaaS.Application.App.Merchants.Validators; + +/// +/// 商户列表查询验证器。 +/// +public sealed class SearchMerchantsQueryValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public SearchMerchantsQueryValidator() + { + RuleFor(x => x.Page).GreaterThan(0); + RuleFor(x => x.PageSize).InclusiveBetween(1, 200); + RuleFor(x => x.SortBy).MaximumLength(64); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/UpdateMerchantCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/UpdateMerchantCommandValidator.cs new file mode 100644 index 0000000..6781a0b --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/UpdateMerchantCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Merchants.Commands; + +namespace TakeoutSaaS.Application.App.Merchants.Validators; + +/// +/// 更新商户命令验证器。 +/// +public sealed class UpdateMerchantCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public UpdateMerchantCommandValidator() + { + RuleFor(x => x.MerchantId).GreaterThan(0); + RuleFor(x => x.BrandName).NotEmpty().MaximumLength(128); + RuleFor(x => x.BrandAlias).MaximumLength(64); + RuleFor(x => x.LogoUrl).MaximumLength(256); + RuleFor(x => x.Category).MaximumLength(64); + RuleFor(x => x.ContactPhone).NotEmpty().MaximumLength(32); + RuleFor(x => x.ContactEmail).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.ContactEmail)); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Dto/OrderDto.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Dto/OrderDto.cs index 0358f7c..6add72b 100644 --- a/src/Application/TakeoutSaaS.Application/App/Orders/Dto/OrderDto.cs +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Dto/OrderDto.cs @@ -138,4 +138,9 @@ public sealed class OrderDto /// 退款申请。 /// public IReadOnlyList Refunds { get; init; } = Array.Empty(); + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/CreateOrderCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/CreateOrderCommandHandler.cs index 057b018..a7b1289 100644 --- a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/CreateOrderCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/CreateOrderCommandHandler.cs @@ -151,6 +151,7 @@ public sealed class CreateOrderCommandHandler( RequestedAt = x.RequestedAt, ProcessedAt = x.ProcessedAt, ReviewNotes = x.ReviewNotes - }).ToList() + }).ToList(), + CreatedAt = order.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/GetOrderByIdQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/GetOrderByIdQueryHandler.cs index dbdd1c2..5d6b791 100644 --- a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/GetOrderByIdQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/GetOrderByIdQueryHandler.cs @@ -97,6 +97,7 @@ public sealed class GetOrderByIdQueryHandler( RequestedAt = x.RequestedAt, ProcessedAt = x.ProcessedAt, ReviewNotes = x.ReviewNotes - }).ToList() + }).ToList(), + CreatedAt = order.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/SearchOrdersQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/SearchOrdersQueryHandler.cs index d1b054a..caf2a50 100644 --- a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/SearchOrdersQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/SearchOrdersQueryHandler.cs @@ -36,7 +36,13 @@ public sealed class SearchOrdersQueryHandler( .ToList(); } - return orders.Select(order => new OrderDto + var sorted = ApplySorting(orders, request.SortBy, request.SortDescending); + var paged = sorted + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .ToList(); + + return paged.Select(order => new OrderDto { Id = order.Id, TenantId = order.TenantId, @@ -59,7 +65,22 @@ public sealed class SearchOrdersQueryHandler( FinishedAt = order.FinishedAt, CancelledAt = order.CancelledAt, CancelReason = order.CancelReason, - Remark = order.Remark + Remark = order.Remark, + CreatedAt = order.CreatedAt }).ToList(); } + + private static IOrderedEnumerable ApplySorting( + IReadOnlyCollection orders, + string? sortBy, + bool sortDescending) + { + return sortBy?.ToLowerInvariant() switch + { + "paidat" => sortDescending ? orders.OrderByDescending(x => x.PaidAt) : orders.OrderBy(x => x.PaidAt), + "status" => sortDescending ? orders.OrderByDescending(x => x.Status) : orders.OrderBy(x => x.Status), + "payableamount" => sortDescending ? orders.OrderByDescending(x => x.PayableAmount) : orders.OrderBy(x => x.PayableAmount), + _ => sortDescending ? orders.OrderByDescending(x => x.CreatedAt) : orders.OrderBy(x => x.CreatedAt) + }; + } } diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/UpdateOrderCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/UpdateOrderCommandHandler.cs index 3901fd9..46df4ed 100644 --- a/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/UpdateOrderCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Handlers/UpdateOrderCommandHandler.cs @@ -129,6 +129,7 @@ public sealed class UpdateOrderCommandHandler( RequestedAt = x.RequestedAt, ProcessedAt = x.ProcessedAt, ReviewNotes = x.ReviewNotes - }).ToList() + }).ToList(), + CreatedAt = order.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Queries/SearchOrdersQuery.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Queries/SearchOrdersQuery.cs index 99fa7b3..6d33583 100644 --- a/src/Application/TakeoutSaaS.Application/App/Orders/Queries/SearchOrdersQuery.cs +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Queries/SearchOrdersQuery.cs @@ -29,4 +29,24 @@ public sealed class SearchOrdersQuery : IRequest> /// 订单号(模糊或精确,由调用方控制)。 /// public string? OrderNo { get; init; } + + /// + /// 页码。 + /// + public int Page { get; init; } = 1; + + /// + /// 每页条数。 + /// + public int PageSize { get; init; } = 20; + + /// + /// 排序字段(createdAt/paidAt/status/payableAmount)。 + /// + public string? SortBy { get; init; } + + /// + /// 是否倒序。 + /// + public bool SortDescending { get; init; } = true; } diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Validators/CreateOrderCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Validators/CreateOrderCommandValidator.cs new file mode 100644 index 0000000..729d166 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Validators/CreateOrderCommandValidator.cs @@ -0,0 +1,46 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Orders.Commands; + +namespace TakeoutSaaS.Application.App.Orders.Validators; + +/// +/// 创建订单命令验证器。 +/// +public sealed class CreateOrderCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public CreateOrderCommandValidator() + { + RuleFor(x => x.OrderNo).NotEmpty().MaximumLength(32); + RuleFor(x => x.StoreId).GreaterThan(0); + RuleFor(x => x.CustomerPhone).MaximumLength(32); + RuleFor(x => x.CustomerName).MaximumLength(64); + RuleFor(x => x.TableNo).MaximumLength(32); + RuleFor(x => x.QueueNumber).MaximumLength(32); + RuleFor(x => x.CancelReason).MaximumLength(256); + RuleFor(x => x.Remark).MaximumLength(512); + RuleFor(x => x.ItemsAmount).GreaterThanOrEqualTo(0); + RuleFor(x => x.DiscountAmount).GreaterThanOrEqualTo(0); + RuleFor(x => x.PayableAmount).GreaterThanOrEqualTo(0); + RuleFor(x => x.PaidAmount).GreaterThanOrEqualTo(0); + + RuleFor(x => x.Items) + .NotEmpty() + .WithMessage("订单明细不能为空"); + + RuleForEach(x => x.Items).ChildRules(item => + { + item.RuleFor(i => i.ProductId).GreaterThan(0); + item.RuleFor(i => i.ProductName).NotEmpty().MaximumLength(128); + item.RuleFor(i => i.SkuName).MaximumLength(128); + item.RuleFor(i => i.Unit).MaximumLength(16); + item.RuleFor(i => i.Quantity).GreaterThan(0); + item.RuleFor(i => i.UnitPrice).GreaterThanOrEqualTo(0); + item.RuleFor(i => i.DiscountAmount).GreaterThanOrEqualTo(0); + item.RuleFor(i => i.SubTotal).GreaterThanOrEqualTo(0); + item.RuleFor(i => i.AttributesJson).MaximumLength(4000); + }); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Validators/SearchOrdersQueryValidator.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Validators/SearchOrdersQueryValidator.cs new file mode 100644 index 0000000..b5dac41 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Validators/SearchOrdersQueryValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Orders.Queries; + +namespace TakeoutSaaS.Application.App.Orders.Validators; + +/// +/// 订单列表查询验证器。 +/// +public sealed class SearchOrdersQueryValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public SearchOrdersQueryValidator() + { + RuleFor(x => x.Page).GreaterThan(0); + RuleFor(x => x.PageSize).InclusiveBetween(1, 200); + RuleFor(x => x.SortBy).MaximumLength(64); + RuleFor(x => x.OrderNo).MaximumLength(32); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Orders/Validators/UpdateOrderCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Orders/Validators/UpdateOrderCommandValidator.cs new file mode 100644 index 0000000..2dba2f7 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Orders/Validators/UpdateOrderCommandValidator.cs @@ -0,0 +1,30 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Orders.Commands; + +namespace TakeoutSaaS.Application.App.Orders.Validators; + +/// +/// 更新订单命令验证器。 +/// +public sealed class UpdateOrderCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public UpdateOrderCommandValidator() + { + RuleFor(x => x.OrderId).GreaterThan(0); + RuleFor(x => x.OrderNo).NotEmpty().MaximumLength(32); + RuleFor(x => x.StoreId).GreaterThan(0); + RuleFor(x => x.CustomerPhone).MaximumLength(32); + RuleFor(x => x.CustomerName).MaximumLength(64); + RuleFor(x => x.TableNo).MaximumLength(32); + RuleFor(x => x.QueueNumber).MaximumLength(32); + RuleFor(x => x.CancelReason).MaximumLength(256); + RuleFor(x => x.Remark).MaximumLength(512); + RuleFor(x => x.ItemsAmount).GreaterThanOrEqualTo(0); + RuleFor(x => x.DiscountAmount).GreaterThanOrEqualTo(0); + RuleFor(x => x.PayableAmount).GreaterThanOrEqualTo(0); + RuleFor(x => x.PaidAmount).GreaterThanOrEqualTo(0); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Payments/Dto/PaymentDto.cs b/src/Application/TakeoutSaaS.Application/App/Payments/Dto/PaymentDto.cs index 807d0f1..d127427 100644 --- a/src/Application/TakeoutSaaS.Application/App/Payments/Dto/PaymentDto.cs +++ b/src/Application/TakeoutSaaS.Application/App/Payments/Dto/PaymentDto.cs @@ -71,4 +71,9 @@ public sealed class PaymentDto /// 退款记录。 /// public IReadOnlyList Refunds { get; init; } = Array.Empty(); + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/CreatePaymentCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/CreatePaymentCommandHandler.cs index 463d903..793a2a9 100644 --- a/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/CreatePaymentCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/CreatePaymentCommandHandler.cs @@ -52,6 +52,7 @@ public sealed class CreatePaymentCommandHandler(IPaymentRepository paymentReposi PaidAt = payment.PaidAt, Remark = payment.Remark, Payload = payment.Payload, + CreatedAt = payment.CreatedAt, Refunds = refunds.Select(x => new PaymentRefundDto { Id = x.Id, diff --git a/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/GetPaymentByIdQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/GetPaymentByIdQueryHandler.cs index 225697c..8e5db7a 100644 --- a/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/GetPaymentByIdQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/GetPaymentByIdQueryHandler.cs @@ -45,6 +45,7 @@ public sealed class GetPaymentByIdQueryHandler( PaidAt = payment.PaidAt, Remark = payment.Remark, Payload = payment.Payload, + CreatedAt = payment.CreatedAt, Refunds = refunds.Select(x => new PaymentRefundDto { Id = x.Id, diff --git a/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/SearchPaymentsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/SearchPaymentsQueryHandler.cs index 45ff7cb..be6c10a 100644 --- a/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/SearchPaymentsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/SearchPaymentsQueryHandler.cs @@ -1,6 +1,7 @@ using MediatR; using TakeoutSaaS.Application.App.Payments.Dto; using TakeoutSaaS.Application.App.Payments.Queries; +using TakeoutSaaS.Domain.Payments.Entities; using TakeoutSaaS.Domain.Payments.Repositories; using TakeoutSaaS.Shared.Abstractions.Tenancy; @@ -28,7 +29,13 @@ public sealed class SearchPaymentsQueryHandler( payments = payments.Where(x => x.OrderId == request.OrderId.Value).ToList(); } - return payments.Select(payment => new PaymentDto + var sorted = ApplySorting(payments, request.SortBy, request.SortDescending); + var paged = sorted + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .ToList(); + + return paged.Select(payment => new PaymentDto { Id = payment.Id, TenantId = payment.TenantId, @@ -40,7 +47,22 @@ public sealed class SearchPaymentsQueryHandler( ChannelTransactionId = payment.ChannelTransactionId, PaidAt = payment.PaidAt, Remark = payment.Remark, - Payload = payment.Payload + Payload = payment.Payload, + CreatedAt = payment.CreatedAt }).ToList(); } + + private static IOrderedEnumerable ApplySorting( + IReadOnlyCollection payments, + string? sortBy, + bool sortDescending) + { + return sortBy?.ToLowerInvariant() switch + { + "paidat" => sortDescending ? payments.OrderByDescending(x => x.PaidAt) : payments.OrderBy(x => x.PaidAt), + "status" => sortDescending ? payments.OrderByDescending(x => x.Status) : payments.OrderBy(x => x.Status), + "amount" => sortDescending ? payments.OrderByDescending(x => x.Amount) : payments.OrderBy(x => x.Amount), + _ => sortDescending ? payments.OrderByDescending(x => x.CreatedAt) : payments.OrderBy(x => x.CreatedAt) + }; + } } diff --git a/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/UpdatePaymentCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/UpdatePaymentCommandHandler.cs index f55a37d..e0e9fec 100644 --- a/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/UpdatePaymentCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Payments/Handlers/UpdatePaymentCommandHandler.cs @@ -62,6 +62,7 @@ public sealed class UpdatePaymentCommandHandler( PaidAt = payment.PaidAt, Remark = payment.Remark, Payload = payment.Payload, + CreatedAt = payment.CreatedAt, Refunds = refunds.Select(x => new PaymentRefundDto { Id = x.Id, diff --git a/src/Application/TakeoutSaaS.Application/App/Payments/Queries/SearchPaymentsQuery.cs b/src/Application/TakeoutSaaS.Application/App/Payments/Queries/SearchPaymentsQuery.cs index 91b5bad..efe8861 100644 --- a/src/Application/TakeoutSaaS.Application/App/Payments/Queries/SearchPaymentsQuery.cs +++ b/src/Application/TakeoutSaaS.Application/App/Payments/Queries/SearchPaymentsQuery.cs @@ -18,4 +18,24 @@ public sealed class SearchPaymentsQuery : IRequest> /// 支付状态。 /// public PaymentStatus? Status { get; init; } + + /// + /// 页码。 + /// + public int Page { get; init; } = 1; + + /// + /// 每页条数。 + /// + public int PageSize { get; init; } = 20; + + /// + /// 排序字段(createdAt/paidAt/status/amount)。 + /// + public string? SortBy { get; init; } + + /// + /// 是否倒序。 + /// + public bool SortDescending { get; init; } = true; } diff --git a/src/Application/TakeoutSaaS.Application/App/Payments/Validators/CreatePaymentCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Payments/Validators/CreatePaymentCommandValidator.cs new file mode 100644 index 0000000..f7e7abb --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Payments/Validators/CreatePaymentCommandValidator.cs @@ -0,0 +1,22 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Payments.Commands; + +namespace TakeoutSaaS.Application.App.Payments.Validators; + +/// +/// 创建支付记录命令验证器。 +/// +public sealed class CreatePaymentCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public CreatePaymentCommandValidator() + { + RuleFor(x => x.OrderId).GreaterThan(0); + RuleFor(x => x.Amount).GreaterThan(0); + RuleFor(x => x.TradeNo).MaximumLength(64); + RuleFor(x => x.ChannelTransactionId).MaximumLength(64); + RuleFor(x => x.Remark).MaximumLength(256); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Payments/Validators/SearchPaymentsQueryValidator.cs b/src/Application/TakeoutSaaS.Application/App/Payments/Validators/SearchPaymentsQueryValidator.cs new file mode 100644 index 0000000..fe7580e --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Payments/Validators/SearchPaymentsQueryValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Payments.Queries; + +namespace TakeoutSaaS.Application.App.Payments.Validators; + +/// +/// 支付记录查询验证器。 +/// +public sealed class SearchPaymentsQueryValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public SearchPaymentsQueryValidator() + { + RuleFor(x => x.Page).GreaterThan(0); + RuleFor(x => x.PageSize).InclusiveBetween(1, 200); + RuleFor(x => x.SortBy).MaximumLength(64); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Payments/Validators/UpdatePaymentCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Payments/Validators/UpdatePaymentCommandValidator.cs new file mode 100644 index 0000000..997e07b --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Payments/Validators/UpdatePaymentCommandValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Payments.Commands; + +namespace TakeoutSaaS.Application.App.Payments.Validators; + +/// +/// 更新支付记录命令验证器。 +/// +public sealed class UpdatePaymentCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public UpdatePaymentCommandValidator() + { + RuleFor(x => x.PaymentId).GreaterThan(0); + RuleFor(x => x.OrderId).GreaterThan(0); + RuleFor(x => x.Amount).GreaterThan(0); + RuleFor(x => x.TradeNo).MaximumLength(64); + RuleFor(x => x.ChannelTransactionId).MaximumLength(64); + RuleFor(x => x.Remark).MaximumLength(256); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Dto/ProductDto.cs b/src/Application/TakeoutSaaS.Application/App/Products/Dto/ProductDto.cs index 7661594..bfcd321 100644 --- a/src/Application/TakeoutSaaS.Application/App/Products/Dto/ProductDto.cs +++ b/src/Application/TakeoutSaaS.Application/App/Products/Dto/ProductDto.cs @@ -112,4 +112,9 @@ public sealed class ProductDto /// 是否推荐。 /// public bool IsFeatured { get; init; } + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/CreateProductCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/CreateProductCommandHandler.cs index 46201fa..2bf1e33 100644 --- a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/CreateProductCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/CreateProductCommandHandler.cs @@ -72,6 +72,7 @@ public sealed class CreateProductCommandHandler(IProductRepository productReposi EnableDineIn = product.EnableDineIn, EnablePickup = product.EnablePickup, EnableDelivery = product.EnableDelivery, - IsFeatured = product.IsFeatured + IsFeatured = product.IsFeatured, + CreatedAt = product.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductByIdQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductByIdQueryHandler.cs index 018c325..c3b6a60 100644 --- a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductByIdQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/GetProductByIdQueryHandler.cs @@ -47,6 +47,7 @@ public sealed class GetProductByIdQueryHandler( EnableDineIn = product.EnableDineIn, EnablePickup = product.EnablePickup, EnableDelivery = product.EnableDelivery, - IsFeatured = product.IsFeatured + IsFeatured = product.IsFeatured, + CreatedAt = product.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/SearchProductsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/SearchProductsQueryHandler.cs index d24784e..5527166 100644 --- a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/SearchProductsQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/SearchProductsQueryHandler.cs @@ -28,7 +28,27 @@ public sealed class SearchProductsQueryHandler( products = products.Where(x => x.StoreId == request.StoreId.Value).ToList(); } - return products.Select(MapToDto).ToList(); + var sorted = ApplySorting(products, request.SortBy, request.SortDescending); + var paged = sorted + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) + .ToList(); + + return paged.Select(MapToDto).ToList(); + } + + private static IOrderedEnumerable ApplySorting( + IReadOnlyCollection products, + string? sortBy, + bool sortDescending) + { + return sortBy?.ToLowerInvariant() switch + { + "name" => sortDescending ? products.OrderByDescending(x => x.Name) : products.OrderBy(x => x.Name), + "price" => sortDescending ? products.OrderByDescending(x => x.Price) : products.OrderBy(x => x.Price), + "status" => sortDescending ? products.OrderByDescending(x => x.Status) : products.OrderBy(x => x.Status), + _ => sortDescending ? products.OrderByDescending(x => x.CreatedAt) : products.OrderBy(x => x.CreatedAt) + }; } private static ProductDto MapToDto(Domain.Products.Entities.Product product) => new() @@ -52,6 +72,7 @@ public sealed class SearchProductsQueryHandler( EnableDineIn = product.EnableDineIn, EnablePickup = product.EnablePickup, EnableDelivery = product.EnableDelivery, - IsFeatured = product.IsFeatured + IsFeatured = product.IsFeatured, + CreatedAt = product.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/UpdateProductCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/UpdateProductCommandHandler.cs index df33a23..e616788 100644 --- a/src/Application/TakeoutSaaS.Application/App/Products/Handlers/UpdateProductCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Products/Handlers/UpdateProductCommandHandler.cs @@ -82,6 +82,7 @@ public sealed class UpdateProductCommandHandler( EnableDineIn = product.EnableDineIn, EnablePickup = product.EnablePickup, EnableDelivery = product.EnableDelivery, - IsFeatured = product.IsFeatured + IsFeatured = product.IsFeatured, + CreatedAt = product.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Queries/SearchProductsQuery.cs b/src/Application/TakeoutSaaS.Application/App/Products/Queries/SearchProductsQuery.cs index 07541c8..e7d3c55 100644 --- a/src/Application/TakeoutSaaS.Application/App/Products/Queries/SearchProductsQuery.cs +++ b/src/Application/TakeoutSaaS.Application/App/Products/Queries/SearchProductsQuery.cs @@ -23,4 +23,24 @@ public sealed class SearchProductsQuery : IRequest> /// 状态过滤。 /// public ProductStatus? Status { get; init; } + + /// + /// 页码。 + /// + public int Page { get; init; } = 1; + + /// + /// 每页条数。 + /// + public int PageSize { get; init; } = 20; + + /// + /// 排序字段(name/price/status/createdAt)。 + /// + public string? SortBy { get; init; } + + /// + /// 是否倒序。 + /// + public bool SortDescending { get; init; } = true; } diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Validators/CreateProductCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Products/Validators/CreateProductCommandValidator.cs new file mode 100644 index 0000000..91bd6ac --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Products/Validators/CreateProductCommandValidator.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Products.Commands; + +namespace TakeoutSaaS.Application.App.Products.Validators; + +/// +/// 创建商品命令验证器。 +/// +public sealed class CreateProductCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public CreateProductCommandValidator() + { + RuleFor(x => x.StoreId).GreaterThan(0); + RuleFor(x => x.CategoryId).GreaterThan(0); + RuleFor(x => x.SpuCode).NotEmpty().MaximumLength(32); + RuleFor(x => x.Name).NotEmpty().MaximumLength(128); + RuleFor(x => x.Subtitle).MaximumLength(256); + RuleFor(x => x.Unit).MaximumLength(16); + RuleFor(x => x.Price).GreaterThanOrEqualTo(0); + RuleFor(x => x.OriginalPrice).GreaterThanOrEqualTo(0).When(x => x.OriginalPrice.HasValue); + RuleFor(x => x.StockQuantity).GreaterThanOrEqualTo(0).When(x => x.StockQuantity.HasValue); + RuleFor(x => x.MaxQuantityPerOrder).GreaterThan(0).When(x => x.MaxQuantityPerOrder.HasValue); + RuleFor(x => x.CoverImage).MaximumLength(256); + RuleFor(x => x.GalleryImages).MaximumLength(1024); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Validators/SearchProductsQueryValidator.cs b/src/Application/TakeoutSaaS.Application/App/Products/Validators/SearchProductsQueryValidator.cs new file mode 100644 index 0000000..23e3962 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Products/Validators/SearchProductsQueryValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Products.Queries; + +namespace TakeoutSaaS.Application.App.Products.Validators; + +/// +/// 商品列表查询验证器。 +/// +public sealed class SearchProductsQueryValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public SearchProductsQueryValidator() + { + RuleFor(x => x.Page).GreaterThan(0); + RuleFor(x => x.PageSize).InclusiveBetween(1, 200); + RuleFor(x => x.SortBy).MaximumLength(64); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Products/Validators/UpdateProductCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Products/Validators/UpdateProductCommandValidator.cs new file mode 100644 index 0000000..20200f2 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Products/Validators/UpdateProductCommandValidator.cs @@ -0,0 +1,30 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Products.Commands; + +namespace TakeoutSaaS.Application.App.Products.Validators; + +/// +/// 更新商品命令验证器。 +/// +public sealed class UpdateProductCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public UpdateProductCommandValidator() + { + RuleFor(x => x.ProductId).GreaterThan(0); + RuleFor(x => x.StoreId).GreaterThan(0); + RuleFor(x => x.CategoryId).GreaterThan(0); + RuleFor(x => x.SpuCode).NotEmpty().MaximumLength(32); + RuleFor(x => x.Name).NotEmpty().MaximumLength(128); + RuleFor(x => x.Subtitle).MaximumLength(256); + RuleFor(x => x.Unit).MaximumLength(16); + RuleFor(x => x.Price).GreaterThanOrEqualTo(0); + RuleFor(x => x.OriginalPrice).GreaterThanOrEqualTo(0).When(x => x.OriginalPrice.HasValue); + RuleFor(x => x.StockQuantity).GreaterThanOrEqualTo(0).When(x => x.StockQuantity.HasValue); + RuleFor(x => x.MaxQuantityPerOrder).GreaterThan(0).When(x => x.MaxQuantityPerOrder.HasValue); + RuleFor(x => x.CoverImage).MaximumLength(256); + RuleFor(x => x.GalleryImages).MaximumLength(1024); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreDto.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreDto.cs index e924b43..5412717 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreDto.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Dto/StoreDto.cs @@ -111,4 +111,9 @@ public sealed class StoreDto /// 支持配送。 /// public bool SupportsDelivery { get; init; } + + /// + /// 创建时间。 + /// + public DateTime CreatedAt { get; init; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreCommandHandler.cs index 56f9d9c..f254d6f 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/CreateStoreCommandHandler.cs @@ -72,6 +72,7 @@ public sealed class CreateStoreCommandHandler(IStoreRepository storeRepository, DeliveryRadiusKm = store.DeliveryRadiusKm, SupportsDineIn = store.SupportsDineIn, SupportsPickup = store.SupportsPickup, - SupportsDelivery = store.SupportsDelivery + SupportsDelivery = store.SupportsDelivery, + CreatedAt = store.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/GetStoreByIdQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/GetStoreByIdQueryHandler.cs index 4cb8cb0..995ddde 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/GetStoreByIdQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/GetStoreByIdQueryHandler.cs @@ -47,6 +47,7 @@ public sealed class GetStoreByIdQueryHandler( DeliveryRadiusKm = store.DeliveryRadiusKm, SupportsDineIn = store.SupportsDineIn, SupportsPickup = store.SupportsPickup, - SupportsDelivery = store.SupportsDelivery + SupportsDelivery = store.SupportsDelivery, + CreatedAt = store.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/SearchStoresQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/SearchStoresQueryHandler.cs index 4efe538..0141ae4 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/SearchStoresQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/SearchStoresQueryHandler.cs @@ -28,9 +28,27 @@ public sealed class SearchStoresQueryHandler( stores = stores.Where(x => x.MerchantId == request.MerchantId.Value).ToList(); } - return stores - .Select(MapToDto) + var sorted = ApplySorting(stores, request.SortBy, request.SortDescending); + var paged = sorted + .Skip((request.Page - 1) * request.PageSize) + .Take(request.PageSize) .ToList(); + + return paged.Select(MapToDto).ToList(); + } + + private static IOrderedEnumerable ApplySorting( + IReadOnlyCollection stores, + string? sortBy, + bool sortDescending) + { + return sortBy?.ToLowerInvariant() switch + { + "name" => sortDescending ? stores.OrderByDescending(x => x.Name) : stores.OrderBy(x => x.Name), + "code" => sortDescending ? stores.OrderByDescending(x => x.Code) : stores.OrderBy(x => x.Code), + "status" => sortDescending ? stores.OrderByDescending(x => x.Status) : stores.OrderBy(x => x.Status), + _ => sortDescending ? stores.OrderByDescending(x => x.CreatedAt) : stores.OrderBy(x => x.CreatedAt) + }; } private static StoreDto MapToDto(Domain.Stores.Entities.Store store) => new() @@ -54,6 +72,7 @@ public sealed class SearchStoresQueryHandler( DeliveryRadiusKm = store.DeliveryRadiusKm, SupportsDineIn = store.SupportsDineIn, SupportsPickup = store.SupportsPickup, - SupportsDelivery = store.SupportsDelivery + SupportsDelivery = store.SupportsDelivery, + CreatedAt = store.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreCommandHandler.cs index 1680932..934bb54 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Handlers/UpdateStoreCommandHandler.cs @@ -82,6 +82,7 @@ public sealed class UpdateStoreCommandHandler( DeliveryRadiusKm = store.DeliveryRadiusKm, SupportsDineIn = store.SupportsDineIn, SupportsPickup = store.SupportsPickup, - SupportsDelivery = store.SupportsDelivery + SupportsDelivery = store.SupportsDelivery, + CreatedAt = store.CreatedAt }; } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Queries/SearchStoresQuery.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Queries/SearchStoresQuery.cs index 085a557..8a0b75b 100644 --- a/src/Application/TakeoutSaaS.Application/App/Stores/Queries/SearchStoresQuery.cs +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Queries/SearchStoresQuery.cs @@ -18,4 +18,24 @@ public sealed class SearchStoresQuery : IRequest> /// 状态过滤。 /// public StoreStatus? Status { get; init; } + + /// + /// 页码。 + /// + public int Page { get; init; } = 1; + + /// + /// 每页条数。 + /// + public int PageSize { get; init; } = 20; + + /// + /// 排序字段(name/code/status/createdAt)。 + /// + public string? SortBy { get; init; } + + /// + /// 是否倒序。 + /// + public bool SortDescending { get; init; } = true; } diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreCommandValidator.cs new file mode 100644 index 0000000..a947ae2 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/CreateStoreCommandValidator.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Stores.Commands; + +namespace TakeoutSaaS.Application.App.Stores.Validators; + +/// +/// 创建门店命令验证器。 +/// +public sealed class CreateStoreCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public CreateStoreCommandValidator() + { + RuleFor(x => x.MerchantId).GreaterThan(0); + RuleFor(x => x.Code).NotEmpty().MaximumLength(32); + RuleFor(x => x.Name).NotEmpty().MaximumLength(128); + RuleFor(x => x.Phone).MaximumLength(32); + RuleFor(x => x.ManagerName).MaximumLength(64); + RuleFor(x => x.Province).MaximumLength(64); + RuleFor(x => x.City).MaximumLength(64); + RuleFor(x => x.District).MaximumLength(64); + RuleFor(x => x.Address).MaximumLength(256); + RuleFor(x => x.Announcement).MaximumLength(512); + RuleFor(x => x.Tags).MaximumLength(256); + RuleFor(x => x.DeliveryRadiusKm).GreaterThanOrEqualTo(0); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/SearchStoresQueryValidator.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/SearchStoresQueryValidator.cs new file mode 100644 index 0000000..67ea047 --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/SearchStoresQueryValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Stores.Queries; + +namespace TakeoutSaaS.Application.App.Stores.Validators; + +/// +/// 门店列表查询验证器。 +/// +public sealed class SearchStoresQueryValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public SearchStoresQueryValidator() + { + RuleFor(x => x.Page).GreaterThan(0); + RuleFor(x => x.PageSize).InclusiveBetween(1, 200); + RuleFor(x => x.SortBy).MaximumLength(64); + } +} diff --git a/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreCommandValidator.cs new file mode 100644 index 0000000..d021aae --- /dev/null +++ b/src/Application/TakeoutSaaS.Application/App/Stores/Validators/UpdateStoreCommandValidator.cs @@ -0,0 +1,30 @@ +using FluentValidation; +using TakeoutSaaS.Application.App.Stores.Commands; + +namespace TakeoutSaaS.Application.App.Stores.Validators; + +/// +/// 更新门店命令验证器。 +/// +public sealed class UpdateStoreCommandValidator : AbstractValidator +{ + /// + /// 初始化验证规则。 + /// + public UpdateStoreCommandValidator() + { + RuleFor(x => x.StoreId).GreaterThan(0); + RuleFor(x => x.MerchantId).GreaterThan(0); + RuleFor(x => x.Code).NotEmpty().MaximumLength(32); + RuleFor(x => x.Name).NotEmpty().MaximumLength(128); + RuleFor(x => x.Phone).MaximumLength(32); + RuleFor(x => x.ManagerName).MaximumLength(64); + RuleFor(x => x.Province).MaximumLength(64); + RuleFor(x => x.City).MaximumLength(64); + RuleFor(x => x.District).MaximumLength(64); + RuleFor(x => x.Address).MaximumLength(256); + RuleFor(x => x.Announcement).MaximumLength(512); + RuleFor(x => x.Tags).MaximumLength(256); + RuleFor(x => x.DeliveryRadiusKm).GreaterThanOrEqualTo(0); + } +} diff --git a/src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj b/src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj index e320f29..7e49e3f 100644 --- a/src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj +++ b/src/Application/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj @@ -9,6 +9,7 @@ +