fix: 对齐 createOrderOne 下游预报请求体,productCode 取预报渠道 code
All checks were successful
Build and Deploy / deploy (push) Successful in 1m46s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m46s
移除 channelCode 等多余字段,改用专用 ItfxCreateOrderOneBody。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
14f3df3fa5
commit
6b920cc49d
57
src/Oms.Application/Dtos/Orders/ItfxCreateOrderOneBody.cs
Normal file
57
src/Oms.Application/Dtos/Orders/ItfxCreateOrderOneBody.cs
Normal file
@ -0,0 +1,57 @@
|
||||
namespace Oms.Application.Dtos.Orders;
|
||||
|
||||
/// <summary>
|
||||
/// itfx createOrderOne(ForwardWithSubscribe)标准请求体。
|
||||
/// 仅包含下游接口字段,不含 OMS 内部扩展参数。
|
||||
/// </summary>
|
||||
public class ItfxCreateOrderOneBody
|
||||
{
|
||||
public string? ClientNo { get; set; }
|
||||
|
||||
public string? TrackNo { get; set; }
|
||||
|
||||
public string? ServiceNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下游产品代码:取服务渠道「预报渠道 code」(ForecastCode)。
|
||||
/// </summary>
|
||||
public string? ProductCode { get; set; }
|
||||
|
||||
public string? CargoAttribute { get; set; }
|
||||
|
||||
public int? WarehouseId { get; set; }
|
||||
|
||||
public int? CustomerId { get; set; }
|
||||
|
||||
public decimal? Length { get; set; }
|
||||
|
||||
public decimal? Width { get; set; }
|
||||
|
||||
public decimal? Height { get; set; }
|
||||
|
||||
public decimal? ActualWeight { get; set; }
|
||||
|
||||
public decimal? VolumetricWeight { get; set; }
|
||||
|
||||
public decimal? ChargeableWeight { get; set; }
|
||||
|
||||
public decimal? Cubic { get; set; }
|
||||
|
||||
public bool IsCod { get; set; }
|
||||
|
||||
public string? CodCurrency { get; set; }
|
||||
|
||||
public string? CodSum { get; set; }
|
||||
|
||||
public string? DeclaredCurrency { get; set; }
|
||||
|
||||
public string? DeclaredSum { get; set; }
|
||||
|
||||
public string? Quantity { get; set; }
|
||||
|
||||
public OrderParcelReceiverInput? Receiver { get; set; }
|
||||
|
||||
public OrderParcelSenderInput? Sender { get; set; }
|
||||
|
||||
public List<OrderParcelItemInput> Items { get; set; } = [];
|
||||
}
|
||||
@ -13,7 +13,7 @@ public class OrderDownstreamCreateOneBodyOutput
|
||||
|
||||
public string SupplierCode { get; set; } = string.Empty;
|
||||
|
||||
public OrderParcelCreateInput Body { get; set; } = new();
|
||||
public ItfxCreateOrderOneBody Body { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -17,6 +17,7 @@ internal sealed class ItfxForwardClient(
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ using Oms.Application.Integrations.SmallBagTrack;
|
||||
using Oms.Application.Messaging;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Application.Storage;
|
||||
using Oms.Domain.ChannelsAggregate.Repositories;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
@ -17,6 +18,7 @@ namespace Oms.Application.Services.Orders.Batch;
|
||||
public sealed class OrderBatchActionExecutor(
|
||||
IOrderRepository orderRepository,
|
||||
ICountryRepository countryRepository,
|
||||
IServiceChannelRepository serviceChannelRepository,
|
||||
IFileStorage fileStorage,
|
||||
ISmallBagTrackOrderClient smallBagTrackOrderClient,
|
||||
ISmallBagTrackUpsertRequestBuilder smallBagTrackUpsertRequestBuilder,
|
||||
@ -145,12 +147,12 @@ public sealed class OrderBatchActionExecutor(
|
||||
|
||||
try
|
||||
{
|
||||
OrderParcelCreateInput body;
|
||||
ItfxCreateOrderOneBody body;
|
||||
Order order;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(message.PayloadJson))
|
||||
{
|
||||
body = JsonConvert.DeserializeObject<OrderParcelCreateInput>(message.PayloadJson!)
|
||||
body = JsonConvert.DeserializeObject<ItfxCreateOrderOneBody>(message.PayloadJson!)
|
||||
?? throw new BusinessException("下游预报消息体无效");
|
||||
|
||||
// 凭证解析仍需订单上的渠道/下游绑定字段;有 payload 时只查主表一次
|
||||
@ -178,6 +180,7 @@ public sealed class OrderBatchActionExecutor(
|
||||
bundle.Declarations,
|
||||
bundle.Weights,
|
||||
countryRepository,
|
||||
serviceChannelRepository,
|
||||
fileStorage,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ using Oms.Application.Integrations.SmallBagTrack;
|
||||
using Oms.Application.Messaging;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Application.Storage;
|
||||
using Oms.Domain.ChannelsAggregate.Repositories;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using System.Linq.Expressions;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
@ -24,6 +25,7 @@ public sealed class OrderBatchJobCoordinator(
|
||||
IOrderBatchJobRepository batchJobRepository,
|
||||
IOrderRepository orderRepository,
|
||||
ICountryRepository countryRepository,
|
||||
IServiceChannelRepository serviceChannelRepository,
|
||||
IFileStorage fileStorage,
|
||||
IOrderBatchJobPublisher publisher,
|
||||
OrderBatchJobProcessor batchJobProcessor,
|
||||
@ -651,6 +653,7 @@ public sealed class OrderBatchJobCoordinator(
|
||||
bundle.Declarations,
|
||||
bundle.Weights,
|
||||
countryRepository,
|
||||
serviceChannelRepository,
|
||||
fileStorage,
|
||||
cancellationToken);
|
||||
result[orderId] = JsonConvert.SerializeObject(body);
|
||||
|
||||
@ -82,7 +82,7 @@ public partial class OrderService
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<(Domain.OrdersAggregate.Entities.Order order, OrderParcelCreateInput body)> LoadDownstreamCreateOneContextAsync(
|
||||
private async Task<(Domain.OrdersAggregate.Entities.Order order, ItfxCreateOrderOneBody body)> LoadDownstreamCreateOneContextAsync(
|
||||
int orderId)
|
||||
{
|
||||
var bundles = await OrderRepository.GetDownstreamCreateOneBundlesAsync([orderId]);
|
||||
@ -98,6 +98,7 @@ public partial class OrderService
|
||||
bundle.Declarations,
|
||||
bundle.Weights,
|
||||
CountryRepository,
|
||||
ServiceChannelRepository,
|
||||
FileStorage);
|
||||
|
||||
return (bundle.Order, body);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Oms.Application.Dtos.Orders;
|
||||
using Oms.Application.Storage;
|
||||
using Oms.Domain.ChannelsAggregate.Repositories;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.SystemAggregate.Repositories;
|
||||
@ -7,17 +8,18 @@ using Oms.Domain.SystemAggregate.Repositories;
|
||||
namespace Oms.Application.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// 从订单表及关联子表组装 createOrderOne 标准请求体。
|
||||
/// 从订单表及关联子表组装 itfx createOrderOne 标准请求体。
|
||||
/// </summary>
|
||||
public static class OrderDownstreamCreateOneBodyBuilder
|
||||
{
|
||||
public static async Task<OrderParcelCreateInput> BuildAsync(
|
||||
public static async Task<ItfxCreateOrderOneBody> BuildAsync(
|
||||
Order order,
|
||||
OrderSender? sender,
|
||||
OrderRecipient? recipient,
|
||||
IReadOnlyList<OrderDeclaration> declarations,
|
||||
IReadOnlyList<OrderWeight> weights,
|
||||
ICountryRepository countryRepository,
|
||||
IServiceChannelRepository serviceChannelRepository,
|
||||
IFileStorage? fileStorage = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
@ -41,16 +43,18 @@ public static class OrderDownstreamCreateOneBodyBuilder
|
||||
string.Equals(x.Type, OrderWeightTypeCodes.Forecast, StringComparison.OrdinalIgnoreCase))
|
||||
?? weights.FirstOrDefault();
|
||||
|
||||
return new OrderParcelCreateInput
|
||||
var productCode = await ResolveForecastProductCodeAsync(
|
||||
order,
|
||||
serviceChannelRepository,
|
||||
cancellationToken);
|
||||
|
||||
return new ItfxCreateOrderOneBody
|
||||
{
|
||||
RequestId = order.RequestId,
|
||||
ClientNo = order.ClientNo,
|
||||
TrackNo = order.TrackNo,
|
||||
ServiceNo = order.ServiceNo,
|
||||
ProductCode = order.ProductCode,
|
||||
ChannelCode = order.ChannelCode,
|
||||
ProductCode = productCode,
|
||||
CargoAttribute = order.CargoAttribute,
|
||||
ExportCustomsMode = order.ExportCustomsMode,
|
||||
WarehouseId = order.WarehouseId,
|
||||
CustomerId = order.CustomerId,
|
||||
Length = forecast?.Length,
|
||||
@ -62,16 +66,45 @@ public static class OrderDownstreamCreateOneBodyBuilder
|
||||
Cubic = forecast?.Cubic,
|
||||
IsCod = order.IsCod > 0,
|
||||
CodCurrency = order.CodCurrency,
|
||||
CodSum = order.CodSum,
|
||||
CodSum = FormatDecimal(order.CodSum),
|
||||
DeclaredCurrency = order.DeclaredCurrency,
|
||||
DeclaredSum = order.DeclaredSum,
|
||||
Quantity = order.Quantity,
|
||||
DeclaredSum = FormatDecimal(order.DeclaredSum),
|
||||
Quantity = order.Quantity?.ToString(),
|
||||
Receiver = MapReceiver(recipient, order.ConsigneeCountry, idToCode),
|
||||
Sender = MapSender(sender, idToCode),
|
||||
Items = declarations.Select(x => MapItem(x, fileStorage)).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// createOrderOne.productCode = 渠道「预报渠道 code」:
|
||||
/// 优先订单固化的 ChannelForecastCode,否则 live 查 t_service_channel.ForecastCode。
|
||||
/// </summary>
|
||||
private static async Task<string?> ResolveForecastProductCodeAsync(
|
||||
Order order,
|
||||
IServiceChannelRepository serviceChannelRepository,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var snapshotted = order.ChannelForecastCode?.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(snapshotted))
|
||||
{
|
||||
return snapshotted;
|
||||
}
|
||||
|
||||
var channelCode = order.ChannelCode?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(channelCode))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var channel = await serviceChannelRepository.GetByChannelCodeAsync(channelCode, cancellationToken);
|
||||
var forecastCode = channel?.ForecastCode?.Trim();
|
||||
return string.IsNullOrWhiteSpace(forecastCode) ? null : forecastCode;
|
||||
}
|
||||
|
||||
private static string? FormatDecimal(decimal? value) =>
|
||||
value.HasValue ? value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture) : null;
|
||||
|
||||
private static string? ResolveCountryIso(int? countryId, string? orderConsigneeCountry, IReadOnlyDictionary<int, string> idToCode)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(orderConsigneeCountry))
|
||||
@ -83,9 +116,9 @@ public static class OrderDownstreamCreateOneBodyBuilder
|
||||
}
|
||||
}
|
||||
|
||||
if (countryId is > 0 && idToCode.TryGetValue(countryId.Value, out var code) && !string.IsNullOrWhiteSpace(code))
|
||||
if (countryId is > 0 && idToCode.TryGetValue(countryId.Value, out var iso) && !string.IsNullOrWhiteSpace(iso))
|
||||
{
|
||||
return code.Trim().ToUpperInvariant();
|
||||
return iso.Trim().ToUpperInvariant();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@ -7,6 +7,7 @@ using Oms.Application.Dtos.Orders;
|
||||
using Oms.Application.Messaging;
|
||||
using Oms.Application.Services.Orders.Batch;
|
||||
using Oms.Application.Storage;
|
||||
using Oms.Domain.ChannelsAggregate.Repositories;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
@ -19,6 +20,7 @@ public class OrderBatchJobCoordinatorGovernanceTests
|
||||
private readonly Mock<IOrderBatchJobRepository> _batchJobRepository = new(MockBehavior.Strict);
|
||||
private readonly Mock<IOrderRepository> _orderRepository = new(MockBehavior.Loose);
|
||||
private readonly Mock<ICountryRepository> _countryRepository = new(MockBehavior.Loose);
|
||||
private readonly Mock<IServiceChannelRepository> _serviceChannelRepository = new(MockBehavior.Loose);
|
||||
private readonly Mock<IFileStorage> _fileStorage = new(MockBehavior.Loose);
|
||||
private readonly Mock<IOrderBatchJobPublisher> _publisher = new(MockBehavior.Strict);
|
||||
private readonly OrderBatchJobCoordinator _coordinator;
|
||||
@ -38,6 +40,7 @@ public class OrderBatchJobCoordinatorGovernanceTests
|
||||
_batchJobRepository.Object,
|
||||
_orderRepository.Object,
|
||||
_countryRepository.Object,
|
||||
_serviceChannelRepository.Object,
|
||||
_fileStorage.Object,
|
||||
_publisher.Object,
|
||||
processor,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user