feat: 增加分页排序与FluentValidation

This commit is contained in:
2025-12-02 10:50:43 +08:00
parent 93141fbf0c
commit 97bf6cacb0
63 changed files with 904 additions and 49 deletions

View File

@@ -138,4 +138,9 @@ public sealed class OrderDto
/// 退款申请。
/// </summary>
public IReadOnlyList<RefundRequestDto> Refunds { get; init; } = Array.Empty<RefundRequestDto>();
/// <summary>
/// 创建时间。
/// </summary>
public DateTime CreatedAt { get; init; }
}

View File

@@ -151,6 +151,7 @@ public sealed class CreateOrderCommandHandler(
RequestedAt = x.RequestedAt,
ProcessedAt = x.ProcessedAt,
ReviewNotes = x.ReviewNotes
}).ToList()
}).ToList(),
CreatedAt = order.CreatedAt
};
}

View File

@@ -97,6 +97,7 @@ public sealed class GetOrderByIdQueryHandler(
RequestedAt = x.RequestedAt,
ProcessedAt = x.ProcessedAt,
ReviewNotes = x.ReviewNotes
}).ToList()
}).ToList(),
CreatedAt = order.CreatedAt
};
}

View File

@@ -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<Domain.Orders.Entities.Order> ApplySorting(
IReadOnlyCollection<Domain.Orders.Entities.Order> 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)
};
}
}

View File

@@ -129,6 +129,7 @@ public sealed class UpdateOrderCommandHandler(
RequestedAt = x.RequestedAt,
ProcessedAt = x.ProcessedAt,
ReviewNotes = x.ReviewNotes
}).ToList()
}).ToList(),
CreatedAt = order.CreatedAt
};
}

View File

@@ -29,4 +29,24 @@ public sealed class SearchOrdersQuery : IRequest<IReadOnlyList<OrderDto>>
/// 订单号(模糊或精确,由调用方控制)。
/// </summary>
public string? OrderNo { get; init; }
/// <summary>
/// 页码。
/// </summary>
public int Page { get; init; } = 1;
/// <summary>
/// 每页条数。
/// </summary>
public int PageSize { get; init; } = 20;
/// <summary>
/// 排序字段createdAt/paidAt/status/payableAmount
/// </summary>
public string? SortBy { get; init; }
/// <summary>
/// 是否倒序。
/// </summary>
public bool SortDescending { get; init; } = true;
}

View File

@@ -0,0 +1,46 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Orders.Commands;
namespace TakeoutSaaS.Application.App.Orders.Validators;
/// <summary>
/// 创建订单命令验证器。
/// </summary>
public sealed class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
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);
});
}
}

View File

@@ -0,0 +1,21 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Orders.Queries;
namespace TakeoutSaaS.Application.App.Orders.Validators;
/// <summary>
/// 订单列表查询验证器。
/// </summary>
public sealed class SearchOrdersQueryValidator : AbstractValidator<SearchOrdersQuery>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
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);
}
}

View File

@@ -0,0 +1,30 @@
using FluentValidation;
using TakeoutSaaS.Application.App.Orders.Commands;
namespace TakeoutSaaS.Application.App.Orders.Validators;
/// <summary>
/// 更新订单命令验证器。
/// </summary>
public sealed class UpdateOrderCommandValidator : AbstractValidator<UpdateOrderCommand>
{
/// <summary>
/// 初始化验证规则。
/// </summary>
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);
}
}