diff --git a/db.sql b/db.sql
index 1a2694e..e02fc29 100644
--- a/db.sql
+++ b/db.sql
@@ -685,6 +685,13 @@ CREATE TABLE `t_inbound_record` (
CREATE TABLE `t_outbound_record` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键,自增,步长 1',
`WaybillNo` VARCHAR(64) NOT NULL COMMENT '运单号',
+ `Weight` DECIMAL(10,2) NULL COMMENT '实重(kg)',
+ `Length` DECIMAL(10,2) NULL COMMENT '长(cm)',
+ `Width` DECIMAL(10,2) NULL COMMENT '宽(cm)',
+ `Height` DECIMAL(10,2) NULL COMMENT '高(cm)',
+ `SalesProductId` INT NULL COMMENT '销售产品ID',
+ `DestinationCountry` VARCHAR(8) NULL COMMENT '目的国',
+ `ServiceChannelId` INT NULL COMMENT '服务渠道ID',
`WarehouseId` INT NULL COMMENT '仓库ID',
`OperateTime` DATETIME NOT NULL COMMENT '操作时间',
`OperatorId` INT NULL COMMENT '操作人ID',
@@ -696,7 +703,9 @@ CREATE TABLE `t_outbound_record` (
`UpdateTime` DATETIME NULL COMMENT '更新时间',
PRIMARY KEY (`Id`),
KEY `idx_out_waybill_time` (`WaybillNo`, `OperateTime`),
- KEY `idx_out_warehouse_time` (`WarehouseId`, `OperateTime`)
+ KEY `idx_out_warehouse_time` (`WarehouseId`, `OperateTime`),
+ KEY `idx_out_product_time` (`SalesProductId`, `OperateTime`),
+ KEY `idx_out_channel_time` (`ServiceChannelId`, `OperateTime`)
) COMMENT='出库记录表';
CREATE TABLE `t_label_print_record` (
diff --git a/sql/migrate_t_outbound_record_columns.sql b/sql/migrate_t_outbound_record_columns.sql
new file mode 100644
index 0000000..5fd492f
--- /dev/null
+++ b/sql/migrate_t_outbound_record_columns.sql
@@ -0,0 +1,13 @@
+-- 出库记录表补齐件重尺、销售产品、目的国、服务渠道字段(与 t_inbound_record 对齐)
+ALTER TABLE `t_outbound_record`
+ ADD COLUMN `Weight` DECIMAL(10,2) NULL COMMENT '实重(kg)' AFTER `WaybillNo`,
+ ADD COLUMN `Length` DECIMAL(10,2) NULL COMMENT '长(cm)' AFTER `Weight`,
+ ADD COLUMN `Width` DECIMAL(10,2) NULL COMMENT '宽(cm)' AFTER `Length`,
+ ADD COLUMN `Height` DECIMAL(10,2) NULL COMMENT '高(cm)' AFTER `Width`,
+ ADD COLUMN `SalesProductId` INT NULL COMMENT '销售产品ID' AFTER `Height`,
+ ADD COLUMN `DestinationCountry` VARCHAR(8) NULL COMMENT '目的国' AFTER `SalesProductId`,
+ ADD COLUMN `ServiceChannelId` INT NULL COMMENT '服务渠道ID' AFTER `DestinationCountry`;
+
+ALTER TABLE `t_outbound_record`
+ ADD KEY `idx_out_product_time` (`SalesProductId`, `OperateTime`),
+ ADD KEY `idx_out_channel_time` (`ServiceChannelId`, `OperateTime`);
diff --git a/src/Oms.Application/Dtos/Channels/SalesProductAllocationProviderSnapshotDto.cs b/src/Oms.Application/Dtos/Channels/SalesProductAllocationProviderSnapshotDto.cs
new file mode 100644
index 0000000..b513545
--- /dev/null
+++ b/src/Oms.Application/Dtos/Channels/SalesProductAllocationProviderSnapshotDto.cs
@@ -0,0 +1,47 @@
+namespace Oms.Application.Dtos.Channels;
+
+///
+/// 排货服务商发布快照项(不含审计时间)。
+///
+public class SalesProductAllocationProviderSnapshotDto
+{
+ ///
+ /// 主键。
+ ///
+ public int Id { get; set; }
+
+ ///
+ /// 销售产品主键。
+ ///
+ public int SalesProductId { get; set; }
+
+ ///
+ /// 序号。
+ ///
+ public int SerialNumber { get; set; }
+
+ ///
+ /// 供应商名称。
+ ///
+ public string ProviderName { get; set; } = string.Empty;
+
+ ///
+ /// 服务渠道主键。
+ ///
+ public int? ServiceChannelId { get; set; }
+
+ ///
+ /// 服务渠道名称。
+ ///
+ public string? ServiceChannelName { get; set; }
+
+ ///
+ /// 权重(评分用)。
+ ///
+ public int Weight { get; set; }
+
+ ///
+ /// 指定客户主键集合,空表示全部。
+ ///
+ public List CustomerIds { get; set; } = [];
+}
diff --git a/src/Oms.Application/Dtos/Operations/CreateOutboundRecordInput.cs b/src/Oms.Application/Dtos/Operations/CreateOutboundRecordInput.cs
index 4b11c74..05ddc61 100644
--- a/src/Oms.Application/Dtos/Operations/CreateOutboundRecordInput.cs
+++ b/src/Oms.Application/Dtos/Operations/CreateOutboundRecordInput.cs
@@ -14,6 +14,42 @@ public class CreateOutboundRecordInput
[StringLength(64)]
public string WaybillNo { get; set; } = string.Empty;
+ ///
+ /// 重量。
+ ///
+ public decimal? Weight { get; set; }
+
+ ///
+ /// 长度。
+ ///
+ public decimal? Length { get; set; }
+
+ ///
+ /// 宽度。
+ ///
+ public decimal? Width { get; set; }
+
+ ///
+ /// 高度。
+ ///
+ public decimal? Height { get; set; }
+
+ ///
+ /// 销售产品主键。
+ ///
+ public int? SalesProductId { get; set; }
+
+ ///
+ /// 目的国家/地区。
+ ///
+ [StringLength(8)]
+ public string? DestinationCountry { get; set; }
+
+ ///
+ /// 服务渠道主键。
+ ///
+ public int? ServiceChannelId { get; set; }
+
///
/// 仓库主键。
///
diff --git a/src/Oms.Application/Dtos/Operations/OutboundRecordOutput.cs b/src/Oms.Application/Dtos/Operations/OutboundRecordOutput.cs
index 67d66b9..350f985 100644
--- a/src/Oms.Application/Dtos/Operations/OutboundRecordOutput.cs
+++ b/src/Oms.Application/Dtos/Operations/OutboundRecordOutput.cs
@@ -15,6 +15,51 @@ public class OutboundRecordOutput
///
public string WaybillNo { get; set; } = string.Empty;
+ ///
+ /// 重量。
+ ///
+ public decimal? Weight { get; set; }
+
+ ///
+ /// 长度。
+ ///
+ public decimal? Length { get; set; }
+
+ ///
+ /// 宽度。
+ ///
+ public decimal? Width { get; set; }
+
+ ///
+ /// 高度。
+ ///
+ public decimal? Height { get; set; }
+
+ ///
+ /// 销售产品主键。
+ ///
+ public int? SalesProductId { get; set; }
+
+ ///
+ /// 销售产品名称。
+ ///
+ public string? SalesProductName { get; set; }
+
+ ///
+ /// 目的国家/地区。
+ ///
+ public string? DestinationCountry { get; set; }
+
+ ///
+ /// 服务渠道主键。
+ ///
+ public int? ServiceChannelId { get; set; }
+
+ ///
+ /// 服务渠道名称。
+ ///
+ public string? ServiceChannelName { get; set; }
+
///
/// 仓库主键。
///
diff --git a/src/Oms.Application/Dtos/Orders/ProductBillingSnapshotDtos.cs b/src/Oms.Application/Dtos/Orders/ProductBillingSnapshotDtos.cs
index 809b136..660322c 100644
--- a/src/Oms.Application/Dtos/Orders/ProductBillingSnapshotDtos.cs
+++ b/src/Oms.Application/Dtos/Orders/ProductBillingSnapshotDtos.cs
@@ -44,9 +44,9 @@ public class SalesProductRulesPublishSnapshotDto
public SalesProductSaveInput? Product { get; set; }
///
- /// 分配的服务商列表。
+ /// 分配的服务商列表(发布快照,不含创建/更新时间)。
///
- public List Providers { get; set; } = [];
+ public List Providers { get; set; } = [];
///
/// 黑名单分区 ID(发布时固化)。
@@ -109,6 +109,16 @@ public class SalesProductRulesPublishSnapshotDto
public List WeightRoundingInfos { get; set; } = [];
}
+///
+/// 服务渠道买价发布快照。
+///
+public class ServiceChannelPricePublishSnapshotDto
+{
+ public int ServiceChannelId { get; set; }
+
+ public List Prices { get; set; } = [];
+}
+
///
/// 绑定到订单的快照版本信息。
///
diff --git a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
index df8f7c4..0b4fad0 100644
--- a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
+++ b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
@@ -166,6 +166,8 @@ public partial class SalesProductService
{
Product = MapProductToRulesSaveInput(product),
Providers = (await GetAllocationCoreAsync(salesProductId)).Providers
+ .Select(PublishSnapshotSlimmer.SlimAllocationProvider)
+ .ToList()
};
await RulesSnapshotHelper.ApplyRulesToSnapshotAsync(
rulesSnapshot,
diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
index ccfa5ca..a6c1fab 100644
--- a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
+++ b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
@@ -173,35 +173,56 @@ public partial class ServiceChannelService
};
}
+ ///
+ /// 组装买价发布快照(不落库),与产品卖价 §5.3.2 对称。
+ ///
+ private async Task BuildPricePublishSnapshotAsync(int serviceChannelId)
+ {
+ return new ServiceChannelPricePublishSnapshot
+ {
+ ServiceChannelId = serviceChannelId,
+ Prices = (await GetPriceOutputsAsync(serviceChannelId))
+ .Select(PublishSnapshotSlimmer.SlimChannelPrice)
+ .ToList()
+ };
+ }
+
+ ///
+ /// 组装渠道规则发布快照(不落库)。
+ ///
+ private async Task BuildRulesPublishSnapshotAsync(int serviceChannelId)
+ {
+ var entity = await GetEntityOrThrowAsync(serviceChannelId);
+ return new ServiceChannelRulesPublishSnapshot
+ {
+ Channel = MapChannelToSaveInput(entity)
+ };
+ }
+
+ private static ServiceChannelSaveInput MapChannelToSaveInput(ServiceChannel entity) =>
+ new()
+ {
+ Id = entity.Id,
+ GroupId = entity.GroupId,
+ ChannelCode = entity.ChannelCode,
+ ChannelName = entity.ChannelName,
+ MinTime = entity.MinTime,
+ MaxTime = entity.MaxTime,
+ ForecastType = entity.ForecastType,
+ ForecastCode = entity.ForecastCode,
+ Description = entity.Description,
+ PreScript = entity.PreScript,
+ IsActive = entity.IsActive
+ };
+
///
/// 为当前配置生成发布快照。
///
private async Task CreatePublishRecordAsync(int serviceChannelId, string operatorType, string? description)
{
- var entity = await GetEntityOrThrowAsync(serviceChannelId);
object snapshot = operatorType == PriceOperatorType
- ? new ServiceChannelPricePublishSnapshot
- {
- ServiceChannelId = serviceChannelId,
- Prices = await GetPriceOutputsAsync(serviceChannelId)
- }
- : new ServiceChannelRulesPublishSnapshot
- {
- Channel = new ServiceChannelSaveInput
- {
- Id = entity.Id,
- GroupId = entity.GroupId,
- ChannelCode = entity.ChannelCode,
- ChannelName = entity.ChannelName,
- MinTime = entity.MinTime,
- MaxTime = entity.MaxTime,
- ForecastType = entity.ForecastType,
- ForecastCode = entity.ForecastCode,
- Description = entity.Description,
- PreScript = entity.PreScript,
- IsActive = entity.IsActive
- }
- };
+ ? await BuildPricePublishSnapshotAsync(serviceChannelId)
+ : await BuildRulesPublishSnapshotAsync(serviceChannelId);
// Content 中保存完整快照,回滚时直接从快照恢复。
await ServiceChannelRepository.InsertPublishRecordAsync(new ServiceChannelPublishRecord
diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.cs
index e41f7f6..5738ef1 100644
--- a/src/Oms.Application/Services/Channels/ServiceChannelService.cs
+++ b/src/Oms.Application/Services/Channels/ServiceChannelService.cs
@@ -423,6 +423,18 @@ public partial class ServiceChannelService : ApplicationService
return GetPublishListAsync(input, PriceOperatorType);
}
+ ///
+ /// 预览即将发布的快照 JSON(不落库),与产品卖价 publish/preview 对称。
+ ///
+ [HttpGet("/channel/service-channel/publish/preview")]
+ public async Task