perf: 运单列表改为轻量映射,减小 /order/list 载荷与 enrichment 耗时
All checks were successful
Build and Deploy / deploy (push) Successful in 1m18s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m18s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
af7909948e
commit
d22fe7220d
@ -16,6 +16,7 @@ using Oms.Application.Storage;
|
||||
using Oms.Domain.OrdersAggregate;
|
||||
using Oms.Domain.ChannelsAggregate.Consts;
|
||||
using Oms.Domain.ChannelsAggregate.Entities;
|
||||
using Oms.Domain.CustomersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.SystemAggregate.Entities;
|
||||
@ -296,6 +297,298 @@ public partial class OrderService
|
||||
return outputs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 列表页专用映射:不加载申报明细/费用明细,并行 enrichment,减小 JSON 与 DB 开销。
|
||||
/// </summary>
|
||||
private async Task<List<OrderOutput>> MapOrderListOutputsAsync(IReadOnlyCollection<Order> orders)
|
||||
{
|
||||
if (orders.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var orderIds = orders.Select(x => x.Id).ToArray();
|
||||
var warehouseIds = orders.Where(x => x.WarehouseId.HasValue).Select(x => x.WarehouseId!.Value).Distinct().ToArray();
|
||||
var productCodes = orders
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.ProductCode))
|
||||
.Select(x => x.ProductCode!.Trim())
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
var channelCodes = orders
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.ChannelCode))
|
||||
.Select(x => x.ChannelCode!.Trim())
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
var countryCodes = orders
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.ConsigneeCountry))
|
||||
.Select(x => x.ConsigneeCountry!.Trim())
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
var customerIds = orders
|
||||
.Where(x => x.CustomerId is > 0)
|
||||
.Select(x => x.CustomerId!.Value)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
var pickupOrderIds = orders
|
||||
.Where(x => x.PickupOrderId is > 0)
|
||||
.Select(x => x.PickupOrderId!.Value)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
var sendersTask = OrderRepository.GetSendersByOrderIdsAsync(orderIds);
|
||||
var recipientsTask = OrderRepository.GetRecipientsByOrderIdsAsync(orderIds);
|
||||
var weightsTask = OrderRepository.GetWeightsByOrderIdsAsync(orderIds);
|
||||
var receivableSumTask = OrderRepository.GetReceivableSumByOrderIdsAsync(orderIds);
|
||||
var warehousesTask = warehouseIds.Length == 0
|
||||
? Task.FromResult(new List<WarehouseAddress>())
|
||||
: WarehouseAddressRepository.GetListAsync(x => warehouseIds.Contains(x.Id));
|
||||
var productsTask = productCodes.Length == 0
|
||||
? Task.FromResult(new List<SalesProduct>())
|
||||
: SalesProductRepository.GetListAsync(x => productCodes.Contains(x.ProductCode));
|
||||
var channelsTask = channelCodes.Length == 0
|
||||
? Task.FromResult(new List<ServiceChannel>())
|
||||
: ServiceChannelRepository.GetListAsync(x => channelCodes.Contains(x.ChannelCode));
|
||||
var countriesTask = countryCodes.Length == 0
|
||||
? Task.FromResult(new List<Country>())
|
||||
: CountryRepository.GetListAsync(x => countryCodes.Contains(x.CountryCode));
|
||||
var auditUserNameTask = UserDisplayNameResolver.ResolveManyAsync(
|
||||
orders.SelectMany(x => new[] { x.CreateUserId, x.UpdateUserId }));
|
||||
var customersTask = customerIds.Length == 0
|
||||
? Task.FromResult(new List<Customer>())
|
||||
: CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
||||
var pickupOrdersTask = pickupOrderIds.Length == 0
|
||||
? Task.FromResult(new List<PickupOrder>())
|
||||
: PickupOrderRepository.GetListAsync(x => pickupOrderIds.Contains(x.Id));
|
||||
var bindSummaryTask = LoadStockBindListSummariesAsync(orderIds);
|
||||
|
||||
await Task.WhenAll(
|
||||
sendersTask,
|
||||
recipientsTask,
|
||||
weightsTask,
|
||||
receivableSumTask,
|
||||
warehousesTask,
|
||||
productsTask,
|
||||
channelsTask,
|
||||
countriesTask,
|
||||
auditUserNameTask,
|
||||
customersTask,
|
||||
pickupOrdersTask,
|
||||
bindSummaryTask);
|
||||
|
||||
var senders = await sendersTask;
|
||||
var recipients = await recipientsTask;
|
||||
var weights = await weightsTask;
|
||||
var receivableSumMap = await receivableSumTask;
|
||||
var warehouses = await warehousesTask;
|
||||
var products = await productsTask;
|
||||
var channels = await channelsTask;
|
||||
var countries = await countriesTask;
|
||||
var auditUserNameMap = await auditUserNameTask;
|
||||
var customers = await customersTask;
|
||||
var pickupOrders = await pickupOrdersTask;
|
||||
var bindSummaryMap = await bindSummaryTask;
|
||||
|
||||
var senderMap = senders.GroupBy(x => x.OrderId).ToDictionary(x => x.Key, x => x.First());
|
||||
var recipientMap = recipients.GroupBy(x => x.OrderId).ToDictionary(x => x.Key, x => x.First());
|
||||
var weightMap = weights.GroupBy(x => x.OrderId).ToDictionary(x => x.Key, x => x.ToList());
|
||||
var warehouseMap = warehouses.ToDictionary(x => x.Id);
|
||||
var productMap = products.ToDictionary(x => x.ProductCode, StringComparer.OrdinalIgnoreCase);
|
||||
var channelMap = channels.ToDictionary(x => x.ChannelCode, StringComparer.OrdinalIgnoreCase);
|
||||
var countryMap = countries.ToDictionary(x => x.CountryCode, StringComparer.OrdinalIgnoreCase);
|
||||
var customerMap = customers.ToDictionary(x => x.Id);
|
||||
var pickupNoMap = pickupOrders.ToDictionary(x => x.Id, x => x.PickupNo);
|
||||
var carrierWaybillNoMap = pickupOrders
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.CarrierWaybillNo))
|
||||
.ToDictionary(x => x.Id, x => x.CarrierWaybillNo!);
|
||||
|
||||
var addressCountryIds = senders
|
||||
.Select(x => x.Country)
|
||||
.Concat(recipients.Select(x => x.Country))
|
||||
.Where(x => x.HasValue)
|
||||
.Select(x => x!.Value)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
var addressCountries = addressCountryIds.Length == 0
|
||||
? []
|
||||
: await CountryRepository.GetListAsync(x => addressCountryIds.Contains(x.Id));
|
||||
var countryIdCodeMap = addressCountries.ToDictionary(
|
||||
x => x.Id,
|
||||
x => x.CountryCode ?? string.Empty);
|
||||
|
||||
var outputs = orders.Select(order =>
|
||||
{
|
||||
senderMap.TryGetValue(order.Id, out var sender);
|
||||
recipientMap.TryGetValue(order.Id, out var recipient);
|
||||
weightMap.TryGetValue(order.Id, out var orderWeights);
|
||||
var chargeableWeight = ResolveDisplayChargeableWeight(orderWeights);
|
||||
decimal? receivableAmount = receivableSumMap.TryGetValue(order.Id, out var sum)
|
||||
? sum
|
||||
: null;
|
||||
var additionalServices = NormalizeAdditionalServiceCodes(
|
||||
OmsDocHelper.DeserializeJson<List<string>>(order.AdditionalServices));
|
||||
var hasInsurance = additionalServices.Any(x =>
|
||||
string.Equals(x, "insurance", StringComparison.OrdinalIgnoreCase));
|
||||
string? customerName = null;
|
||||
string? customerCode = null;
|
||||
if (order.CustomerId is > 0 && customerMap.TryGetValue(order.CustomerId.Value, out var customer))
|
||||
{
|
||||
customerName = customer.CustomerName;
|
||||
customerCode = customer.CustomerCode;
|
||||
}
|
||||
|
||||
SalesProduct? product = null;
|
||||
if (!string.IsNullOrWhiteSpace(order.ProductCode))
|
||||
{
|
||||
productMap.TryGetValue(order.ProductCode.Trim(), out product);
|
||||
}
|
||||
|
||||
bindSummaryMap.TryGetValue(order.Id, out var bindSummary);
|
||||
|
||||
return new OrderOutput
|
||||
{
|
||||
Id = order.Id,
|
||||
ClientNo = order.ClientNo,
|
||||
TrackNo = order.TrackNo,
|
||||
DownstreamServiceNo = order.DownstreamServiceNo,
|
||||
ServiceNo = order.ServiceNo,
|
||||
ProductCode = order.ProductCode,
|
||||
ProductName = product?.ProductName,
|
||||
MaxTimeliness = product?.MaxTimeliness,
|
||||
ChannelCode = order.ChannelCode,
|
||||
DownstreamSupplierCode = order.DownstreamSupplierCode,
|
||||
DownstreamSupplierName = order.DownstreamSupplierName,
|
||||
DownstreamAppKey = order.DownstreamAppKey,
|
||||
DownstreamAppId = order.DownstreamAppId,
|
||||
ChannelForecastCode = order.ChannelForecastCode,
|
||||
ChannelName = !string.IsNullOrWhiteSpace(order.ChannelCode)
|
||||
&& channelMap.TryGetValue(order.ChannelCode.Trim(), out var channel)
|
||||
? channel.ChannelName
|
||||
: null,
|
||||
ConsigneeCountry = order.ConsigneeCountry,
|
||||
ConsigneeCountryName = ResolveConsigneeCountryName(order, countryMap),
|
||||
CargoAttribute = order.CargoAttribute,
|
||||
WarehouseId = order.WarehouseId,
|
||||
Warehouse = order.WarehouseId.HasValue && warehouseMap.TryGetValue(order.WarehouseId.Value, out var warehouse)
|
||||
? warehouse.WarehouseName
|
||||
: null,
|
||||
CustomerId = order.CustomerId,
|
||||
CustomerName = customerName,
|
||||
CustomerCode = customerCode,
|
||||
OrderSource = string.IsNullOrWhiteSpace(order.OrderSource)
|
||||
? OrderSourceCodes.Manual
|
||||
: order.OrderSource,
|
||||
PickupTime = order.PickupTime,
|
||||
CustomerSenderAddressId = order.CustomerSenderAddressId,
|
||||
InboundMethod = order.InboundMethod,
|
||||
FulfillmentType = string.IsNullOrWhiteSpace(order.FulfillmentType)
|
||||
? FulfillmentTypeCodes.Direct
|
||||
: order.FulfillmentType,
|
||||
FulfillmentSource = order.FulfillmentSource,
|
||||
StockBindStatus = bindSummary?.BindStatus ?? "none",
|
||||
StockBindLineCount = bindSummary?.LineCount ?? 0,
|
||||
StockBindQtyTotal = bindSummary?.QtyTotal ?? 0,
|
||||
DomesticTrackingNos = bindSummary?.TrackingNosText,
|
||||
FulfillProgress = FulfillmentProgressCodes.Resolve(
|
||||
FulfillmentTypeCodes.IsWarehouse(order.FulfillmentType),
|
||||
bindSummary?.BindStatus ?? "none",
|
||||
bindSummary?.LineCount ?? 0,
|
||||
bindSummary?.HasInTransitParcel ?? false),
|
||||
PrimaryBinCode = bindSummary?.PrimaryBinCode,
|
||||
PrimarySkuCodes = bindSummary?.PrimarySkuCodes,
|
||||
PickupOrderId = order.PickupOrderId,
|
||||
PickupNo = order.PickupOrderId is > 0
|
||||
&& pickupNoMap.TryGetValue(order.PickupOrderId.Value, out var pickupNo)
|
||||
? pickupNo
|
||||
: null,
|
||||
CarrierWaybillNo = order.PickupOrderId is > 0
|
||||
&& carrierWaybillNoMap.TryGetValue(order.PickupOrderId.Value, out var carrierWaybillNo)
|
||||
? carrierWaybillNo
|
||||
: null,
|
||||
RequestId = order.RequestId,
|
||||
ExportCustomsMode = order.ExportCustomsMode,
|
||||
ExportQualificationId = order.ExportQualificationId,
|
||||
PredeclareFailReason = string.IsNullOrWhiteSpace(order.PredeclareFailReason)
|
||||
? null
|
||||
: order.PredeclareFailReason.Trim(),
|
||||
AllocationPendingReason = string.IsNullOrWhiteSpace(order.AllocationPendingReason)
|
||||
? null
|
||||
: order.AllocationPendingReason.Trim(),
|
||||
AllocationScheme = string.IsNullOrWhiteSpace(order.AllocationScheme)
|
||||
? null
|
||||
: order.AllocationScheme.Trim(),
|
||||
// 分页已 IgnoreColumns(AllocationTraceJson);与写入路径一并落库的 TraceTime 作为布尔依据
|
||||
HasAllocationTrace = order.AllocationTraceTime.HasValue
|
||||
|| !string.IsNullOrWhiteSpace(order.AllocationTraceJson),
|
||||
DeclaredCurrency = order.DeclaredCurrency,
|
||||
DeclaredSum = order.DeclaredSum,
|
||||
Quantity = order.Quantity,
|
||||
IsCod = order.IsCod > 0,
|
||||
CodCurrency = order.CodCurrency,
|
||||
CodSum = order.CodSum,
|
||||
OrderStatus = OmsDocHelper.GetOrderStatusName(order.OrderStatus),
|
||||
Status = order.OrderStatus,
|
||||
HandleStatus = OrderHandleStatusCodes.IsProcessed(order.HandleStatus)
|
||||
? OrderHandleStatusCodes.Processed
|
||||
: OrderHandleStatusCodes.Pending,
|
||||
HandleStatusName = OrderHandleStatusCodes.GetDisplayName(order.HandleStatus),
|
||||
IssueType = order.IssueType,
|
||||
IssueTypeName = IssueTypeCodes.GetDisplayName(order.IssueType),
|
||||
IssueTitle = order.IssueTitle,
|
||||
IssueContent = order.IssueContent,
|
||||
IssueTemplateId = order.IssueTemplateId,
|
||||
IssueRemark = order.IssueRemark,
|
||||
IssueTime = order.IssueTime,
|
||||
ClaimType = order.ClaimType,
|
||||
ClaimTypeName = ClaimTypeCodes.GetDisplayName(order.ClaimType),
|
||||
ClaimAmount = order.ClaimAmount,
|
||||
ClaimCurrency = order.ClaimCurrency,
|
||||
ClaimReason = order.ClaimReason,
|
||||
ClaimTime = order.ClaimTime,
|
||||
PreviousOrderStatus = order.PreviousOrderStatus,
|
||||
ReceiveTime = order.ReceiveTime,
|
||||
ShipTime = order.ShipTime,
|
||||
PayTime = order.PayTime,
|
||||
IsPaid = order.IsPaid > 0,
|
||||
SignTime = order.SignTime,
|
||||
IsIntercept = order.IsIntercept > 0,
|
||||
IsRemote = order.IsRemote > 0,
|
||||
AdditionalServices = additionalServices,
|
||||
HasInsurance = hasInsurance,
|
||||
ForecastReceivableAmount = order.ForecastReceivableAmount,
|
||||
ForecastPayableAmount = order.ForecastPayableAmount,
|
||||
ReceivableSubtotal = order.ReceivableSubtotal,
|
||||
LevelDiscountAmount = order.LevelDiscountAmount,
|
||||
CouponDiscountAmount = order.CouponDiscountAmount,
|
||||
ActualPayAmount = order.PayableAmount,
|
||||
CouponInstanceId = order.CouponInstanceId,
|
||||
SalesProductRulesVersion = string.IsNullOrWhiteSpace(order.SalesProductRulesVersion)
|
||||
? null
|
||||
: order.SalesProductRulesVersion.Trim(),
|
||||
SalesProductPriceVersion = string.IsNullOrWhiteSpace(order.SalesProductPriceVersion)
|
||||
? null
|
||||
: order.SalesProductPriceVersion.Trim(),
|
||||
CreateTime = order.CreateTime,
|
||||
CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(order.CreateUserId, auditUserNameMap),
|
||||
UpdateTime = OmsDocHelper.NullIfDefault(order.UpdateTime),
|
||||
UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(order.UpdateUserId, auditUserNameMap),
|
||||
Sender = MapSender(sender, countryIdCodeMap),
|
||||
Receiver = MapReceiver(recipient, countryIdCodeMap),
|
||||
Items = [],
|
||||
WeightList = [],
|
||||
FeeList = [],
|
||||
ChargeableWeight = chargeableWeight,
|
||||
ReceivableAmount = receivableAmount,
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
if (OmsSessionHelper.IsCustomerLogin(CurrentUser))
|
||||
{
|
||||
RedactChannelFieldsForCustomer(outputs);
|
||||
}
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
private sealed class StockBindListSummary
|
||||
{
|
||||
public string BindStatus { get; init; } = "none";
|
||||
|
||||
@ -70,7 +70,11 @@ public partial class OrderService : ApplicationService
|
||||
pageIndex,
|
||||
pageSize,
|
||||
filter);
|
||||
return new OmsPageResult<OrderOutput> { Items = await MapOrderOutputsAsync(orders), Total = total };
|
||||
return new OmsPageResult<OrderOutput>
|
||||
{
|
||||
Items = await MapOrderListOutputsAsync(orders),
|
||||
Total = total,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -70,6 +70,13 @@ public interface IOrderRepository : IRepository<Order>
|
||||
|
||||
Task<List<OrderFee>> GetFeesByOrderIdsAsync(IEnumerable<int> orderIds, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 列表页:按订单聚合应收金额合计(不加载费用明细行)。
|
||||
/// </summary>
|
||||
Task<Dictionary<int, decimal>> GetReceivableSumByOrderIdsAsync(
|
||||
IEnumerable<int> orderIds,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<(List<OrderReceivablePayableRow> Items, int Total)> GetReceivablePayablePagedListAsync(
|
||||
int pageIndex,
|
||||
int pageSize,
|
||||
|
||||
@ -23,7 +23,9 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
var query = ApplyListFilter(db.Queryable<Order>(), filter);
|
||||
var total = await query.CountAsync(cancellationToken);
|
||||
query = ApplyListSort(query, filter);
|
||||
// 列表仅需 HasAllocationTrace 布尔语义,勿拉取大字段 AllocationTraceJson
|
||||
var items = await query
|
||||
.IgnoreColumns(x => x.AllocationTraceJson)
|
||||
.ToPageListAsync(pageIndex, pageSize, cancellationToken);
|
||||
return (items, total);
|
||||
}
|
||||
@ -1104,6 +1106,34 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 列表页:按订单聚合应收金额合计(不加载费用明细行)。
|
||||
/// </summary>
|
||||
public async Task<Dictionary<int, decimal>> GetReceivableSumByOrderIdsAsync(
|
||||
IEnumerable<int> orderIds,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var ids = orderIds.Distinct().ToArray();
|
||||
if (ids.Length == 0)
|
||||
{
|
||||
return new Dictionary<int, decimal>();
|
||||
}
|
||||
|
||||
var db = await GetDbContextAsync(cancellationToken);
|
||||
var rows = await db.Queryable<OrderFee>()
|
||||
.Where(x => ids.Contains(x.OrderId)
|
||||
&& !x.IsDeleted
|
||||
&& x.Type == OrderFeeTypeCodes.Receivable)
|
||||
.GroupBy(x => x.OrderId)
|
||||
.Select(x => new
|
||||
{
|
||||
OrderId = x.OrderId,
|
||||
Amount = SqlFunc.AggregateSum(x.Amount),
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
return rows.ToDictionary(x => x.OrderId, x => x.Amount);
|
||||
}
|
||||
|
||||
public async Task<(List<OrderReceivablePayableRow> Items, int Total)> GetReceivablePayablePagedListAsync(
|
||||
int pageIndex,
|
||||
int pageSize,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user