diff --git a/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs b/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs
index f7a61ca..332da0d 100644
--- a/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs
+++ b/src/Oms.Application/Dtos/PlatformStore/PlatformStoreDtos.cs
@@ -364,6 +364,60 @@ public class PlatformStoreOrderMatchInventoryOutput
public string? ClientNo { get; set; }
}
+///
+/// 店铺订单创建运单时填写的运单基本信息(对齐下单页基本信息)。
+///
+public class PlatformStoreOrderParcelBasicInput
+{
+ /// 客户单号;空则沿用平台订单号。
+ public string? ClientNo { get; set; }
+
+ /// 货物属性:general / electronics / sensitive。
+ public string? CargoAttribute { get; set; }
+
+ /// 实重(kg)。
+ public decimal? ActualWeight { get; set; }
+
+ /// 包裹数量。
+ public int? Quantity { get; set; }
+
+ /// 长(cm)。
+ public decimal? Length { get; set; }
+
+ /// 宽(cm)。
+ public decimal? Width { get; set; }
+
+ /// 高(cm)。
+ public decimal? Height { get; set; }
+
+ /// 出口报关方式。
+ public string? ExportCustomsMode { get; set; }
+
+ /// 出口资质备案 Id(9610Summary 时必填)。
+ public int? ExportQualificationId { get; set; }
+
+ /// 电商平台编码(9610Summary 时必填)。
+ public string? PlatformCode { get; set; }
+
+ /// 到仓方式:selfShip / doorPickup。
+ public string? InboundMethod { get; set; }
+
+ /// 上门揽收发件地址簿 Id。
+ public int? SenderAddressId { get; set; }
+
+ /// 期望揽收时间。
+ public DateTime? ExpectPickupTime { get; set; }
+
+ /// 揽收方向。
+ public string? PickupDirection { get; set; }
+
+ /// 承运商编码。
+ public string? CarrierCode { get; set; }
+
+ /// 承运服务。
+ public string? CarrierService { get; set; }
+}
+
public class PlatformStoreOrderBatchCreateLogisticsInput
{
[Required]
@@ -373,6 +427,9 @@ public class PlatformStoreOrderBatchCreateLogisticsInput
public int? CustomerId { get; set; }
+ /// 运单基本信息(创建运单/代发运单必填)。
+ public PlatformStoreOrderParcelBasicInput? ParcelBasic { get; set; }
+
[Required]
public List Items { get; set; } = [];
}
@@ -387,6 +444,9 @@ public class PlatformStoreOrderBatchCreateFulfillInput
public int? CustomerId { get; set; }
+ /// 运单基本信息(创建运单/代发运单必填)。
+ public PlatformStoreOrderParcelBasicInput? ParcelBasic { get; set; }
+
[Required]
public List Items { get; set; } = [];
}
@@ -396,6 +456,9 @@ public class PlatformStoreOrderBatchCreateItemInput
[Required]
public int StoreOrderId { get; set; }
+ /// 覆盖本单客户单号;空则用 ParcelBasic.ClientNo 或平台订单号。
+ public string? ClientNo { get; set; }
+
public OrderParcelReceiverInput? Receiver { get; set; }
public List BindItems { get; set; } = [];
diff --git a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
index 33d7ea0..a364dbf 100644
--- a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
+++ b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
@@ -174,11 +174,11 @@ public partial class OrderService
// 4. 保存草稿:不做预报计费。9610、上门揽收与订单一起提交。
if (input.SaveAsDraft)
{
- await EventBus.PublishAsync(new OrderParcelCustoms9610UpsertIntegrationEvent
- {
- OrderId = order.Id,
- PlatformCode = input.PlatformCode
- });
+ await RunParcelCreateSideEffectsAsync(
+ order,
+ input,
+ operatorId,
+ includeForecastAndLabel: false);
var draftOutput = await BuildParcelCreateOutputAsync(order);
await TryAttachPickupAfterParcelCreateAsync(input, draftOutput);
return draftOutput;
@@ -199,21 +199,13 @@ public partial class OrderService
order.UpdateTime = DateTime.Now;
await OrderRepository.UpdateAsync(order);
- // 6. 与订单同一事务写入 Outbox。Commit 后由 Fake 投递;任一条失败则整单回滚。
+ // 6. 有事务 UoW 时写 Outbox;无事务(店铺订单创建运单等)则同步执行,避免 Outbox 拒发。
// 轨迹不在此列:要等预报消费端排货拿到 ChannelCode 再发。
- var authorization = CaptureRequestAuthorization();
- await EventBus.PublishAsync(new OrderParcelCustoms9610UpsertIntegrationEvent
- {
- OrderId = order.Id,
- PlatformCode = input.PlatformCode
- });
- await EnqueueForecastBillingAsync(order, input.CouponInstanceId, operatorId, authorization);
- await EventBus.PublishAsync(new OrderParcelLabelGenerateIntegrationEvent
- {
- OrderId = order.Id,
- OperatorUserId = operatorId
- });
- await AutoTriggerRuntimeService.EnqueueFireAsync(order.Id, AutoTriggerTiming.OrderCreate, operatorId);
+ await RunParcelCreateSideEffectsAsync(
+ order,
+ input,
+ operatorId,
+ includeForecastAndLabel: true);
var output = await BuildParcelCreateOutputAsync(order);
output.ForecastBillingQueued = string.Equals(
@@ -225,6 +217,87 @@ public partial class OrderService
return output;
}
+ ///
+ /// 创单后置:9610 / 预报计费 / 面单 / 自动触发。
+ /// 环境有事务 UoW 时走 Outbox;否则同步执行(店铺订单创建运单无 [UnitOfWork])。
+ ///
+ private async Task RunParcelCreateSideEffectsAsync(
+ Order order,
+ OrderParcelCreateInput input,
+ int operatorId,
+ bool includeForecastAndLabel)
+ {
+ var uowManager = LazyServiceProvider.GetRequiredService();
+ var useOutbox = OmsOutboxUnitOfWork.IsAmbientTransactional(uowManager);
+ var authorization = CaptureRequestAuthorization();
+
+ if (useOutbox)
+ {
+ await EventBus.PublishAsync(new OrderParcelCustoms9610UpsertIntegrationEvent
+ {
+ OrderId = order.Id,
+ PlatformCode = input.PlatformCode
+ });
+ if (includeForecastAndLabel)
+ {
+ await EnqueueForecastBillingAsync(order, input.CouponInstanceId, operatorId, authorization);
+ await EventBus.PublishAsync(new OrderParcelLabelGenerateIntegrationEvent
+ {
+ OrderId = order.Id,
+ OperatorUserId = operatorId
+ });
+ await AutoTriggerRuntimeService.EnqueueFireAsync(
+ order.Id,
+ AutoTriggerTiming.OrderCreate,
+ operatorId);
+ }
+
+ return;
+ }
+
+ await TryUpsertCustoms9610Async(order, input.PlatformCode);
+ if (!includeForecastAndLabel)
+ {
+ return;
+ }
+
+ var payloadJson = JsonConvert.SerializeObject(new ForecastBillingJobPayload
+ {
+ CouponInstanceId = input.CouponInstanceId,
+ Authorization = authorization
+ });
+ await ProcessForecastBillingForBatchAsync(
+ order.Id,
+ payloadJson,
+ operatorId,
+ authorization: authorization);
+
+ try
+ {
+ var labelResult = await GenerateLabelPdfForBatchAsync(order.Id);
+ if (!labelResult.Success)
+ {
+ LazyServiceProvider.GetRequiredService()
+ .CreateLogger("Oms.ParcelCreate")
+ .LogWarning(
+ "Label generate failed OrderId={OrderId} Error={Error}",
+ order.Id,
+ labelResult.Error);
+ }
+ }
+ catch (Exception ex)
+ {
+ LazyServiceProvider.GetRequiredService()
+ .CreateLogger("Oms.ParcelCreate")
+ .LogWarning(ex, "Label generate failed OrderId={OrderId}", order.Id);
+ }
+
+ await AutoTriggerRuntimeService.FireByOrderIdAsync(
+ order.Id,
+ AutoTriggerTiming.OrderCreate,
+ operatorId);
+ }
+
private async Task TryUpsertCustoms9610Async(Order order, string? platformCode = null)
{
try
diff --git a/src/Oms.Application/Services/Orders/OrderService.cs b/src/Oms.Application/Services/Orders/OrderService.cs
index 61eaddd..d4e4090 100644
--- a/src/Oms.Application/Services/Orders/OrderService.cs
+++ b/src/Oms.Application/Services/Orders/OrderService.cs
@@ -14,6 +14,7 @@ using Oms.Application.Labeling;
using Oms.Application.Shared;
using Oms.Application.Storage;
using Newtonsoft.Json;
+using Oms.Application.Messaging;
using Oms.Application.Messaging.ParcelCreate;
using Oms.Application.Services.Orders.Batch;
using Oms.Domain.ChannelsAggregate.Repositories;
@@ -722,16 +723,37 @@ public partial class OrderService : ApplicationService
try
{
- await EventBus.PublishAsync(new OrderParcelPickupAttachIntegrationEvent
+ var uowManager = LazyServiceProvider.GetRequiredService();
+ if (OmsOutboxUnitOfWork.IsAmbientTransactional(uowManager))
+ {
+ await EventBus.PublishAsync(new OrderParcelPickupAttachIntegrationEvent
+ {
+ OrderId = output.OrderId,
+ CustomerId = input.CustomerId,
+ SenderAddressId = input.SenderAddressId.Value,
+ WarehouseId = input.WarehouseId.Value,
+ PickupDirection = input.PickupDirection,
+ CarrierCode = input.CarrierCode,
+ CarrierService = input.CarrierService,
+ ExpectPickupTime = input.ExpectPickupTime.Value
+ });
+ output.PickupQueued = true;
+ return;
+ }
+
+ // 无事务 UoW(店铺订单创建运单)时同步挂接揽收,避免 Outbox 拒发
+ var pickupService = LazyServiceProvider.GetRequiredService();
+ await pickupService.CreateForParcelInternalAsync(new PickupOrderCreateInput
{
- OrderId = output.OrderId,
CustomerId = input.CustomerId,
SenderAddressId = input.SenderAddressId.Value,
+ Direction = PickupDirectionCodes.NormalizeOrDefault(input.PickupDirection),
WarehouseId = input.WarehouseId.Value,
- PickupDirection = input.PickupDirection,
CarrierCode = input.CarrierCode,
CarrierService = input.CarrierService,
- ExpectPickupTime = input.ExpectPickupTime.Value
+ ExpectPickupTime = input.ExpectPickupTime.Value,
+ OrderIds = [output.OrderId],
+ Remark = null
});
output.PickupQueued = true;
}
diff --git a/src/Oms.Application/Services/Orders/PlatformStoreOrderParcelBuilder.cs b/src/Oms.Application/Services/Orders/PlatformStoreOrderParcelBuilder.cs
index 244ad2f..1e460e5 100644
--- a/src/Oms.Application/Services/Orders/PlatformStoreOrderParcelBuilder.cs
+++ b/src/Oms.Application/Services/Orders/PlatformStoreOrderParcelBuilder.cs
@@ -2,6 +2,7 @@ using System.Text.Json;
using System.Text.Json.Nodes;
using Fake.Application;
using Oms.Application.Dtos.Orders;
+using Oms.Application.Dtos.PlatformStore;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.SystemAggregate.Entities;
@@ -59,6 +60,105 @@ public static class PlatformStoreOrderParcelBuilder
};
}
+ ///
+ /// 将下单页同款「运单基本信息」覆盖到组装好的小包入参。
+ ///
+ public static void ApplyParcelBasic(
+ OrderParcelCreateInput parcelInput,
+ PlatformStoreOrderParcelBasicInput? basic,
+ string? clientNoOverride = null)
+ {
+ if (!string.IsNullOrWhiteSpace(clientNoOverride))
+ {
+ parcelInput.ClientNo = clientNoOverride.Trim();
+ }
+ else if (!string.IsNullOrWhiteSpace(basic?.ClientNo))
+ {
+ parcelInput.ClientNo = basic.ClientNo.Trim();
+ }
+
+ if (basic == null)
+ {
+ return;
+ }
+
+ if (!string.IsNullOrWhiteSpace(basic.CargoAttribute))
+ {
+ parcelInput.CargoAttribute = basic.CargoAttribute.Trim();
+ }
+
+ if (basic.ActualWeight is > 0)
+ {
+ parcelInput.ActualWeight = basic.ActualWeight;
+ parcelInput.ChargeableWeight = basic.ActualWeight;
+ }
+
+ if (basic.Quantity is > 0)
+ {
+ parcelInput.Quantity = basic.Quantity;
+ }
+
+ if (basic.Length is > 0)
+ {
+ parcelInput.Length = basic.Length;
+ }
+
+ if (basic.Width is > 0)
+ {
+ parcelInput.Width = basic.Width;
+ }
+
+ if (basic.Height is > 0)
+ {
+ parcelInput.Height = basic.Height;
+ }
+
+ if (!string.IsNullOrWhiteSpace(basic.ExportCustomsMode))
+ {
+ parcelInput.ExportCustomsMode = basic.ExportCustomsMode.Trim();
+ }
+
+ if (basic.ExportQualificationId is > 0)
+ {
+ parcelInput.ExportQualificationId = basic.ExportQualificationId;
+ }
+
+ if (!string.IsNullOrWhiteSpace(basic.PlatformCode))
+ {
+ parcelInput.PlatformCode = basic.PlatformCode.Trim();
+ }
+
+ if (!string.IsNullOrWhiteSpace(basic.InboundMethod))
+ {
+ parcelInput.InboundMethod = basic.InboundMethod.Trim();
+ }
+
+ if (basic.SenderAddressId is > 0)
+ {
+ parcelInput.SenderAddressId = basic.SenderAddressId;
+ }
+
+ if (basic.ExpectPickupTime.HasValue && basic.ExpectPickupTime != default)
+ {
+ parcelInput.ExpectPickupTime = basic.ExpectPickupTime;
+ }
+
+ if (!string.IsNullOrWhiteSpace(basic.PickupDirection))
+ {
+ parcelInput.PickupDirection = basic.PickupDirection.Trim();
+ }
+
+ if (!string.IsNullOrWhiteSpace(basic.CarrierCode))
+ {
+ parcelInput.CarrierCode = basic.CarrierCode.Trim();
+ }
+
+ if (!string.IsNullOrWhiteSpace(basic.CarrierService))
+ {
+ parcelInput.CarrierService = basic.CarrierService.Trim();
+ }
+ }
+
///
/// 按平台商品行组装小包入参(不要求库内 SKU 映射;用于创建物流 / 物流&代发)。
///
diff --git a/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs b/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs
index 2f99043..a6ed453 100644
--- a/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs
+++ b/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs
@@ -411,7 +411,8 @@ public class PlatformStoreOrderService : ApplicationService
input.WarehouseId,
input.Items,
requireBind: false,
- requireWmsProductGroup: false);
+ requireWmsProductGroup: false,
+ parcelBasic: input.ParcelBasic);
}
///
@@ -432,7 +433,8 @@ public class PlatformStoreOrderService : ApplicationService
input.WarehouseId,
input.Items,
requireBind: true,
- requireWmsProductGroup: false);
+ requireWmsProductGroup: false,
+ parcelBasic: input.ParcelBasic);
}
///
@@ -454,7 +456,8 @@ public class PlatformStoreOrderService : ApplicationService
input.WarehouseId,
input.Items,
requireBind: true,
- requireWmsProductGroup: true);
+ requireWmsProductGroup: true,
+ parcelBasic: input.ParcelBasic);
}
private async Task BatchCreateLogisticsInternalAsync(
@@ -463,7 +466,8 @@ public class PlatformStoreOrderService : ApplicationService
int? warehouseId,
List items,
bool requireBind,
- bool requireWmsProductGroup = false)
+ bool requireWmsProductGroup = false,
+ PlatformStoreOrderParcelBasicInput? parcelBasic = null)
{
if (items == null || items.Count == 0)
{
@@ -512,7 +516,9 @@ public class PlatformStoreOrderService : ApplicationService
warehouseId,
resolvedProductCode,
itemInput.Receiver,
- requireBind ? itemInput.BindItems : null);
+ requireBind ? itemInput.BindItems : null,
+ parcelBasic,
+ itemInput.ClientNo);
item.Success = true;
item.Message = requireWmsProductGroup
? $"已创建代发订单 {result.ServiceNo} 并绑定"
@@ -548,7 +554,9 @@ public class PlatformStoreOrderService : ApplicationService
int? warehouseId,
string productCode,
OrderParcelReceiverInput? receiver,
- List? bindItems)
+ List? bindItems,
+ PlatformStoreOrderParcelBasicInput? parcelBasic = null,
+ string? clientNoOverride = null)
{
var order = await GetOrderOrThrowAsync(customerId, storeOrderId);
await EnsureCanCreateLogisticsAsync(order);
@@ -572,6 +580,11 @@ public class PlatformStoreOrderService : ApplicationService
throw new BusinessException("请指定发货仓库");
}
+ if (parcelBasic != null && resolvedWarehouseId is not > 0)
+ {
+ throw new BusinessException("请选择仓库");
+ }
+
var parcelInput = PlatformStoreOrderParcelBuilder.BuildFromPlatformLines(
order,
lines,
@@ -580,6 +593,7 @@ public class PlatformStoreOrderService : ApplicationService
resolvedWarehouseId,
customerId,
receiver);
+ PlatformStoreOrderParcelBuilder.ApplyParcelBasic(parcelInput, parcelBasic, clientNoOverride);
var parcelOutput = await OrderService.CreateParcelForBatchAsync(
parcelInput,