fix(order): move all-orders list to database pagination
All checks were successful
Build and Deploy TenantApi + SkuWorker / build-and-deploy (push) Successful in 1m50s
All checks were successful
Build and Deploy TenantApi + SkuWorker / build-and-deploy (push) Successful in 1m50s
This commit is contained in:
@@ -21,34 +21,29 @@ public sealed class SearchOrderAllListQueryHandler(
|
||||
public async Task<PagedResult<OrderAllListItemDto>> Handle(SearchOrderAllListQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
||||
var page = Math.Max(1, request.Page);
|
||||
var pageSize = Math.Clamp(request.PageSize, 1, 200);
|
||||
|
||||
var orders = await orderRepository.SearchAllOrdersAsync(
|
||||
var (pagedOrders, totalCount) = await orderRepository.SearchAllOrdersPageAsync(
|
||||
tenantId,
|
||||
request.StoreId,
|
||||
request.StartAt,
|
||||
request.EndAt,
|
||||
request.Status,
|
||||
request.RefundedOnly,
|
||||
request.DeliveryType,
|
||||
request.PaymentMethod,
|
||||
request.Keyword,
|
||||
request.SortBy,
|
||||
request.SortDescending,
|
||||
page,
|
||||
pageSize,
|
||||
cancellationToken);
|
||||
|
||||
var filteredOrders = orders.ToList();
|
||||
var refundedSet = await LoadRefundedOrderIdsAsync(filteredOrders, tenantId, cancellationToken);
|
||||
if (request.RefundedOnly)
|
||||
{
|
||||
filteredOrders = filteredOrders
|
||||
.Where(order => refundedSet.Contains(order.Id))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var sorted = ApplySorting(filteredOrders, request.SortBy, request.SortDescending);
|
||||
var page = Math.Max(1, request.Page);
|
||||
var pageSize = Math.Clamp(request.PageSize, 1, 200);
|
||||
var pagedOrders = sorted
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
var refundedSet = await LoadRefundedOrderIdsAsync(
|
||||
pagedOrders.Select(order => order.Id).ToList(),
|
||||
tenantId,
|
||||
cancellationToken);
|
||||
|
||||
var itemsLookup = await orderRepository.GetItemsByOrderIdsAsync(
|
||||
pagedOrders.Select(order => order.Id).ToList(),
|
||||
@@ -75,15 +70,14 @@ public sealed class SearchOrderAllListQueryHandler(
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return new PagedResult<OrderAllListItemDto>(rows, page, pageSize, filteredOrders.Count);
|
||||
return new PagedResult<OrderAllListItemDto>(rows, page, pageSize, totalCount);
|
||||
}
|
||||
|
||||
private async Task<HashSet<long>> LoadRefundedOrderIdsAsync(
|
||||
IReadOnlyCollection<Order> orders,
|
||||
IReadOnlyCollection<long> orderIds,
|
||||
long tenantId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var orderIds = orders.Select(order => order.Id).ToList();
|
||||
if (orderIds.Count == 0)
|
||||
{
|
||||
return [];
|
||||
@@ -116,23 +110,4 @@ public sealed class SearchOrderAllListQueryHandler(
|
||||
|
||||
return $"{first}等{totalQuantity}件";
|
||||
}
|
||||
|
||||
private static IEnumerable<Order> ApplySorting(
|
||||
IReadOnlyCollection<Order> orders,
|
||||
string? sortBy,
|
||||
bool sortDescending)
|
||||
{
|
||||
return sortBy?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"amount" => sortDescending
|
||||
? orders.OrderByDescending(ResolveDisplayAmount).ThenByDescending(order => order.CreatedAt)
|
||||
: orders.OrderBy(ResolveDisplayAmount).ThenBy(order => order.CreatedAt),
|
||||
"status" => sortDescending
|
||||
? orders.OrderByDescending(order => order.Status).ThenByDescending(order => order.CreatedAt)
|
||||
: orders.OrderBy(order => order.Status).ThenBy(order => order.CreatedAt),
|
||||
_ => sortDescending
|
||||
? orders.OrderByDescending(order => order.CreatedAt)
|
||||
: orders.OrderBy(order => order.CreatedAt)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,40 @@ public interface IOrderRepository
|
||||
string? keyword,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 全部订单分页筛选查询。
|
||||
/// </summary>
|
||||
/// <param name="tenantId">租户 ID。</param>
|
||||
/// <param name="storeId">门店 ID。</param>
|
||||
/// <param name="startAt">开始时间(含)。</param>
|
||||
/// <param name="endAt">结束时间(不含)。</param>
|
||||
/// <param name="status">订单状态。</param>
|
||||
/// <param name="refundedOnly">是否仅退款。</param>
|
||||
/// <param name="deliveryType">履约方式。</param>
|
||||
/// <param name="paymentMethod">支付方式。</param>
|
||||
/// <param name="keyword">关键词(订单号/手机号)。</param>
|
||||
/// <param name="sortBy">排序字段。</param>
|
||||
/// <param name="sortDescending">是否倒序。</param>
|
||||
/// <param name="page">页码。</param>
|
||||
/// <param name="pageSize">每页条数。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>分页订单与总数。</returns>
|
||||
Task<(IReadOnlyList<Order> Items, int TotalCount)> SearchAllOrdersPageAsync(
|
||||
long tenantId,
|
||||
long? storeId,
|
||||
DateTime? startAt,
|
||||
DateTime? endAt,
|
||||
OrderStatus? status,
|
||||
bool refundedOnly,
|
||||
DeliveryType? deliveryType,
|
||||
PaymentMethod? paymentMethod,
|
||||
string? keyword,
|
||||
string? sortBy,
|
||||
bool sortDescending,
|
||||
int page,
|
||||
int pageSize,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 获取订单明细行。
|
||||
/// </summary>
|
||||
|
||||
@@ -71,52 +71,16 @@ public sealed class EfOrderRepository(TakeoutAppDbContext context) : IOrderRepos
|
||||
string? keyword,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = context.Orders
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId);
|
||||
|
||||
if (storeId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.StoreId == storeId.Value);
|
||||
}
|
||||
|
||||
if (startAt.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.CreatedAt >= startAt.Value);
|
||||
}
|
||||
|
||||
if (endAt.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.CreatedAt < endAt.Value);
|
||||
}
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.Status == status.Value);
|
||||
}
|
||||
|
||||
if (deliveryType.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.DeliveryType == deliveryType.Value);
|
||||
}
|
||||
|
||||
if (paymentMethod.HasValue)
|
||||
{
|
||||
query = query.Where(x =>
|
||||
context.PaymentRecords.Any(record =>
|
||||
record.TenantId == tenantId &&
|
||||
record.OrderId == x.Id &&
|
||||
record.Method == paymentMethod.Value &&
|
||||
(record.Status == PaymentStatus.Paid || record.Status == PaymentStatus.Refunded)));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
var normalized = keyword.Trim();
|
||||
query = query.Where(x =>
|
||||
x.OrderNo.Contains(normalized) ||
|
||||
(x.CustomerPhone != null && x.CustomerPhone.Contains(normalized)));
|
||||
}
|
||||
var query = BuildSearchAllOrdersQuery(
|
||||
tenantId,
|
||||
storeId,
|
||||
startAt,
|
||||
endAt,
|
||||
status,
|
||||
false,
|
||||
deliveryType,
|
||||
paymentMethod,
|
||||
keyword);
|
||||
|
||||
return await query
|
||||
.OrderByDescending(x => x.CreatedAt)
|
||||
@@ -124,6 +88,48 @@ public sealed class EfOrderRepository(TakeoutAppDbContext context) : IOrderRepos
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<(IReadOnlyList<Order> Items, int TotalCount)> SearchAllOrdersPageAsync(
|
||||
long tenantId,
|
||||
long? storeId,
|
||||
DateTime? startAt,
|
||||
DateTime? endAt,
|
||||
OrderStatus? status,
|
||||
bool refundedOnly,
|
||||
DeliveryType? deliveryType,
|
||||
PaymentMethod? paymentMethod,
|
||||
string? keyword,
|
||||
string? sortBy,
|
||||
bool sortDescending,
|
||||
int page,
|
||||
int pageSize,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var normalizedPage = Math.Max(1, page);
|
||||
var normalizedPageSize = Math.Clamp(pageSize, 1, 200);
|
||||
|
||||
var query = BuildSearchAllOrdersQuery(
|
||||
tenantId,
|
||||
storeId,
|
||||
startAt,
|
||||
endAt,
|
||||
status,
|
||||
refundedOnly,
|
||||
deliveryType,
|
||||
paymentMethod,
|
||||
keyword);
|
||||
|
||||
var total = await query.CountAsync(cancellationToken);
|
||||
var sorted = ApplyOrderSorting(query, sortBy, sortDescending);
|
||||
|
||||
var items = await sorted
|
||||
.Skip((normalizedPage - 1) * normalizedPageSize)
|
||||
.Take(normalizedPageSize)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return (items, total);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<OrderItem>> GetItemsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -306,4 +312,105 @@ public sealed class EfOrderRepository(TakeoutAppDbContext context) : IOrderRepos
|
||||
|
||||
context.Orders.Remove(existing);
|
||||
}
|
||||
|
||||
private IQueryable<Order> BuildSearchAllOrdersQuery(
|
||||
long tenantId,
|
||||
long? storeId,
|
||||
DateTime? startAt,
|
||||
DateTime? endAt,
|
||||
OrderStatus? status,
|
||||
bool refundedOnly,
|
||||
DeliveryType? deliveryType,
|
||||
PaymentMethod? paymentMethod,
|
||||
string? keyword)
|
||||
{
|
||||
var query = context.Orders
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId);
|
||||
|
||||
if (storeId.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.StoreId == storeId.Value);
|
||||
}
|
||||
|
||||
if (startAt.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.CreatedAt >= startAt.Value);
|
||||
}
|
||||
|
||||
if (endAt.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.CreatedAt < endAt.Value);
|
||||
}
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.Status == status.Value);
|
||||
}
|
||||
|
||||
if (deliveryType.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.DeliveryType == deliveryType.Value);
|
||||
}
|
||||
|
||||
if (paymentMethod.HasValue)
|
||||
{
|
||||
query = query.Where(x =>
|
||||
context.PaymentRecords.Any(record =>
|
||||
record.TenantId == tenantId &&
|
||||
record.OrderId == x.Id &&
|
||||
record.Method == paymentMethod.Value &&
|
||||
(record.Status == PaymentStatus.Paid || record.Status == PaymentStatus.Refunded)));
|
||||
}
|
||||
|
||||
if (refundedOnly)
|
||||
{
|
||||
query = query.Where(x =>
|
||||
context.RefundRequests.Any(refund =>
|
||||
refund.TenantId == tenantId &&
|
||||
refund.OrderId == x.Id &&
|
||||
refund.Status == RefundStatus.Refunded));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
var normalized = keyword.Trim();
|
||||
query = query.Where(x =>
|
||||
x.OrderNo.Contains(normalized) ||
|
||||
(x.CustomerPhone != null && x.CustomerPhone.Contains(normalized)));
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private static IOrderedQueryable<Order> ApplyOrderSorting(
|
||||
IQueryable<Order> query,
|
||||
string? sortBy,
|
||||
bool sortDescending)
|
||||
{
|
||||
return sortBy?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"amount" => sortDescending
|
||||
? query
|
||||
.OrderByDescending(order => order.PaidAmount > 0 ? order.PaidAmount : order.PayableAmount)
|
||||
.ThenByDescending(order => order.CreatedAt)
|
||||
.ThenByDescending(order => order.Id)
|
||||
: query
|
||||
.OrderBy(order => order.PaidAmount > 0 ? order.PaidAmount : order.PayableAmount)
|
||||
.ThenBy(order => order.CreatedAt)
|
||||
.ThenBy(order => order.Id),
|
||||
"status" => sortDescending
|
||||
? query
|
||||
.OrderByDescending(order => order.Status)
|
||||
.ThenByDescending(order => order.CreatedAt)
|
||||
.ThenByDescending(order => order.Id)
|
||||
: query
|
||||
.OrderBy(order => order.Status)
|
||||
.ThenBy(order => order.CreatedAt)
|
||||
.ThenBy(order => order.Id),
|
||||
_ => sortDescending
|
||||
? query.OrderByDescending(order => order.CreatedAt).ThenByDescending(order => order.Id)
|
||||
: query.OrderBy(order => order.CreatedAt).ThenBy(order => order.Id)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user