admin/src/Oms.Application/Services/Orders/Batch/OrderBatchActionExecutor.cs
zzh 6f436f795f Fix downstream forecast success detection and persist provider service no.
Treat itfx body code/msg as business result, store full request/response, and write Data.serviceNo/trackNo back to the order.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 10:21:23 +08:00

243 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Fake.Application;
using Fake.DependencyInjection;
using Newtonsoft.Json;
using Oms.Application.Dtos.Orders;
using Oms.Application.Integrations.Itfx;
using Oms.Application.Integrations.SmallBagTrack;
using Oms.Application.Messaging;
using Oms.Application.Shared;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.OrdersAggregate.Repositories;
using Oms.Domain.SystemAggregate.Repositories;
namespace Oms.Application.Services.Orders.Batch;
public sealed class OrderBatchActionExecutor(
IOrderRepository orderRepository,
ICountryRepository countryRepository,
ISmallBagTrackOrderClient smallBagTrackOrderClient,
ISmallBagTrackUpsertRequestBuilder smallBagTrackUpsertRequestBuilder,
OrderDownstreamCredentialResolver downstreamCredentialResolver,
IItfxForwardClient itfxForwardClient,
OrderBatchCreateActionHandler createActionHandler,
OrderBatchOrderActionHandler orderActionHandler) : IOrderBatchActionExecutor, IScopedDependency
{
public async Task<OrderBatchActionExecuteResult> ExecuteAsync(
OrderBatchJobMessage message,
CancellationToken cancellationToken = default)
{
try
{
return message.Action switch
{
OrderBatchActionCodes.TrackUpsertSmallBag => await ExecuteTrackUpsertAsync(message, cancellationToken),
OrderBatchActionCodes.DownstreamForwardCreateOne => await ExecuteDownstreamForwardAsync(message, cancellationToken),
OrderBatchActionCodes.ImportRow => await createActionHandler.ExecuteImportRowAsync(message, cancellationToken),
OrderBatchActionCodes.ParcelCreate => await createActionHandler.ExecuteParcelCreateAsync(message, cancellationToken),
OrderBatchActionCodes.Pay => await orderActionHandler.ExecuteAsync(message, cancellationToken),
OrderBatchActionCodes.PreDeclare => await orderActionHandler.ExecuteAsync(message, cancellationToken),
OrderBatchActionCodes.Intercept => await orderActionHandler.ExecuteAsync(message, cancellationToken),
OrderBatchActionCodes.CancelIntercept => await orderActionHandler.ExecuteAsync(message, cancellationToken),
OrderBatchActionCodes.Delete => await orderActionHandler.ExecuteAsync(message, cancellationToken),
OrderBatchActionCodes.Print => await orderActionHandler.ExecuteAsync(message, cancellationToken),
OrderBatchActionCodes.GenerateLabelPdf => await orderActionHandler.ExecuteAsync(message, cancellationToken),
OrderBatchActionCodes.MarkProblematic => await orderActionHandler.ExecuteAsync(message, cancellationToken),
OrderBatchActionCodes.MarkClaimed => await orderActionHandler.ExecuteAsync(message, cancellationToken),
OrderBatchActionCodes.HandleStatusUpdate => await orderActionHandler.ExecuteAsync(message, cancellationToken),
_ => OrderBatchActionExecuteResult.BusinessFail($"未实现的批量操作: {message.Action}")
};
}
catch (BusinessException ex)
{
return OrderBatchActionExecuteResult.BusinessFail(ex.Message);
}
catch (Exception ex)
{
return OrderBatchActionExecuteResult.SystemFail(ex.Message);
}
}
private async Task<OrderBatchActionExecuteResult> ExecuteTrackUpsertAsync(
OrderBatchJobMessage message,
CancellationToken cancellationToken)
{
SmallBagTrackUpsertRequest request;
if (!string.IsNullOrWhiteSpace(message.PayloadJson))
{
request = JsonConvert.DeserializeObject<SmallBagTrackUpsertRequest>(message.PayloadJson!)
?? throw new BusinessException("轨迹同步消息体无效");
}
else
{
var order = await orderRepository.FirstOrDefaultAsync(x => x.Id == message.OrderId, cancellationToken);
if (order == null)
{
return OrderBatchActionExecuteResult.BusinessFail("订单不存在");
}
if (string.IsNullOrWhiteSpace(order.ServiceNo))
{
return OrderBatchActionExecuteResult.BusinessFail("订单运单号为空,无法同步轨迹");
}
request = await smallBagTrackUpsertRequestBuilder.BuildAsync(order, cancellationToken: cancellationToken);
// 手动「创建轨迹」无 payload:创单成功后补默认轨迹节点 A0101
request.AppendInitialTrackOnSuccess = true;
}
if (string.IsNullOrWhiteSpace(request.Authorization)
&& !string.IsNullOrWhiteSpace(message.Authorization))
{
request.Authorization = message.Authorization.Trim();
}
var trackResult = await smallBagTrackOrderClient.UpsertDebugAsync(request, cancellationToken);
var serviceNo = request.OrderNo?.Trim();
if (trackResult.Skipped)
{
var reason = string.IsNullOrWhiteSpace(trackResult.SkipReason)
? "轨迹服务未启用或配置不完整"
: trackResult.SkipReason.Trim();
return OrderBatchActionExecuteResult.BusinessFail(
reason,
resultJson: OrderBatchTrackUpsertResultBuilder.BuildFailure(
trackResult,
message.OrderId,
serviceNo,
reason));
}
if (!trackResult.Success)
{
var reason = $"轨迹订单创建失败: HTTP {trackResult.StatusCode}";
return OrderBatchActionExecuteResult.BusinessFail(
reason,
resultJson: OrderBatchTrackUpsertResultBuilder.BuildFailure(
trackResult,
message.OrderId,
serviceNo,
reason));
}
return OrderBatchActionExecuteResult.Ok(
serviceNo: serviceNo,
resultJson: OrderBatchTrackUpsertResultBuilder.BuildSuccess(
trackResult,
message.OrderId,
serviceNo));
}
private async Task<OrderBatchActionExecuteResult> ExecuteDownstreamForwardAsync(
OrderBatchJobMessage message,
CancellationToken cancellationToken)
{
var authorization = message.Authorization;
if (!string.IsNullOrWhiteSpace(authorization))
{
ItfxAuthorizationContext.Authorization = authorization.Trim();
}
try
{
OrderParcelCreateInput body;
Order order;
if (!string.IsNullOrWhiteSpace(message.PayloadJson))
{
body = JsonConvert.DeserializeObject<OrderParcelCreateInput>(message.PayloadJson!)
?? throw new BusinessException("下游预报消息体无效");
// 凭证解析仍需订单上的渠道/下游绑定字段;有 payload 时只查主表一次
var existing = await orderRepository.FirstOrDefaultAsync(x => x.Id == message.OrderId, cancellationToken);
if (existing == null)
{
return OrderBatchActionExecuteResult.BusinessFail("订单不存在");
}
order = existing;
}
else
{
var bundles = await orderRepository.GetDownstreamCreateOneBundlesAsync([message.OrderId], cancellationToken);
if (!bundles.TryGetValue(message.OrderId, out var bundle))
{
return OrderBatchActionExecuteResult.BusinessFail("订单不存在");
}
order = bundle.Order;
body = await OrderDownstreamCreateOneBodyBuilder.BuildAsync(
bundle.Order,
bundle.Sender,
bundle.Recipient,
bundle.Declarations,
bundle.Weights,
countryRepository,
cancellationToken);
}
if (string.IsNullOrWhiteSpace(order.ServiceNo))
{
return OrderBatchActionExecuteResult.BusinessFail("订单运单号为空,无法预报下游");
}
var creds = await downstreamCredentialResolver.ResolveAsync(order, cancellationToken);
var forward = await itfxForwardClient.ForwardCreateOrderOneAsync(
creds.SupplierCode,
creds.AppKey,
creds.AppSecret,
body,
cancellationToken);
ItfxForwardResultEvaluator.IsBusinessSuccess(
forward.StatusCode,
forward.ResponseBody,
out var businessCode,
out var businessMessage);
if (!forward.Success)
{
var reason = ItfxForwardResultEvaluator.BuildFailReason(
forward.StatusCode,
forward.ResponseBody,
businessCode,
businessMessage);
return OrderBatchActionExecuteResult.BusinessFail(
reason,
resultJson: OrderBatchDownstreamForwardResultBuilder.BuildFailure(
forward,
message.OrderId,
order.ServiceNo,
creds.SupplierCode,
reason,
businessCode,
businessMessage));
}
if (ItfxCreateOrderOneDataParser.TryParse(forward.ResponseBody, out var data)
&& data != null
&& ItfxCreateOrderOneDataParser.ApplyToOrder(order, data))
{
await orderRepository.UpdateAsync(order, cancellationToken);
}
return OrderBatchActionExecuteResult.Ok(
serviceNo: order.ServiceNo,
trackNo: order.TrackNo,
resultJson: OrderBatchDownstreamForwardResultBuilder.BuildSuccess(
forward,
message.OrderId,
order.ServiceNo,
creds.SupplierCode,
businessCode,
businessMessage,
order.DownstreamServiceNo,
order.TrackNo));
}
finally
{
ItfxAuthorizationContext.Authorization = null;
}
}
}