docs(finance): 文档口径切到渠道模型并标注历史文档

- API 契约/成本中心模型改为 fulfillment_channel/status 与 channel 字段
- 验收 SQL 的销售事实检查改用 channel
- 2026-10 之前的口径文档加历史横幅,避免被当成现行实现
This commit is contained in:
yml2213
2026-10-31 20:26:20 +08:00
parent 6cd6401eca
commit fef1d0b77e
9 changed files with 98 additions and 102 deletions
+4 -4
View File
@@ -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 |
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -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;