admin/src/Oms.Application/Services/Orders/OrderService.Helpers.cs
zzh 341a318f0b feat: 客户发件地址与揽收订单,复用小包创建全链路。
新增地址簿 CRUD、pickup/create、订单 orderSource 筛选;附带包裹商品图片上传接口。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-15 18:58:48 +08:00

842 lines
33 KiB
C#

using System.Linq.Expressions;
using System.Text;
using System.Text.Json;
using Fake.Application;
using Fake.Users;
using Microsoft.AspNetCore.Mvc;
using MiniExcelLibs;
using Newtonsoft.Json;
using Oms.Application.DataPerm;
using Oms.Application.Dtos.Orders;
using Oms.Application.Dtos.Shared;
using Oms.Application.Messaging;
using Oms.Application.Services.Orders.Templates;
using Oms.Application.Shared;
using Oms.Domain.OrdersAggregate;
using Oms.Domain.ChannelsAggregate.Consts;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.SystemAggregate.Entities;
namespace Oms.Application.Services.Orders;
public partial class OrderService
{
private async Task<List<OrderOutput>> MapOrderOutputsAsync(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 senders = await OrderRepository.GetSendersByOrderIdsAsync(orderIds);
var recipients = await OrderRepository.GetRecipientsByOrderIdsAsync(orderIds);
var declarations = await OrderRepository.GetDeclarationsByOrderIdsAsync(orderIds);
var weights = await OrderRepository.GetWeightsByOrderIdsAsync(orderIds);
var fees = await OrderRepository.GetFeesByOrderIdsAsync(orderIds);
var warehouses = warehouseIds.Length == 0
? []
: await WarehouseAddressRepository.GetListAsync(x => warehouseIds.Contains(x.Id));
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 declarationMap = declarations.GroupBy(x => x.OrderId).ToDictionary(x => x.Key, x => x.ToList());
var weightMap = weights.GroupBy(x => x.OrderId).ToDictionary(x => x.Key, x => x.ToList());
var feeMap = fees.GroupBy(x => x.OrderId).ToDictionary(x => x.Key, x => x.ToList());
var warehouseMap = warehouses.ToDictionary(x => x.Id);
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 products = productCodes.Length == 0
? []
: await SalesProductRepository.GetListAsync(x => productCodes.Contains(x.ProductCode));
var channels = channelCodes.Length == 0
? []
: await ServiceChannelRepository.GetListAsync(x => channelCodes.Contains(x.ChannelCode));
var productMap = products.ToDictionary(x => x.ProductCode, StringComparer.OrdinalIgnoreCase);
var channelMap = channels.ToDictionary(x => x.ChannelCode, StringComparer.OrdinalIgnoreCase);
var countryCodes = orders
.Where(x => !string.IsNullOrWhiteSpace(x.ConsigneeCountry))
.Select(x => x.ConsigneeCountry!.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
var countries = countryCodes.Length == 0
? []
: await CountryRepository.GetListAsync(x => countryCodes.Contains(x.CountryCode));
var countryMap = countries.ToDictionary(x => x.CountryCode, StringComparer.OrdinalIgnoreCase);
var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
orders.SelectMany(x => new[] { x.CreateUserId, x.UpdateUserId }));
return orders.Select(order =>
{
senderMap.TryGetValue(order.Id, out var sender);
recipientMap.TryGetValue(order.Id, out var recipient);
declarationMap.TryGetValue(order.Id, out var items);
weightMap.TryGetValue(order.Id, out var orderWeights);
feeMap.TryGetValue(order.Id, out var orderFees);
var feeOutputs = (orderFees ?? []).Select(MapFee).ToList();
var chargeableWeight = ResolveDisplayChargeableWeight(orderWeights);
var receivableAmount = feeOutputs
.Where(x => x.Type == OrderFeeTypeCodes.Receivable)
.Sum(x => x.Amount);
return new OrderOutput
{
Id = order.Id,
ClientNo = order.ClientNo,
TrackNo = order.TrackNo,
ServiceNo = order.ServiceNo,
ProductCode = order.ProductCode,
ProductName = !string.IsNullOrWhiteSpace(order.ProductCode)
&& productMap.TryGetValue(order.ProductCode.Trim(), out var product)
? product.ProductName
: null,
ChannelCode = order.ChannelCode,
DownstreamSupplierCode = order.DownstreamSupplierCode,
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,
OrderSource = string.IsNullOrWhiteSpace(order.OrderSource)
? OrderSourceCodes.Manual
: order.OrderSource,
PickupTime = order.PickupTime,
CustomerSenderAddressId = order.CustomerSenderAddressId,
RequestId = order.RequestId,
ExportCustomsMode = order.ExportCustomsMode,
PredeclareFailReason = string.IsNullOrWhiteSpace(order.PredeclareFailReason)
? null
: order.PredeclareFailReason.Trim(),
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,
CreateTime = order.CreateTime,
CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(order.CreateUserId, auditUserNameMap),
UpdateTime = OmsDocHelper.NullIfDefault(order.UpdateTime),
UpdateBy = OmsRbacUserDisplayNameResolver.FormatUserId(order.UpdateUserId, auditUserNameMap),
Sender = MapSender(sender),
Receiver = MapReceiver(recipient),
Items = (items ?? []).Select(MapDeclaration).ToList(),
WeightList = (orderWeights ?? []).Select(MapWeight).ToList(),
FeeList = feeOutputs,
ChargeableWeight = chargeableWeight,
ReceivableAmount = feeOutputs.Count == 0 ? null : receivableAmount
};
}).ToList();
}
private async Task<List<object>> BuildOrderExportRowsAsync(IReadOnlyCollection<Order> orders)
{
var mapped = await MapOrderOutputsAsync(orders);
return mapped.Select(x => (object)new
{
x.Id,
x.ClientNo,
x.TrackNo,
x.ServiceNo,
x.ProductCode,
x.ProductName,
x.ChannelCode,
x.ChannelName,
x.CargoAttribute,
x.WarehouseId,
x.Warehouse,
x.CustomerId,
x.Status,
x.OrderStatus,
x.PredeclareFailReason,
x.ReceiveTime,
x.ShipTime,
x.PayTime,
x.IsPaid,
x.SignTime,
x.IsCod,
x.CodCurrency,
x.CodSum,
x.IsIntercept,
x.IsRemote,
x.CreateTime,
x.UpdateTime
}).ToList();
}
private async Task<OmsPageResult<OrderTemplateOutput>> GetTemplateOutputsAsync(OrderTemplateListInput input)
{
if (string.IsNullOrWhiteSpace(input.Type)
|| string.Equals(input.Type, "standard", StringComparison.OrdinalIgnoreCase))
{
await EnsureDedicatedSmallParcelStandardTemplateAsync();
}
var page = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var keyword = input.Keyword?.Trim();
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
null);
var hasCustomerFilter = customerId.HasValue;
var customerIdValue = customerId.GetValueOrDefault();
var predicate = (Expression<Func<OrderTemplate, bool>>)(x =>
(string.IsNullOrWhiteSpace(input.Type) || x.Type == input.Type) &&
(string.IsNullOrWhiteSpace(keyword) || x.Name.Contains(keyword) || (x.TemplateKey ?? string.Empty).Contains(keyword)) &&
(!hasCustomerFilter || x.Type == "standard" || x.CustomerId == customerIdValue));
var templates = await OrderTemplateRepository.GetPagedListAsync(
predicate,
page,
pageSize,
new Dictionary<string, bool> { [nameof(OrderTemplate.UpdateTime)] = false, [nameof(OrderTemplate.Id)] = false });
var total = await OrderTemplateRepository.CountAsync(predicate);
return new OmsPageResult<OrderTemplateOutput>
{
Items = templates.Select(x => new OrderTemplateOutput
{
Id = x.Id,
Name = x.Name,
Type = x.Type,
TemplateKey = x.TemplateKey,
CustomerId = x.CustomerId,
MappingStatus = string.Equals(x.Type, "standard", StringComparison.OrdinalIgnoreCase)
? OrderTemplateMappingStatus.Ready
: x.MappingStatus,
StandardTemplateKey = x.StandardTemplateKey ?? x.TemplateKey,
SourceFileName = x.SourceFileName,
CreateTime = x.CreateTime,
UpdateTime = OmsDocHelper.NullIfDefault(x.UpdateTime)
}).ToList(),
Total = total
};
}
private async Task<FileContentResult> BuildTemplateDownloadAsync(int templateId)
{
var template = await OrderTemplateRepository.FirstOrDefaultAsync(x => x.Id == templateId);
if (template == null)
{
throw new BusinessException("模板不存在");
}
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
null);
if (customerId.HasValue)
{
EnsureTemplateAccessible(template, customerId.Value);
}
if (string.Equals(template.Type, "custom", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrWhiteSpace(template.SourceFilePath))
{
throw new BusinessException("自定义模板样例文件不存在,请重新上传");
}
var absolutePath = OmsFileUploadHelper.ResolveWebRootAbsolutePath(WebHostEnvironment, template.SourceFilePath);
if (!File.Exists(absolutePath))
{
throw new BusinessException("自定义模板样例文件不存在,请重新上传");
}
var bytes = await File.ReadAllBytesAsync(absolutePath);
var downloadName = string.IsNullOrWhiteSpace(template.SourceFileName)
? $"{template.Name}.xlsx"
: template.SourceFileName;
return new FileContentResult(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = downloadName
};
}
var templateKey = template.TemplateKey ?? template.StandardTemplateKey ?? OrderTemplateKeys.DedicatedSmallParcel;
var exampleOverrides = await BuildTemplateExampleOverridesAsync(customerId);
var dictOptions = await LoadTemplateDictOptionsAsync(
["sales_product", "cargo_attribute", "export_customs_mode", "currency", "warehouse"]);
var fileBytes = await OrderTemplateExcelGenerator.BuildStandardTemplateAsync(
templateKey,
exampleOverrides,
dictOptions);
return new FileContentResult(fileBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = $"{template.Name}-{DateTime.Now:yyyyMMddHHmmss}.xlsx"
};
}
private async Task<OrderUploadOutput> ImportOrdersAsync(OrderUploadInput input)
{
if (input.File == null || input.File.Length == 0)
{
throw new BusinessException("上传文件不能为空");
}
if (BatchJobPublisher.IsEnabled)
{
return await ImportOrdersViaQueueAsync(input);
}
return await ImportOrdersSyncAsync(input);
}
private async Task<OrderUploadOutput> ImportOrdersViaQueueAsync(OrderUploadInput input)
{
var template = await OrderTemplateRepository.FirstOrDefaultAsync(x => x.Id == input.TemplateId);
if (template == null)
{
throw new BusinessException("模板不存在");
}
var effectiveCustomer = await ResolveEffectiveCustomerAsync();
EnsureTemplateAccessible(template, effectiveCustomer.Id);
OrderTemplateImportMapping.EnsureTemplateReadyForImport(template);
var mapping = OrderTemplateImportMapping.Resolve(template);
await using var stream = input.File!.OpenReadStream();
var parsedRows = await OrderTemplateExcelReader.ReadDataRowsAsync(stream, mapping);
if (parsedRows.Count == 0)
{
throw new BusinessException("Excel 中未读取到可用数据");
}
if (parsedRows.Count > ParcelBatchMaxCount)
{
throw new BusinessException($"单次最多导入 {ParcelBatchMaxCount} 行");
}
var task = await OrderTaskRepository.InsertAsync(new OrderTask
{
TaskName = $"批量下单_{DateTime.Now:yyyyMMddHHmmss}",
Quantity = parsedRows.Count,
SuccessCount = 0,
FailCount = 0,
Status = "processing",
TemplateId = template.Id,
CustomerId = effectiveCustomer.Id
});
var jobPayload = JsonConvert.SerializeObject(new OrderBatchImportJobPayload
{
TaskId = task.Id,
TemplateId = template.Id,
CustomerId = effectiveCustomer.Id,
});
var payloadItems = parsedRows.Select(row =>
{
var parcelInput = OrderTemplateRowMapper.MapToParcelCreateInput(row.SourceValues, mapping, effectiveCustomer.Id);
return new OrderBatchPayloadItemInput
{
OrderId = 0,
ServiceNo = parcelInput.ServiceNo,
ItemKey = $"import:{task.Id}:{row.RowNumber}",
PayloadJson = JsonConvert.SerializeObject(new OrderBatchImportRowPayload
{
TaskId = task.Id,
RowNumber = row.RowNumber,
CustomerId = effectiveCustomer.Id,
TemplateId = template.Id,
RowDataJson = JsonConvert.SerializeObject(row.SourceValues),
}),
};
}).ToList();
var job = await BatchJobCoordinator.CreatePayloadJobAsync(
new OrderBatchPayloadJobCreateInput
{
Action = OrderBatchActionCodes.ImportRow,
CustomerId = effectiveCustomer.Id,
JobPayloadJson = jobPayload,
RequestId = $"import-task-{task.Id}",
Items = payloadItems,
},
CurrentUser.GetId<int>());
return new OrderUploadOutput
{
TaskId = task.Id,
JobId = job.JobId,
Async = true,
};
}
private async Task<OrderUploadOutput> ImportOrdersSyncAsync(OrderUploadInput input)
{
var template = await OrderTemplateRepository.FirstOrDefaultAsync(x => x.Id == input.TemplateId);
if (template == null)
{
throw new BusinessException("模板不存在");
}
var effectiveCustomer = await ResolveEffectiveCustomerAsync();
EnsureTemplateAccessible(template, effectiveCustomer.Id);
OrderTemplateImportMapping.EnsureTemplateReadyForImport(template);
var mapping = OrderTemplateImportMapping.Resolve(template);
await using var stream = input.File!.OpenReadStream();
var parsedRows = await OrderTemplateExcelReader.ReadDataRowsAsync(stream, mapping);
var task = await OrderTaskRepository.InsertAsync(new OrderTask
{
TaskName = $"批量下单_{DateTime.Now:yyyyMMddHHmmss}",
Quantity = 0,
SuccessCount = 0,
FailCount = 0,
Status = "processing",
TemplateId = template.Id,
CustomerId = effectiveCustomer.Id
});
var quantity = 0;
var successCount = 0;
var failCount = 0;
var failList = new List<OrderTaskFail>();
foreach (var row in parsedRows)
{
quantity++;
try
{
var parcelInput = OrderTemplateRowMapper.MapToParcelCreateInput(
row.SourceValues,
mapping,
effectiveCustomer.Id);
await CreateParcelCoreAsync(parcelInput);
successCount++;
}
catch (Exception ex)
{
failCount++;
failList.Add(BuildTaskFail(task.Id, row.RowNumber, ex.Message, row.SourceValues));
}
}
if (parsedRows.Count == 0)
{
quantity = 1;
failCount = 1;
failList.Add(BuildTaskFail(task.Id, 1, "Excel 中未读取到可用数据", new Dictionary<string, object>()));
}
if (failList.Count > 0)
{
await OrderTaskRepository.InsertFailListAsync(failList);
}
task.Quantity = quantity;
task.SuccessCount = successCount;
task.FailCount = failCount;
task.Status = "completed";
await OrderTaskRepository.UpdateAsync(task);
return new OrderUploadOutput { TaskId = task.Id };
}
private async Task<OmsPageResult<OrderTaskOutput>> GetTaskOutputsAsync(OrderTaskListInput input)
{
var page = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var keyword = input.Keyword?.Trim();
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
null);
var hasCustomerFilter = customerId.HasValue;
var customerIdValue = customerId.GetValueOrDefault();
var predicate = (Expression<Func<OrderTask, bool>>)(x =>
(string.IsNullOrWhiteSpace(input.Status) || x.Status == input.Status) &&
(string.IsNullOrWhiteSpace(keyword) || x.TaskName.Contains(keyword)) &&
(!hasCustomerFilter || x.CustomerId == customerIdValue));
var tasks = await OrderTaskRepository.GetPagedListAsync(
predicate,
page,
pageSize,
new Dictionary<string, bool> { [nameof(OrderTask.UpdateTime)] = false, [nameof(OrderTask.Id)] = false });
var total = await OrderTaskRepository.CountAsync(predicate);
return new OmsPageResult<OrderTaskOutput>
{
Items = tasks.Select(x => new OrderTaskOutput
{
Id = x.Id,
TaskName = x.TaskName,
Quantity = x.Quantity,
Status = x.Status,
TemplateId = x.TemplateId,
CreateTime = x.CreateTime,
UpdateTime = OmsDocHelper.NullIfDefault(x.UpdateTime)
}).ToList(),
Total = total
};
}
private async Task<OrderTaskDetailOutput> GetTaskDetailCoreAsync(int taskId)
{
var task = await OrderTaskRepository.FirstOrDefaultAsync(x => x.Id == taskId);
if (task == null)
{
throw new BusinessException("任务不存在");
}
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
null);
if (customerId.HasValue && task.CustomerId != customerId.Value)
{
throw new BusinessException("任务不存在");
}
var failList = await OrderTaskRepository.GetFailListAsync(task.Id);
return new OrderTaskDetailOutput
{
Id = task.Id,
TaskName = task.TaskName,
Quantity = task.Quantity,
SuccessCount = task.SuccessCount,
FailCount = task.FailCount,
Status = task.Status,
TemplateId = task.TemplateId,
CreateTime = task.CreateTime,
UpdateTime = OmsDocHelper.NullIfDefault(task.UpdateTime),
FailList = failList.Select(x => new OrderTaskFailOutput
{
Id = x.Id,
TaskId = x.TaskId,
RowNumber = x.RowNumber,
Reason = x.Reason,
RawDataJson = OmsDocHelper.DeserializeJsonObject(x.RawDataJson)
}).ToList()
};
}
private async Task<string> CreateOrderPrintFileAsync(OrderOutput order)
{
var directory = Path.Combine(GetWebRootDirectory(), "uploads", "prints", "orders");
Directory.CreateDirectory(directory);
var fileName = $"{order.ServiceNo}-{DateTime.Now:yyyyMMddHHmmss}.html";
var html = $"""
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>{order.ServiceNo}</title></head>
<body>
<h2>Order Label</h2>
<p>ServiceNo: {order.ServiceNo}</p>
<p>ClientNo: {order.ClientNo}</p>
<p>TrackNo: {order.TrackNo}</p>
<p>Warehouse: {order.Warehouse}</p>
</body>
</html>
""";
await File.WriteAllTextAsync(Path.Combine(directory, fileName), html, Encoding.UTF8);
return $"/uploads/prints/orders/{fileName}";
}
private string GetOrderExportDirectory() => Path.Combine(GetWebRootDirectory(), "uploads", "exports", "orders");
private string GetWebRootDirectory()
{
var webRoot = string.IsNullOrWhiteSpace(WebHostEnvironment.WebRootPath)
? Path.Combine(WebHostEnvironment.ContentRootPath, "wwwroot")
: WebHostEnvironment.WebRootPath;
Directory.CreateDirectory(webRoot);
return webRoot;
}
private async Task CleanupExpiredFilesAsync(string directory)
{
if (!Directory.Exists(directory))
{
return;
}
await Task.Run(() =>
{
foreach (var file in Directory.GetFiles(directory))
{
if (File.GetCreationTime(file) <= DateTime.Now.AddHours(-24))
{
File.Delete(file);
}
}
});
}
private static string? ResolveConsigneeCountryName(
Order order,
IReadOnlyDictionary<string, Country> countryMap)
{
if (!string.IsNullOrWhiteSpace(order.ConsigneeCountryName))
{
return order.ConsigneeCountryName.Trim();
}
if (string.IsNullOrWhiteSpace(order.ConsigneeCountry))
{
return null;
}
return countryMap.TryGetValue(order.ConsigneeCountry.Trim(), out var country)
? country.ChineseName
: null;
}
private static OrderSenderOutput? MapSender(OrderSender? sender)
{
return sender == null
? null
: new OrderSenderOutput
{
SenderName = sender.SenderName,
Company = sender.Company,
Phone = sender.Phone,
TelePhone = sender.TelePhone,
Email = sender.Email,
CountryCode = sender.Country,
Province = sender.Province,
City = sender.City,
ZipCode = sender.ZipCode,
StreetAddress1 = sender.StreetAddress1,
StreetAddress2 = sender.StreetAddress2,
StreetAddress3 = sender.StreetAddress3,
HouseNumber = sender.HouseNumber
};
}
private static OrderReceiverOutput? MapReceiver(OrderRecipient? recipient)
{
return recipient == null
? null
: new OrderReceiverOutput
{
RecipientsName = recipient.RecipientsName,
CompanyName = recipient.CompanyName,
Phone = recipient.Phone,
TelePhone = recipient.TelePhone,
Email = recipient.Email,
CountryCode = recipient.Country,
Province = recipient.Province,
City = recipient.City,
StreetNumber = recipient.StreetNumber,
StreetAddress1 = recipient.StreetAddress1,
StreetAddress2 = recipient.StreetAddress2,
StreetAddress3 = recipient.StreetAddress3,
ZipCode = recipient.ZipCode,
TaxType = recipient.TaxType,
TaxNo = recipient.TaxNo,
CertificateType = recipient.CertificateType,
CertificateNo = recipient.CertificateNo,
Eori = recipient.Eori,
Vat = recipient.Vat
};
}
private static OrderItemOutput MapDeclaration(OrderDeclaration x)
{
return new OrderItemOutput
{
CnName = x.CnName,
EnName = x.EnName,
Sku = x.Sku,
Unit = x.Unit,
UnitPrice = x.UnitPrice,
UnitWeight = x.UnitWeight,
Quantity = x.Quantity,
Amount = x.Amount,
Weight = x.Weight,
HsCode = x.ExHsCode ?? x.ImHsCode ?? x.HsCode,
SkuImage = x.SkuImage,
Category = x.Category,
Brand = x.Brand,
BrandType = x.BrandType,
IsMagnetic = x.IsMagnetic > 0,
Distribution = x.Distribution,
Material = x.Material,
EnMaterial = x.EnMaterial,
Purpose = x.Purpose,
Attributes = x.Attributes,
OriginCountry = x.OriginCountry,
SalesLink = x.SalesLink,
Model = x.Model,
Specifications = x.Specifications,
Comment = x.Comment
};
}
private static OrderWeightOutput MapWeight(OrderWeight x)
{
return new OrderWeightOutput
{
Type = x.Type,
Length = x.Length,
Width = x.Width,
Height = x.Height,
ActualWeight = x.ActualWeight,
VolumetricWeight = x.VolumetricWeight,
ChargeableWeight = x.ChargeableWeight,
Cubic = x.Cubic
};
}
private static OrderFeeOutput MapFee(OrderFee x) =>
new()
{
Id = x.Id,
Type = x.Type,
CostItem = x.CostItem,
Amount = x.Amount,
Currency = x.Currency,
DownstreamProvider = x.DownstreamProvider,
PriceNo = x.PriceNo,
PriceEffectiveTime = x.PriceEffectiveTime,
ChargingStage = x.ChargingStage
};
private static decimal? ResolveDisplayChargeableWeight(IReadOnlyList<OrderWeight>? weights)
{
if (weights == null || weights.Count == 0)
{
return null;
}
var receive = weights.FirstOrDefault(x => x.Type == OrderWeightTypeCodes.Receive);
if (receive?.ChargeableWeight is > 0)
{
return receive.ChargeableWeight;
}
var forecast = weights.FirstOrDefault(x => x.Type == OrderWeightTypeCodes.Forecast);
return forecast?.ChargeableWeight;
}
private static OrderTaskFail BuildTaskFail(int taskId, int rowNumber, string reason, object rawData)
{
return new OrderTaskFail
{
TaskId = taskId,
RowNumber = rowNumber,
Reason = reason,
RawDataJson = JsonConvert.SerializeObject(rawData),
CreateUserId = 0,
CreateTime = DateTime.Now
};
}
private static string? GetStringCell(IDictionary<string, object> row, string key) => row.TryGetValue(key, out var value) ? value?.ToString() : null;
private static int? GetintCell(IDictionary<string, object> row, string key) => int.TryParse(GetStringCell(row, key), out var value) ? value : null;
private static int? GetIntCell(IDictionary<string, object> row, string key) => int.TryParse(GetStringCell(row, key), out var value) ? value : null;
private static decimal? GetDecimalCell(IDictionary<string, object> row, string key) => decimal.TryParse(GetStringCell(row, key), out var value) ? value : null;
private static bool GetBoolCell(IDictionary<string, object> row, string key) => GetStringCell(row, key) is "1" or "true" or "True" or "TRUE" or "是";
private static void EnsureTemplateAccessible(OrderTemplate template, int effectiveCustomerId)
{
if (string.Equals(template.Type, "standard", StringComparison.OrdinalIgnoreCase))
{
return;
}
if (template.CustomerId != effectiveCustomerId)
{
throw new BusinessException("模板不存在");
}
}
private OrderWeight? BuildForecastWeight(int orderId, IDictionary<string, object> row)
{
var length = GetDecimalCell(row, "Length");
var width = GetDecimalCell(row, "Width");
var height = GetDecimalCell(row, "Height");
var actualWeight = GetDecimalCell(row, "ActualWeight");
var volumetricWeight = GetDecimalCell(row, "VolumetricWeight");
var chargeableWeight = GetDecimalCell(row, "ChargeableWeight");
var cubic = GetDecimalCell(row, "Cubic");
if (!length.HasValue && !width.HasValue && !height.HasValue && !actualWeight.HasValue && !volumetricWeight.HasValue && !chargeableWeight.HasValue && !cubic.HasValue)
{
return null;
}
return new OrderWeight
{
OrderId = orderId,
Type = OrderWeightTypeCodes.Forecast,
Length = length,
Width = width,
Height = height,
ActualWeight = actualWeight,
VolumetricWeight = volumetricWeight,
ChargeableWeight = chargeableWeight,
Cubic = cubic
};
}
private async Task ApplyInternalOrderDataScopeAsync(OrderListFilter filter)
{
if (OmsSessionHelper.IsCustomerLogin(CurrentUser))
{
return;
}
var userGuidRaw = CurrentUser.FindClaimValueOrNull(global::System.Security.Claims.ClaimTypes.NameIdentifier);
if (string.IsNullOrWhiteSpace(userGuidRaw) || !Guid.TryParse(userGuidRaw, out var userGuid))
{
return;
}
var context = await DataScopeBuilder.BuildAsync(userGuid, DataScopeApplier.OrderResourceCode);
DataScopeApplier.ApplyToOrderListFilter(filter, context);
}
}