diff --git a/src/Oms.Application/Labeling/LabelPdfGenerationService.cs b/src/Oms.Application/Labeling/LabelPdfGenerationService.cs
index a6da67b..7e3287b 100644
--- a/src/Oms.Application/Labeling/LabelPdfGenerationService.cs
+++ b/src/Oms.Application/Labeling/LabelPdfGenerationService.cs
@@ -53,8 +53,7 @@ public sealed class LabelPdfGenerationService(
var relativeUrl = await SavePdfAsync(order.ServiceNo, pdfBytes, cancellationToken);
order.LabelTemplateId = templateId;
- order.UpdateTime = DateTime.Now;
- await orderRepository.UpdateAsync(order);
+ await orderRepository.UpdateLabelTemplateIdAsync(order.Id, templateId.Value, cancellationToken);
await orderFileManager.ReplaceSystemLabelAsync(
order.Id,
diff --git a/src/Oms.Application/Messaging/OmsAfterCommit.cs b/src/Oms.Application/Messaging/OmsAfterCommit.cs
new file mode 100644
index 0000000..66f07fb
--- /dev/null
+++ b/src/Oms.Application/Messaging/OmsAfterCommit.cs
@@ -0,0 +1,49 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Fake.UnitOfWork;
+
+namespace Oms.Application.Messaging;
+
+///
+/// 把创单/预报之后的副作用延后到当前事务提交成功再执行,每一步单独提交。
+///
+internal static class OmsAfterCommit
+{
+ public static async Task ScheduleAsync(IUnitOfWorkManager unitOfWorkManager, Func action)
+ {
+ var current = unitOfWorkManager.Current;
+ if (current != null && !current.IsCompleted)
+ {
+ current.OnCompleted(action);
+ return;
+ }
+
+ await action();
+ }
+
+ ///
+ /// 新开独立事务执行一步。失败只记日志,不抛回调用方。
+ ///
+ public static async Task RunStepAsync(
+ IServiceScopeFactory scopeFactory,
+ ILogger logger,
+ string step,
+ Func work)
+ {
+ try
+ {
+ using var scope = scopeFactory.CreateScope();
+ var unitOfWorkManager = scope.ServiceProvider.GetRequiredService();
+ using var unitOfWork = unitOfWorkManager.Begin(OmsOutboxUnitOfWork.Independent());
+ await OmsIntegrationEventPublisher.EnsureOutboxDbContextAsync(unitOfWorkManager);
+ await work(scope.ServiceProvider);
+ await unitOfWork.CompleteAsync();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "订单后置步骤失败 Step={Step}", step);
+ return false;
+ }
+ }
+}
diff --git a/src/Oms.Application/Messaging/OmsOutboxUnitOfWork.cs b/src/Oms.Application/Messaging/OmsOutboxUnitOfWork.cs
index dd1c83b..8d868d5 100644
--- a/src/Oms.Application/Messaging/OmsOutboxUnitOfWork.cs
+++ b/src/Oms.Application/Messaging/OmsOutboxUnitOfWork.cs
@@ -17,6 +17,16 @@ public static class OmsOutboxUnitOfWork
IsolationLevel = IsolationLevel.ReadCommitted
};
+ ///
+ /// 与当前业务事务脱开的短事务,用于创单/预报成功之后的独立事件。
+ ///
+ public static UnitOfWorkAttribute Independent() => new()
+ {
+ IsTransactional = true,
+ RequiresNew = true,
+ IsolationLevel = IsolationLevel.ReadCommitted
+ };
+
public static bool IsAmbientTransactional(IUnitOfWorkManager unitOfWorkManager) =>
unitOfWorkManager.Current?.Context.IsTransactional == true;
}
diff --git a/src/Oms.Application/Messaging/OrderBatchJobEventHandler.cs b/src/Oms.Application/Messaging/OrderBatchJobEventHandler.cs
index 95b3ad4..32451db 100644
--- a/src/Oms.Application/Messaging/OrderBatchJobEventHandler.cs
+++ b/src/Oms.Application/Messaging/OrderBatchJobEventHandler.cs
@@ -29,7 +29,7 @@ public sealed class OrderBatchJobEventHandler(
return;
}
- // ForecastBilling 成功后继续写轨迹/面单/自动触发 Outbox,须事务性 UoW
+ // 业务结果在本事务提交;轨迹/面单/自动触发由处理过程登记到提交后单独执行。
using var unitOfWork = unitOfWorkManager.Begin(OmsOutboxUnitOfWork.Transactional());
await processor.ProcessAsync(@event.ToMessage(), cancellationToken);
await unitOfWork.CompleteAsync(cancellationToken);
diff --git a/src/Oms.Application/Services/Orders/Batch/OrderBatchJobProcessor.cs b/src/Oms.Application/Services/Orders/Batch/OrderBatchJobProcessor.cs
index 9b04635..071ad64 100644
--- a/src/Oms.Application/Services/Orders/Batch/OrderBatchJobProcessor.cs
+++ b/src/Oms.Application/Services/Orders/Batch/OrderBatchJobProcessor.cs
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Oms.Application.Dtos.Orders;
using Oms.Application.Messaging;
+using Oms.Application.Services.Orders;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.OrdersAggregate.Repositories;
@@ -94,6 +95,7 @@ public sealed class OrderBatchJobProcessor(
await OrderBatchJobItemFeedback.EnrichIdentifiersAsync(item, action, orderRepository, cancellationToken);
OrderBatchJobItemFeedback.ApplyBusinessFailure(item, result, action);
await batchJobRepository.UpdateItemAsync(item, cancellationToken);
+ await TryStampForecastBillingFailureAsync(message, item.FailReason, cancellationToken);
await RefreshJobSummaryAsync(item.JobId, cancellationToken);
return;
@@ -111,6 +113,7 @@ public sealed class OrderBatchJobProcessor(
action,
item.RetryCount,
item.FailReason);
+ await TryStampForecastBillingFailureAsync(message, item.FailReason, cancellationToken);
await RefreshJobSummaryAsync(item.JobId, cancellationToken);
return;
}
@@ -148,6 +151,43 @@ public sealed class OrderBatchJobProcessor(
return null;
}
+ ///
+ /// 预报计费重试耗尽或业务失败时,把原因写回订单,避免一直显示「处理中」。
+ /// 更具体的失败原因已经写上时不再覆盖。
+ ///
+ private async Task TryStampForecastBillingFailureAsync(
+ OrderBatchJobMessage message,
+ string? reason,
+ CancellationToken cancellationToken)
+ {
+ if (!string.Equals(message.Action, OrderBatchActionCodes.ForecastBilling, StringComparison.OrdinalIgnoreCase)
+ || message.OrderId <= 0)
+ {
+ return;
+ }
+
+ var order = await orderRepository.FirstOrDefaultAsync(x => x.Id == message.OrderId, cancellationToken);
+ if (order == null
+ || !string.Equals(
+ order.PredeclareFailReason,
+ OrderService.ForecastBillingProcessingReason,
+ StringComparison.Ordinal))
+ {
+ return;
+ }
+
+ var text = string.IsNullOrWhiteSpace(reason) ? "预报计费失败" : reason.Trim();
+ if (text.Length > 512)
+ {
+ text = text[..512];
+ }
+
+ order.OrderStatus = OrderStatusCodes.Draft;
+ order.PredeclareFailReason = text;
+ order.UpdateTime = DateTime.Now;
+ await orderRepository.UpdateAsync(order, cancellationToken);
+ }
+
private async Task RefreshJobSummaryAsync(int jobId, CancellationToken cancellationToken)
{
var job = await batchJobRepository.GetByIdAsync(jobId, cancellationToken);
diff --git a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
index 1085937..afe1617 100644
--- a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
+++ b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
@@ -3,6 +3,7 @@ using Fake.Application;
using Fake.UnitOfWork;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Oms.Application.Dtos.Channels;
@@ -39,7 +40,7 @@ public partial class OrderService
private const int ParcelBatchMaxCount = 2000;
private const string InboundBillingProcessingReason = "入库后计费/排货处理中";
private const string InboundBillingEnqueueFailReason = "入库计费任务投递失败,请重跑入库计费";
- private const string ForecastBillingProcessingReason = "预报计费/排货处理中";
+ internal const string ForecastBillingProcessingReason = "预报计费/排货处理中";
private const string ForecastBillingEnqueueFailReason = "预报计费任务投递失败,请重新预报";
private static readonly ConcurrentDictionary InboundBillingInFlight = new();
@@ -99,19 +100,6 @@ public partial class OrderService
};
}
- ///
- /// 业务写库前先打开 Outbox DbContext,让 EF 先占环境事务连接,
- /// 避免 SqlSugar 先开事务后 Outbox UseTransaction 失败而落入「短事务提前 Commit」。
- ///
- private async Task EnsureOutboxReadyAsync()
- {
- var uowManager = LazyServiceProvider.GetRequiredService();
- if (OmsOutboxUnitOfWork.IsAmbientTransactional(uowManager))
- {
- await OmsIntegrationEventPublisher.EnsureOutboxDbContextAsync(uowManager);
- }
- }
-
private async Task CreateParcelCoreAsync(
OrderParcelCreateInput input,
int? operatorUserId = null,
@@ -164,12 +152,10 @@ public partial class OrderService
country.ChineseName);
var serviceNo = await ResolveServiceNoAsync();
+ var operatorId = operatorUserId ?? (CurrentUser.IsAuthenticated ? CurrentUser.GetId() : 0);
Order? created = null;
try
{
- // 必须在任何 SqlSugar 写之前:保证 9610 / ForecastBilling / Pickup Outbox 与建单同事务提交。
- await EnsureOutboxReadyAsync();
-
var order = BuildDraftOrderEntity(
input,
product,
@@ -184,12 +170,11 @@ public partial class OrderService
var weights = BuildInitialWeightRows(order.Id, input);
await SaveParcelChildrenAsync(order, input, weights, country);
- // 客户主动「先保存为草稿」:落库草稿后直接返回,不跑预报计费。
+ // 客户主动「先保存为草稿」:落库后返回。9610 / 揽收在事务提交后各自执行。
if (input.SaveAsDraft)
{
- await ParcelCreateEventPublisher.PublishCustoms9610UpsertAsync(order, input.PlatformCode);
var draftOutput = await BuildParcelCreateOutputAsync(order);
- await TryAttachPickupAfterParcelCreateAsync(input, draftOutput);
+ await ScheduleCreateSideEffectsAsync(order.Id, input, draftOutput, includeForecast: false, operatorId);
return draftOutput;
}
@@ -207,18 +192,8 @@ public partial class OrderService
order.UpdateTime = DateTime.Now;
await OrderRepository.UpdateAsync(order);
- await ParcelCreateEventPublisher.PublishCustoms9610UpsertAsync(order, input.PlatformCode);
- await EnqueueForecastBillingAsync(
- order,
- input.CouponInstanceId,
- operatorUserId ?? (CurrentUser.IsAuthenticated ? CurrentUser.GetId() : 0));
-
var output = await BuildParcelCreateOutputAsync(order);
- output.ForecastBillingQueued = string.Equals(
- order.PredeclareFailReason,
- ForecastBillingProcessingReason,
- StringComparison.Ordinal);
- await TryAttachPickupAfterParcelCreateAsync(input, output);
+ await ScheduleCreateSideEffectsAsync(order.Id, input, output, includeForecast: true, operatorId);
return output;
}
catch (BusinessException)
@@ -2326,8 +2301,6 @@ public partial class OrderService
try
{
- await EnsureOutboxReadyAsync();
-
var snapshotBinding = await ResolveSnapshotBindingFromOrderAsync(product, order);
var address = await ResolveOrderBillingAddressAsync(order);
@@ -2355,10 +2328,24 @@ public partial class OrderService
order.UpdateTime = DateTime.Now;
await OrderRepository.UpdateAsync(order);
- await EnqueueForecastBillingAsync(
- order,
- order.CouponInstanceId,
- operatorUserId ?? (CurrentUser.IsAuthenticated ? CurrentUser.GetId() : 0));
+ var operatorId = operatorUserId ?? (CurrentUser.IsAuthenticated ? CurrentUser.GetId() : 0);
+ var publisher = LazyServiceProvider.GetRequiredService();
+ if (!publisher.IsEnabled)
+ {
+ var payloadJson = JsonConvert.SerializeObject(new ForecastBillingJobPayload
+ {
+ CouponInstanceId = order.CouponInstanceId
+ });
+ await ProcessForecastBillingForBatchAsync(order.Id, payloadJson, operatorId);
+ }
+ else
+ {
+ var orderId = order.Id;
+ var couponInstanceId = order.CouponInstanceId;
+ await ScheduleForecastEnqueueAsync(orderId, couponInstanceId, operatorId);
+ }
+
+ await ScheduleForecastSideEffectsAsync(order.Id, operatorId);
// 同步回落失败时给出明确错误;入队成功则保持「处理中」由列表观察结果。
var refreshed = await OrderRepository.FirstOrDefaultAsync(x => x.Id == order.Id);
@@ -2681,10 +2668,10 @@ public partial class OrderService
var order = await OrderRepository.FirstOrDefaultAsync(x => x.Id == orderId, cancellationToken);
if (order == null)
{
- return;
+ // 创单事务尚未提交时消息可能先到。抛出让总线重试,避免当成成功 Ack 后订单永远停在处理中。
+ throw new InvalidOperationException($"预报计费时订单尚未可见,稍后重试 OrderId={orderId}");
}
- // 创单失败回滚 / 软删后,可能仍有短事务提前投递的孤儿任务
if (string.Equals(order.OrderStatus, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase))
{
return;
@@ -2852,21 +2839,6 @@ public partial class OrderService
operatorName: null,
operatorType: "staff",
source: "job");
-
- var customer = order.CustomerId.HasValue
- ? await CustomerRepository.FirstOrDefaultAsync(x => x.Id == order.CustomerId.Value)
- : null;
- await PublishParcelCreateTrackAndLabelAsync(
- order,
- customer,
- receiver: null,
- appendInitialTrack: true,
- operatorUserId);
-
- await AutoTriggerRuntimeService.EnqueueFireAsync(
- order.Id,
- AutoTriggerTiming.OrderCreate,
- operatorUserId);
}
private sealed class ForecastBillingJobPayload
@@ -2874,6 +2846,188 @@ public partial class OrderService
public int? CouponInstanceId { get; set; }
}
+ ///
+ /// 创单事务提交后,9610、预报计费、揽收、轨迹、面单、自动触发各自提交,互不等待。
+ ///
+ private async Task ScheduleCreateSideEffectsAsync(
+ int orderId,
+ OrderParcelCreateInput input,
+ OrderParcelCreateOutput output,
+ bool includeForecast,
+ int operatorUserId)
+ {
+ var unitOfWorkManager = LazyServiceProvider.GetRequiredService();
+ var scopeFactory = LazyServiceProvider.GetRequiredService();
+ var logger = LazyServiceProvider.GetRequiredService().CreateLogger("Oms.OrderParcelCreate");
+ var platformCode = input.PlatformCode;
+ var couponInstanceId = input.CouponInstanceId;
+
+ await OmsAfterCommit.ScheduleAsync(unitOfWorkManager, async () =>
+ {
+ try
+ {
+ var customsTask = OmsAfterCommit.RunStepAsync(scopeFactory, logger, "customs-9610", sp =>
+ sp.GetRequiredService().PublishCustomsForOrderAsync(orderId, platformCode));
+ var forecastTask = includeForecast
+ ? OmsAfterCommit.RunStepAsync(scopeFactory, logger, "forecast-billing", async sp =>
+ {
+ var service = sp.GetRequiredService();
+ await service.EnqueueForecastBillingByIdAsync(orderId, couponInstanceId, operatorUserId);
+ var latest = await sp.GetRequiredService()
+ .FirstOrDefaultAsync(x => x.Id == orderId);
+ if (latest != null)
+ {
+ output.OrderStatus = latest.OrderStatus;
+ output.PredeclareFailReason = latest.PredeclareFailReason;
+ output.ForecastBillingQueued = string.Equals(
+ latest.PredeclareFailReason,
+ ForecastBillingProcessingReason,
+ StringComparison.Ordinal);
+ }
+ })
+ : Task.FromResult(true);
+ var pickupTask = OmsAfterCommit.RunStepAsync(scopeFactory, logger, "pickup-attach", sp =>
+ sp.GetRequiredService().TryAttachPickupAfterParcelCreateAsync(input, output));
+ var trackTask = includeForecast
+ ? OmsAfterCommit.RunStepAsync(scopeFactory, logger, "track-upsert", sp =>
+ sp.GetRequiredService().PublishForecastTrackAsync(orderId))
+ : Task.FromResult(true);
+ var labelTask = includeForecast
+ ? OmsAfterCommit.RunStepAsync(scopeFactory, logger, "label-generate", sp =>
+ sp.GetRequiredService().PublishForecastLabelAsync(orderId, operatorUserId))
+ : Task.FromResult(true);
+ var triggerTask = includeForecast
+ ? OmsAfterCommit.RunStepAsync(scopeFactory, logger, "auto-trigger", sp =>
+ sp.GetRequiredService().PublishForecastAutoTriggerAsync(orderId, operatorUserId))
+ : Task.FromResult(true);
+
+ await Task.WhenAll(customsTask, forecastTask, pickupTask, trackTask, labelTask, triggerTask);
+
+ if (includeForecast && !await forecastTask)
+ {
+ await OmsAfterCommit.RunStepAsync(scopeFactory, logger, "forecast-billing-fail", sp =>
+ sp.GetRequiredService().MarkForecastEnqueueFailedIfStillProcessingAsync(orderId));
+ output.ForecastBillingQueued = false;
+ output.PredeclareFailReason = ForecastBillingEnqueueFailReason;
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "创单后置步骤调度失败 OrderId={OrderId}", orderId);
+ }
+ });
+ }
+
+ private async Task ScheduleForecastEnqueueAsync(int orderId, int? couponInstanceId, int operatorUserId)
+ {
+ var unitOfWorkManager = LazyServiceProvider.GetRequiredService();
+ var scopeFactory = LazyServiceProvider.GetRequiredService();
+ var logger = LazyServiceProvider.GetRequiredService().CreateLogger("Oms.ForecastBilling");
+ await OmsAfterCommit.ScheduleAsync(unitOfWorkManager, () =>
+ OmsAfterCommit.RunStepAsync(scopeFactory, logger, "forecast-billing", sp =>
+ sp.GetRequiredService().EnqueueForecastBillingByIdAsync(
+ orderId,
+ couponInstanceId,
+ operatorUserId)));
+ }
+
+ ///
+ /// 创单或草稿重新预报提交后,轨迹、面单、下单自动触发各自入队,不等预报计费。
+ ///
+ private async Task ScheduleForecastSideEffectsAsync(int orderId, int operatorUserId)
+ {
+ var unitOfWorkManager = LazyServiceProvider.GetRequiredService();
+ var scopeFactory = LazyServiceProvider.GetRequiredService();
+ var logger = LazyServiceProvider.GetRequiredService().CreateLogger("Oms.ForecastBilling");
+ await OmsAfterCommit.ScheduleAsync(unitOfWorkManager, async () =>
+ {
+ await OmsAfterCommit.RunStepAsync(scopeFactory, logger, "track-upsert", sp =>
+ sp.GetRequiredService().PublishForecastTrackAsync(orderId));
+ await OmsAfterCommit.RunStepAsync(scopeFactory, logger, "label-generate", sp =>
+ sp.GetRequiredService().PublishForecastLabelAsync(orderId, operatorUserId));
+ await OmsAfterCommit.RunStepAsync(scopeFactory, logger, "auto-trigger", sp =>
+ sp.GetRequiredService().PublishForecastAutoTriggerAsync(orderId, operatorUserId));
+ });
+ }
+
+ private async Task PublishCustomsForOrderAsync(int orderId, string? platformCode)
+ {
+ var order = await OrderRepository.FirstOrDefaultAsync(x => x.Id == orderId);
+ if (order == null
+ || string.Equals(order.OrderStatus, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ await ParcelCreateEventPublisher.PublishCustoms9610UpsertAsync(order, platformCode);
+ }
+
+ private async Task EnqueueForecastBillingByIdAsync(int orderId, int? couponInstanceId, int operatorUserId)
+ {
+ var order = await OrderRepository.FirstOrDefaultAsync(x => x.Id == orderId);
+ if (order == null
+ || string.Equals(order.OrderStatus, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ await EnqueueForecastBillingAsync(order, couponInstanceId, operatorUserId);
+ }
+
+ private async Task MarkForecastEnqueueFailedIfStillProcessingAsync(int orderId)
+ {
+ var order = await OrderRepository.FirstOrDefaultAsync(x => x.Id == orderId);
+ if (order == null
+ || !string.Equals(order.PredeclareFailReason, ForecastBillingProcessingReason, StringComparison.Ordinal))
+ {
+ return;
+ }
+
+ await MarkParcelDraftFailedAsync(order, ForecastBillingEnqueueFailReason);
+ }
+
+ private async Task PublishForecastTrackAsync(int orderId)
+ {
+ var order = await OrderRepository.FirstOrDefaultAsync(x => x.Id == orderId);
+ if (order == null
+ || string.Equals(order.OrderStatus, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ var customer = order.CustomerId.HasValue
+ ? await CustomerRepository.FirstOrDefaultAsync(x => x.Id == order.CustomerId.Value)
+ : null;
+ var request = await BuildSmallBagTrackUpsertRequestAsync(order, receiverInput: null);
+ request.AppendInitialTrackOnSuccess = true;
+ ApplyTrackAuthorization(request);
+ await ParcelCreateEventPublisher.PublishTrackUpsertAsync(order, request, customer?.Id);
+ }
+
+ private async Task PublishForecastLabelAsync(int orderId, int operatorUserId)
+ {
+ var order = await OrderRepository.FirstOrDefaultAsync(x => x.Id == orderId);
+ if (order == null
+ || string.Equals(order.OrderStatus, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ await ParcelCreateEventPublisher.PublishLabelGenerateAsync(order, operatorUserId);
+ }
+
+ private async Task PublishForecastAutoTriggerAsync(int orderId, int operatorUserId)
+ {
+ var order = await OrderRepository.FirstOrDefaultAsync(x => x.Id == orderId);
+ if (order == null
+ || string.Equals(order.OrderStatus, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ await AutoTriggerRuntimeService.EnqueueFireAsync(orderId, AutoTriggerTiming.OrderCreate, operatorUserId);
+ }
+
private async Task EnqueueForecastBillingAsync(
Order order,
int? couponInstanceId,
@@ -3206,40 +3360,8 @@ public partial class OrderService
private IOrderSideEffectEventPublisher SideEffectEventPublisher =>
LazyServiceProvider.GetRequiredService();
- ///
- /// 创单/草稿预报成功后:轨迹订单+A0101 合并事件,系统面单独立事件(Fake.EventBus.RabbitMQ)。
- ///
- private async Task PublishParcelCreateTrackAndLabelAsync(
- Order order,
- Customer? customer,
- OrderParcelReceiverInput? receiver,
- bool appendInitialTrack,
- int? operatorUserId = null)
- {
- var request = await BuildSmallBagTrackUpsertRequestAsync(order, receiver);
- request.AppendInitialTrackOnSuccess = appendInitialTrack;
- var authorization = LazyServiceProvider
- .GetRequiredService()
- .HttpContext?
- .Request
- .Headers
- .Authorization
- .ToString();
- if (!string.IsNullOrWhiteSpace(authorization))
- {
- request.Authorization = authorization;
- }
-
- await ParcelCreateEventPublisher.PublishTrackUpsertAsync(order, request, customer?.Id);
- await ParcelCreateEventPublisher.PublishLabelGenerateAsync(
- order,
- operatorUserId
- ?? (CurrentUser.IsAuthenticated ? CurrentUser.GetId() : 0));
- }
-
///
/// 小包预报成功后异步同步轨迹订单(POST /ProviderService/Order/UpsertSmallBag)。
- /// 入库等非创单路径仍可调用;创单路径请走 。
///
private async Task EnqueueSmallBagTrackUpsertAsync(
Order order,
@@ -3249,6 +3371,12 @@ public partial class OrderService
{
var request = await BuildSmallBagTrackUpsertRequestAsync(order, receiver);
request.AppendInitialTrackOnSuccess = appendInitialTrack;
+ ApplyTrackAuthorization(request);
+ await ParcelCreateEventPublisher.PublishTrackUpsertAsync(order, request, customer?.Id);
+ }
+
+ private void ApplyTrackAuthorization(SmallBagTrackUpsertRequest request)
+ {
var authorization = LazyServiceProvider
.GetRequiredService()
.HttpContext?
@@ -3260,8 +3388,6 @@ public partial class OrderService
{
request.Authorization = authorization;
}
-
- await ParcelCreateEventPublisher.PublishTrackUpsertAsync(order, request, customer?.Id);
}
///
diff --git a/src/Oms.Domain/OrdersAggregate/Repositories/IOrderRepository.cs b/src/Oms.Domain/OrdersAggregate/Repositories/IOrderRepository.cs
index 1cd7722..c5d8ab0 100644
--- a/src/Oms.Domain/OrdersAggregate/Repositories/IOrderRepository.cs
+++ b/src/Oms.Domain/OrdersAggregate/Repositories/IOrderRepository.cs
@@ -132,4 +132,9 @@ public interface IOrderRepository : IRepository
DateTime startInclusive,
DateTime endExclusive,
CancellationToken cancellationToken = default);
+
+ ///
+ /// 只更新标签模板 Id,避免面单生成覆盖预报计费正在写入的订单字段。
+ ///
+ Task UpdateLabelTemplateIdAsync(int orderId, int labelTemplateId, CancellationToken cancellationToken = default);
}
diff --git a/src/Oms.SqlSugarCore/Orders/OrderRepository.cs b/src/Oms.SqlSugarCore/Orders/OrderRepository.cs
index b7d0884..c85d43f 100644
--- a/src/Oms.SqlSugarCore/Orders/OrderRepository.cs
+++ b/src/Oms.SqlSugarCore/Orders/OrderRepository.cs
@@ -1021,6 +1021,15 @@ public class OrderRepository : OmsSqlSugarRepository, IOrderRepository, I
}
}
+ public async Task UpdateLabelTemplateIdAsync(int orderId, int labelTemplateId, CancellationToken cancellationToken = default)
+ {
+ var db = await GetDbContextAsync(cancellationToken);
+ await db.Updateable()
+ .SetColumns(x => new Order { LabelTemplateId = labelTemplateId, UpdateTime = DateTime.Now })
+ .Where(x => x.Id == orderId)
+ .ExecuteCommandAsync(cancellationToken);
+ }
+
public async Task DeleteReceivableFeesAsync(int orderId, CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);