docs(finance): 文档口径切到渠道模型并标注历史文档
- API 契约/成本中心模型改为 fulfillment_channel/status 与 channel 字段 - 验收 SQL 的销售事实检查改用 channel - 2026-10 之前的口径文档加历史横幅,避免被当成现行实现
This commit is contained in:
+4
-4
@@ -28,7 +28,7 @@
|
||||
|
||||
### 3. 履约方式与成本模型必须分开
|
||||
|
||||
- `fulfillment_mode` 回答“谁执行”;`cost_model` 回答“成本怎么算”
|
||||
- `fulfillment_channel`(+ `fulfillment_status`)回答“谁执行”;`cost_model` 回答“成本怎么算”
|
||||
- 自动发货:`affiliate_purchase`
|
||||
- 接单平台:`work_order_reward`
|
||||
- 人工:`manual_actual`(逐单实际金额)
|
||||
@@ -80,7 +80,7 @@
|
||||
| 后台用户中文显示名 | **已实施** | `admin_users.display_name`,备用金姓名触发器同步;审计、登录记录、任务处理人优先显示中文名 |
|
||||
| 商品主数据 + 渠道 SKU 映射 | **已实施 P0/P1** | 2,017 个标准商品、4,897 条映射:`tpl:` 710、`man:` 1,195、`aff:` 49、`wo:` 63;订单项已按履约证据/人工内部 SKU 归一 |
|
||||
| 成本中心 | **已实施 P0** | 迁移 `103`;710 个规则级版本 + 2 个全局占位版本;工单成本快照 151,939 条,其中 actual 129,430、estimated 22,509 |
|
||||
| 销售事实 / 订单毛利 | **已实施 P0** | 迁移 `103`;375,447 项、收入 ¥3,143.61 万、成本 ¥572.95 万、毛利 ¥2,570.66 万;`sales_daily_facts.fulfillment_mode` |
|
||||
| 销售事实 / 订单毛利 | **已实施 P0** | 迁移 `103`;375,447 项、收入 ¥3,143.61 万、成本 ¥572.95 万、毛利 ¥2,570.66 万;`sales_daily_facts.channel` |
|
||||
| 人工成本录入(确认履约) | **已实施 P0(2026-10-26 单事务 + 防重复)** | 确认即扣积分/备用金并写 `actual` 快照;钱与成本同事务、订单项级唯一;无登记单/审批/报销 |
|
||||
| 人工 SKU 配置自动匹配 | **已实施(2026-10-26)** | 保存配置立即回填 + 定时 rematch + 确认时按 SKU 兜底匹配 |
|
||||
| 人工缺成本监控 | **已实施(2026-10-26)** | `manual_cost_missing`:首次人工配置之后的订单项未确认即入队,确认后自动收口 |
|
||||
@@ -97,7 +97,7 @@
|
||||
## 三、统一实施顺序
|
||||
|
||||
```text
|
||||
0. 统一履约事实(fulfillment_mode + 成本模型正交) 已完成 P0(迁移 103)
|
||||
0. 统一履约事实(渠道 + 成本模型正交) 已完成(迁移 112–114)
|
||||
1. 商品主数据 + 渠道 SKU 映射 已完成 P0(仅自动发货/接单平台)
|
||||
2. 成本中心 + 销售事实 已完成 P0
|
||||
2.5 人工成本链路收敛(确认履约 → 扣积分/备用金 → 写快照) 已完成 P0(迁移 104)
|
||||
@@ -158,7 +158,7 @@
|
||||
| 文件 | 用途 | 接入前必须处理 |
|
||||
| --- | --- | --- |
|
||||
| [drafts/2026-10-17-载体判定与人工块量化.sql](drafts/2026-10-17-载体判定与人工块量化.sql) | 载体分布/人工块量化查询、`carrier_type` 判定与回填 | **已过时**:`carrier_type` 已从商品/成本/快照/异常表删除;只读查询里的口径结论已吸收进总纲 §二 |
|
||||
| [drafts/2026-10-17-成本中心与销售事实-DDL草案.sql](drafts/2026-10-17-成本中心与销售事实-DDL草案.sql) | `product_catalog`、`channel_product_mappings`、`cost_items/versions/snapshots/exceptions`、`sales_daily_facts` | 已落地为 `103`–`105`;草案里的 `carrier_type` 已被 `cost_model` / `fulfillment_mode` 取代,仅作设计追溯 |
|
||||
| [drafts/2026-10-17-成本中心与销售事实-DDL草案.sql](drafts/2026-10-17-成本中心与销售事实-DDL草案.sql) | `product_catalog`、`channel_product_mappings`、`cost_items/versions/snapshots/exceptions`、`sales_daily_facts` | 已落地为 `103`–`105`;草案里的 `carrier_type` 已被 `cost_model` / 渠道三列取代,仅作设计追溯 |
|
||||
| [drafts/2026-10-31-112-履约渠道拆分-迁移草案.sql](drafts/2026-10-31-112-履约渠道拆分-迁移草案.sql) | 渠道/状态/来源拆列、`sku_channel_rules`、异常两级化、水位表、卡券成本项 | 已落地为 `apps/backend/src/db/migrations/112_fulfillment_channel_split.sql`;草案保留作设计追溯 |
|
||||
| [drafts/2026-10-31-同步链路重构-旧函数对照.md](drafts/2026-10-31-同步链路重构-旧函数对照.md) | 16 个旧同步/回填入口 → 6 个新管道作业的对照与验收口径 | 已落地:三条管道服务 + 六个定时任务 + 后台「履约渠道」页 |
|
||||
| [archive/2026-10-23/2026-10-17-人工支出登记-DDL草案.sql](archive/2026-10-23/2026-10-17-人工支出登记-DDL草案.sql) | `manual_fulfillment_cases`、`manual_expense_records`、`manual_expense_events` | **已作废**:`manual_expense_records` / `manual_expense_events` 已随迁移 `104` 归并下线,人工成本改走「人工录单 → 扣备用金 → 写快照」;文件已移入 archive |
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# dev 合并 main 评估(2026-10-27)
|
||||
|
||||
|
||||
> **历史文档(2026-10-31 前口径)**:文中 `fulfillment_mode` / `legacy_platform` 已随迁移 112–114 删除;
|
||||
> 现行口径为 `order_items.fulfillment_channel + fulfillment_status`,销售事实维度为 `sales_daily_facts.channel`。
|
||||
> 本文保留用于追溯当时的决策,不再作为实现依据。
|
||||
> 评估对象:`main`(2fb7aae1)与 `dev`(0de0fd7b + 当前未提交的财务 P0/P1 改动)。
|
||||
> 生产基线:生产库当前 `schema_migrations=102`;验证用 dump 恢复到 `101` 后再补 `102`。
|
||||
> 结论:**代码可以合并;生产发布还需要执行一组一次性数据初始化/回填,不能只合并就上线。**
|
||||
|
||||
+28
-15
@@ -1,8 +1,8 @@
|
||||
# 成本中心 API 契约
|
||||
|
||||
> 状态:**现行**。后端服务与前端页面都必须严格按此实现,不得各自发挥。
|
||||
> 最后校准:2026-10-25
|
||||
> 代码基线:`dev`(合并 dev1023 后)+ 迁移 `103`–`111`(已合并到 `dev`,**未推送、未部署生产**)
|
||||
> 最后校准:2026-10-31(履约渠道模型,迁移 112–114)
|
||||
> 代码基线:`main`
|
||||
> 详细业务口径见 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md);数据模型见 [成本中心与履约模型.md](成本中心与履约模型.md)。
|
||||
|
||||
## 一、字段替换总表
|
||||
@@ -14,7 +14,7 @@
|
||||
| `cost_snapshots.carrier_type` | (删除) | DB |
|
||||
| `cost_exceptions.carrier_type` | (删除) | DB |
|
||||
| `product_catalog.carrier_type` | (删除,改由 `canonical_sku` 前缀推导) | DB / 商品接口 |
|
||||
| `sales_daily_facts.carrier_type` | `sales_daily_facts.fulfillment_mode` | DB / 销售事实接口 |
|
||||
| `sales_daily_facts.carrier_type` | `sales_daily_facts.channel` | DB / 销售事实接口 |
|
||||
| `work_product_rules.legacy_cost_source` | (删除) | DB |
|
||||
| `manual_expense_records` / `manual_expense_events` | (删除,人工成本只走确认履约) | DB |
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
| --- | --- | --- |
|
||||
| `affiliate_purchase` | 自动发货上游采购成本 | `cost_type=auto_purchase` |
|
||||
| `work_order_reward` | 接单平台打手报酬 | `cost_type=work_order_reward` |
|
||||
| `third_party_voucher_cost` | 第三方卡券采购成本 | 新增 |
|
||||
| `manual_actual` | 人工客服实际支出 | `cost_type=manual_service` |
|
||||
| `platform_fee` | 平台服务费 | `cost_type=platform_fee` |
|
||||
| `payment_fee` | 支付通道费 | `cost_type=payment_fee` |
|
||||
@@ -39,9 +40,12 @@
|
||||
`per_item` / `per_order` / `percent_of_revenue`
|
||||
(删除 `per_hour`、`fixed_daily`——人工成本逐单按实际金额,不按工时)
|
||||
|
||||
### 2.3 `fulfillment_mode`(履约方式,唯一真源)
|
||||
### 2.3 `channel`(履约渠道,唯一真源)
|
||||
|
||||
`work_order` / `affiliate_dash` / `legacy_platform` / `manual`
|
||||
`work_order` / `affiliate_dash` / `third_party_voucher` / `manual`,
|
||||
另有两个派生值:`pending`(待分类)、`archived`(断代归档)、`unclassified`(事实桶)。
|
||||
|
||||
DB 真源是 `order_items.fulfillment_channel` + `fulfillment_status`;接口字段名统一为 `channel`。
|
||||
|
||||
## 三、成本项接口
|
||||
|
||||
@@ -116,11 +120,14 @@ AND (
|
||||
|
||||
| 接口 | 说明 |
|
||||
| --- | --- |
|
||||
| `GET /api/v1/admin/cost-center/mappings/diagnostics` | 标准商品数 / 映射数 / 有映射商品数 / 未归一订单项 / 未归一 SKU 组;按 `fulfillment_mode` 拆分聚合 |
|
||||
| `GET /api/v1/admin/cost-center/mappings/unmapped` | 未归一队列,按「来源 + 店铺 + 平台 SKU + 履约方式」聚合,支持 `keyword` / `provider` / `fulfillmentMode` 与分页 |
|
||||
| `GET /api/v1/admin/cost-center/mappings/diagnostics` | 标准商品数 / 映射数 / 有映射商品数 / 未归一订单项 / 未归一 SKU 组;按 `channel` 拆分聚合 |
|
||||
| `GET /api/v1/admin/cost-center/mappings/unmapped` | 未归一队列,按「来源 + 店铺 + 平台 SKU + 履约方式」聚合,支持 `keyword` / `provider` / `channel` 与分页 |
|
||||
| `POST /api/v1/admin/cost-center/mappings/backfill` | 回填订单项映射;返回 `resolvedExceptionCount`(已归一的 `product_mapping_missing` 异常自动收口) |
|
||||
|
||||
### 4.2 人工商品归一(P1)
|
||||
### 4.2 人工商品归一(已删除)
|
||||
|
||||
> 2026-10-31 起商品归一只走 `product_normalize` 管道(模板/人工配置/`aff:`/`tpv:` 直达),
|
||||
> 不再提供手工归一接口。以下为历史记录。
|
||||
|
||||
`POST /api/v1/admin/cost-center/manual-products/normalize`,`finance.config`。
|
||||
|
||||
@@ -177,15 +184,21 @@ AND (
|
||||
|
||||
## 六、成本异常接口
|
||||
|
||||
路径不变。请求参数 `carrierType` **删除**;响应 `carrierType` **删除**,改为 `fulfillmentMode`。
|
||||
若前端要按履约方式筛选异常,用参数 `fulfillmentMode`:
|
||||
请求参数 `carrierType` / `fulfillmentMode` **删除**;筛选统一用 `channel`:
|
||||
|
||||
```jsonc
|
||||
// 请求
|
||||
{ "status": "open", "subjectType": "order_item", "channel": "manual" }
|
||||
```
|
||||
|
||||
旧参数示例(已不生效):
|
||||
|
||||
```jsonc
|
||||
// 请求
|
||||
{ "status": "open", "subjectType": "order_item", "fulfillmentMode": "manual" }
|
||||
```
|
||||
|
||||
后端实现:join `order_items oi ON oi.id = ce.order_item_id` 并过滤 `oi.fulfillment_mode`。
|
||||
后端实现:join `order_items oi ON oi.id = ce.order_item_id` 并过滤 `oi.fulfillment_channel`。
|
||||
|
||||
响应项:
|
||||
|
||||
@@ -196,7 +209,7 @@ AND (
|
||||
"subjectId": 316571,
|
||||
"orderItemId": 316571,
|
||||
"workOrderId": null,
|
||||
"fulfillmentMode": "manual",
|
||||
"channel": "manual",
|
||||
"reason": "cost_missing",
|
||||
"status": "open",
|
||||
"detail": {},
|
||||
@@ -211,11 +224,11 @@ AND (
|
||||
|
||||
## 七、销售事实接口
|
||||
|
||||
`GET /api/v1/admin/sales-profit` 的分组维度字段名从 `carrierType` 改为 `fulfillmentMode`:
|
||||
`GET /api/v1/admin/sales-profit` 的分组维度字段名为 `channel`,响应分组为 `byChannel`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"fulfillmentMode": "manual",
|
||||
"channel": "manual",
|
||||
"itemCount": 102955,
|
||||
"grossRevenueFen": 1219202470,
|
||||
"costAmountFen": 32000,
|
||||
@@ -224,7 +237,7 @@ AND (
|
||||
}
|
||||
```
|
||||
|
||||
筛选参数同样从 `carrierType` 改为 `fulfillmentMode`,取值四种履约方式。
|
||||
筛选参数用 `channel`,取值 `work_order` / `affiliate_dash` / `third_party_voucher` / `manual` / `unclassified`。
|
||||
|
||||
## 八、成本中心概览接口
|
||||
|
||||
|
||||
+44
-73
@@ -1,11 +1,11 @@
|
||||
# 成本中心与履约模型
|
||||
|
||||
> 状态:**现行**。商品主数据、成本中心、成本快照、销售事实、履约事实均已落地。
|
||||
> 最后校准:2026-10-27
|
||||
> 代码基线:`dev`(合并 dev1023 + 人工成本完整性修复,迁移基线收敛为 `103`–`105`)
|
||||
> 数据基线:生产 dump `order_site-20261022-092601.dump`(2026-07-10 ~ 2026-10-22);本地只跑了近 30 天增量,历史数字仅作基线。
|
||||
> 状态:**现行**。2026-10-31 起履约事实改为「渠道 + 状态」两列,旧 `fulfillment_channel` 已删除。
|
||||
> 最后校准:2026-10-31(迁移 112–114)
|
||||
> 代码基线:`main`
|
||||
> 数据基线:2026-10-29 生产 dump;历史数字仅作迁移参照。
|
||||
> 上线初始化/验收见 [财务上线初始化与验收-2026-10-26.md](财务上线初始化与验收-2026-10-26.md)。
|
||||
> **业务口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准**;本文只负责数据模型、迁移与运维。
|
||||
> 业务口径见 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md)(10-31 前的历史口径);本文负责数据模型、迁移与运维。
|
||||
> 接口字段见 [成本中心API契约.md](成本中心API契约.md)。
|
||||
|
||||
---
|
||||
@@ -13,23 +13,24 @@
|
||||
## 〇、模型总览
|
||||
|
||||
```text
|
||||
orders ──┬── order_items ──── fulfillment_mode (唯一履约事实)
|
||||
orders ──┬── order_items ──── fulfillment_channel + fulfillment_status (唯一履约事实)
|
||||
│ ├── canonical_product_id ── product_catalog
|
||||
│ ├── normalized_quantity / normalized_sku_name / normalized_sku_series
|
||||
│ └── revenue_amount_fen / revenue_basis
|
||||
│
|
||||
├── cost_snapshots ──── cost_versions ──── cost_items (cost_model)
|
||||
├── cost_exceptions (order_item_id → order_items.fulfillment_mode)
|
||||
├── sales_daily_facts (fulfillment_mode)
|
||||
├── cost_exceptions (order_item_id → order_items 渠道)
|
||||
├── sales_daily_facts (channel)
|
||||
└── manual_expenses (客服备用金支出) ── subject_id ── cost_snapshots
|
||||
```
|
||||
|
||||
四条不变式:
|
||||
五条不变式:
|
||||
|
||||
1. **履约方式只有一处存**:`order_items.fulfillment_mode`。快照、异常、销售事实要展示履约方式时 join `order_items`。
|
||||
2. **商品来源只由 `canonical_sku` 前缀表达**,商品表不存载体列。
|
||||
3. **成本模型只表达"怎么算"**:`cost_items.cost_model` 与履约方式正交,不复用同一字段。
|
||||
4. **成本金额只在一处改**:`cost_versions`;业务表只保存快照,历史成本永不回写。
|
||||
1. **履约渠道只有一处存**:`order_items.fulfillment_channel` + `fulfillment_status`。快照、异常、销售事实只 join `order_items`,不再冗余渠道。
|
||||
2. **渠道判定必须留痕**:`fulfillment_source` 记录规则/证据/人工指定;指定后 `fulfillment_locked=true`,自动判定不再覆盖。
|
||||
3. **商品来源只由 `canonical_sku` 前缀表达**(`aff:` / `tpl:` / `wo:` / `tpv:` / `man:`),商品表不存载体列。
|
||||
4. **成本模型只表达"怎么算"**:`cost_items.cost_model` 与渠道正交,不复用同一字段。
|
||||
5. **成本金额只在一处改**:`cost_versions`;业务表只保存快照,历史成本永不回写。
|
||||
|
||||
---
|
||||
|
||||
@@ -58,7 +59,7 @@ orders ──┬── order_items ──── fulfillment_mode (唯一履约
|
||||
| `cost_snapshots.carrier_type` | (删除) | DB |
|
||||
| `cost_exceptions.carrier_type` | (删除) | DB |
|
||||
| `product_catalog.carrier_type` | (删除,改由 `canonical_sku` 前缀推导) | DB / 商品接口 |
|
||||
| `sales_daily_facts.carrier_type` | `sales_daily_facts.fulfillment_mode` | DB / 销售事实接口 |
|
||||
| `sales_daily_facts.carrier_type` | `sales_daily_facts.channel` | DB / 销售事实接口 |
|
||||
| `work_product_rules.legacy_cost_source` | (删除) | DB |
|
||||
| `cost_versions.cost_item_carrier_type` | `cost_versions.cost_item_cost_model` | 成本版本接口 |
|
||||
| `pricing_mode: per_hour / fixed_daily` | (删除,只剩三种) | 成本项接口 |
|
||||
@@ -253,7 +254,7 @@ resolved_at, superseded_at, created_at
|
||||
- 同一主体 + 同一成本项只允许一条"当前有效"快照(`uq_cost_snapshots_active_subject_item`);
|
||||
- 预估快照代表支付时点的成本,**不因后续改价重写**;只有实际成本允许替代当前有效行;
|
||||
- 匹配不到成本时写 `cost_missing` 异常,**不能默认 0**;
|
||||
- 履约方式不存快照里,需要时 join `order_items.fulfillment_mode`;
|
||||
- 履约方式不存快照里,需要时 join `order_items.fulfillment_channel`;
|
||||
- 人工单的快照主体是 `subject_type='manual_case'` + `subject_id=order_item_id`;资金流水 id 只写 `metadata_json`,**没有独立的登记单表**;
|
||||
- 负数 `subject_id` 表示历史孤儿快照(迁移 `103` 约定),迁移 `104` 保留不动,不参与新链路;
|
||||
- 一个订单项最多一条有效人工快照(`uq_cost_snapshots_manual_order_item`)。
|
||||
@@ -270,7 +271,7 @@ detail_json
|
||||
created_at, resolved_at, resolved_by
|
||||
```
|
||||
|
||||
`open` 数量必须在后台暴露。按履约方式筛选时用 `EXISTS`/`IN` 半连接 `order_items.fulfillment_mode`(本地 248,878 条,`IN` 半连接 + `idx_order_items_fulfillment_id` 索引约 170ms)。
|
||||
`open` 数量必须在后台暴露。按履约方式筛选时用 `EXISTS`/`IN` 半连接 `order_items.fulfillment_channel`(本地 248,878 条,`IN` 半连接 + `idx_order_items_fulfillment_id` 索引约 170ms)。
|
||||
|
||||
---
|
||||
|
||||
@@ -299,7 +300,7 @@ created_at, resolved_at, resolved_by
|
||||
stat_date -- Asia/Shanghai,按 paid_at 归属
|
||||
provider, shop_id
|
||||
canonical_product_id -- 允许为空:未归一桶
|
||||
fulfillment_mode -- work_order / affiliate_dash / legacy_platform / manual
|
||||
channel -- work_order / affiliate_dash / third_party_voucher / manual / unclassified
|
||||
order_count, item_count, quantity
|
||||
gross_revenue_fen, refund_amount_fen, net_revenue_fen
|
||||
cost_amount_fen, cost_missing_count, cost_stage
|
||||
@@ -309,7 +310,9 @@ computed_at
|
||||
|
||||
口径:
|
||||
|
||||
- 只统计 `pay_status IN ('paid','refunded')` 的订单;
|
||||
- 收入 = `order_items.revenue_amount_fen`,不直接拿整单金额;
|
||||
- 退款 = `order_refunds` 中 `confirmed` 的金额,净收入 = 收入 − 退款;
|
||||
- 成本来自 `cost_snapshots` 当前有效行;没有快照的订单项计入 `cost_missing_count`;
|
||||
- 未归一商品允许 `canonical_product_id` 为空,单独成"未归一"行,保证总收入对平;
|
||||
- `cost_stage`:`missing`(全部无快照)/ `partial`(部分无)/ `estimated`(全部预估)/ `actual`(全部实际);
|
||||
@@ -329,73 +332,41 @@ computed_at
|
||||
|
||||
## 六、履约事实
|
||||
|
||||
### 6.1 `order_items.fulfillment_mode`
|
||||
### 6.1 `order_items` 渠道三列
|
||||
|
||||
```text
|
||||
fulfillment_mode -- work_order / affiliate_dash / legacy_platform / manual;允许 NULL
|
||||
fulfillment_decided_at -- 最后一次判定时间
|
||||
fulfillment_reason -- 判定来源:order_sync / reconcile / backfill
|
||||
fulfillment_channel -- work_order / affiliate_dash / third_party_voucher / manual;pending 时为空
|
||||
fulfillment_status -- pending 待分类 / assigned 已确定 / archived 断代归档
|
||||
fulfillment_source -- rule / evidence / operator / manual_confirm / cutover_archive ...
|
||||
fulfillment_locked -- 人工指定后为 true,自动判定跳过
|
||||
fulfillment_decided_at / fulfillment_reason
|
||||
```
|
||||
|
||||
判定顺序固定:**工单 → 自动发货任务 → 历史平台任务 → 人工**。当前库中三种载体没有重叠,顺序不影响结果;后续出现重叠必须告警,不能静默覆盖。
|
||||
判定顺序固定:**规则命中 → 载体证据(工单/自动发货任务)→ 无证据则 pending**。
|
||||
渠道值不再有 `legacy_platform`:断代归档后统一是 `archived`。
|
||||
|
||||
决策服务在 `services/fulfillment/fulfillment-mode-service.ts`:
|
||||
决策服务在 `services/fulfillment/channel-assign-service.ts`:
|
||||
|
||||
- `classifyFulfillmentMode()` —— 纯函数,供单测与文档对照;
|
||||
- `decideOrderItemsFulfillmentModes()` —— 订单同步后批量落库;
|
||||
- `reconcilePendingFulfillmentModes()` —— 24 小时兜底归集;
|
||||
- `getFulfillmentModeCounts()` —— 计数核对。
|
||||
- `resolveChannelDecision()` —— 纯函数,规则与证据的优先级;
|
||||
- `assignPendingOrderItemChannels()` —— 按水位游标批量判定;
|
||||
- `assignChannelBySku()` —— 运营按 SKU 指定,写 `operator` 规则并锁定。
|
||||
|
||||
### 6.2 回填
|
||||
### 6.2 断代与归档
|
||||
|
||||
回填是**一次性脚本**,不塞进迁移:31 万行 `UPDATE` 放进迁移会拖慢部署,也无法在中途失败后按批重试。
|
||||
断代不再依赖独立回填脚本,执行 `finance:cutover-v2` 时:
|
||||
|
||||
```bash
|
||||
npm run fulfillment:backfill
|
||||
```
|
||||
|
||||
只填 `NULL`,可重复执行。判定 SQL:
|
||||
|
||||
```sql
|
||||
WITH decided AS (
|
||||
SELECT
|
||||
oi.id,
|
||||
CASE
|
||||
WHEN EXISTS (SELECT 1 FROM work_orders wo WHERE wo.order_item_id = oi.id)
|
||||
THEN 'work_order'
|
||||
WHEN EXISTS (
|
||||
SELECT 1 FROM fulfillment_tasks ft
|
||||
WHERE ft.order_item_id = oi.id AND ft.executor_key = 'affiliate_dash'
|
||||
) THEN 'affiliate_dash'
|
||||
WHEN EXISTS (
|
||||
SELECT 1 FROM fulfillment_tasks ft
|
||||
WHERE ft.order_item_id = oi.id
|
||||
AND ft.executor_key IN ('kuaishou_ct_assisted', 'kuaishou_feifei')
|
||||
) THEN 'legacy_platform'
|
||||
ELSE 'manual'
|
||||
END AS fulfillment_mode
|
||||
FROM order_items oi
|
||||
)
|
||||
UPDATE order_items oi
|
||||
SET fulfillment_mode = d.fulfillment_mode,
|
||||
fulfillment_decided_at = now(),
|
||||
fulfillment_reason = 'backfill_2026_10_23'
|
||||
FROM decided d
|
||||
WHERE oi.id = d.id
|
||||
AND oi.fulfillment_mode IS NULL;
|
||||
```
|
||||
|
||||
> 上述计数是 2026-10-23 历史回填的结果,仅作迁移正确性参照;当前本地生产副本只跑了近 30 天增量,**不要用这些数字判断上线是否完成**。
|
||||
> 本地验收(2026-10-23 重写后重跑):`work_order` 118,053 / `manual` 102,955 / `legacy_platform` 53,901 / `affiliate_dash` 41,676,四项相加 = 316,585,`NULL = 0`。
|
||||
1. 把断代日之前的订单项批量置为 `fulfillment_status='archived'`、渠道清空、`source='cutover_archive'`;
|
||||
2. 清空成本快照、异常、销售事实、总账等派生数据;
|
||||
3. 写入 `finance_cutovers` 断代记录,防止重复执行。
|
||||
|
||||
### 6.3 决策边界(写进代码注释与测试)
|
||||
|
||||
1. `fulfillment_mode` 允许短暂为 `NULL`;订单项创建时通常还没有工单/任务,不能强制非空。
|
||||
2. 上线后新订单项超过 **24 小时**仍为 `NULL`,进入异常队列;不是要求在创建瞬间就有值。
|
||||
3. 模式可以因业务改派而变化,但每次变化必须写原因与操作人,禁止静默 `UPDATE`。
|
||||
4. **商品来源不能反推履约方式。** 当前 11,349 个人工项指向 `tpl:*` 商品,就是典型反例。
|
||||
1. 新订单项默认 `pending`;由 `channel_assign` 管道在分钟级判定,不要求下单瞬间有渠道。
|
||||
2. 规则优先级:运营指定(10)> 自动发货(60)> 人工配置(70)> 默认;冲突会记 `routing_conflict` 异常。
|
||||
3. 被运营指定的订单项 `fulfillment_locked=true`,自动判定与回填都不得覆盖。
|
||||
4. **商品来源不能反推渠道**:`man:` 商品也可能由自动发货履约,判定只认规则与证据。
|
||||
5. `voucher_state` 只做展示与核销流程,不参与任何成本/履约判断。
|
||||
6. 三种载体同时命中的订单项必须先告警人工复核;当前本地为 0。
|
||||
6. 多渠道证据同时命中必须先告警人工复核,不能静默取其一。
|
||||
|
||||
---
|
||||
|
||||
@@ -462,8 +433,8 @@ npm run revenue:backfill -- --batch-size=2000 --max-batches=100
|
||||
|
||||
| 索引 | 用途 |
|
||||
| --- | --- |
|
||||
| `idx_order_items_fulfillment_mode (fulfillment_mode, order_id)` | 按履约方式筛订单项 |
|
||||
| `idx_order_items_fulfillment_id (fulfillment_mode, id)` | 异常按履约方式半连接 |
|
||||
| `idx_order_items_fulfillment_channel (fulfillment_channel, order_id)` | 按渠道筛订单项 |
|
||||
| `idx_order_items_fulfillment_id (fulfillment_status, id)` | 管道按状态扫增量 |
|
||||
| `idx_order_items_canonical_product` | 按标准商品查订单项 |
|
||||
| `idx_order_items_normalized_sku_lookup` | 按归一名称/系列匹配映射 |
|
||||
| `idx_channel_product_mappings_match_lookup` | 映射查找 |
|
||||
|
||||
@@ -103,7 +103,7 @@ SELECT
|
||||
COUNT(*) FILTER (WHERE oi.fulfillment_config_id IS NULL) AS unconfigured_items,
|
||||
COUNT(*) AS manual_items
|
||||
FROM order_items oi
|
||||
WHERE oi.fulfillment_mode = 'manual'
|
||||
WHERE oi.fulfillment_status = 'assigned' AND oi.fulfillment_channel = 'manual'
|
||||
AND oi.created_at >= (SELECT MIN(created_at) FROM manual_fulfillment_configs);
|
||||
|
||||
-- 需要补配置的 SKU(按订单量排序)
|
||||
@@ -113,7 +113,7 @@ SELECT
|
||||
MAX(oi.sku_name) AS sku_name,
|
||||
COUNT(*) AS items
|
||||
FROM order_items oi
|
||||
WHERE oi.fulfillment_mode = 'manual'
|
||||
WHERE oi.fulfillment_status = 'assigned' AND oi.fulfillment_channel = 'manual'
|
||||
AND oi.fulfillment_config_id IS NULL
|
||||
AND oi.created_at >= (SELECT MIN(created_at) FROM manual_fulfillment_configs)
|
||||
GROUP BY 1
|
||||
@@ -138,7 +138,7 @@ ORDER BY items DESC;
|
||||
-- 上线后人工项:已配置但未确认(客服待办)
|
||||
SELECT COUNT(*) AS pending_confirmed
|
||||
FROM order_items oi
|
||||
WHERE oi.fulfillment_mode = 'manual'
|
||||
WHERE oi.fulfillment_status = 'assigned' AND oi.fulfillment_channel = 'manual'
|
||||
AND oi.fulfillment_config_id IS NOT NULL
|
||||
AND oi.created_at >= :launch_at
|
||||
AND NOT EXISTS (
|
||||
@@ -199,18 +199,18 @@ SELECT COUNT(*) FROM order_items WHERE revenue_basis = 'unknown'; -- 退款上
|
||||
|
||||
```sql
|
||||
-- 1. 履约方式覆盖:四项相加 = order_items 总数,NULL = 0
|
||||
SELECT COALESCE(fulfillment_mode, '(null)') AS mode, COUNT(*)
|
||||
SELECT COALESCE(channel, '(null)') AS channel, COUNT(*)
|
||||
FROM order_items GROUP BY 1 ORDER BY 2 DESC;
|
||||
|
||||
-- 2. 上线后人工项必须有履约配置(允许配置跟进期)
|
||||
SELECT COUNT(*) FROM order_items
|
||||
WHERE fulfillment_mode = 'manual'
|
||||
WHERE channel = 'manual'
|
||||
AND created_at >= :launch_at
|
||||
AND fulfillment_config_id IS NULL;
|
||||
|
||||
-- 3. 上线后人工项必须有成本快照(关账门槛)
|
||||
SELECT COUNT(*) FROM order_items oi
|
||||
WHERE oi.fulfillment_mode = 'manual'
|
||||
WHERE oi.fulfillment_status = 'assigned' AND oi.fulfillment_channel = 'manual'
|
||||
AND oi.created_at >= :launch_at
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM cost_snapshots cs
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# 财务与履约口径总纲(2026-10-23 重写版)
|
||||
|
||||
> 状态:**现行唯一口径**。本文是财务、成本、履约分类的总入口;其他文档与本文件冲突时,以本文为准。
|
||||
|
||||
> **历史文档(2026-10-31 前口径)**:文中 `fulfillment_mode` / `legacy_platform` 已随迁移 112–114 删除;
|
||||
> 现行口径为 `order_items.fulfillment_channel + fulfillment_status`,销售事实维度为 `sales_daily_facts.channel`。
|
||||
> 本文保留用于追溯当时的决策,不再作为实现依据。
|
||||
> 状态:**已归档**。现行口径见《成本中心与履约模型》(2026-10-31 校准)与《成本中心API契约》。
|
||||
> 最后校准:2026-10-26
|
||||
> 代码基线:`dev`(合并 dev1023 + 人工成本完整性修复,迁移到 `111`)
|
||||
> 数据基线:§二/§七 的数字来自 2026-10-22 生产 dump 的**历史快照**,只用于理解历史缺口,**不再作为上线验收依据**;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# order_site 财务系统深度分析(2026-10-27)
|
||||
|
||||
|
||||
> **历史文档(2026-10-31 前口径)**:文中 `fulfillment_mode` / `legacy_platform` 已随迁移 112–114 删除;
|
||||
> 现行口径为 `order_items.fulfillment_channel + fulfillment_status`,销售事实维度为 `sales_daily_facts.channel`。
|
||||
> 本文保留用于追溯当时的决策,不再作为实现依据。
|
||||
> 分析对象:订单履约、成本中心、客服备用金/积分、销售事实、影子总账、期间关账与平台对账。
|
||||
> 代码基线:`dev`,HEAD `0de0fd7b`(迁移收敛到 `101–111`)。
|
||||
> 文档基线:`docs/README.md` 标注的现行口径(2026-10-26)。
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@
|
||||
| 商品主数据 / 渠道 SKU 映射 / 成本中心 | **已实施 P0(`dev` 分支)**,迁移 `103/104`;标准商品 745 个,只覆盖自动发货 38 + 接单平台 707 |
|
||||
| `sales_daily_facts` / 订单级毛利 | **已实施 P0(`dev` 分支)**,迁移 `105`;94 天收入 ¥2,650.87 万、成本 ¥950.27 万 |
|
||||
| 人工成本录入(确认履约 → 扣积分/备用金 → 写快照) | **已实施 P0(`dev` 分支)**,迁移 `104/107`;2026-10-24 人工履约页下线、入口收敛到订单页;**无登记单、无审批、无报销** |
|
||||
| 履约方式统一 `fulfillment_mode` | **已实施 P0(`dev` 分支)**;迁移 `105`,316,585 项已回填;订单页、成本快照、销售事实统一读该字段 |
|
||||
| 履约渠道统一 `fulfillment_channel + fulfillment_status` | **已实施(2026-10-31,迁移 112–114)**;旧 `fulfillment_mode` 已删除,订单/成本/事实统一读渠道 |
|
||||
| `finance_accounts` / `finance_journal_entries` / `finance_journal_lines` | **已实施 P0(迁移 108)** |
|
||||
| `finance_periods` / `finance_reconciliations` / `finance_reconciliation_items` | **已实施 P0(迁移 108)** |
|
||||
| `finance_business_events` / `finance_settlements` | 未实施(当前用条目唯一键幂等) |
|
||||
@@ -93,7 +93,7 @@
|
||||
因此建议顺序是:
|
||||
|
||||
```text
|
||||
统一履约事实(fulfillment_mode + 成本模型)
|
||||
统一履约事实(渠道 + 成本模型)
|
||||
-> 商品主数据 + 渠道SKU映射
|
||||
-> 成本中心(cost_items / cost_versions / cost_snapshots)
|
||||
-> 销售事实(sales_daily_facts)
|
||||
|
||||
@@ -254,7 +254,7 @@ FROM (
|
||||
SELECT count(*) AS v
|
||||
FROM sales_daily_facts
|
||||
WHERE stat_date >= current_date - 6
|
||||
AND fulfillment_mode <> 'manual'
|
||||
AND channel <> 'manual'
|
||||
AND cost_missing_count > 0
|
||||
) s;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user