All checks were successful
Build and Deploy / deploy (push) Successful in 1m3s
Co-authored-by: Cursor <cursoragent@cursor.com>
291 lines
12 KiB
C#
291 lines
12 KiB
C#
using Fake.Application;
|
||
using Fake.DependencyInjection;
|
||
using Fake.ObjectStorage;
|
||
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.Application.Storage;
|
||
using Oms.Domain.ChannelsAggregate.Repositories;
|
||
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,
|
||
IServiceChannelRepository serviceChannelRepository,
|
||
IObjectStorage fileStorage,
|
||
ISmallBagTrackOrderClient smallBagTrackOrderClient,
|
||
ISmallBagTrackUpsertRequestBuilder smallBagTrackUpsertRequestBuilder,
|
||
OrderDownstreamCredentialResolver downstreamCredentialResolver,
|
||
IItfxForwardClient itfxForwardClient,
|
||
OrderFileManager orderFileManager,
|
||
OrderOperationLogWriter operationLogWriter,
|
||
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.InboundBilling => await orderActionHandler.ExecuteAsync(message, cancellationToken),
|
||
OrderBatchActionCodes.ForecastBilling => await orderActionHandler.ExecuteAsync(message, cancellationToken),
|
||
OrderBatchActionCodes.Reallocate => 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
|
||
{
|
||
ItfxCreateOrderOneBody body;
|
||
Order order;
|
||
|
||
if (!string.IsNullOrWhiteSpace(message.PayloadJson))
|
||
{
|
||
body = JsonConvert.DeserializeObject<ItfxCreateOrderOneBody>(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,
|
||
serviceChannelRepository,
|
||
fileStorage,
|
||
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));
|
||
}
|
||
|
||
var beforeSnap = OrderOperationLogSnapshot.CaptureOrder(order);
|
||
|
||
if (ItfxCreateOrderOneDataParser.TryParse(forward.ResponseBody, out var data)
|
||
&& data != null)
|
||
{
|
||
if (ItfxCreateOrderOneDataParser.ApplyToOrder(order, data))
|
||
{
|
||
await orderRepository.UpdateAsync(order, cancellationToken);
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(data.LabelUrl))
|
||
{
|
||
await orderFileManager.AddOrReplaceDownstreamLabelAsync(
|
||
order.Id,
|
||
data.LabelUrl!,
|
||
message.OperatorUserId,
|
||
cancellationToken);
|
||
}
|
||
}
|
||
|
||
var afterSnap = OrderOperationLogSnapshot.CaptureOrder(order);
|
||
var remarkParts = new List<string> { $"供应商 {creds.SupplierCode}" };
|
||
if (!string.IsNullOrWhiteSpace(order.DownstreamServiceNo))
|
||
{
|
||
remarkParts.Add($"服务商单号 {order.DownstreamServiceNo}");
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(order.TrackNo))
|
||
{
|
||
remarkParts.Add($"跟踪号 {order.TrackNo}");
|
||
}
|
||
|
||
await operationLogWriter.TryWriteAsync(
|
||
order,
|
||
OrderOperationActionCodes.DownstreamForward,
|
||
beforeSnap,
|
||
afterSnap,
|
||
string.Join(";", remarkParts),
|
||
message.OperatorUserId > 0 ? message.OperatorUserId : null,
|
||
operatorName: null,
|
||
operatorType: "staff",
|
||
source: "job");
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|