feat(order): add all-orders APIs and query workflow
All checks were successful
Build and Deploy TenantApi + SkuWorker / build-and-deploy (push) Successful in 1m51s

This commit is contained in:
2026-02-27 10:09:19 +08:00
parent dd90bdfe0f
commit 11a1521b6a
17 changed files with 2020 additions and 0 deletions

View File

@@ -1,7 +1,9 @@
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Deliveries.Entities;
using TakeoutSaaS.Domain.Orders.Entities;
using TakeoutSaaS.Domain.Orders.Enums;
using TakeoutSaaS.Domain.Orders.Repositories;
using TakeoutSaaS.Domain.Payments.Entities;
using TakeoutSaaS.Domain.Payments.Enums;
using TakeoutSaaS.Infrastructure.App.Persistence;
@@ -57,6 +59,70 @@ public sealed class EfOrderRepository(TakeoutAppDbContext context) : IOrderRepos
return orders;
}
/// <inheritdoc />
public async Task<IReadOnlyList<Order>> SearchAllOrdersAsync(
long tenantId,
long? storeId,
DateTime? startAt,
DateTime? endAt,
OrderStatus? status,
DeliveryType? deliveryType,
PaymentMethod? paymentMethod,
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));
}
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
.OrderByDescending(x => x.CreatedAt)
.ThenByDescending(x => x.Id)
.ToListAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<IReadOnlyList<OrderItem>> GetItemsAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
@@ -69,6 +135,29 @@ public sealed class EfOrderRepository(TakeoutAppDbContext context) : IOrderRepos
return items;
}
/// <inheritdoc />
public async Task<IReadOnlyDictionary<long, IReadOnlyList<OrderItem>>> GetItemsByOrderIdsAsync(
IReadOnlyCollection<long> orderIds,
long tenantId,
CancellationToken cancellationToken = default)
{
if (orderIds.Count == 0)
{
return new Dictionary<long, IReadOnlyList<OrderItem>>();
}
var items = await context.OrderItems
.AsNoTracking()
.Where(x => x.TenantId == tenantId && orderIds.Contains(x.OrderId))
.OrderBy(x => x.OrderId)
.ThenBy(x => x.Id)
.ToListAsync(cancellationToken);
return items
.GroupBy(item => item.OrderId)
.ToDictionary(group => group.Key, group => (IReadOnlyList<OrderItem>)group.ToList());
}
/// <inheritdoc />
public async Task<IReadOnlyList<OrderStatusHistory>> GetStatusHistoryAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
@@ -93,6 +182,55 @@ public sealed class EfOrderRepository(TakeoutAppDbContext context) : IOrderRepos
return refunds;
}
/// <inheritdoc />
public async Task<IReadOnlySet<long>> GetRefundedOrderIdsAsync(
IReadOnlyCollection<long> orderIds,
long tenantId,
CancellationToken cancellationToken = default)
{
if (orderIds.Count == 0)
{
return new HashSet<long>();
}
var result = await context.RefundRequests
.AsNoTracking()
.Where(x =>
x.TenantId == tenantId &&
orderIds.Contains(x.OrderId) &&
x.Status == RefundStatus.Refunded)
.Select(x => x.OrderId)
.Distinct()
.ToListAsync(cancellationToken);
return result.ToHashSet();
}
/// <inheritdoc />
public Task<PaymentRecord?> GetLatestPaymentRecordAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
return context.PaymentRecords
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.OrderByDescending(x => x.PaidAt ?? DateTime.MinValue)
.ThenByDescending(x => x.CreatedAt)
.ThenByDescending(x => x.Id)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public Task<DeliveryOrder?> GetLatestDeliveryOrderAsync(long orderId, long tenantId, CancellationToken cancellationToken = default)
{
return context.DeliveryOrders
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.OrderId == orderId)
.OrderByDescending(x => x.DeliveredAt ?? DateTime.MinValue)
.ThenByDescending(x => x.PickedUpAt ?? DateTime.MinValue)
.ThenByDescending(x => x.CreatedAt)
.ThenByDescending(x => x.Id)
.FirstOrDefaultAsync(cancellationToken);
}
/// <inheritdoc />
public Task AddOrderAsync(Order order, CancellationToken cancellationToken = default)
{