feat: 重算应付放行锁渠道池校验,支持切换价格/规则版本与服务商名称
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2b0158acb4
commit
da27fa4377
1
db.sql
1
db.sql
@ -118,6 +118,7 @@ CREATE TABLE `t_order` (
|
||||
`ProductCode` VARCHAR(128) NULL COMMENT '产品编码',
|
||||
`ChannelCode` VARCHAR(128) NULL COMMENT '渠道编码',
|
||||
`DownstreamSupplierCode` VARCHAR(64) NULL COMMENT '下游 API 服务商编码 supplierCode',
|
||||
`DownstreamSupplierName` VARCHAR(128) NULL COMMENT '下游 API 服务商名称',
|
||||
`DownstreamAppKey` VARCHAR(128) NULL COMMENT '下游 itfx 应用 AppKey',
|
||||
`DownstreamAppId` INT NULL COMMENT '下游 itfx 应用 Id',
|
||||
`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>
|
||||
public string? DownstreamSupplierCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下游 API 服务商名称。
|
||||
/// </summary>
|
||||
public string? DownstreamSupplierName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下游 itfx 应用 AppKey。
|
||||
/// </summary>
|
||||
@ -363,6 +368,16 @@ public class OrderOutput
|
||||
/// </summary>
|
||||
public decimal? ForecastPayableAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单绑定的销售产品规则发布版本。
|
||||
/// </summary>
|
||||
public string? SalesProductRulesVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单绑定的销售产品价格发布版本。
|
||||
/// </summary>
|
||||
public string? SalesProductPriceVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间。
|
||||
/// </summary>
|
||||
|
||||
@ -28,6 +28,8 @@ public static class OrderOperationLogSnapshot
|
||||
["payableAmount"] = "应付金额",
|
||||
["downstreamServiceNo"] = "服务商单号",
|
||||
["allocationScheme"] = "排货方案",
|
||||
["salesProductRulesVersion"] = "规则版本",
|
||||
["salesProductPriceVersion"] = "价格版本",
|
||||
["isPaid"] = "是否已支付",
|
||||
["sender.name"] = "发件人姓名",
|
||||
["sender.phone"] = "发件人电话",
|
||||
@ -70,6 +72,8 @@ public static class OrderOperationLogSnapshot
|
||||
["payableAmount"] = FormatDecimal(order.PayableAmount),
|
||||
["downstreamServiceNo"] = Norm(order.DownstreamServiceNo),
|
||||
["allocationScheme"] = Norm(order.AllocationScheme),
|
||||
["salesProductRulesVersion"] = Norm(order.SalesProductRulesVersion),
|
||||
["salesProductPriceVersion"] = Norm(order.SalesProductPriceVersion),
|
||||
["isPaid"] = FormatByte(order.IsPaid),
|
||||
};
|
||||
}
|
||||
|
||||
@ -165,6 +165,7 @@ public partial class OrderService
|
||||
MaxTimeliness = product?.MaxTimeliness,
|
||||
ChannelCode = order.ChannelCode,
|
||||
DownstreamSupplierCode = order.DownstreamSupplierCode,
|
||||
DownstreamSupplierName = order.DownstreamSupplierName,
|
||||
DownstreamAppKey = order.DownstreamAppKey,
|
||||
DownstreamAppId = order.DownstreamAppId,
|
||||
ChannelForecastCode = order.ChannelForecastCode,
|
||||
@ -262,6 +263,12 @@ public partial class OrderService
|
||||
HasInsurance = hasInsurance,
|
||||
ForecastReceivableAmount = order.ForecastReceivableAmount,
|
||||
ForecastPayableAmount = order.ForecastPayableAmount,
|
||||
SalesProductRulesVersion = string.IsNullOrWhiteSpace(order.SalesProductRulesVersion)
|
||||
? null
|
||||
: order.SalesProductRulesVersion.Trim(),
|
||||
SalesProductPriceVersion = string.IsNullOrWhiteSpace(order.SalesProductPriceVersion)
|
||||
? null
|
||||
: order.SalesProductPriceVersion.Trim(),
|
||||
CreateTime = order.CreateTime,
|
||||
CreateBy = OmsRbacUserDisplayNameResolver.FormatUserId(order.CreateUserId, auditUserNameMap),
|
||||
UpdateTime = OmsDocHelper.NullIfDefault(order.UpdateTime),
|
||||
@ -387,6 +394,7 @@ public partial class OrderService
|
||||
output.ChannelCode = null;
|
||||
output.ChannelName = null;
|
||||
output.DownstreamSupplierCode = null;
|
||||
output.DownstreamSupplierName = null;
|
||||
output.DownstreamAppKey = null;
|
||||
output.DownstreamAppId = null;
|
||||
output.ChannelForecastCode = null;
|
||||
|
||||
@ -286,6 +286,127 @@ public partial class OrderService
|
||||
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>
|
||||
/// 重跑入库计费(应收 wipe+upsert + 排货应付)。用于队列投递失败或待排货补救。
|
||||
/// </summary>
|
||||
@ -1010,6 +1131,7 @@ public partial class OrderService
|
||||
|
||||
// 重新排货:产品规则/分区与渠道买价均取当前有效发布,避免沿用下单时旧快照。
|
||||
// 对账重算 / 普通重算应付:沿用订单快照绑定(分区等),买价仍由排货评估取当前有效发布。
|
||||
// 锁渠道重算应付:允许渠道不在旧规则排货池内(渠道已确认,只重算买价)。
|
||||
var snapshotBinding = enforceReallocateGate
|
||||
? await ResolveSnapshotBindingAsync(product, DateTime.Now)
|
||||
: await ResolveSnapshotBindingFromOrderAsync(product, order);
|
||||
@ -1032,7 +1154,14 @@ public partial class OrderService
|
||||
billing,
|
||||
chargingStage,
|
||||
schemeOverride,
|
||||
forceChannelId);
|
||||
forceChannelId,
|
||||
allowDesignatedOutsidePool: lockCurrentChannel);
|
||||
|
||||
// 重新排货成功后写回规则版本,使后续重算应付与当前排货池一致;卖价版本保持契约,不随排货改写。
|
||||
if (enforceReallocateGate)
|
||||
{
|
||||
order.SalesProductRulesVersion = snapshotBinding.RulesVersion;
|
||||
}
|
||||
|
||||
if (string.Equals(order.OrderStatus, OrderStatusCodes.PendingAllocation, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
||||
@ -848,7 +848,8 @@ public partial class OrderService
|
||||
string chargingStage,
|
||||
string? _,
|
||||
string? schemeOverride = null,
|
||||
int? forceServiceChannelId = null)
|
||||
int? forceServiceChannelId = null,
|
||||
bool allowDesignatedOutsidePool = false)
|
||||
{
|
||||
var evaluation = await EvaluateAllocationAsync(
|
||||
order,
|
||||
@ -858,7 +859,8 @@ public partial class OrderService
|
||||
chargeableWeight,
|
||||
chargingStage,
|
||||
schemeOverride,
|
||||
forceServiceChannelId);
|
||||
forceServiceChannelId,
|
||||
allowDesignatedOutsidePool);
|
||||
|
||||
ApplyAllocationTraceToOrder(order, evaluation);
|
||||
|
||||
@ -928,7 +930,8 @@ public partial class OrderService
|
||||
decimal chargeableWeight,
|
||||
string chargingStage,
|
||||
string? schemeOverride,
|
||||
int? forceServiceChannelId)
|
||||
int? forceServiceChannelId,
|
||||
bool allowDesignatedOutsidePool = false)
|
||||
{
|
||||
var warehouseName = await ResolveWarehouseNameAsync(order.WarehouseId);
|
||||
var providers = await ResolveAllocationProvidersAsync(product, snapshotBinding.Rules);
|
||||
@ -985,27 +988,61 @@ public partial class OrderService
|
||||
if (designated.Count == 0)
|
||||
{
|
||||
var poolRow = providers.FirstOrDefault(x => x.ServiceChannelId == forceServiceChannelId.Value);
|
||||
var failReason = poolRow == null
|
||||
? "指定渠道不在该销售产品排货池"
|
||||
: "指定渠道不满足判断条件";
|
||||
if (poolRow != null
|
||||
&& skipped.All(x => x.ServiceChannelId != forceServiceChannelId.Value))
|
||||
// 重算应付锁定已确认渠道:渠道已在单上,不再用旧规则排货池/判断条件拦截,只重算买价。
|
||||
if (allowDesignatedOutsidePool)
|
||||
{
|
||||
skipped.Add(new OrderAllocationSkippedOutput
|
||||
var lockedChannel = await ServiceChannelRepository.FirstOrDefaultAsync(x =>
|
||||
x.Id == forceServiceChannelId.Value);
|
||||
if (lockedChannel == null)
|
||||
{
|
||||
ServiceChannelId = forceServiceChannelId.Value,
|
||||
ChannelCode = poolRow.ChannelCode,
|
||||
ChannelName = poolRow.ServiceChannelName ?? poolRow.ProviderName,
|
||||
Stage = AllocationResolver.SkipStageCondition,
|
||||
MatchCondition = AllocationResolver.TruncateMatchCondition(poolRow.MatchCondition),
|
||||
Reason = failReason
|
||||
});
|
||||
return BaseEval("订单服务渠道不存在,无法重算应付");
|
||||
}
|
||||
|
||||
var lockedProvider = poolRow ?? new SalesProductAllocationProviderSnapshotDto
|
||||
{
|
||||
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)
|
||||
@ -1304,7 +1341,8 @@ public partial class OrderService
|
||||
OrderBillingContext billing,
|
||||
string chargingStage,
|
||||
string? schemeOverride = null,
|
||||
int? forceServiceChannelId = null) =>
|
||||
int? forceServiceChannelId = null,
|
||||
bool allowDesignatedOutsidePool = false) =>
|
||||
ConfirmServiceChannelAndPayableAsync(
|
||||
order,
|
||||
product,
|
||||
@ -1314,7 +1352,8 @@ public partial class OrderService
|
||||
chargingStage,
|
||||
billing.PriceTierKey,
|
||||
schemeOverride,
|
||||
forceServiceChannelId);
|
||||
forceServiceChannelId,
|
||||
allowDesignatedOutsidePool);
|
||||
|
||||
/// <summary>
|
||||
/// 尝试排货并写入应付;失败时不抛异常,写入 <see cref="Order.AllocationPendingReason"/> 供运营补配置。
|
||||
@ -1325,7 +1364,8 @@ public partial class OrderService
|
||||
OrderBillingContext billing,
|
||||
string chargingStage,
|
||||
string? schemeOverride = null,
|
||||
int? forceServiceChannelId = null)
|
||||
int? forceServiceChannelId = null,
|
||||
bool allowDesignatedOutsidePool = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -1335,7 +1375,8 @@ public partial class OrderService
|
||||
billing,
|
||||
chargingStage,
|
||||
schemeOverride,
|
||||
forceServiceChannelId);
|
||||
forceServiceChannelId,
|
||||
allowDesignatedOutsidePool);
|
||||
return true;
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
|
||||
@ -108,6 +108,9 @@ public class AllocationProviderSnapshotEnricher(
|
||||
ServiceChannel? channelFallback = null)
|
||||
{
|
||||
order.DownstreamSupplierCode = NullIfEmpty(provider.SupplierCode);
|
||||
order.DownstreamSupplierName = NullIfEmpty(provider.ApiServiceProviderName)
|
||||
?? NullIfEmpty(provider.GroupName)
|
||||
?? NullIfEmpty(provider.ProviderName);
|
||||
order.DownstreamAppKey = NullIfEmpty(provider.AppKey);
|
||||
order.DownstreamAppId = provider.AppId;
|
||||
order.ChannelForecastCode = NullIfEmpty(provider.ForecastCode)
|
||||
|
||||
@ -7,6 +7,7 @@ public static class OrderOperationActionCodes
|
||||
public const string RecalcReceivable = "recalcReceivable";
|
||||
public const string RecalcPayable = "recalcPayable";
|
||||
public const string Reallocate = "reallocate";
|
||||
public const string BindSnapshotVersions = "bindSnapshotVersions";
|
||||
|
||||
public static string GetActionName(string action) => action switch
|
||||
{
|
||||
@ -15,6 +16,7 @@ public static class OrderOperationActionCodes
|
||||
RecalcReceivable => "重算应收",
|
||||
RecalcPayable => "重算应付",
|
||||
Reallocate => "重新排货",
|
||||
BindSnapshotVersions => "切换价格/规则版本",
|
||||
_ => action
|
||||
};
|
||||
}
|
||||
|
||||
@ -29,6 +29,12 @@ public class Order : OmsAuditedAggregateRoot
|
||||
/// </summary>
|
||||
public string? DownstreamSupplierCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下游 API 服务商名称(itfx service provider name)。
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 128, IsNullable = true)]
|
||||
public string? DownstreamSupplierName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下游 itfx 应用 AppKey。
|
||||
/// </summary>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user