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)
|
public async Task<PagedResult<OrderAllListItemDto>> Handle(SearchOrderAllListQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tenantId = tenantProvider.GetCurrentTenantId();
|
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,
|
tenantId,
|
||||||
request.StoreId,
|
request.StoreId,
|
||||||
request.StartAt,
|
request.StartAt,
|
||||||
request.EndAt,
|
request.EndAt,
|
||||||
request.Status,
|
request.Status,
|
||||||
|
request.RefundedOnly,
|
||||||
request.DeliveryType,
|
request.DeliveryType,
|
||||||
request.PaymentMethod,
|
request.PaymentMethod,
|
||||||
request.Keyword,
|
request.Keyword,
|
||||||
|
request.SortBy,
|
||||||
|
request.SortDescending,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
var filteredOrders = orders.ToList();
|
var refundedSet = await LoadRefundedOrderIdsAsync(
|
||||||
var refundedSet = await LoadRefundedOrderIdsAsync(filteredOrders, tenantId, cancellationToken);
|
pagedOrders.Select(order => order.Id).ToList(),
|
||||||
if (request.RefundedOnly)
|
tenantId,
|
||||||
{
|
cancellationToken);
|
||||||
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 itemsLookup = await orderRepository.GetItemsByOrderIdsAsync(
|
var itemsLookup = await orderRepository.GetItemsByOrderIdsAsync(
|
||||||
pagedOrders.Select(order => order.Id).ToList(),
|
pagedOrders.Select(order => order.Id).ToList(),
|
||||||
@@ -75,15 +70,14 @@ public sealed class SearchOrderAllListQueryHandler(
|
|||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return new PagedResult<OrderAllListItemDto>(rows, page, pageSize, filteredOrders.Count);
|
return new PagedResult<OrderAllListItemDto>(rows, page, pageSize, totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HashSet<long>> LoadRefundedOrderIdsAsync(
|
private async Task<HashSet<long>> LoadRefundedOrderIdsAsync(
|
||||||
IReadOnlyCollection<Order> orders,
|
IReadOnlyCollection<long> orderIds,
|
||||||
long tenantId,
|
long tenantId,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var orderIds = orders.Select(order => order.Id).ToList();
|
|
||||||
if (orderIds.Count == 0)
|
if (orderIds.Count == 0)
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
@@ -116,23 +110,4 @@ public sealed class SearchOrderAllListQueryHandler(
|
|||||||
|
|
||||||
return $"{first}等{totalQuantity}件";
|
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,
|
string? keyword,
|
||||||
CancellationToken cancellationToken = default);
|
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>
|
||||||
/// 获取订单明细行。
|
/// 获取订单明细行。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -71,52 +71,16 @@ public sealed class EfOrderRepository(TakeoutAppDbContext context) : IOrderRepos
|
|||||||
string? keyword,
|
string? keyword,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var query = context.Orders
|
var query = BuildSearchAllOrdersQuery(
|
||||||
.AsNoTracking()
|
tenantId,
|
||||||
.Where(x => x.TenantId == tenantId);
|
storeId,
|
||||||
|
startAt,
|
||||||
if (storeId.HasValue)
|
endAt,
|
||||||
{
|
status,
|
||||||
query = query.Where(x => x.StoreId == storeId.Value);
|
false,
|
||||||
}
|
deliveryType,
|
||||||
|
paymentMethod,
|
||||||
if (startAt.HasValue)
|
keyword);
|
||||||
{
|
|
||||||
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)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return await query
|
return await query
|
||||||
.OrderByDescending(x => x.CreatedAt)
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
@@ -124,6 +88,48 @@ public sealed class EfOrderRepository(TakeoutAppDbContext context) : IOrderRepos
|
|||||||
.ToListAsync(cancellationToken);
|
.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 />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<OrderItem>> GetItemsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
|
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);
|
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