fix(finance): 收敛内部 SKU 履约路由与载体状态

统一内部 SKU 与渠道主/兜底解析,禁止并列主渠道。

运营指定渠道改为先创建真实载体,失败保持明确 pending。

历史缺载体、多载体冲突和自动发货兜底统一由管道修复。

迁移人工配置与旧 sku_code 规则,补充严格预检和集成测试。
This commit is contained in:
yml2213
2026-11-02 15:02:49 +08:00
parent 5fe33b0b46
commit 2f87445ed9
33 changed files with 2199 additions and 446 deletions
@@ -1,7 +1,7 @@
# 履约与财务分流统一方案
日期:2026-11-02
状态:设计稿,待实施
状态:已落地(2026-11-02);代码基线:迁移 121 + backend/frontend 当前 HEAD
目标:以内部中文 SKU 为唯一主键,统一「下单路由 → 履约载体 → 财务渠道 → 成本」链路。
## 1. 现状结论
@@ -220,9 +220,19 @@ pending 只保留:
- 后端/前端测试、构建、SQL 护栏通过;
- 生产迁移/部署验证。
## 8. 待确认
## 8. 最终落地口径(2026-11-02)
1. PDD 纯数字内部 SKU 的中文映射由谁维护(商品名/规格/运营表)?
2. 双渠道商品(自动发货 + 人工)的兜底触发条件:任务失败、超时、还是人工手动切换?
3. 接单平台是否所有内部 SKU 都必须自动建工单,还是保留“运营指定”模式?
4. 同名 man/tpl 商品是否合并,还是保留多载体配置、只统一 internal_sku_channels?
1. **内部 SKU 只有一个解析函数**:`resolve_internal_sku(manual_sku_key, sku_name, sku_code)`;入口 TS 侧使用同一顺序,不再由各脚本自行拼 SQL。
2. **渠道配置只有一个运行时入口**:`internal_sku_channels`。一个内部 SKU 最多一个主渠道,其余渠道必须 `is_fallback = true`;数据库唯一索引阻止并列主渠道。
3. **载体先行**:自动发货/接单先创建 `fulfillment_tasks` / `work_orders`,成功后才写 `fulfillment_status = assigned`。创建失败保持 `pending / no_carrier`。
4. **兜底触发**:主渠道为自动发货/接单且载体无法创建时,按 priority 尝试显式 fallback;人工配置、卡券为无需外部载体的安全渠道。已分配后自动任务失败仍由 `affiliate_fallback` 接管。
5. **财务只镜像事实**:已有唯一真实载体优先;否则读取统一渠道计划;旧 `sku_channel_rules` 仅保留兼容回退,新增/修改会同步到统一渠道表。
6. **人工配置即时入表**:保存/停用人工 SKU 配置会同步 `internal_sku_channels`,历史配置由迁移 120 回填。
7. **旧规则自动迁移**:可确定的 `sku_code` 自动发货/卡券规则在迁移中转为内部 SKU 渠道并停用旧规则;无法唯一解析的规则由严格预检阻断。
8. **同名 man/tpl 不强制合并**:保留不同载体能力,但同一内部 SKU 只能配置一个主渠道,其余显式兜底。
### 数据与回填约定
- PDD 纯数字码优先由 `internal_sku_mappings` 维护;未命中时使用中文 `sku_name`,仍无中文则保留原码并进入待分类。
- 所有历史人工配置进入 `manual` 兜底能力;存在自动/接单/卡券能力时,人工 priority 固定不低于 90。
- `npm run products:merge-duplicates`、`cleanup-legacy-sku-code-rules`、`reclassify-post-cutover` 保持 dry-run 默认;执行前后都运行严格预检。
+28 -4
View File
@@ -1,7 +1,7 @@
# 生产部署核对清单(履约与财务分流统一)
日期:2026-11-02
适用:生产当前在迁移 115,本次新增 116–119 + backend/frontend。
适用:生产当前在迁移 115,本次新增 116–121 + backend/frontend。
## 1. 部署前
@@ -24,6 +24,8 @@
117_internal_sku_mappings
118_internal_sku_channels
119_fulfillment_pending_reason
120_fulfillment_routing_invariants
121_internal_sku_single_primary
```
重点检查:
@@ -32,10 +34,12 @@
- 118:按商品能力/规则回填 `internal_sku_channels`;
- SQL 已做聚合,避免同名商品同渠道 `ON CONFLICT` 二次更新报错;
- 119:只加列和索引,风险低。
- 120:建立统一内部 SKU SQL 函数、回填人工配置、迁移可确定的旧 sku_code 规则。
- 121:建立唯一主渠道索引;若仍有并列主渠道会主动阻断迁移。
## 3. 部署顺序
1. 执行迁移 116–119;
1. 执行迁移 116–121;
2. 部署 backend;
3. 部署 frontend;
4. 启动/恢复定时任务。
@@ -56,6 +60,7 @@ GROUP BY 1;
- `no_internal_sku`
- `no_channel_capability`
- `no_carrier`
- `routing_conflict`(如仍有配置冲突,必须处理)
### 11月改名卡
@@ -110,6 +115,25 @@ WHERE enabled
GROUP BY 1;
```
### 严格预检
```bash
# 本地演示数据默认排除 sim-*;生产应直接执行
npx tsx scripts/preflight-fulfillment-routing.ts --strict
# 如确实要把演示数据也纳入检查
npx tsx scripts/preflight-fulfillment-routing.ts --strict --include-demo
```
严格预检必须为 0 失败,重点包括:
- 有内部 SKU 但无渠道能力的 pending;
- pending 未写入明确原因(说明管道尚未跑完);
- 已分配自动发货/接单但没有真实载体;
- 已分配订单同时存在多个真实履约载体;
- 多个主渠道;
- 仍启用的 `sku_code` 兼容规则。
## 5. 数据清理(确认后执行)
```bash
@@ -136,6 +160,6 @@ npx tsx scripts/reclassify-post-cutover.ts --apply
- 代码:回退到部署前镜像/commit;
- 数据库:
- 116–119 均为新增表/列/数据,不删旧表,可保留;
- 116–121 均为新增表/列/函数/数据,不删旧表,可保留;
- 若必须回滚,恢复到部署前备份;
- 迁移 118/119 不改旧字段语义,回滚代码后旧逻辑仍可运行。
- 迁移 118–121 不改旧字段语义,回滚代码后旧逻辑仍可运行;121 的唯一主渠道索引会阻止旧代码写入并列主渠道。