diff --git a/src/Oms.Application/Services/Orders/OrderService.Helpers.cs b/src/Oms.Application/Services/Orders/OrderService.Helpers.cs index d61441a..9e91592 100644 --- a/src/Oms.Application/Services/Orders/OrderService.Helpers.cs +++ b/src/Oms.Application/Services/Orders/OrderService.Helpers.cs @@ -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; } + /// + /// 列表页专用映射:不加载申报明细/费用明细,并行 enrichment,减小 JSON 与 DB 开销。 + /// + private async Task> MapOrderListOutputsAsync(IReadOnlyCollection 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()) + : WarehouseAddressRepository.GetListAsync(x => warehouseIds.Contains(x.Id)); + var productsTask = productCodes.Length == 0 + ? Task.FromResult(new List()) + : SalesProductRepository.GetListAsync(x => productCodes.Contains(x.ProductCode)); + var channelsTask = channelCodes.Length == 0 + ? Task.FromResult(new List()) + : ServiceChannelRepository.GetListAsync(x => channelCodes.Contains(x.ChannelCode)); + var countriesTask = countryCodes.Length == 0 + ? Task.FromResult(new List()) + : 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()) + : CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id)); + var pickupOrdersTask = pickupOrderIds.Length == 0 + ? Task.FromResult(new List()) + : 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>(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"; diff --git a/src/Oms.Application/Services/Orders/OrderService.cs b/src/Oms.Application/Services/Orders/OrderService.cs index d4e4090..068e567 100644 --- a/src/Oms.Application/Services/Orders/OrderService.cs +++ b/src/Oms.Application/Services/Orders/OrderService.cs @@ -70,7 +70,11 @@ public partial class OrderService : ApplicationService pageIndex, pageSize, filter); - return new OmsPageResult { Items = await MapOrderOutputsAsync(orders), Total = total }; + return new OmsPageResult + { + Items = await MapOrderListOutputsAsync(orders), + Total = total, + }; } /// diff --git a/src/Oms.Domain/OrdersAggregate/Repositories/IOrderRepository.cs b/src/Oms.Domain/OrdersAggregate/Repositories/IOrderRepository.cs index c5d8ab0..1e1397d 100644 --- a/src/Oms.Domain/OrdersAggregate/Repositories/IOrderRepository.cs +++ b/src/Oms.Domain/OrdersAggregate/Repositories/IOrderRepository.cs @@ -70,6 +70,13 @@ public interface IOrderRepository : IRepository Task> GetFeesByOrderIdsAsync(IEnumerable orderIds, CancellationToken cancellationToken = default); + /// + /// 列表页:按订单聚合应收金额合计(不加载费用明细行)。 + /// + Task> GetReceivableSumByOrderIdsAsync( + IEnumerable orderIds, + CancellationToken cancellationToken = default); + Task<(List Items, int Total)> GetReceivablePayablePagedListAsync( int pageIndex, int pageSize, diff --git a/src/Oms.SqlSugarCore/Orders/OrderRepository.cs b/src/Oms.SqlSugarCore/Orders/OrderRepository.cs index ad4e985..9819fbd 100644 --- a/src/Oms.SqlSugarCore/Orders/OrderRepository.cs +++ b/src/Oms.SqlSugarCore/Orders/OrderRepository.cs @@ -23,7 +23,9 @@ public class OrderRepository : OmsSqlSugarRepository, IOrderRepository, I var query = ApplyListFilter(db.Queryable(), 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, IOrderRepository, I .ToListAsync(cancellationToken); } + /// + /// 列表页:按订单聚合应收金额合计(不加载费用明细行)。 + /// + public async Task> GetReceivableSumByOrderIdsAsync( + IEnumerable orderIds, + CancellationToken cancellationToken = default) + { + var ids = orderIds.Distinct().ToArray(); + if (ids.Length == 0) + { + return new Dictionary(); + } + + var db = await GetDbContextAsync(cancellationToken); + var rows = await db.Queryable() + .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 Items, int Total)> GetReceivablePayablePagedListAsync( int pageIndex, int pageSize,