- 全量替换文档里的旧迁移编号:106→107、107→105、108/109/111→104、112→106、110→107;
- 删除 113/114 的残留引用(这两个编号从未落地);
- 分支状态校准为 `dev @ 465a9f65 + 迁移收敛`,本地库按「清 103–112 记录后重跑」对齐;
- 成本中心与履约模型 §一 重写迁移基线表,§八 改为迁移基线与本地库同步;
- 归档原地改造脚本的索引补登记。
305 lines
17 KiB
Markdown
305 lines
17 KiB
Markdown
# 财务与履约口径总纲(2026-10-23 重写版)
|
||
|
||
> 状态:**现行唯一口径**。本文是财务、成本、履约分类的总入口;其他文档与本文件冲突时,以本文为准。
|
||
> 最后校准:2026-10-24
|
||
> 代码基线:`dev @ 790de676 + 本地未提交修复`(**未推送、未部署生产**)
|
||
> 数据基线:生产 dump `order_site-20261022-092601.dump`(2026-07-10 ~ 2026-10-22);本地已升级到迁移 107,并完成履约方式、商品、成本快照与销售事实初始化
|
||
> 适用范围:订单接入、履约判定、商品归一、成本快照、销售事实、人工录单、备用金、后续总账与对账。
|
||
|
||
---
|
||
|
||
## 〇、八条硬口径
|
||
|
||
1. **电子凭证不是履约方式,也不是成本类型。** 快手电子凭证只是核销码/兑换码,抖音 DYC 券(`redeem_certificates`)同理;一个有核销码的 91/快手或抖音订单,仍可能走自动发货、接单平台或人工。
|
||
2. **旧平台通道已经下线。** `kuaishou_ct_assisted`、`kuaishou_feifei` 只保留历史数据,报表中统一标记为 `legacy_platform`,不得混入人工。
|
||
3. **人工的定义只有一个:** 已支付订单项,没有工单、没有自动发货任务、没有历史平台任务,即为人工;有没有核销码不影响判定。
|
||
4. **人工成本逐单、按实际金额。** 不按小时、不按固定单量、不按固定单价;必须从客服备用金账户扣款,客服只能使用本人账户。
|
||
5. **人工成本只走「人工录单 → 扣备用金 → 写成本快照」一条路。** 没有登记单、没有审批、没有报销环节,录入即计入毛利。
|
||
6. **历史人工支出找不回来,直接放弃。** 历史缺口统一标记为"历史未采集",不补录、不做样本回填;只保证上线后的新单不丢。
|
||
7. **上线后人工账单必须 100% 可监控。** 每一笔人工单必须有处理人、实际金额、备用金支出与成本快照;缺失即进入异常队列,不允许关账。
|
||
8. **履约方式只有一个真源。** `order_items.fulfillment_mode` 是唯一事实来源,商品表、成本项表、快照表、异常表、销售事实表都不再各自存一份"载体"。
|
||
|
||
---
|
||
|
||
## 一、三个正交维度
|
||
|
||
系统过去把"怎么交付"和"成本怎么算"混在同一个 `carrier_type` 里,导致同一订单在订单页、成本快照、销售事实中出现不同分类。正确模型拆成三个正交维度:
|
||
|
||
| 维度 | 取值 | 回答的问题 | 存储位置 |
|
||
| --- | --- | --- | --- |
|
||
| `fulfillment_mode` | `work_order` / `affiliate_dash` / `legacy_platform` / `manual` | 这单最终由谁执行 | `order_items.fulfillment_mode`(唯一真源) |
|
||
| `voucher_state` | 无 / 已发码 / 已核销 / 已销毁 | 有没有核销码、走到哪一步 | 订单读取时单独计算 |
|
||
| `cost_model` | `affiliate_purchase` / `work_order_reward` / `manual_actual` / `platform_fee` / `payment_fee` / `after_sales_loss` / `other` | 这单成本怎么算 | `cost_items.cost_model` |
|
||
|
||
硬规则:
|
||
|
||
- `voucher_state` **不参与** `fulfillment_mode` 判定;
|
||
- `fulfillment_mode` 必须在履约决策时写入 `order_items`,不能每次查询临时推断;
|
||
- 成本快照、销售事实、后台订单页只读同一份 `fulfillment_mode`;
|
||
- 一个订单项只能有一个 `fulfillment_mode`,核销码可以有多个状态事件;
|
||
- **商品来源不由履约方式推导**:商品的来源由 `canonical_sku` 前缀表达(`aff:` / `tpl:` / `wo:` / `man:`),不能反推履约方式。
|
||
|
||
---
|
||
|
||
## 二、当前真实履约构成(本地库,2026-07-10 ~ 2026-10-22)
|
||
|
||
| 履约方式 | 订单项 | 收入 | 当前成本情况 |
|
||
| --- | ---: | ---: | --- |
|
||
| 接单平台 `work_order` | 153,148 | ¥680.27 万 | 151,939 条快照,成本 ¥572.95 万;1,209 项缺成本(取消/0 报酬等) |
|
||
| 自动发货 `affiliate_dash` | 53,204 | ¥627.20 万 | 商品已归一;上游采购成本版本未配置,全部进入 `cost_missing` |
|
||
| 历史平台 `legacy_platform` | 53,901 | ¥389.12 万 | 已下线通道,历史成本未采集,商品与成本均保持缺口 |
|
||
| **人工 `manual`** | **115,194** | **¥1,447.01 万** | 商品已归一为 1,195 个 `man:*`;历史实际支出未采集,全部进入 `cost_missing` |
|
||
| 合计 | 375,447 | ¥3,143.61 万 | 成本 ¥572.95 万,毛利 ¥2,570.66 万 |
|
||
|
||
平台分布:91kaquan 368,343 项 / ¥3,023.996 万;pdd 5,563 项 / ¥105.051 万;douyin_mashida 1,541 项 / ¥14.560 万(2026-10-12 ~ 2026-10-22)。
|
||
|
||
> **商品前缀只表示归一来源,不反推履约方式。** `tpl:` 710 个接单模板商品、`man:` 1,195 个人工内部 SKU、`aff:` 49 个自动发货 SKU、`wo:` 63 个工单长尾商品;渠道映射共 4,897 条(接单模板 4,748 + 工单长尾 149),自动发货与人工不写渠道映射。
|
||
|
||
成本缺口口径:`cost_missing` 共 223,508 项,其中人工 115,194、自动发货 53,204、历史平台 53,901、接单平台 1,209。人工与历史平台属于明确不补录的历史缺口;自动发货需配置上游采购成本版本;工单 1,209 项主要是取消/0 报酬,不属于商品未归一。
|
||
|
||
异常队列:`product_mapping_missing` 仅剩历史平台 53,901 条,属于已下线通道的预期缺口;工单、自动发货、人工的商品归一均为 0 缺失。
|
||
|
||
---
|
||
|
||
## 三、成本模型规则
|
||
|
||
| 成本模型 | 适用履约方式 | 成本来源 | 现状 |
|
||
| --- | --- | --- | --- |
|
||
| `affiliate_purchase` | 自动发货 | 上游 SKU 固定成本版本 | 49 个商品,本次快照恢复未配置采购成本版本 |
|
||
| `work_order_reward` | 接单平台 | 模板规则级版本 / 工单实际报酬 | 710 个规则版本;1,209 项取消/0 报酬缺口 |
|
||
| `manual_actual` | 人工 | 订单页人工录单的实际支出(直接扣备用金) | 已实现,历史未采集 |
|
||
| `platform_fee` / `payment_fee` / `after_sales_loss` / `other` | 全部履约方式 | 通用成本项 | 未配置 |
|
||
|
||
硬规则:
|
||
|
||
- 未确认成本时保持 `cost_missing`,**绝不默认记 0**;
|
||
- 人工成本只走 `sys_manual_service`,由订单页「人工录单」写 `actual` 快照(主体 = `manual_expenses.id`);
|
||
- 自动发货、接单平台继续使用现有成本版本与规则版本;
|
||
- 电子凭证不建成本项,也不建"凭证载体"成本;
|
||
- 计价方式只保留三种:`per_item`(按件)、`per_order`(按单)、`percent_of_revenue`(按收入比例)。**已删除 `per_hour` / `fixed_daily`**——人工成本逐单按实际金额,不按工时;
|
||
- 历史平台成本单独标记,不混入人工成本。
|
||
|
||
---
|
||
|
||
## 四、上线后人工账单全覆盖规则
|
||
|
||
### 4.1 判定
|
||
|
||
每个已支付订单项按以下顺序判定,命中即停止:
|
||
|
||
```text
|
||
有 work_orders -> work_order
|
||
有 affiliate_dash 任务 -> affiliate_dash
|
||
有 ct_assisted/feifei -> legacy_platform
|
||
其余 -> manual
|
||
```
|
||
|
||
核销码只写入 `voucher_state`,不改变上述结果。
|
||
|
||
### 4.2 人工单必须有的数据
|
||
|
||
每一笔 `manual` 订单项必须具备:
|
||
|
||
- 处理人(客服/操作人);
|
||
- 实际付款金额(分);
|
||
- 费用项目;
|
||
- 收款方与付款方式;
|
||
- 备用金账户与备用金支出单(`manual_expenses.id`);
|
||
- 凭证或备注;
|
||
- 成本快照(`manual_case` / `actual`)。
|
||
|
||
录入路径只有一条:订单页「人工录单」→ 扣客服备用金 → 写 `actual` 快照。**没有单独的登记单,也没有审批与报销环节**;钱与成本在同一次操作里落地。
|
||
|
||
### 4.3 监控指标(上线后每日检查)
|
||
|
||
| 指标 | 目标 |
|
||
| --- | ---: |
|
||
| 已支付订单项无 `fulfillment_mode` | 0 |
|
||
| 人工订单项无备用金支出单 | 0 |
|
||
| 人工订单项无成本快照 | 0 |
|
||
| 人工支出金额与备用金流水不一致 | 0 |
|
||
| 成本异常 open 中人工项 | 0(历史标记除外) |
|
||
|
||
### 4.4 关账门槛
|
||
|
||
- 存在人工单无成本记录:**不允许关账**;
|
||
- 存在人工单未扣备用金:**不允许关账**;
|
||
- 存在人工支出与备用金流水金额不一致:**不允许关账**。
|
||
|
||
---
|
||
|
||
## 五、历史数据处理
|
||
|
||
- 历史人工支出**找不回来,直接放弃**:不补录、不做样本回填、不做均价估算;
|
||
- 历史人工单在销售事实中保留收入,成本标记为 `historical_missing`,不计入"上线后监控缺口";
|
||
- 历史平台 `legacy_platform` 单独成行,成本未采集的标记 `legacy_missing`;
|
||
- 自动发货、接单平台历史成本不回写、不重算版本,沿用现有快照。
|
||
|
||
---
|
||
|
||
## 六、实施顺序
|
||
|
||
| 阶段 | 内容 | 状态 |
|
||
| --- | --- | --- |
|
||
| P0 | 统一履约事实(`fulfillment_mode`)+ 成本模型正交 | **已完成(本地,未部署生产)** |
|
||
| P0.5 | 人工成本链路收敛为「人工录单 → 扣备用金 → 写快照」(下线登记/审批/报销) | **已完成(本地,未部署生产)** |
|
||
| P1 | 人工商品归一(`man:<skuId>`)+ 上线后人工成本全覆盖 | **已完成(本地,未部署生产)** |
|
||
| P2 | 退款冲减与异常闭环 | **已完成(手动登记先行,平台退款 API 待接入)** |
|
||
| P3 | 影子总账与对账 | 未实施 |
|
||
|
||
P0 的具体迁移、回填、代码清单与验收 SQL 见 [成本中心与履约模型](成本中心与履约模型.md);接口契约见 [成本中心API契约](成本中心API契约.md)。
|
||
|
||
> 下一步只做 P3,不要同时改总账与平台对接。
|
||
|
||
---
|
||
|
||
## 七、当前实施进度(2026-10-24 更新)
|
||
|
||
| 能力 | 状态 | 证据 |
|
||
| --- | --- | --- |
|
||
| 订单页人工录单,按单实际金额 | **已实施** | `74b62f34` |
|
||
| 人工支出从客服备用金扣款 | **已实施** | `2aeb789e` |
|
||
| 客服只能使用本人备用金账户 | **已实施** | `a7f60414` |
|
||
| 备用金账户编号 `FA-<用户名>` | **已实施** | `fac34c84` + 迁移 `107_manual_fulfillment` |
|
||
| 订单页只展示平台判断 SKU | **已实施** | `c38cd8ca`、`cd9bb141` |
|
||
| `order_items.fulfillment_mode` 持久化 | **已实施** | 迁移 `105` + 统一决策服务 |
|
||
| 订单页 / 成本快照 / 销售事实统一读字段 | **已实施** | `admin-order-read-helpers`、`sales-facts-service`、`cost-snapshot-sync-service` |
|
||
| 本地 316,585 项回填 | **已实施** | `fulfillment:backfill`,四项计数与 §二 一致 |
|
||
| 成本模型与履约方式正交(去 `carrier_type`) | **已实施(本地库已改造)** | 迁移 `103`–`107` 重写;`cost_items.cost_model` |
|
||
| 10 个迁移收敛为 5 个 | **已实施** | `103_product_catalog` ~ `107_manual_fulfillment` |
|
||
| 人工成本链路收敛为「人工录单 → 扣备用金 → 写快照」 | **已实施(本地库已改造)** | 迁移 `104`;登记单与审批报销已下线,口径固化在成本中心迁移 |
|
||
| 人工商品归一 `man:<skuId>` | **已实施(本地库已归一)** | `manual-product-normalization-service` + `POST /cost-center/manual-products/normalize`;1,116 个 `man:` 商品、91,606 个人工订单项归一,人工未归一 = 0 |
|
||
| 人工商品归一后的销售事实 | **已实施** | 94 天分 30 天窗口重算:26,507 行;人工 ¥1,219.20 万收入全部按 `man:` 商品归集 |
|
||
| 退款冲减、异常闭环 | **已实施(手动登记先行)** | 迁移 `106_order_refunds`;订单页「退款登记」(整单/指定商品、可撤销);销售事实按有效退款冲减;全额退完后自动关闭该订单项成本异常并写 `resolution_note` |
|
||
| 平台退款 API 接入 | **未实施** | 预留 `order_refunds.source='api'/'platform'` 与 `external_ref` |
|
||
| 影子总账、对账、关账 | **未实施** | §六 P3 |
|
||
|
||
> 历史平台仍有 53,901 个订单项(¥389.12 万)未归一,不属于 P1 范围;人工历史成本按既定口径不补录,因此人工毛利当前是“收入已归商品、成本仅新单有”的状态。
|
||
> 分支事实:以上实现已快进合并到 `dev`;迁移 `103`–`107` **未推送远端、未部署生产**。生产口径仍以 `main` 为准。
|
||
|
||
---
|
||
|
||
## 八、上线验收查询
|
||
|
||
> 以下 SQL 在 `fulfillment_mode` 落地后使用;`DATE '2026-11-01'` 替换为实际上线日。
|
||
|
||
```sql
|
||
-- 1. 履约方式覆盖:四项相加应等于订单项总数
|
||
SELECT fulfillment_mode, COUNT(*)
|
||
FROM order_items
|
||
GROUP BY fulfillment_mode
|
||
ORDER BY 2 DESC;
|
||
|
||
-- 2. 上线后超过 24 小时仍未分类:应为 0
|
||
SELECT COUNT(*)
|
||
FROM order_items oi
|
||
JOIN orders o ON o.id = oi.order_id
|
||
WHERE oi.fulfillment_mode IS NULL
|
||
AND COALESCE(o.paid_at, o.created_at) < now() - interval '24 hours'
|
||
AND COALESCE(o.paid_at, o.created_at) >= DATE '2026-11-01';
|
||
|
||
-- 3. 人工单无成本:上线后应为 0
|
||
SELECT COUNT(*)
|
||
FROM order_items oi
|
||
JOIN orders o ON o.id = oi.order_id
|
||
WHERE oi.fulfillment_mode = 'manual'
|
||
AND COALESCE(o.paid_at, o.created_at) >= DATE '2026-11-01'
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM cost_snapshots cs
|
||
WHERE cs.order_item_id = oi.id AND cs.superseded_at IS NULL
|
||
);
|
||
|
||
-- 4. 人工订单项没有备用金支出单:上线后应为 0
|
||
SELECT COUNT(*)
|
||
FROM order_items oi
|
||
WHERE oi.fulfillment_mode = 'manual'
|
||
AND oi.created_at >= DATE '2026-11-01'
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM manual_expenses me
|
||
WHERE me.order_item_id = oi.id
|
||
);
|
||
|
||
-- 5. 人工成本快照金额与备用金支出金额不一致:应为 0
|
||
SELECT cs.id, me.expense_no, me.amount_fen, cs.total_amount_fen
|
||
FROM cost_snapshots cs
|
||
JOIN manual_expenses me ON me.id = cs.subject_id
|
||
WHERE cs.subject_type = 'manual_case'
|
||
AND cs.superseded_at IS NULL
|
||
AND cs.total_amount_fen <> me.amount_fen
|
||
AND me.created_at >= DATE '2026-11-01';
|
||
|
||
-- 6. 人工单未归一商品:P1 完成后应为 0
|
||
SELECT COUNT(*)
|
||
FROM order_items oi
|
||
WHERE oi.fulfillment_mode = 'manual'
|
||
AND oi.canonical_product_id IS NULL
|
||
AND oi.created_at >= DATE '2026-11-01';
|
||
|
||
-- 7. 核销码不得改变履约方式:带快手凭证的 91 人工单仍应为 manual
|
||
SELECT oi.fulfillment_mode, COUNT(*)
|
||
FROM order_items oi
|
||
JOIN orders o ON o.id = oi.order_id
|
||
WHERE o.provider = '91kaquan'
|
||
AND EXISTS (
|
||
SELECT 1 FROM kuaishou_industry_vouchers v
|
||
WHERE v.oid = o.platform_order_id
|
||
)
|
||
GROUP BY oi.fulfillment_mode;
|
||
|
||
-- 8. 历史平台不得混入人工:应为 0
|
||
SELECT COUNT(*)
|
||
FROM order_items oi
|
||
WHERE oi.fulfillment_mode = 'manual'
|
||
AND EXISTS (
|
||
SELECT 1 FROM fulfillment_tasks ft
|
||
WHERE ft.order_item_id = oi.id
|
||
AND ft.executor_key IN ('kuaishou_ct_assisted', 'kuaishou_feifei')
|
||
);
|
||
|
||
-- 9. 人工项误挂接单模板商品:P1 完成后应为 0
|
||
SELECT COUNT(*)
|
||
FROM order_items oi
|
||
JOIN product_catalog pc ON pc.id = oi.canonical_product_id
|
||
WHERE oi.fulfillment_mode = 'manual'
|
||
AND pc.canonical_sku LIKE 'tpl:%';
|
||
```
|
||
|
||
---
|
||
|
||
## 九、常见错误(防回归)
|
||
|
||
| # | 错误 | 后果 | 正确做法 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | 把"有快手核销码"当成 `voucher_only` | 91 人工单全部从订单页消失 | 核销码只做状态展示 |
|
||
| 2 | 按 `provider=91kaquan/douyin_mashida` 直接判自动发货 | 人工单被藏进自动发货 | 只看工单/任务/历史任务 |
|
||
| 3 | 用 `product_catalog` 的来源反推履约方式 | 11,349 个人工项被误认为接单平台 | 只读 `order_items.fulfillment_mode` |
|
||
| 4 | 在商品/成本项/快照/异常四张表各存一份载体 | 四处口径分叉 | 载体只存 `order_items.fulfillment_mode` |
|
||
| 5 | 只回填已支付历史单,不做增量兜底 | 新单长期停在 `NULL` | 24 小时定时归集 + 异常队列 |
|
||
| 6 | 把"人工 91,606"当成"人工全量" | 与 102,955 对不上,报表互相矛盾 | 写清"全量 102,955 / 未归一 91,606" |
|
||
| 7 | 给人工成本再造一张「登记单 + 审批 + 报销」表 | 两套资金语义混用,客服和财务都要录两遍 | 人工录单直接扣备用金并写快照,`manual_expenses` 是唯一资金台账 |
|
||
| 8 | 用均价、工时、固定单价补历史人工 | 违背逐单实际金额,毛利失真 | 历史不补录,只做样本区间 |
|
||
| 9 | 只改订单页,不改成本/销售事实 | 三处口径继续分叉 | 同一字段、同一决策入口 |
|
||
| 10 | 在旧文档里保留"三块/按小时/公司直付"旧结论 | 下次开发又按旧口径实现 | 旧文只留追溯标记,现行结论只写本文件 |
|
||
|
||
---
|
||
|
||
## 十、文档地图与维护规则
|
||
|
||
| 文档 | 唯一职责 | 状态 |
|
||
| --- | --- | --- |
|
||
| **本文件** | 履约/成本/人工口径、实施顺序、验收 SQL | **现行唯一口径** |
|
||
| [成本中心与履约模型.md](成本中心与履约模型.md) | 商品、成本版本、快照、销售事实的数据模型与迁移 | 现行 |
|
||
| [成本中心API契约.md](成本中心API契约.md) | 成本中心后端/前端接口字段与模块边界 | 现行 |
|
||
| [人工支出与备用金.md](人工支出与备用金.md) | 人工录单、客服备用金台账与权限 | 现行(P0 已实施) |
|
||
| [财务系统集成方案.md](财务系统集成方案.md) | `finance_*` 复式总账层(未实施) | 方案 |
|
||
| [README.md](README.md) | 文档索引、分支与进度 | 现行入口 |
|
||
|
||
维护规则:
|
||
|
||
1. 履约分类只在本文定义;其他文档只链接,不复制判定 SQL。
|
||
2. 任何数字必须注明口径:全量 / 已归一 / 未归一 / 上线后。
|
||
3. 每次迁移后更新本文的代码基线、数据基线、§七 进度和 §二 数字。
|
||
4. 新文档必须登记到 `README.md`;过期文档移入 `docs/archive/`,不得在现行文档里保留冲突结论。
|
||
5. 不再使用"三块履约"作为分类名。
|
||
6. 代码评审必须检查:订单页、`sales-facts-service`、`cost-snapshot-sync-service` 是否都读同一字段。
|