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);