1.02 #3
1
db.sql
1
db.sql
@ -118,6 +118,7 @@ CREATE TABLE `t_order` (
|
|||||||
`ProductCode` VARCHAR(128) NULL COMMENT '产品编码',
|
`ProductCode` VARCHAR(128) NULL COMMENT '产品编码',
|
||||||
`ChannelCode` VARCHAR(128) NULL COMMENT '渠道编码',
|
`ChannelCode` VARCHAR(128) NULL COMMENT '渠道编码',
|
||||||
`DownstreamSupplierCode` VARCHAR(64) NULL COMMENT '下游 API 服务商编码 supplierCode',
|
`DownstreamSupplierCode` VARCHAR(64) NULL COMMENT '下游 API 服务商编码 supplierCode',
|
||||||
|
`DownstreamSupplierName` VARCHAR(128) NULL COMMENT '下游 API 服务商名称',
|
||||||
`DownstreamAppKey` VARCHAR(128) NULL COMMENT '下游 itfx 应用 AppKey',
|
`DownstreamAppKey` VARCHAR(128) NULL COMMENT '下游 itfx 应用 AppKey',
|
||||||
`DownstreamAppId` INT NULL COMMENT '下游 itfx 应用 Id',
|
`DownstreamAppId` INT NULL COMMENT '下游 itfx 应用 Id',
|
||||||
`ChannelForecastCode` VARCHAR(128) NULL COMMENT '渠道预报编码 ForecastCode',
|
`ChannelForecastCode` VARCHAR(128) NULL COMMENT '渠道预报编码 ForecastCode',
|
||||||
|
|||||||
105
sql/migrate_20261009_order_warehouse.sql
Normal file
105
sql/migrate_20261009_order_warehouse.sql
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
-- =============================================================================
|
||||||
|
-- migrate_20261009_order_warehouse.sql
|
||||||
|
-- 汇总:
|
||||||
|
-- 1) t_order.DownstreamSupplierName(下游服务商名称)
|
||||||
|
-- 2) t_order_operation_log(运单操作日志)
|
||||||
|
-- 3) t_warehouse_address.Sort / Remark(仓库地址排序与备注)
|
||||||
|
-- 可重复执行。
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
SET @db := DATABASE();
|
||||||
|
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- 1) 订单:下游服务商名称
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
SET @exists := (
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db
|
||||||
|
AND TABLE_NAME = 't_order'
|
||||||
|
AND COLUMN_NAME = 'DownstreamSupplierName'
|
||||||
|
);
|
||||||
|
SET @sql := IF(
|
||||||
|
@exists = 0,
|
||||||
|
'ALTER TABLE `t_order` ADD COLUMN `DownstreamSupplierName` VARCHAR(128) NULL COMMENT ''下游 API 服务商名称'' AFTER `DownstreamSupplierCode`',
|
||||||
|
'SELECT ''t_order.DownstreamSupplierName already exists'' AS info'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- 2) 运单操作日志表
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
SET @tbl := (
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM information_schema.TABLES
|
||||||
|
WHERE TABLE_SCHEMA = @db
|
||||||
|
AND TABLE_NAME = 't_order_operation_log'
|
||||||
|
);
|
||||||
|
SET @sql := IF(
|
||||||
|
@tbl = 0,
|
||||||
|
'CREATE TABLE `t_order_operation_log` (
|
||||||
|
`Id` INT NOT NULL AUTO_INCREMENT COMMENT ''主键'',
|
||||||
|
`OrderId` INT NOT NULL COMMENT ''运单Id'',
|
||||||
|
`ServiceNo` VARCHAR(64) NULL COMMENT ''运单号冗余'',
|
||||||
|
`Action` VARCHAR(32) NOT NULL COMMENT ''动作码 editBasic/editWeight/recalcReceivable/recalcPayable/reallocate'',
|
||||||
|
`ActionName` VARCHAR(64) NOT NULL COMMENT ''动作中文名'',
|
||||||
|
`OperatorId` INT NOT NULL DEFAULT 0 COMMENT ''操作人用户Id'',
|
||||||
|
`OperatorName` VARCHAR(64) NULL COMMENT ''操作人展示名'',
|
||||||
|
`OperatorType` VARCHAR(16) NOT NULL DEFAULT ''staff'' COMMENT ''staff/customer/system/openapi'',
|
||||||
|
`Source` VARCHAR(16) NOT NULL DEFAULT ''pc'' COMMENT ''pc/h5/openapi/job'',
|
||||||
|
`ChangesJson` JSON NULL COMMENT ''字段 diff 数组 [{path,label,before,after}]'',
|
||||||
|
`Remark` VARCHAR(512) NULL COMMENT ''补充说明'',
|
||||||
|
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT ''软删除:0-否,1-是'',
|
||||||
|
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT ''并发戳'',
|
||||||
|
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT ''创建人'',
|
||||||
|
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ''创建时间'',
|
||||||
|
`UpdateUserId` INT NULL COMMENT ''更新人'',
|
||||||
|
`UpdateTime` DATETIME NULL COMMENT ''更新时间'',
|
||||||
|
PRIMARY KEY (`Id`),
|
||||||
|
KEY `idx_ool_order_time` (`OrderId`, `CreateTime`),
|
||||||
|
KEY `idx_ool_service_no` (`ServiceNo`)
|
||||||
|
) COMMENT=''运单操作日志''',
|
||||||
|
'SELECT ''t_order_operation_log already exists'' AS info'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
-- 3) 仓库地址:展示排序 + 备注
|
||||||
|
-- -----------------------------------------------------------------------------
|
||||||
|
SET @col := (
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db
|
||||||
|
AND TABLE_NAME = 't_warehouse_address'
|
||||||
|
AND COLUMN_NAME = 'Sort'
|
||||||
|
);
|
||||||
|
SET @sql := IF(
|
||||||
|
@col = 0,
|
||||||
|
'ALTER TABLE `t_warehouse_address`
|
||||||
|
ADD COLUMN `Sort` INT NULL DEFAULT NULL COMMENT ''展示排序,越小越靠前'' AFTER `ShippingAddress`',
|
||||||
|
'SELECT ''t_warehouse_address.Sort already exists'' AS info'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @col := (
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db
|
||||||
|
AND TABLE_NAME = 't_warehouse_address'
|
||||||
|
AND COLUMN_NAME = 'Remark'
|
||||||
|
);
|
||||||
|
SET @sql := IF(
|
||||||
|
@col = 0,
|
||||||
|
'ALTER TABLE `t_warehouse_address`
|
||||||
|
ADD COLUMN `Remark` VARCHAR(512) NULL DEFAULT NULL COMMENT ''备注'' AFTER `Sort`',
|
||||||
|
'SELECT ''t_warehouse_address.Remark already exists'' AS info'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
22
sql/migrate_order_downstream_supplier_name.sql
Normal file
22
sql/migrate_order_downstream_supplier_name.sql
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
-- 订单增加下游服务商名称(排货确认时写入 itfx 服务商名 / 分组名)。
|
||||||
|
-- 可重复执行。
|
||||||
|
|
||||||
|
SET @db := DATABASE();
|
||||||
|
|
||||||
|
SET @exists := (
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db
|
||||||
|
AND TABLE_NAME = 't_order'
|
||||||
|
AND COLUMN_NAME = 'DownstreamSupplierName'
|
||||||
|
);
|
||||||
|
|
||||||
|
SET @sql := IF(
|
||||||
|
@exists = 0,
|
||||||
|
'ALTER TABLE `t_order` ADD COLUMN `DownstreamSupplierName` VARCHAR(128) NULL COMMENT ''下游 API 服务商名称'' AFTER `DownstreamSupplierCode`',
|
||||||
|
'SELECT ''t_order.DownstreamSupplierName already exists'' AS info'
|
||||||
|
);
|
||||||
|
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Oms.Application.Dtos.Orders;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 切换订单绑定的销售产品规则/价格发布版本。
|
||||||
|
/// </summary>
|
||||||
|
public class OrderBindSnapshotVersionsInput
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目标规则版本;空表示不改。特殊值 <c>CURRENT</c> 表示绑定当前有效规则发布。
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(64)]
|
||||||
|
public string? RulesVersion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目标价格版本;空表示不改。特殊值 <c>CURRENT</c> 表示绑定当前有效价格发布。
|
||||||
|
/// </summary>
|
||||||
|
[StringLength(64)]
|
||||||
|
public string? PriceVersion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 绑定后是否立即重算应收(已支付会拒绝)。
|
||||||
|
/// </summary>
|
||||||
|
public bool RecalculateReceivable { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 绑定后是否立即重算应付(锁定当前渠道)。
|
||||||
|
/// </summary>
|
||||||
|
public bool RecalculatePayable { get; set; }
|
||||||
|
}
|
||||||
@ -55,6 +55,11 @@ public class OrderOutput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? DownstreamSupplierCode { get; set; }
|
public string? DownstreamSupplierCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下游 API 服务商名称。
|
||||||
|
/// </summary>
|
||||||
|
public string? DownstreamSupplierName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 下游 itfx 应用 AppKey。
|
/// 下游 itfx 应用 AppKey。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -363,6 +368,16 @@ public class OrderOutput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal? ForecastPayableAmount { get; set; }
|
public decimal? ForecastPayableAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 订单绑定的销售产品规则发布版本。
|
||||||
|
/// </summary>
|
||||||
|
public string? SalesProductRulesVersion { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 订单绑定的销售产品价格发布版本。
|
||||||
|
/// </summary>
|
||||||
|
public string? SalesProductPriceVersion { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建时间。
|
/// 创建时间。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -28,6 +28,8 @@ public static class OrderOperationLogSnapshot
|
|||||||
["payableAmount"] = "应付金额",
|
["payableAmount"] = "应付金额",
|
||||||
["downstreamServiceNo"] = "服务商单号",
|
["downstreamServiceNo"] = "服务商单号",
|
||||||
["allocationScheme"] = "排货方案",
|
["allocationScheme"] = "排货方案",
|
||||||
|
["salesProductRulesVersion"] = "规则版本",
|
||||||
|
["salesProductPriceVersion"] = "价格版本",
|
||||||
["isPaid"] = "是否已支付",
|
["isPaid"] = "是否已支付",
|
||||||
["sender.name"] = "发件人姓名",
|
["sender.name"] = "发件人姓名",
|
||||||
["sender.phone"] = "发件人电话",
|
["sender.phone"] = "发件人电话",
|
||||||
@ -70,6 +72,8 @@ public static class OrderOperationLogSnapshot
|
|||||||
["payableAmount"] = FormatDecimal(order.PayableAmount),
|
["payableAmount"] = FormatDecimal(order.PayableAmount),
|
||||||
["downstreamServiceNo"] = Norm(order.DownstreamServiceNo),
|
["downstreamServiceNo"] = Norm(order.DownstreamServiceNo),
|
||||||
["allocationScheme"] = Norm(order.AllocationScheme),
|
["allocationScheme"] = Norm(order.AllocationScheme),
|
||||||
|
["salesProductRulesVersion"] = Norm(order.SalesProductRulesVersion),
|
||||||
|
["salesProductPriceVersion"] = Norm(order.SalesProductPriceVersion),
|
||||||
["isPaid"] = FormatByte(order.IsPaid),
|
["isPaid"] = FormatByte(order.IsPaid),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -165,6 +165,7 @@ public partial class OrderService
|
|||||||
MaxTimeliness = product?.MaxTimeliness,
|
MaxTimeliness = product?.MaxTimeliness,
|
||||||
ChannelCode = order.ChannelCode,
|
ChannelCode = order.ChannelCode,
|
||||||
DownstreamSupplierCode = order.DownstreamSupplierCode,
|
DownstreamSupplierCode = order.DownstreamSupplierCode,
|
||||||
|
DownstreamSupplierName = order.DownstreamSupplierName,
|
||||||
DownstreamAppKey = order.DownstreamAppKey,
|
DownstreamAppKey = order.DownstreamAppKey,
|
||||||
DownstreamAppId = order.DownstreamAppId,
|
DownstreamAppId = order.DownstreamAppId,
|
||||||
ChannelForecastCode = order.ChannelForecastCode,
|
ChannelForecastCode = order.ChannelForecastCode,
|
||||||
@ -262,6 +263,12 @@ public partial class OrderService
|
|||||||
HasInsurance = hasInsurance,
|
HasInsurance = hasInsurance,
|
||||||
ForecastReceivableAmount = order.ForecastReceivableAmount,
|
ForecastReceivableAmount = order.ForecastReceivableAmount,
|
||||||
ForecastPayableAmount = order.ForecastPayableAmount,
|
ForecastPayableAmount = order.ForecastPayableAmount,
|
||||||
|
SalesProductRulesVersion = string.IsNullOrWhiteSpace(order.SalesProductRulesVersion)
|
||||||
|
? null
|
||||||
|
: order.SalesProductRulesVersion.Trim(),
|
||||||
|
SalesProductPriceVersion = string.IsNullOrWhiteSpace(order.SalesProductPriceVersion)
|
||||||
|
? null
|
||||||
|
: order.SalesProductPriceVersion.Trim(),
|
||||||
CreateTime = order.CreateTime,
|
CreateTime = order.CreateTime,
|
||||||
CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(order.CreateUserId, auditUserNameMap),
|
CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(order.CreateUserId, auditUserNameMap),
|
||||||
UpdateTime = OmsDocHelper.NullIfDefault(order.UpdateTime),
|
UpdateTime = OmsDocHelper.NullIfDefault(order.UpdateTime),
|
||||||
@ -387,6 +394,7 @@ public partial class OrderService
|
|||||||
output.ChannelCode = null;
|
output.ChannelCode = null;
|
||||||
output.ChannelName = null;
|
output.ChannelName = null;
|
||||||
output.DownstreamSupplierCode = null;
|
output.DownstreamSupplierCode = null;
|
||||||
|
output.DownstreamSupplierName = null;
|
||||||
output.DownstreamAppKey = null;
|
output.DownstreamAppKey = null;
|
||||||
output.DownstreamAppId = null;
|
output.DownstreamAppId = null;
|
||||||
output.ChannelForecastCode = null;
|
output.ChannelForecastCode = null;
|
||||||
|
|||||||
@ -286,6 +286,127 @@ public partial class OrderService
|
|||||||
return (await MapOrderOutputsAsync([order])).First();
|
return (await MapOrderOutputsAsync([order])).First();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 切换订单绑定的销售产品规则/价格发布版本(显式改契约后再重算)。
|
||||||
|
/// </summary>
|
||||||
|
[HttpPost("/order/fee/bind-snapshot-versions")]
|
||||||
|
public async Task<OrderOutput> BindSnapshotVersionsAsync([FromBody] OrderBindSnapshotVersionsInput input)
|
||||||
|
{
|
||||||
|
var order = await GetOrderOrThrowAsync(input.Id);
|
||||||
|
var beforeSnap = await CaptureOrderAuditSnapshotAsync(order, includeFees: true);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(order.ProductCode))
|
||||||
|
{
|
||||||
|
throw new BusinessException("订单未绑定销售产品,无法切换版本");
|
||||||
|
}
|
||||||
|
|
||||||
|
var product = await SalesProductRepository.GetByProductCodeAsync(order.ProductCode.Trim());
|
||||||
|
if (product == null)
|
||||||
|
{
|
||||||
|
throw new BusinessException("销售产品不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
var wantRules = !string.IsNullOrWhiteSpace(input.RulesVersion);
|
||||||
|
var wantPrice = !string.IsNullOrWhiteSpace(input.PriceVersion);
|
||||||
|
if (!wantRules && !wantPrice)
|
||||||
|
{
|
||||||
|
throw new BusinessException("请至少指定规则版本或价格版本");
|
||||||
|
}
|
||||||
|
|
||||||
|
var remarkParts = new List<string>();
|
||||||
|
if (wantRules)
|
||||||
|
{
|
||||||
|
var resolved = await ResolveBindTargetVersionAsync(
|
||||||
|
product,
|
||||||
|
RulesOperatorType,
|
||||||
|
input.RulesVersion!.Trim(),
|
||||||
|
"规则");
|
||||||
|
if (!string.Equals(order.SalesProductRulesVersion, resolved, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
remarkParts.Add($"rules:{order.SalesProductRulesVersion ?? "-"}→{resolved}");
|
||||||
|
order.SalesProductRulesVersion = resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantPrice)
|
||||||
|
{
|
||||||
|
var resolved = await ResolveBindTargetVersionAsync(
|
||||||
|
product,
|
||||||
|
PriceOperatorType,
|
||||||
|
input.PriceVersion!.Trim(),
|
||||||
|
"价格");
|
||||||
|
if (!string.Equals(order.SalesProductPriceVersion, resolved, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
remarkParts.Add($"price:{order.SalesProductPriceVersion ?? "-"}→{resolved}");
|
||||||
|
order.SalesProductPriceVersion = resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remarkParts.Count == 0 && !input.RecalculateReceivable && !input.RecalculatePayable)
|
||||||
|
{
|
||||||
|
throw new BusinessException("目标版本与当前绑定相同,且未勾选重算");
|
||||||
|
}
|
||||||
|
|
||||||
|
order.UpdateTime = DateTime.Now;
|
||||||
|
await OrderRepository.UpdateAsync(order);
|
||||||
|
|
||||||
|
if (input.RecalculateReceivable)
|
||||||
|
{
|
||||||
|
EnsureReceivableMutable(order);
|
||||||
|
await RecalculateReceivableCoreAsync(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.RecalculatePayable)
|
||||||
|
{
|
||||||
|
await RecalculatePayableCoreAsync(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
order = await GetOrderOrThrowAsync(order.Id);
|
||||||
|
var afterSnap = await CaptureOrderAuditSnapshotAsync(order, includeFees: true);
|
||||||
|
await OperationLogWriter.TryWriteAsync(
|
||||||
|
order,
|
||||||
|
OrderOperationActionCodes.BindSnapshotVersions,
|
||||||
|
beforeSnap,
|
||||||
|
afterSnap,
|
||||||
|
remarkParts.Count == 0 ? null : string.Join(';', remarkParts));
|
||||||
|
return (await MapOrderOutputsAsync([order])).First();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析绑定目标版本:CURRENT 取当前有效发布,否则按版本号校验存在。
|
||||||
|
/// </summary>
|
||||||
|
private async Task<string> ResolveBindTargetVersionAsync(
|
||||||
|
SalesProduct product,
|
||||||
|
string operatorType,
|
||||||
|
string versionOrCurrent,
|
||||||
|
string label)
|
||||||
|
{
|
||||||
|
if (string.Equals(versionOrCurrent, "CURRENT", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var effective = await SalesProductRepository.GetEffectivePublishRecordAsync(
|
||||||
|
product.Id,
|
||||||
|
operatorType,
|
||||||
|
DateTime.Now);
|
||||||
|
if (effective == null || string.IsNullOrWhiteSpace(effective.Version))
|
||||||
|
{
|
||||||
|
throw new BusinessException($"未命中当前有效{label}发布版本");
|
||||||
|
}
|
||||||
|
|
||||||
|
return effective.Version.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
var record = await SalesProductRepository.GetPublishRecordByVersionAsync(
|
||||||
|
product.Id,
|
||||||
|
operatorType,
|
||||||
|
versionOrCurrent);
|
||||||
|
if (record == null || string.IsNullOrWhiteSpace(record.Version))
|
||||||
|
{
|
||||||
|
throw new BusinessException($"未找到{label}发布版本:{versionOrCurrent}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return record.Version.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 重跑入库计费(应收 wipe+upsert + 排货应付)。用于队列投递失败或待排货补救。
|
/// 重跑入库计费(应收 wipe+upsert + 排货应付)。用于队列投递失败或待排货补救。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1010,6 +1131,7 @@ public partial class OrderService
|
|||||||
|
|
||||||
// 重新排货:产品规则/分区与渠道买价均取当前有效发布,避免沿用下单时旧快照。
|
// 重新排货:产品规则/分区与渠道买价均取当前有效发布,避免沿用下单时旧快照。
|
||||||
// 对账重算 / 普通重算应付:沿用订单快照绑定(分区等),买价仍由排货评估取当前有效发布。
|
// 对账重算 / 普通重算应付:沿用订单快照绑定(分区等),买价仍由排货评估取当前有效发布。
|
||||||
|
// 锁渠道重算应付:允许渠道不在旧规则排货池内(渠道已确认,只重算买价)。
|
||||||
var snapshotBinding = enforceReallocateGate
|
var snapshotBinding = enforceReallocateGate
|
||||||
? await ResolveSnapshotBindingAsync(product, DateTime.Now)
|
? await ResolveSnapshotBindingAsync(product, DateTime.Now)
|
||||||
: await ResolveSnapshotBindingFromOrderAsync(product, order);
|
: await ResolveSnapshotBindingFromOrderAsync(product, order);
|
||||||
@ -1032,7 +1154,14 @@ public partial class OrderService
|
|||||||
billing,
|
billing,
|
||||||
chargingStage,
|
chargingStage,
|
||||||
schemeOverride,
|
schemeOverride,
|
||||||
forceChannelId);
|
forceChannelId,
|
||||||
|
allowDesignatedOutsidePool: lockCurrentChannel);
|
||||||
|
|
||||||
|
// 重新排货成功后写回规则版本,使后续重算应付与当前排货池一致;卖价版本保持契约,不随排货改写。
|
||||||
|
if (enforceReallocateGate)
|
||||||
|
{
|
||||||
|
order.SalesProductRulesVersion = snapshotBinding.RulesVersion;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(order.OrderStatus, OrderStatusCodes.PendingAllocation, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(order.OrderStatus, OrderStatusCodes.PendingAllocation, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -848,7 +848,8 @@ public partial class OrderService
|
|||||||
string chargingStage,
|
string chargingStage,
|
||||||
string? _,
|
string? _,
|
||||||
string? schemeOverride = null,
|
string? schemeOverride = null,
|
||||||
int? forceServiceChannelId = null)
|
int? forceServiceChannelId = null,
|
||||||
|
bool allowDesignatedOutsidePool = false)
|
||||||
{
|
{
|
||||||
var evaluation = await EvaluateAllocationAsync(
|
var evaluation = await EvaluateAllocationAsync(
|
||||||
order,
|
order,
|
||||||
@ -858,7 +859,8 @@ public partial class OrderService
|
|||||||
chargeableWeight,
|
chargeableWeight,
|
||||||
chargingStage,
|
chargingStage,
|
||||||
schemeOverride,
|
schemeOverride,
|
||||||
forceServiceChannelId);
|
forceServiceChannelId,
|
||||||
|
allowDesignatedOutsidePool);
|
||||||
|
|
||||||
ApplyAllocationTraceToOrder(order, evaluation);
|
ApplyAllocationTraceToOrder(order, evaluation);
|
||||||
|
|
||||||
@ -928,7 +930,8 @@ public partial class OrderService
|
|||||||
decimal chargeableWeight,
|
decimal chargeableWeight,
|
||||||
string chargingStage,
|
string chargingStage,
|
||||||
string? schemeOverride,
|
string? schemeOverride,
|
||||||
int? forceServiceChannelId)
|
int? forceServiceChannelId,
|
||||||
|
bool allowDesignatedOutsidePool = false)
|
||||||
{
|
{
|
||||||
var warehouseName = await ResolveWarehouseNameAsync(order.WarehouseId);
|
var warehouseName = await ResolveWarehouseNameAsync(order.WarehouseId);
|
||||||
var providers = await ResolveAllocationProvidersAsync(product, snapshotBinding.Rules);
|
var providers = await ResolveAllocationProvidersAsync(product, snapshotBinding.Rules);
|
||||||
@ -985,27 +988,61 @@ public partial class OrderService
|
|||||||
if (designated.Count == 0)
|
if (designated.Count == 0)
|
||||||
{
|
{
|
||||||
var poolRow = providers.FirstOrDefault(x => x.ServiceChannelId == forceServiceChannelId.Value);
|
var poolRow = providers.FirstOrDefault(x => x.ServiceChannelId == forceServiceChannelId.Value);
|
||||||
var failReason = poolRow == null
|
// 重算应付锁定已确认渠道:渠道已在单上,不再用旧规则排货池/判断条件拦截,只重算买价。
|
||||||
? "指定渠道不在该销售产品排货池"
|
if (allowDesignatedOutsidePool)
|
||||||
: "指定渠道不满足判断条件";
|
|
||||||
if (poolRow != null
|
|
||||||
&& skipped.All(x => x.ServiceChannelId != forceServiceChannelId.Value))
|
|
||||||
{
|
{
|
||||||
skipped.Add(new OrderAllocationSkippedOutput
|
var lockedChannel = await ServiceChannelRepository.FirstOrDefaultAsync(x =>
|
||||||
|
x.Id == forceServiceChannelId.Value);
|
||||||
|
if (lockedChannel == null)
|
||||||
{
|
{
|
||||||
ServiceChannelId = forceServiceChannelId.Value,
|
return BaseEval("订单服务渠道不存在,无法重算应付");
|
||||||
ChannelCode = poolRow.ChannelCode,
|
}
|
||||||
ChannelName = poolRow.ServiceChannelName ?? poolRow.ProviderName,
|
|
||||||
Stage = AllocationResolver.SkipStageCondition,
|
var lockedProvider = poolRow ?? new SalesProductAllocationProviderSnapshotDto
|
||||||
MatchCondition = AllocationResolver.TruncateMatchCondition(poolRow.MatchCondition),
|
{
|
||||||
Reason = failReason
|
SalesProductId = product.Id,
|
||||||
});
|
ServiceChannelId = lockedChannel.Id,
|
||||||
|
ServiceChannelName = lockedChannel.ChannelName,
|
||||||
|
ProviderName = lockedChannel.ChannelName ?? lockedChannel.ChannelCode,
|
||||||
|
ChannelCode = lockedChannel.ChannelCode,
|
||||||
|
Weight = 0,
|
||||||
|
CustomerIds = [],
|
||||||
|
MatchCondition = null
|
||||||
|
};
|
||||||
|
if (poolRow == null
|
||||||
|
|| AllocationProviderSnapshotEnricher.NeedsDownstreamEnrichment(lockedProvider))
|
||||||
|
{
|
||||||
|
await AllocationProviderSnapshotEnricher.EnrichAsync([lockedProvider]);
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = [lockedProvider];
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var failReason = poolRow == null
|
||||||
|
? "指定渠道不在该销售产品排货池"
|
||||||
|
: "指定渠道不满足判断条件";
|
||||||
|
if (poolRow != null
|
||||||
|
&& skipped.All(x => x.ServiceChannelId != forceServiceChannelId.Value))
|
||||||
|
{
|
||||||
|
skipped.Add(new OrderAllocationSkippedOutput
|
||||||
|
{
|
||||||
|
ServiceChannelId = forceServiceChannelId.Value,
|
||||||
|
ChannelCode = poolRow.ChannelCode,
|
||||||
|
ChannelName = poolRow.ServiceChannelName ?? poolRow.ProviderName,
|
||||||
|
Stage = AllocationResolver.SkipStageCondition,
|
||||||
|
MatchCondition = AllocationResolver.TruncateMatchCondition(poolRow.MatchCondition),
|
||||||
|
Reason = failReason
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return BaseEval(failReason);
|
return BaseEval(failReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filtered = designated;
|
||||||
}
|
}
|
||||||
|
|
||||||
filtered = designated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filtered.Count == 0)
|
if (filtered.Count == 0)
|
||||||
@ -1304,7 +1341,8 @@ public partial class OrderService
|
|||||||
OrderBillingContext billing,
|
OrderBillingContext billing,
|
||||||
string chargingStage,
|
string chargingStage,
|
||||||
string? schemeOverride = null,
|
string? schemeOverride = null,
|
||||||
int? forceServiceChannelId = null) =>
|
int? forceServiceChannelId = null,
|
||||||
|
bool allowDesignatedOutsidePool = false) =>
|
||||||
ConfirmServiceChannelAndPayableAsync(
|
ConfirmServiceChannelAndPayableAsync(
|
||||||
order,
|
order,
|
||||||
product,
|
product,
|
||||||
@ -1314,7 +1352,8 @@ public partial class OrderService
|
|||||||
chargingStage,
|
chargingStage,
|
||||||
billing.PriceTierKey,
|
billing.PriceTierKey,
|
||||||
schemeOverride,
|
schemeOverride,
|
||||||
forceServiceChannelId);
|
forceServiceChannelId,
|
||||||
|
allowDesignatedOutsidePool);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 尝试排货并写入应付;失败时不抛异常,写入 <see cref="Order.AllocationPendingReason"/> 供运营补配置。
|
/// 尝试排货并写入应付;失败时不抛异常,写入 <see cref="Order.AllocationPendingReason"/> 供运营补配置。
|
||||||
@ -1325,7 +1364,8 @@ public partial class OrderService
|
|||||||
OrderBillingContext billing,
|
OrderBillingContext billing,
|
||||||
string chargingStage,
|
string chargingStage,
|
||||||
string? schemeOverride = null,
|
string? schemeOverride = null,
|
||||||
int? forceServiceChannelId = null)
|
int? forceServiceChannelId = null,
|
||||||
|
bool allowDesignatedOutsidePool = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -1335,7 +1375,8 @@ public partial class OrderService
|
|||||||
billing,
|
billing,
|
||||||
chargingStage,
|
chargingStage,
|
||||||
schemeOverride,
|
schemeOverride,
|
||||||
forceServiceChannelId);
|
forceServiceChannelId,
|
||||||
|
allowDesignatedOutsidePool);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (BusinessException ex)
|
catch (BusinessException ex)
|
||||||
|
|||||||
@ -108,6 +108,9 @@ public class AllocationProviderSnapshotEnricher(
|
|||||||
ServiceChannel? channelFallback = null)
|
ServiceChannel? channelFallback = null)
|
||||||
{
|
{
|
||||||
order.DownstreamSupplierCode = NullIfEmpty(provider.SupplierCode);
|
order.DownstreamSupplierCode = NullIfEmpty(provider.SupplierCode);
|
||||||
|
order.DownstreamSupplierName = NullIfEmpty(provider.ApiServiceProviderName)
|
||||||
|
?? NullIfEmpty(provider.GroupName)
|
||||||
|
?? NullIfEmpty(provider.ProviderName);
|
||||||
order.DownstreamAppKey = NullIfEmpty(provider.AppKey);
|
order.DownstreamAppKey = NullIfEmpty(provider.AppKey);
|
||||||
order.DownstreamAppId = provider.AppId;
|
order.DownstreamAppId = provider.AppId;
|
||||||
order.ChannelForecastCode = NullIfEmpty(provider.ForecastCode)
|
order.ChannelForecastCode = NullIfEmpty(provider.ForecastCode)
|
||||||
|
|||||||
@ -7,6 +7,7 @@ public static class OrderOperationActionCodes
|
|||||||
public const string RecalcReceivable = "recalcReceivable";
|
public const string RecalcReceivable = "recalcReceivable";
|
||||||
public const string RecalcPayable = "recalcPayable";
|
public const string RecalcPayable = "recalcPayable";
|
||||||
public const string Reallocate = "reallocate";
|
public const string Reallocate = "reallocate";
|
||||||
|
public const string BindSnapshotVersions = "bindSnapshotVersions";
|
||||||
|
|
||||||
public static string GetActionName(string action) => action switch
|
public static string GetActionName(string action) => action switch
|
||||||
{
|
{
|
||||||
@ -15,6 +16,7 @@ public static class OrderOperationActionCodes
|
|||||||
RecalcReceivable => "重算应收",
|
RecalcReceivable => "重算应收",
|
||||||
RecalcPayable => "重算应付",
|
RecalcPayable => "重算应付",
|
||||||
Reallocate => "重新排货",
|
Reallocate => "重新排货",
|
||||||
|
BindSnapshotVersions => "切换价格/规则版本",
|
||||||
_ => action
|
_ => action
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,12 @@ public class Order : OmsAuditedAggregateRoot
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? DownstreamSupplierCode { get; set; }
|
public string? DownstreamSupplierCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下游 API 服务商名称(itfx service provider name)。
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(Length = 128, IsNullable = true)]
|
||||||
|
public string? DownstreamSupplierName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 下游 itfx 应用 AppKey。
|
/// 下游 itfx 应用 AppKey。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user