docs(finance): 现行财务文档收敛为 5 篇,履约口径只定义一次
原 9 篇 3,647 行,履约分类口径在总纲、模型文档、交接总结里重复三遍, 且仍留着"三块履约""按小时人工成本"等已作废表述。本次收敛为 5 篇 1,778 行: - 财务与履约口径总纲-2026-10-23.md(300 行):唯一口径来源 七条硬口径、三个正交维度、真实履约构成、成本模型规则、人工全覆盖、 实施顺序、进度、验收 SQL、防回归清单 - 成本中心与履约模型.md(471 行):数据模型、迁移 103–107、履约回填、 销售事实、本地库原地改造、索引与性能 - 成本中心API契约.md(270 行):字段替换总表、枚举、前后端模块边界 - 人工支出与备用金.md(218 行):人工支出登记字段/状态机/权限 + 客服备用金台账 - 财务系统集成方案.md:复式总账层(未实施) 旧的 4 篇(成本中心与三块履约模型、人工支出登记落地方案、 客服备用金台账实施方案、财务履约改造交接总结)移入 docs/archive/2026-10-23/ 并加归档提示。 履约分类判定 SQL 只在总纲保留一份,其它文档改为链接。
This commit is contained in:
+21
-21
@@ -1,7 +1,7 @@
|
||||
# order_site 文档索引
|
||||
|
||||
> 最后校准:2026-10-23,代码基线 `dev @ 5d1ba115 + 迁移 103–114`。
|
||||
> 本文是 `docs/` 的唯一入口。**财务与履约口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准**;接手改造先读 [财务履约改造交接总结-2026-10-23.md](财务履约改造交接总结-2026-10-23.md),本文只做索引与速查。
|
||||
> 最后校准:2026-10-23,代码基线 `feat/finance-rewrite @ 7557de6d + 迁移 103–107`。
|
||||
> 本文是 `docs/` 的唯一入口。**财务与履约口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准**;数据模型看 [成本中心与履约模型.md](成本中心与履约模型.md),接口看 [成本中心API契约.md](成本中心API契约.md)。
|
||||
> 新增文档必须在下表登记状态;过期文档不再直接改写结论,统一移入 `docs/archive/`。
|
||||
|
||||
## 一、先记住六条口径
|
||||
@@ -48,7 +48,7 @@
|
||||
| 名称 | 业务含义 | 当前状态 |
|
||||
| --- | --- | --- |
|
||||
| `manual_expenses` | **客服备用金支出明细**:客服从自己的备用金里充值/采购/赔偿,减少客服余额 | 已实施(迁移 100) |
|
||||
| `manual_expense_records` | **公司人工支出/垫付登记**:按订单/成本项记录公司实际付出去的钱、付款人、收款人、凭证、审批 | 已实施 P0(迁移 111) |
|
||||
| `manual_expense_records` | **公司人工支出/垫付登记**:按订单/成本项记录公司实际付出去的钱、付款人、收款人、凭证、审批 | 已实施 P0(迁移 106) |
|
||||
|
||||
后续做财务系统时,**不要把 `manual_expenses` 直接改名或复用为 `manual_expense_records`**。两者业务含义、审批链和报表口径不同,必须通过 `fund_expense_id` 关联;新人工支出登记会先扣备用金,再写人工支出记录。
|
||||
|
||||
@@ -71,24 +71,24 @@
|
||||
|
||||
| 模块 | 状态 | 事实 |
|
||||
| --- | --- | --- |
|
||||
| 客服备用金台账 | **已实施 P0** | 迁移 `100/101/113`;账户、流水、支出、对账;编号 `FA-<用户名>`;客服只能使用本人账户 |
|
||||
| 客服备用金台账 | **已实施 P0** | 迁移 `100/101/106`;账户、流水、支出、对账;编号 `FA-<用户名>`;客服只能使用本人账户 |
|
||||
| 后台用户中文显示名 | **已实施** | `admin_users.display_name`,备用金姓名触发器同步;审计、登录记录、任务处理人优先显示中文名 |
|
||||
| 商品主数据 + 渠道 SKU 映射 | **已实施 P0(dev 分支)** | 745 个标准商品、4,426 条映射;只覆盖自动发货 38 个 + 接单平台 707 个。人工 11,349 项虽已归一但全部挂 `tpl:*` 模板商品,需在 P1 复核;人工/历史平台专用商品尚未建立 |
|
||||
| 成本中心 | **已实施 P0(dev 分支)** | 迁移 `104/105/107/108/110/112`;38 个商品级版本 + 662 个规则级版本;人工走 `sys_manual_service` 实际快照 |
|
||||
| 销售事实 / 订单毛利 | **已实施 P0(dev 分支)** | 迁移 `109`;94 天事实收入 ¥2,650.87 万、成本 ¥950.27 万;四种履约方式已统一读 `fulfillment_mode` |
|
||||
| 公司人工支出登记 | **已实施 P0(dev 分支)** | 迁移 `111`;登记时强制扣客服备用金并写 `fund_expense_id`;客服只能使用本人账户 |
|
||||
| 履约方式统一 | **已实施 P0(dev 分支)** | 迁移 `114`;316,585 项已回填;订单页、成本快照、销售事实只读 `fulfillment_mode`;人工项带核销码仍显示人工 |
|
||||
| 商品主数据 + 渠道 SKU 映射 | **已实施 P0** | 745 个标准商品、4,426 条映射;只覆盖自动发货 38 个 + 接单平台 707 个。人工 11,349 项虽已归一但全部挂 `tpl:*` 模板商品,需在 P1 复核;人工专用商品尚未建立 |
|
||||
| 成本中心 | **已实施 P0** | 迁移 `104`;`cost_items.cost_model` 取代 `cost_type + carrier_type`;38 个商品级版本 + 662 个规则级版本;人工走 `sys_manual_service` 实际快照 |
|
||||
| 销售事实 / 订单毛利 | **已实施 P0** | 迁移 `105`;94 天事实收入 ¥2,650.87 万、成本 ¥950.27 万;`sales_daily_facts.fulfillment_mode` |
|
||||
| 公司人工支出登记 | **已实施 P0** | 迁移 `106`;登记时强制扣客服备用金并写 `fund_expense_id`;客服只能使用本人账户 |
|
||||
| 履约方式统一 | **已实施 P0** | 迁移 `107`;316,585 项已回填,四项计数与总纲 §二 一致;订单页、成本快照、销售事实只读 `fulfillment_mode` |
|
||||
| 人工商品归一 | **未实施(P1)** | 人工单按 `spec.skuId` 建 `man:<skuId>`;历史 11,349 项 `tpl:*` 商品待复核 |
|
||||
| 影子总账 | 未实施 | 现有财务页仍是 `UNION ALL` 业务事件拼装,不是复式总账 |
|
||||
| 退款冲减 / 平台对账 / 期间关账 | 未实施 | 仅方案 |
|
||||
|
||||
> 分支状态(2026-10-23):`dev` 领先 `main` 20+ 个提交,迁移 `103–114` 尚未合并、未部署生产;生产口径仍以 `main` 为准。
|
||||
> 分支状态(2026-10-23):重写分支 `feat/finance-rewrite` 把原 `103–114` 十二个迁移合并为 `103–107` 五个;本地库已按新 schema 原地改造并重跑回填,**尚未合并到 `dev`、未部署生产**;生产口径仍以 `main` 为准。
|
||||
> 财务与履约口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准。
|
||||
|
||||
## 三、统一实施顺序
|
||||
|
||||
```text
|
||||
0. 统一履约事实(fulfillment_mode + 成本模型) 已完成 P0(迁移 114)
|
||||
0. 统一履约事实(fulfillment_mode + 成本模型正交) 已完成 P0(迁移 107)
|
||||
1. 商品主数据 + 渠道 SKU 映射 已完成 P0(仅自动发货/接单平台)
|
||||
2. 成本中心 + 销售事实 已完成 P0
|
||||
3. 人工商品归一与上线后人工成本全覆盖 未实施(下一阶段)
|
||||
@@ -97,7 +97,7 @@
|
||||
6. 平台对账、报表、期间关账 未实施
|
||||
```
|
||||
|
||||
**第 0 步已完成。** 迁移、回填、代码改造与验收结果见 [总纲 §八](财务与履约口径总纲-2026-10-23.md#八p0-实施记录统一履约事实)。下一步只做第 3 步人工商品归一,不要同时改退款或总账。
|
||||
**第 0 步已完成。** 迁移、回填、代码改造与验收结果见 [成本中心与履约模型](成本中心与履约模型.md) §六、§八。下一步只做第 3 步人工商品归一,不要同时改退款或总账。
|
||||
|
||||
## 四、当前必须拍板
|
||||
|
||||
@@ -118,14 +118,13 @@
|
||||
|
||||
| 文档 | 状态 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) | **现行唯一口径** | 履约方式、成本模型、人工全覆盖、历史策略、P0 迁移/回填/代码清单、运营日检与验收 SQL |
|
||||
| [财务履约改造交接总结-2026-10-23.md](财务履约改造交接总结-2026-10-23.md) | **交接摘要** | 本次改了什么、关键数字、下一步 P0、运营硬要求、验证命令与阅读顺序 |
|
||||
| [项目全量分析报告-2026-10-17.md](项目全量分析报告-2026-10-17.md) | 历史参考 | 2026-10-17 架构与风险;财务口径以总纲为准 |
|
||||
| [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) | **现行唯一口径** | 履约方式、成本模型、人工全覆盖、历史策略、实施顺序、运营日检与验收 SQL |
|
||||
| [成本中心与履约模型.md](成本中心与履约模型.md) | 现行 | 商品、成本版本、快照、异常、销售事实的数据模型;迁移 `103–107`;履约回填;本地库原地改造;索引与性能 |
|
||||
| [成本中心API契约.md](成本中心API契约.md) | 现行 | 成本中心接口字段替换总表、枚举、模块边界(后端 5 模块 + 前端 8 面板) |
|
||||
| [人工支出与备用金.md](人工支出与备用金.md) | 现行(P0 已实施) | 人工支出登记字段/状态机/权限、客服备用金台账、本人账户与编号规则 |
|
||||
| [财务系统集成方案.md](财务系统集成方案.md) | 现行方案(总账未实施) | 复式记账、总账表、对账、关账 |
|
||||
| [成本中心与履约方式模型.md](成本中心与三块履约模型.md) | 现行(文件名保留) | 商品、成本快照、销售事实;履约分类以总纲为准 |
|
||||
| [人工支出登记落地方案-2026-10-17.md](人工支出登记落地方案-2026-10-17.md) | 现行(P0 已实施) | 人工支出字段、入口、状态机、权限、备用金衔接 |
|
||||
| [影子总账说明.md](影子总账说明.md) | 现行说明(未实施) | 影子总账是什么、和钱包流水的区别、对账与转正标准 |
|
||||
| [客服备用金台账实施方案-2026-10-18.md](客服备用金台账实施方案-2026-10-18.md) | **已实施 P0** | 备用金账户、流水、支出、对账、本人账户与编号规则 |
|
||||
| [项目全量分析报告-2026-10-17.md](项目全量分析报告-2026-10-17.md) | 历史参考 | 2026-10-17 架构与风险;财务口径以总纲为准 |
|
||||
| [接单模板匹配引擎重构设计.md](接单模板匹配引擎重构设计.md) | 现行(P0/P1 已实施) | `kuaishou-91 / pdd / douyin-mashida` 三来源匹配引擎 |
|
||||
| [履约配置/多发货平台与电子凭证整体链路.md](履约配置/多发货平台与电子凭证整体链路.md) | 现行 | 进单、履约、领取、电子凭证的整体链路 |
|
||||
| [affiliate-dash-发货平台/affiliate-dash对接.md](affiliate-dash-发货平台/affiliate-dash对接.md) | 现行 | affiliate-dash 自动履约接口与字段 |
|
||||
@@ -143,9 +142,9 @@
|
||||
|
||||
| 文件 | 用途 | 接入前必须处理 |
|
||||
| --- | --- | --- |
|
||||
| [drafts/2026-10-17-载体判定与人工块量化.sql](drafts/2026-10-17-载体判定与人工块量化.sql) | 载体分布/人工块量化查询、`carrier_type` 判定与回填 | 只读查询可直接用;DDL 部分按当前迁移号顺延,不占用 100/101 |
|
||||
| [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–109`);系统成本项在 `110`;草案仅作设计追溯,不再作为建表依据 |
|
||||
| [drafts/2026-10-17-人工支出登记-DDL草案.sql](drafts/2026-10-17-人工支出登记-DDL草案.sql) | `manual_fulfillment_cases`、`manual_expense_records`、`manual_expense_events` | 人工支出登记已落地为 `111`;轻量人工 case 仍为草案 |
|
||||
| [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) | `manual_fulfillment_cases`、`manual_expense_records`、`manual_expense_events` | 人工支出登记已落地为 `106`;轻量人工 case 仍为草案 |
|
||||
|
||||
## 六、厂商协议与外部资料
|
||||
|
||||
@@ -165,6 +164,7 @@
|
||||
| --- | --- |
|
||||
| [archive/README.md](archive/README.md) | 2026-10-08 及更早的阶段分析、寄卖方案、性能快照、一次性迁移检查脚本;2026-10-17 复核文档 |
|
||||
| [archive/2026-10-17/载体判定与成本中心落地-复核与草案.md](archive/2026-10-17/载体判定与成本中心落地-复核与草案.md) | 2026-10-17 第二轮复核与数据量化;结论已吸收进现行成本中心文档,仅作历史追溯 |
|
||||
| [archive/2026-10-23/](archive/2026-10-23/) | 2026-10-23 重写前的四篇:`成本中心与三块履约模型`、`人工支出登记落地方案`、`客服备用金台账实施方案`、`财务履约改造交接总结`;结论已分别并入现行 5 篇文档 |
|
||||
|
||||
## 八、维护规则
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 载体判定与成本中心落地:复核与草案(2026-10-17 第二轮)
|
||||
|
||||
> 状态:**历史(2026-10-17 第二轮复核,已归档)**;基线:`main @ ccd5ed2a`;代码未改动。
|
||||
> 说明:本文的现行结论已吸收进 [成本中心与三块履约模型.md](../../成本中心与三块履约模型.md) 与 [财务系统集成方案.md](../../财务系统集成方案.md);本文件只用于追溯 2026-10-17 的数据量化与 DDL 验证过程,不再作为实现依据。
|
||||
> 说明:本文的现行结论已吸收进 [成本中心与三块履约模型.md](../../成本中心与履约模型.md) 与 [财务系统集成方案.md](../../财务系统集成方案.md);本文件只用于追溯 2026-10-17 的数据量化与 DDL 验证过程,不再作为实现依据。
|
||||
> 数据来源:**本地只读克隆**(生产快照,`orders` 316,597 行,数据截至 2026-10-14 14:52),全程只读 SELECT,未连生产、未写入任何业务库。
|
||||
> 金额单位:**分**(`orders.total_amount` / `work_orders.reward_amount` / `source_order_amount_fen` 均为分)。
|
||||
> 上游文档:[成本中心与三块履约模型.md](../../成本中心与三块履约模型.md)、[项目全量分析报告-2026-10-17.md](../../项目全量分析报告-2026-10-17.md)。
|
||||
> 上游文档:[成本中心与三块履约模型.md](../../成本中心与履约模型.md)、[项目全量分析报告-2026-10-17.md](../../项目全量分析报告-2026-10-17.md)。
|
||||
|
||||
本轮任务是把上一轮「只分析」的结论**复核到底、量化到数、落成可执行草案**。三件事:① 提交并推送待提交文档;② 收敛本机生产备份暴露面;③ 用只读 SQL 量化载体分布与人工块,并产出载体判定 + 成本中心两份 SQL 草案。
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# 人工支出登记落地方案(2026-10-18 修订)
|
||||
|
||||
> **归档说明(2026-10-23):** 本文是重写前的版本,仅作历史追溯,**不得作为实现依据**。结论已并入现行文档,见 [文档索引](../README.md)。
|
||||
|
||||
> 状态:**已实施 P0(`dev` 分支,迁移 111;订单页人工录单、备用金扣款为 2026-10-22/23 增量)**
|
||||
> 最后校准:2026-10-23
|
||||
> 代码基线:`dev @ 5d1ba115 + 迁移 103–114`
|
||||
> **总口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准。**
|
||||
> **总口径以 [财务与履约口径总纲-2026-10-23.md](../../财务与履约口径总纲-2026-10-23.md) 为准。**
|
||||
> 依据:业务口径「人工块当前不以 SLA 监管为目标,先把流程搞清楚:哪个客服处理了哪些东西、付了多少钱、什么项目、关联哪个订单」。
|
||||
> 数据来源:本地生产快照克隆(数据截至 2026-10-14)。
|
||||
> 重要:**客服备用金支出已经实施**(`manual_expenses`);**公司人工支出登记**(`manual_expense_records`)也已实施,两张表通过 `fund_expense_id` 关联,不是同一张表。
|
||||
@@ -11,8 +13,8 @@
|
||||
> 重要:**人工支出实际上都走客服备用金**——登记时必须选择备用金账户,系统在同一事务内扣减余额、写 `fund_entries` 与 `manual_expenses`,再回填 `manual_expense_records.fund_expense_id`。
|
||||
> 重要:**客服角色只能使用自己的备用金账户**(默认自动带入,不能选择别人);后端越权直接 403。
|
||||
> 重要:**历史人工支出不补录**,只取少量样本做校准;**上线后每一笔人工单必须 100% 有成本记录**,否则不允许关账。
|
||||
> 相关文档:[成本中心与履约方式模型.md](成本中心与三块履约模型.md) §8 · [财务系统集成方案.md](财务系统集成方案.md) §八 · [客服备用金台账实施方案](客服备用金台账实施方案-2026-10-18.md) ·
|
||||
> DDL 草案 [drafts/2026-10-17-人工支出登记-DDL草案.sql](drafts/2026-10-17-人工支出登记-DDL草案.sql)
|
||||
> 相关文档:[成本中心与履约方式模型.md](../../成本中心与履约模型.md) §8 · [财务系统集成方案.md](../../财务系统集成方案.md) §八 · [客服备用金台账实施方案](../../人工支出与备用金.md) ·
|
||||
> DDL 草案 [drafts/2026-10-17-人工支出登记-DDL草案.sql](../../drafts/2026-10-17-人工支出登记-DDL草案.sql)
|
||||
|
||||
---
|
||||
|
||||
@@ -282,7 +284,7 @@ reversed(预留) 反向登记 + 反向凭证,原凭证不动
|
||||
|
||||
## 八、在全局路线中的位置
|
||||
|
||||
> 全局顺序以 [README.md](README.md) §三 为准。本文只负责“公司人工支出登记”,不重复商品、成本中心、销售事实和影子总账的完整设计。
|
||||
> 全局顺序以 [README.md](../../README.md) §三 为准。本文只负责“公司人工支出登记”,不重复商品、成本中心、销售事实和影子总账的完整设计。
|
||||
|
||||
| 本文内部阶段 | 内容 | 前置 |
|
||||
| --- | --- | --- |
|
||||
@@ -1,9 +1,11 @@
|
||||
# 客服备用金台账实施方案(2026-10-18)
|
||||
|
||||
> **归档说明(2026-10-23):** 本文是重写前的版本,仅作历史追溯,**不得作为实现依据**。结论已并入现行文档,见 [文档索引](../README.md)。
|
||||
|
||||
> 状态:**已实施 P0**(迁移 100/101/113;代码已上线到代码库,未部署生产)。
|
||||
> 最后校准:2026-10-23
|
||||
> 代码基线:`dev @ 5d1ba115 + 迁移 103–114`
|
||||
> **总口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准。**
|
||||
> **总口径以 [财务与履约口径总纲-2026-10-23.md](../../财务与履约口径总纲-2026-10-23.md) 为准。**
|
||||
> 背景:两位主管向客服发放备用金,客服用于充值、采购、赔偿等;目前全部走人工,无法监控每笔资金用途和每个客服余额。
|
||||
> 结论:这不是报销系统,而是**客服备用金(周转金)台账**。P0 已完成“账户 + 流水 + 支出 + 对账”,不做复杂审批和总账。
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# 成本中心与履约方式模型(文件名保留)
|
||||
|
||||
> **归档说明(2026-10-23):** 本文是重写前的版本,仅作历史追溯,**不得作为实现依据**。结论已并入现行文档,见 [文档索引](../README.md)。
|
||||
|
||||
> 状态:**现行(P0 已实施,履约方式已统一)**。商品主数据、成本中心、成本快照、销售事实、人工支出、`fulfillment_mode` 均已落地;人工商品归一与全覆盖仍待实施。
|
||||
> 最后校准:2026-10-23
|
||||
> 代码基线:`dev @ 5d1ba115 + 迁移 103–114`
|
||||
> 数据基线:本地生产快照克隆(数据截至 2026-10-14)
|
||||
> **财务与履约口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准**;本文只负责商品、成本、销售事实的数据模型。
|
||||
> **财务与履约口径以 [财务与履约口径总纲-2026-10-23.md](../../财务与履约口径总纲-2026-10-23.md) 为准**;本文只负责商品、成本、销售事实的数据模型。
|
||||
> 文件名保留是为了历史链接稳定,正文不再使用“三块”作为分类依据。
|
||||
> 关联:
|
||||
> - [项目全量分析报告-2026-10-17.md](项目全量分析报告-2026-10-17.md)
|
||||
> - [影子总账说明.md](影子总账说明.md)
|
||||
> - [财务系统集成方案.md](财务系统集成方案.md)
|
||||
> - [人工支出登记落地方案-2026-10-17.md](人工支出登记落地方案-2026-10-17.md)
|
||||
> - [项目全量分析报告-2026-10-17.md](../../项目全量分析报告-2026-10-17.md)
|
||||
> - [影子总账说明.md](../../影子总账说明.md)
|
||||
> - [财务系统集成方案.md](../../财务系统集成方案.md)
|
||||
> - [人工支出登记落地方案-2026-10-17.md](../../人工支出与备用金.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -429,7 +431,7 @@ orders + order_items + channel_product_mappings + cost_snapshots
|
||||
> **人工支出可追溯** ——「主要是把流程搞清楚:哪个人处理了哪些东西、付了多少钱。现在人工完全监管不到,
|
||||
> 很多地方需要人工付款,也不知道付款项目、金额、订单号」。
|
||||
> 因此本章顺序调整为:**① 人工支出登记 → ② 处理人可见(轻量 case) → ③ SLA 与监管指标(延后)**。
|
||||
> DDL 见 [drafts/2026-10-17-人工支出登记-DDL草案.sql](drafts/2026-10-17-人工支出登记-DDL草案.sql)。
|
||||
> DDL 见 [drafts/2026-10-17-人工支出登记-DDL草案.sql](../../drafts/2026-10-17-人工支出登记-DDL草案.sql)。
|
||||
>
|
||||
> **2026-10-22 进度**:客服备用金支出已实施(`fund.manual_expenses`),解决“客服手里有多少钱、这笔钱花到哪”;
|
||||
> 公司口径的人工支出登记(`manual_expense_records`)也已实施(迁移 `111`),并在订单界面提供「人工录单」逐单录入实际成本;
|
||||
@@ -467,7 +469,7 @@ carrier_reason
|
||||
|
||||
### 8.3 人工 case 模型
|
||||
|
||||
> 落地版见 [drafts/2026-10-17-人工支出登记-DDL草案.sql](drafts/2026-10-17-人工支出登记-DDL草案.sql):已去掉 `due_at`/优先级等 SLA 字段,先只保留「谁在处理哪个订单项 + 结果 + 证据 + 支出登记」。下表为完整形态(阶段③)。
|
||||
> 落地版见 [drafts/2026-10-17-人工支出登记-DDL草案.sql](../../drafts/2026-10-17-人工支出登记-DDL草案.sql):已去掉 `due_at`/优先级等 SLA 字段,先只保留「谁在处理哪个订单项 + 结果 + 证据 + 支出登记」。下表为完整形态(阶段③)。
|
||||
|
||||
```text
|
||||
manual_fulfillment_cases
|
||||
@@ -1,6 +1,8 @@
|
||||
# 财务/履约人工块修复总结(交接用)
|
||||
|
||||
> 状态:**交接摘要**。详细口径、迁移 SQL、代码清单与验收查询以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准。
|
||||
> **归档说明(2026-10-23):** 本文是重写前的版本,仅作历史追溯,**不得作为实现依据**。结论已并入现行文档,见 [文档索引](../README.md)。
|
||||
|
||||
> 状态:**交接摘要**。详细口径、迁移 SQL、代码清单与验收查询以 [财务与履约口径总纲-2026-10-23.md](../../财务与履约口径总纲-2026-10-23.md) 为准。
|
||||
> 代码基线:`dev @ 5d1ba115 + 迁移 103–114`。
|
||||
> 数据基线:本地生产快照克隆,数据截至 2026-10-14。
|
||||
> 分支:`dev` 领先 `main` 20+ 个提交;迁移 `103–114` 未部署生产;本轮只做本地提交,暂不推送。
|
||||
@@ -60,7 +62,7 @@ provider === 'douyin_mashida' -> 自动发货
|
||||
|
||||
## 五、下一步只做 P1:人工商品归一
|
||||
|
||||
完整口径与验收见 [总纲 §八、§九](财务与履约口径总纲-2026-10-23.md#八p0-实施记录统一履约事实)。
|
||||
完整口径与验收见 [总纲 §八、§九](../../财务与履约口径总纲-2026-10-23.md#八p0-实施记录统一履约事实)。
|
||||
|
||||
P0 已完成(本地):
|
||||
|
||||
@@ -122,12 +124,12 @@ PY
|
||||
|
||||
## 八、文档阅读顺序
|
||||
|
||||
1. [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md):唯一口径 + P0 执行清单。
|
||||
2. [成本中心与履约方式模型.md](成本中心与三块履约模型.md):商品、成本版本、快照、销售事实。
|
||||
3. [人工支出登记落地方案-2026-10-17.md](人工支出登记落地方案-2026-10-17.md):人工录单字段、审批、权限。
|
||||
4. [客服备用金台账实施方案-2026-10-18.md](客服备用金台账实施方案-2026-10-18.md):备用金账户与流水。
|
||||
5. [财务系统集成方案.md](财务系统集成方案.md) / [影子总账说明.md](影子总账说明.md):后续总账层。
|
||||
6. [README.md](README.md):文档索引与分支状态。
|
||||
1. [财务与履约口径总纲-2026-10-23.md](../../财务与履约口径总纲-2026-10-23.md):唯一口径 + P0 执行清单。
|
||||
2. [成本中心与履约方式模型.md](../../成本中心与履约模型.md):商品、成本版本、快照、销售事实。
|
||||
3. [人工支出登记落地方案-2026-10-17.md](../../人工支出与备用金.md):人工录单字段、审批、权限。
|
||||
4. [客服备用金台账实施方案-2026-10-18.md](../../人工支出与备用金.md):备用金账户与流水。
|
||||
5. [财务系统集成方案.md](../../财务系统集成方案.md) / [影子总账说明.md](../../影子总账说明.md):后续总账层。
|
||||
6. [README.md](../../README.md):文档索引与分支状态。
|
||||
|
||||
## 九、交接注意
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
# 人工支出与客服备用金
|
||||
|
||||
> 状态:**现行(P0 已实施,未部署生产)**。
|
||||
> 最后校准:2026-10-23
|
||||
> 代码基线:`feat/finance-rewrite @ 7557de6d + 迁移 100/101/106`
|
||||
> **业务口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准**;本文只负责人工支出的字段、流程、权限与备用金台账。
|
||||
> 本文合并自原《人工支出登记落地方案-2026-10-17.md》与《客服备用金台账实施方案-2026-10-18.md》。
|
||||
|
||||
---
|
||||
|
||||
## 〇、先分清两张表
|
||||
|
||||
| 表 | 业务含义 | 状态 | 关键字段 |
|
||||
| --- | --- | --- | --- |
|
||||
| `manual_expenses` | **客服备用金支出**:客服从自己的备用金账户里充值/采购/赔偿 | 已实施(迁移 `100`) | `account_id` / `fund_entry_id` / `category` / `payee_*` / `evidence_json` |
|
||||
| `manual_expense_records` | **公司人工支出登记**:按订单/成本项记录公司实际付出去的钱 | 已实施 P0(迁移 `106`) | `order_id` / `order_item_id` / `cost_item_id` / `paid_by_admin_user_id` / `status` / `cost_snapshot_id` |
|
||||
|
||||
结论:
|
||||
|
||||
1. 客服侧已收口的是"备用金余额与用途",解决"客服手里有多少钱、这笔钱花到哪"。
|
||||
2. **公司口径的实际支出已落地**:后台「人工支出」页(`/admin/manual-expenses`)支持登记、审批、驳回、报销;通过后按成本项写 `cost_snapshots`,没有有效成本版本时进入异常队列。
|
||||
3. 订单界面已接入「人工录单」:订单列表对人工单显示按钮、订单详情始终可录,弹窗锁定订单号、选择**备用金账户**并逐单填写**实际金额**,不存在小时单价或固定单量计价。
|
||||
4. 两张表通过 `fund_expense_id` 关联,且该字段由系统写入:人工支出先从客服备用金账户扣款并生成 `manual_expenses`,再写 `manual_expense_records`。
|
||||
5. **不要**把 `manual_expenses` 直接改名为 `manual_expense_records`,也不要让两张表共用索引/约束名。
|
||||
|
||||
---
|
||||
|
||||
## 一、核心口径
|
||||
|
||||
1. **人工成本逐单、按实际金额。** 不按小时、不按固定单量、不按固定单价。
|
||||
2. **人工支出从客服备用金账户扣款。** 余额不足直接拒绝,不允许负余额。
|
||||
3. **客服角色只能使用自己的备用金账户。** 后端越权直接 403;管理员/财务可代录到指定客服账户。
|
||||
4. **历史人工支出不补录。** 只取少量样本做校准;历史缺口标记 `historical_missing`,不阻塞上线。
|
||||
5. **上线后每一笔人工单必须 100% 有成本记录**,否则不允许关账。
|
||||
6. **登记必须"发生在垫付当天"才有监管价值**;事后补记只能算账,不能算监管。
|
||||
7. **客服备用金是周转金台账,不是报销系统。** P0 只做"账户 + 流水 + 支出 + 对账",不做复杂审批和总账。
|
||||
|
||||
---
|
||||
|
||||
## 二、客服备用金台账
|
||||
|
||||
### 2.1 落地结果
|
||||
|
||||
| 项 | 实际实现 |
|
||||
| --- | --- |
|
||||
| 数据库 | 迁移 `100_fund_accounts.sql`:`fund_accounts` / `fund_entries` / `manual_expenses` / `fund_reconciliations` |
|
||||
| 显示名 | 迁移 `101_admin_display_name.sql`:`admin_users.display_name`,备用金 `owner_name` 由触发器同步 |
|
||||
| 账户编号 | 迁移 `106_manual_expense_records.sql`:`FA-<大写用户名>`(如 `FA-KF_1`、`FA-JIAJIALI`),重名追加 `-2`、`-3` |
|
||||
| 后端 | `admin-fund-service.ts`、`routes/admin/fund.ts`;金额全部用分,写操作使用事务与行锁 |
|
||||
| 前端 | `AdminFundPage.tsx`、`FundManagementView.tsx`、`FundSupportView.tsx`;管理端与客服端分流 |
|
||||
| 权限 | `fund.view / fund.issue / fund.expense / fund.reconcile / fund.adjust / fund.export` |
|
||||
| 审计 | 所有写接口接入 `createJsonHandler({ audit })` |
|
||||
|
||||
### 2.2 系统要回答的四个问题
|
||||
|
||||
1. 公司现在有多少钱在主管和客服手里?
|
||||
2. 每个客服当前余额是多少?
|
||||
3. 每一笔钱花到哪、为什么花、有没有凭证?
|
||||
4. 系统余额和客服实际余额对不对得上?
|
||||
|
||||
### 2.3 账户
|
||||
|
||||
`fund_accounts` 一行 = 一个备用金账户,归属一个管理员(客服或主管)。`account_no` 必须是 `FA-<大写用户名>`,重名时按 `id` 顺序追加 `-2`、`-3`。
|
||||
|
||||
### 2.4 流水
|
||||
|
||||
`fund_entries` 是**只追加**的账户流水。每笔备用金变动写一行,正数=入账,负数=出账,带 `entry_type`(`issue` 发放 / `expense` 支出 / `reconcile` 对账调整 / `adjust` 人工调整)与 `balance_after` 快照。
|
||||
|
||||
### 2.5 支出
|
||||
|
||||
`manual_expenses` 记录一次具体支出:账户、金额(分)、分类(`purchase` 采购 / `compensation` 赔偿 / `freight` 运费 / `platform_recharge` 平台充值 / `other`)、收款方信息、凭证、关联的 `fund_entry_id`。
|
||||
|
||||
### 2.6 对账
|
||||
|
||||
`fund_reconciliations` 记录期初/期末账面余额、实际盘点余额与差异说明。差异必须由主管确认并留痕。
|
||||
|
||||
### 2.7 P0 未实现(仍在 P1)
|
||||
|
||||
异常队列、批量发放/对账、导出、低余额提醒、大额支出主管确认;支出凭证目前只校验数组非空,未校验对象存储 key 是否真实存在。
|
||||
|
||||
---
|
||||
|
||||
## 三、公司人工支出登记
|
||||
|
||||
### 3.1 落点
|
||||
|
||||
- 迁移:`apps/backend/src/db/migrations/106_manual_expense_records.sql`
|
||||
- 服务:`apps/backend/src/services/finance/manual-expense-service.ts`;备用金扣款唯一写入口 `admin-fund-service.ts` 的 `writeFundExpense`
|
||||
- 接口:`apps/backend/src/routes/admin/finance.ts` 的 `/finance/manual-expenses*`;订单页专用 `POST /finance/manual-order-costs`
|
||||
- 页面:`apps/frontend/src/pages/admin/AdminManualExpensePage.tsx`,入口 `/admin/manual-expenses`
|
||||
- 订单入口:订单列表/详情「人工录单」打开 `ManualOrderCostModal`,锁定订单号、选择订单项与备用金账户、填写本单实际金额;支持深链 `/admin/orders/:orderId?manualCost=1`
|
||||
- 权限:查看 `finance.view` / `fund.view`,登记 `fund.expense` / `finance.config`,审核 `finance.review`
|
||||
|
||||
### 3.2 资金流(同一事务)
|
||||
|
||||
```text
|
||||
锁定 fund_accounts 行
|
||||
→ 校验余额
|
||||
→ 扣减余额
|
||||
→ 写 fund_entries
|
||||
→ 写 manual_expenses
|
||||
→ 写 manual_expense_records.fund_expense_id
|
||||
```
|
||||
|
||||
余额不足直接拒绝,**不允许负余额**。
|
||||
|
||||
### 3.3 成本快照
|
||||
|
||||
通过后以 `subject_type='manual_case'`、`subject_id=expense.id` 写 `actual` 快照,金额 = 登记的实际金额。同一订单项多笔支出可累加,不会互相覆盖。
|
||||
|
||||
没有匹配到有效成本版本时,写 `manual_expense_cost_missing` 异常(`status='open'`),不允许静默记 0。
|
||||
|
||||
### 3.4 状态机
|
||||
|
||||
```text
|
||||
draft ──> submitted ──> approved ──> reimbursed
|
||||
│
|
||||
└──> rejected
|
||||
```
|
||||
|
||||
| 状态 | 含义 | 可执行动作 |
|
||||
| --- | --- | --- |
|
||||
| `draft` | 草稿 | 编辑、提交 |
|
||||
| `submitted` | 待审批 | 审批通过 / 驳回 |
|
||||
| `approved` | 已通过 | 标记已报销;此时写 `actual` 成本快照 |
|
||||
| `rejected` | 已驳回 | 需填 `reject_reason` |
|
||||
| `reimbursed` | 已报销 | 终态 |
|
||||
| `reversed` | 已冲正 | 预留,关联 `reversal_of_id` |
|
||||
|
||||
状态变更只追加到 `manual_expense_events`,不更新、不删除历史事件。
|
||||
|
||||
### 3.5 字段
|
||||
|
||||
```text
|
||||
manual_expense_records
|
||||
expense_no -- 唯一编号
|
||||
order_id, order_item_id, work_order_id
|
||||
fund_expense_id -- 关联 manual_expenses.id;系统写入
|
||||
cost_item_id -- 付款项目 -> cost_items.cost_model
|
||||
expense_category -- goods_purchase / compensation / refund_advance /
|
||||
-- service_fee / freight / recharge / other
|
||||
amount_fen -- 实际金额(分),> 0
|
||||
currency
|
||||
paid_by_admin_user_id, paid_by_name
|
||||
payee_type, payee_name, payee_account
|
||||
payment_method
|
||||
paid_at
|
||||
status, approved_by, approved_at, rejected_by, rejected_at, reject_reason
|
||||
reimbursed_by, reimbursed_at
|
||||
evidence_json -- 凭证:支付截图对象 key、流水号、聊天记录 key
|
||||
cost_snapshot_id
|
||||
reversal_of_id
|
||||
expense_batch_no, note
|
||||
```
|
||||
|
||||
### 3.6 凭证
|
||||
|
||||
`evidence_json` **至少留一项**可追溯凭证(对象存储 key、流水号或聊天记录 key),否则拒绝提交。
|
||||
|
||||
---
|
||||
|
||||
## 四、订单页「人工录单」
|
||||
|
||||
订单里的数据足够预填 90%:店铺、商品、规格、数量、用户实付、支付时间、平台单号、履约标签、状态,后台订单列表/详情已经全部具备。客服实际只需要补 **4 件事**:渠道/项目、金额、收款方、凭证(外加默认带出的「谁、什么时候」)。
|
||||
|
||||
弹窗规则:
|
||||
|
||||
- 锁定订单号,不允许改;
|
||||
- 必须选择备用金账户(客服角色自动带入本人账户且不可改);
|
||||
- 金额按本单实际金额填写,**没有小时单价或固定单量**;
|
||||
- 付款时间不能晚于当前时间,也不能早于订单支付时间。
|
||||
|
||||
---
|
||||
|
||||
## 五、每日必查与关账门槛
|
||||
|
||||
| 指标 | 目标 |
|
||||
| --- | ---: |
|
||||
| 人工订单项无人工支出记录 | 0 |
|
||||
| 人工支出无 `fund_expense_id`(上线后) | 0 |
|
||||
| 人工支出无成本快照 | 0 |
|
||||
| 人工支出金额与备用金流水不一致 | 0 |
|
||||
| 成本异常 open 中人工项 | 0(历史标记除外) |
|
||||
|
||||
关账门槛:
|
||||
|
||||
- 存在人工单无成本记录:**不允许关账**;
|
||||
- 存在人工支出未关联备用金:**不允许关账**;
|
||||
- 存在人工支出与备用金流水金额不一致:**不允许关账**。
|
||||
|
||||
核对 SQL 见 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) §八。
|
||||
|
||||
---
|
||||
|
||||
## 六、异常处理
|
||||
|
||||
- 发现人工单无成本:当天补录;补录仍走备用金扣款,不允许直接改余额。
|
||||
- 备用金余额不足:拒绝登记,由财务充值后再录;不允许负余额。
|
||||
- 同一订单项多笔支出:允许累加(渠道费、加急费、补差价),但每笔都必须有独立备用金流水。
|
||||
- 历史人工单:标记 `historical_missing`,不补录、不计入上线后缺口。
|
||||
|
||||
---
|
||||
|
||||
## 七、历史样本校准(只做参考)
|
||||
|
||||
- 每个来源抽 20–30 单,覆盖低价/中价/高价;
|
||||
- 只用于估算历史人工成本区间与毛利率,**不写 `cost_snapshots`**;
|
||||
- 样本结论单独记录,禁止回填成逐单成本;
|
||||
- 历史平台 53,901 项同理,只有拿到旧平台结算数据才允许按历史成本入账。
|
||||
|
||||
---
|
||||
|
||||
## 八、关联文档
|
||||
|
||||
- 业务口径: [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md)
|
||||
- 数据模型: [成本中心与履约模型.md](成本中心与履约模型.md)
|
||||
- 影子总账: [财务系统集成方案.md](财务系统集成方案.md)
|
||||
- 订单读取与履约展示: [成本中心与履约模型.md](成本中心与履约模型.md) §六
|
||||
@@ -40,7 +40,7 @@ kuaishou-cloud / kuaishou-feifei 通道已下线,affiliate-dash 负责真实
|
||||
|
||||
当前真实数据:work_order 118,053 / affiliate_dash 41,676 / legacy_platform 53,901 / manual 102,955,合计 316,585。
|
||||
|
||||
详细设计见 [../财务与履约口径总纲-2026-10-23.md](../财务与履约口径总纲-2026-10-23.md) 与 [../成本中心与履约方式模型.md](../成本中心与三块履约模型.md)。
|
||||
详细设计见 [../财务与履约口径总纲-2026-10-23.md](../财务与履约口径总纲-2026-10-23.md) 与 [../成本中心与履约模型.md](../成本中心与履约模型.md)。
|
||||
|
||||
## 2. 目标链路
|
||||
|
||||
|
||||
+2
-2
@@ -2,13 +2,13 @@
|
||||
|
||||
> 状态:**现行说明(未实施)**
|
||||
> 最后校准:2026-10-23
|
||||
> 代码基线:`dev @ 5d1ba115 + 迁移 103–114`
|
||||
> 代码基线:`feat/finance-rewrite @ 7557de6d + 迁移 103–107`
|
||||
> 配套方案:[财务系统集成方案.md](财务系统集成方案.md)
|
||||
> **总口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准。**
|
||||
> 前置:统一履约事实、商品主数据、渠道映射、成本中心与销售事实;实施顺序以总纲第六节为准。
|
||||
> 一句话:**影子总账是一套“跟着真实业务跑、但暂时不接管余额”的复式会计账。** 它先把每一笔业务翻译成借贷分录,和现有钱包/订单/提现数据逐日对账;确认长期对得平之后,再让它成为正式总账。
|
||||
>
|
||||
> 边界:影子总账解决“钱怎么记、怎么对”,不负责“商品是什么、成本是多少、人工谁处理”。商品与成本见 [成本中心与履约方式模型.md](成本中心与三块履约模型.md),人工履约见同一文档第八节。
|
||||
> 边界:影子总账解决“钱怎么记、怎么对”,不负责“商品是什么、成本是多少、人工谁处理”。商品与成本见 [成本中心与履约模型.md](成本中心与履约模型.md),人工支出见 [人工支出与备用金.md](人工支出与备用金.md)。
|
||||
|
||||
> 当前进度:客服备用金、商品与成本中心、成本快照、销售事实、公司人工支出登记已实施;`finance_*` 表、分录、对账、转正均未实施。人工支出现在强制走客服备用金;历史人工支出不补录,上线后必须全覆盖。
|
||||
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
# 成本中心 API 契约
|
||||
|
||||
> 状态:**现行**。后端服务与前端页面都必须严格按此实现,不得各自发挥。
|
||||
> 最后校准:2026-10-23
|
||||
> 代码基线:`feat/finance-rewrite @ 7557de6d + 迁移 103–107`
|
||||
> 详细业务口径见 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md);数据模型见 [成本中心与履约模型.md](成本中心与履约模型.md)。
|
||||
|
||||
## 一、字段替换总表
|
||||
|
||||
| 旧字段 | 新字段 | 出现位置 |
|
||||
| --- | --- | --- |
|
||||
| `cost_items.carrier_type` | `cost_items.cost_model` | DB / 成本项接口 |
|
||||
| `cost_items.cost_type` | (并入 `cost_model`) | DB / 成本项接口 |
|
||||
| `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 / 销售事实接口 |
|
||||
| `work_product_rules.legacy_cost_source` | (删除) | DB |
|
||||
|
||||
## 二、枚举映射
|
||||
|
||||
### 2.1 `cost_model`(取代 `cost_type` + `carrier_type`)
|
||||
|
||||
成本模型与履约方式正交:专属模型只对对应履约方式生效,通用模型对所有履约方式生效。
|
||||
|
||||
| 新值 | 含义 | 取代旧的 |
|
||||
| --- | --- | --- |
|
||||
| `affiliate_purchase` | 自动发货上游采购成本 | `cost_type=auto_purchase` |
|
||||
| `work_order_reward` | 接单平台打手报酬 | `cost_type=work_order_reward` |
|
||||
| `manual_actual` | 人工客服实际支出 | `cost_type=manual_service` |
|
||||
| `platform_fee` | 平台服务费 | `cost_type=platform_fee` |
|
||||
| `payment_fee` | 支付通道费 | `cost_type=payment_fee` |
|
||||
| `after_sales_loss` | 售后损失 | `cost_type=after_sales_loss` |
|
||||
| `other` | 其他 | `cost_type=shipping_other` / `cost_type=other` |
|
||||
|
||||
### 2.2 `pricing_mode`(精简为 3 个)
|
||||
|
||||
`per_item` / `per_order` / `percent_of_revenue`
|
||||
(删除 `per_hour`、`fixed_daily`——人工成本逐单按实际金额,不按工时)
|
||||
|
||||
### 2.3 `fulfillment_mode`(履约方式,唯一真源)
|
||||
|
||||
`work_order` / `affiliate_dash` / `legacy_platform` / `manual`
|
||||
|
||||
## 三、成本项接口
|
||||
|
||||
```
|
||||
POST /api/v1/admin/cost-center/cost-items
|
||||
GET /api/v1/admin/cost-center/cost-items
|
||||
PATCH /api/v1/admin/cost-center/cost-items/:id
|
||||
DELETE /api/v1/admin/cost-center/cost-items/:id
|
||||
```
|
||||
|
||||
请求体(创建/更新):
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"code": "可选;留空则按 costModel 自动生成",
|
||||
"name": "必填",
|
||||
"costModel": "affiliate_purchase | work_order_reward | manual_actual | platform_fee | payment_fee | after_sales_loss | other",
|
||||
"pricingMode": "per_item | per_order | percent_of_revenue",
|
||||
"unitName": "件",
|
||||
"currency": "CNY",
|
||||
"status": "active | archived",
|
||||
"note": ""
|
||||
}
|
||||
```
|
||||
|
||||
**不再接受 `costType` 与 `carrierType`。**
|
||||
|
||||
响应项(`CostItem`):
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"id": 10,
|
||||
"code": "sys_affiliate_purchase",
|
||||
"name": "自动发货上游采购成本",
|
||||
"costModel": "affiliate_purchase",
|
||||
"pricingMode": "per_item",
|
||||
"unitName": "件",
|
||||
"currency": "CNY",
|
||||
"status": "active",
|
||||
"note": "",
|
||||
"snapshotCount": 0,
|
||||
"createdAt": "...",
|
||||
"updatedAt": "..."
|
||||
}
|
||||
```
|
||||
|
||||
## 四、商品接口
|
||||
|
||||
商品**不再返回 `carrierType`**。前端若要按来源分组,用 `canonicalSku` 前缀:
|
||||
|
||||
| 前缀 | 来源 | 建议展示名 |
|
||||
| --- | --- | --- |
|
||||
| `aff:` | 自动发货 | 自动发货 |
|
||||
| `tpl:` | 接单模板 | 接单平台 |
|
||||
| `wo:` | 工单证据 | 接单平台 |
|
||||
| `man:` | 人工 | 人工 |
|
||||
| 其他 | 未分类 | 未分类 |
|
||||
|
||||
`GET /api/v1/admin/cost-center/products` 的 `carrierType` 查询参数**保留**,
|
||||
但语义改为「按 `canonical_sku` 前缀筛选」,取值 `affiliate_dash | work_order | manual`。
|
||||
后端实现方式:
|
||||
|
||||
```sql
|
||||
AND (
|
||||
($n = 'affiliate_dash' AND p.canonical_sku LIKE 'aff:%')
|
||||
OR ($n = 'work_order' AND (p.canonical_sku LIKE 'tpl:%' OR p.canonical_sku LIKE 'wo:%'))
|
||||
OR ($n = 'manual' AND p.canonical_sku LIKE 'man:%')
|
||||
)
|
||||
```
|
||||
|
||||
## 五、成本版本接口
|
||||
|
||||
路径不变,字段不变:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"id": 1,
|
||||
"costItemId": 10,
|
||||
"costItemCode": "sys_affiliate_purchase",
|
||||
"costItemName": "自动发货上游采购成本",
|
||||
"costModel": "affiliate_purchase",
|
||||
"pricingMode": "per_item",
|
||||
"scopeType": "product | rule | shop | provider | global",
|
||||
"provider": "", "shopId": "",
|
||||
"canonicalProductId": 3, "canonicalSku": "aff:SKU-1", "canonicalName": "...",
|
||||
"workProductRuleId": null, "workProductRuleName": "", "workProductRuleKey": "",
|
||||
"ruleRewardAmountFen": 0, "ruleUnitPriceFen": 0,
|
||||
"revenueItemCount": 0, "revenueFen": 0, "revenueAvgFen": 0,
|
||||
"rewardAvgFen": 0, "rewardFen": 0, "amountKind": "unit_price | fixed",
|
||||
"isSystemPlaceholder": false,
|
||||
"amountFen": 1000, "rateBps": 0,
|
||||
"effectiveFrom": "...", "effectiveTo": null,
|
||||
"source": "manual | upstream_sync | import",
|
||||
"status": "draft | active | retired",
|
||||
"changeReason": "",
|
||||
"createdByAdminUserId": null, "approvedByAdminUserId": null,
|
||||
"createdAt": "...", "updatedAt": "..."
|
||||
}
|
||||
```
|
||||
|
||||
`costItemCarrierType` 字段**删除**,改为 `costItemCostModel`。
|
||||
|
||||
## 六、成本异常接口
|
||||
|
||||
路径不变。请求参数 `carrierType` **删除**;响应 `carrierType` **删除**,改为 `fulfillmentMode`。
|
||||
若前端要按履约方式筛选异常,用参数 `fulfillmentMode`:
|
||||
|
||||
```jsonc
|
||||
// 请求
|
||||
{ "status": "open", "subjectType": "order_item", "fulfillmentMode": "manual" }
|
||||
```
|
||||
|
||||
后端实现:join `order_items oi ON oi.id = ce.order_item_id` 并过滤 `oi.fulfillment_mode`。
|
||||
|
||||
响应项:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"id": 1,
|
||||
"subjectType": "order_item",
|
||||
"subjectId": 316571,
|
||||
"orderItemId": 316571,
|
||||
"workOrderId": null,
|
||||
"fulfillmentMode": "manual",
|
||||
"reason": "cost_missing",
|
||||
"status": "open",
|
||||
"detail": {},
|
||||
"createdAt": "...", "resolvedAt": null, "resolvedByAdminUserId": null
|
||||
}
|
||||
```
|
||||
|
||||
## 七、销售事实接口
|
||||
|
||||
`GET /api/v1/admin/sales-profit` 的分组维度字段名从 `carrierType` 改为 `fulfillmentMode`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"fulfillmentMode": "manual",
|
||||
"itemCount": 102955,
|
||||
"grossRevenueFen": 1219202470,
|
||||
"costAmountFen": 32000,
|
||||
"costMissingCount": 102954,
|
||||
"grossProfitFen": -1219200470
|
||||
}
|
||||
```
|
||||
|
||||
筛选参数同样从 `carrierType` 改为 `fulfillmentMode`,取值四种履约方式。
|
||||
|
||||
## 八、成本中心概览接口
|
||||
|
||||
`GET /api/v1/admin/cost-center/overview` 的统计改为按来源前缀与成本模型分组:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"productCount": 745,
|
||||
"mappingCount": 4426,
|
||||
"costItemCount": 3,
|
||||
"activeVersionCount": 701,
|
||||
"openExceptionCount": 157822,
|
||||
"unmappedOrderItemCount": 145507,
|
||||
"productSourceCounts": {
|
||||
"affiliate_dash": 38,
|
||||
"work_order": 707,
|
||||
"manual": 0,
|
||||
"uncategorized": 0
|
||||
},
|
||||
"costModelCounts": {
|
||||
"affiliate_purchase": 1,
|
||||
"work_order_reward": 1,
|
||||
"manual_actual": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`GET /api/v1/admin/cost-center/options` 返回的 `products[].source` 同样是来源前缀推导值,
|
||||
`costItems[].costModel` 取代原 `carrierType`。
|
||||
|
||||
## 九、后端模块边界(已完成拆分)
|
||||
|
||||
原 `apps/backend/src/services/admin/admin-cost-center-service.ts`(4,248 行)已拆为:
|
||||
|
||||
| 新文件 | 行数 | 负责的导出 |
|
||||
| --- | ---: | --- |
|
||||
| `services/cost-center/cost-center-shared.ts` | ~800 | 类型、常量、归一化函数、`mapXxx` 映射函数、provider 归一、`costModelScopeSql` |
|
||||
| `services/cost-center/cost-center-overview-service.ts` | ~150 | 总览统计与选项 |
|
||||
| `services/cost-center/product-catalog-service.ts` | ~1,000 | 商品与渠道映射的 CRUD、回填、选项 |
|
||||
| `services/cost-center/template-bootstrap-service.ts` | ~1,380 | 接单模板引导、自动发货商品同步、工单证据回填、模板成本版本同步、`buildTemplateMappingPlan` |
|
||||
| `services/cost-center/cost-definition-service.ts` | ~1,240 | 成本项 / 成本版本 / 成本异常 / 成本预览 / 成本快照写入 |
|
||||
|
||||
原文件**已删除**,路由 `apps/backend/src/routes/admin/cost-center.ts` 从新模块导入。
|
||||
`routes/admin/worker-platform/config.ts` 的模板成本同步也从新模块导入。
|
||||
|
||||
## 十、前端模块边界(已完成拆分)
|
||||
|
||||
原 `apps/frontend/src/pages/admin/AdminCostCenterPage.tsx`(2,887 行)已拆为:
|
||||
|
||||
| 新文件 | 内容 |
|
||||
| --- | --- |
|
||||
| `pages/admin/cost-center/CostCenterShell.tsx` | 页面骨架、顶部来源切换(全部/自动发货/接单平台/人工)、Tab 定义 |
|
||||
| `pages/admin/cost-center/shared.ts` | 常量、类型、标签函数 |
|
||||
| `pages/admin/cost-center/CostCenterContext.ts` | 页面上下文(`useCostCenter`) |
|
||||
| `pages/admin/cost-center/ProductCatalogPanel.tsx` | 标准商品 Tab |
|
||||
| `pages/admin/cost-center/ChannelMappingPanel.tsx` | 渠道映射 Tab |
|
||||
| `pages/admin/cost-center/AffiliateSkuPanel.tsx` | 自动发货 SKU Tab |
|
||||
| `pages/admin/cost-center/CostItemPanel.tsx` | 成本项 Tab |
|
||||
| `pages/admin/cost-center/CostVersionPanel.tsx` | 成本版本 Tab |
|
||||
| `pages/admin/cost-center/CostExceptionPanel.tsx` | 成本异常 Tab |
|
||||
| `pages/admin/cost-center/CostPreviewPanel.tsx` | 成本预览 Tab |
|
||||
|
||||
`AdminCostCenterPage.tsx` 只保留 `export default` 转发到 `CostCenterShell`(18 行)。
|
||||
|
||||
类型文件 `apps/frontend/src/types/admin/cost-center.ts` 已按本契约改名:
|
||||
|
||||
```ts
|
||||
export type FulfillmentMode = 'work_order' | 'affiliate_dash' | 'legacy_platform' | 'manual'
|
||||
export type ProductSource = 'affiliate_dash' | 'work_order' | 'manual' | ''
|
||||
export type CostModel =
|
||||
| 'affiliate_purchase'
|
||||
| 'work_order_reward'
|
||||
| 'manual_actual'
|
||||
| 'platform_fee'
|
||||
| 'payment_fee'
|
||||
| 'after_sales_loss'
|
||||
| 'other'
|
||||
```
|
||||
|
||||
`CostCarrierType` 已删除。
|
||||
@@ -0,0 +1,471 @@
|
||||
# 成本中心与履约模型
|
||||
|
||||
> 状态:**现行**。商品主数据、成本中心、成本快照、销售事实、履约事实均已落地。
|
||||
> 最后校准:2026-10-23
|
||||
> 代码基线:`feat/finance-rewrite @ 7557de6d + 迁移 103–107`
|
||||
> 数据基线:本地生产快照克隆(数据截至 2026-10-14),已按新 schema 原地改造
|
||||
> **业务口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准**;本文只负责数据模型、迁移与运维。
|
||||
> 接口字段见 [成本中心API契约.md](成本中心API契约.md)。
|
||||
|
||||
---
|
||||
|
||||
## 〇、模型总览
|
||||
|
||||
```text
|
||||
orders ──┬── order_items ──── fulfillment_mode (唯一履约事实)
|
||||
│ ├── 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)
|
||||
└── manual_expense_records ── fund_expense_id ── manual_expenses
|
||||
```
|
||||
|
||||
四条不变式:
|
||||
|
||||
1. **履约方式只有一处存**:`order_items.fulfillment_mode`。快照、异常、销售事实要展示履约方式时 join `order_items`。
|
||||
2. **商品来源只由 `canonical_sku` 前缀表达**,商品表不存载体列。
|
||||
3. **成本模型只表达"怎么算"**:`cost_items.cost_model` 与履约方式正交,不复用同一字段。
|
||||
4. **成本金额只在一处改**:`cost_versions`;业务表只保存快照,历史成本永不回写。
|
||||
|
||||
---
|
||||
|
||||
## 一、迁移基线:12 个合并为 5 个
|
||||
|
||||
原 `dev` 分支上,商品与成本中心用 12 个迁移(`103`–`114`,1,087 行)分批落地,字段反复摇摆。2026-10-23 重写为 5 个:
|
||||
|
||||
| 迁移 | 内容 | 行数 |
|
||||
| --- | --- | ---: |
|
||||
| `103_product_catalog.sql` | `product_catalog`(无载体列)+ `channel_product_mappings`(含匹配键)+ `order_items` 商品/归一字段 | 152 |
|
||||
| `104_cost_center.sql` | `cost_items`(`cost_model`)+ `cost_versions` + `cost_snapshots`(无载体列)+ `cost_exceptions`(无载体列)+ 订单项收入固化 + 系统成本项 | 476 |
|
||||
| `105_sales_daily_facts.sql` | `sales_daily_facts`(`fulfillment_mode`) | 104 |
|
||||
| `106_manual_expense_records.sql` | `manual_expense_records` + `manual_expense_events` + 备用金账户编号可读化 | 201 |
|
||||
| `107_order_item_fulfillment_mode.sql` | `order_items.fulfillment_mode` 三列 | 48 |
|
||||
|
||||
### 1.1 字段替换对照
|
||||
|
||||
| 旧字段 | 新字段 | 出现位置 |
|
||||
| --- | --- | --- |
|
||||
| `cost_items.carrier_type` | `cost_items.cost_model` | DB / 成本项接口 |
|
||||
| `cost_items.cost_type` | (并入 `cost_model`) | DB / 成本项接口 |
|
||||
| `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 / 销售事实接口 |
|
||||
| `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` | (删除,只剩三种) | 成本项接口 |
|
||||
|
||||
### 1.2 成本模型映射
|
||||
|
||||
| 旧 `cost_type` | 新 `cost_model` |
|
||||
| --- | --- |
|
||||
| `auto_purchase` | `affiliate_purchase` |
|
||||
| `work_order_reward` | `work_order_reward` |
|
||||
| `manual_service` | `manual_actual` |
|
||||
| `platform_fee` | `platform_fee` |
|
||||
| `payment_fee` | `payment_fee` |
|
||||
| `after_sales_loss` | `after_sales_loss` |
|
||||
| `shipping_other` / `other` | `other` |
|
||||
|
||||
### 1.3 商品来源前缀
|
||||
|
||||
| 前缀 | 来源 | 对应履约方式(仅参考,不作推导依据) |
|
||||
| --- | --- | --- |
|
||||
| `aff:` | 自动发货 | `affiliate_dash` |
|
||||
| `tpl:` | 接单模板 | `work_order` |
|
||||
| `wo:` | 历史工单证据 | `work_order` |
|
||||
| `man:` | 人工 | `manual` |
|
||||
| 其他 | 未分类 | 不得推断 |
|
||||
|
||||
本地现状:`aff:` 38 个、`tpl:` 662 个、`wo:` 45 个,`man:` 0 个(P1 待建)。
|
||||
|
||||
---
|
||||
|
||||
## 二、商品主数据
|
||||
|
||||
### 2.1 `product_catalog`
|
||||
|
||||
```text
|
||||
id
|
||||
canonical_sku -- 内部稳定编码;前缀表达来源
|
||||
canonical_name
|
||||
category
|
||||
unit_name -- 件/个/颗等,配合 normalized_quantity
|
||||
status -- active / archived
|
||||
attributes_json
|
||||
```
|
||||
|
||||
`canonical_sku` 是内部稳定编码,创建后不随平台改名。`attributes_json` 会被价格导入覆盖,**不能作为统计源**。
|
||||
|
||||
### 2.2 `channel_product_mappings`
|
||||
|
||||
```text
|
||||
provider -- 落库值:91kaquan / pdd / douyin_mashida
|
||||
shop_id -- 空串 = 来源级;具体店铺优先
|
||||
platform_sku_code
|
||||
platform_sku_name
|
||||
platform_sku_name_key -- 归一后的精确名称或数量系列键
|
||||
match_type -- sku_code / sku_code_name / sku_code_series / sku_name_series
|
||||
canonical_product_id
|
||||
quantity_multiplier -- 数量已在归一数量中体现,系列 SKU 固定为 1
|
||||
confidence -- exact / rule / manual
|
||||
source -- manual / import / rule
|
||||
enabled
|
||||
```
|
||||
|
||||
唯一键 = `provider + shop_id + platform_sku_code + match_type + platform_sku_name_key`。91 在同一店铺复用 `sku_code`,只有加入名称键才能消歧。
|
||||
|
||||
匹配优先级:
|
||||
|
||||
```text
|
||||
店铺 + SKU 编码
|
||||
> 来源 + SKU 编码
|
||||
> 店铺 + SKU 编码 + 精确名称
|
||||
> 来源 + SKU 编码 + 精确名称
|
||||
> 店铺 + SKU 编码 + 名称系列
|
||||
> 来源 + SKU 编码 + 名称系列
|
||||
> 店铺 + 名称系列
|
||||
> 来源 + 名称系列
|
||||
```
|
||||
|
||||
匹配不到的商品进入 `product_mapping_missing` 异常队列,**不能直接拿原始 `sku_code` 当报表维度**。
|
||||
|
||||
### 2.3 `order_items` 的商品与数量字段
|
||||
|
||||
```text
|
||||
canonical_product_id -- 归一到标准商品
|
||||
normalized_quantity -- 从 SKU 名称解析并固化;未解析时为 NULL
|
||||
normalized_sku_name -- NFKC、小写、去空白后的名称
|
||||
normalized_sku_series -- 剥离数量并归一同义字后的名称系列
|
||||
```
|
||||
|
||||
**`normalized_quantity` 不预填。** 预填会把"5 个"类 SKU 永久锁死成 1,必须由回填脚本按 SKU 名称解析后写入。`quantity` 长期几乎恒为 1,禁止直接拿它当销量。
|
||||
|
||||
### 2.4 接单模板批量初始化
|
||||
|
||||
662 个接单模板不允许逐个手工建商品。后台「从接单模板初始化」按以下规则批量生成:
|
||||
|
||||
1. **每个启用模板生成 1 个标准商品**,内部编码 `tpl:<rule_key>`;暂无历史工单的模板也先建商品。
|
||||
2. **历史工单直接命中模板**,以 `work_orders.product_rule_id` 作为映射的第一手证据,不只看原始 `sku_code`。
|
||||
3. 同一 `provider + shop_id + sku_code` 只命中一个模板时,生成 `sku_code` 精确映射。
|
||||
4. 编码被多个模板复用时,继续用"编码 + 归一名称系列"和"编码 + 精确名称"消歧;只有唯一归属才生成映射。
|
||||
5. 同一编码 + 同一名称仍命中多个模板时,**不选最新、不按次数猜**;跳过并列入冲突清单。
|
||||
6. 来源 + 店铺内名称系列唯一时,额外生成 `sku_name_series` 兜底,永远排在编码类映射之后。
|
||||
7. 数量系列(1个/2个/5个/10个,秘钥/密钥等等价写法)归为同一标准商品;数量写入 `normalized_quantity`,**不能把"5个"再写成 `quantity_multiplier=5`**,否则会重复乘算。
|
||||
8. 重跑初始化时只清理 `source='import'` 且备注为"接单模板 … 自动初始化"的映射,并重置由这些映射带出的订单项 `canonical_product_id`;人工映射、成本版本、快照和真实订单/工单不删除。
|
||||
|
||||
本地验证结果(2026-10-18):启用模板 662 个;分层映射 4,426 条(编码精确 2,302、编码 + 精确名称 440、编码 + 名称系列 160、名称系列兜底 1,524);可唯一归一 86,533 个有证据订单项,另有 25,918 个订单项属于 32 组真实冲突,保持未映射。
|
||||
|
||||
### 2.5 自动发货商品
|
||||
|
||||
自动发货只有 affiliate-dash 一个上游渠道,SKU 固定归一为 `aff:<skuCode>`,**不写 `channel_product_mappings`**。渠道映射只代表"平台 SKU 能归一到标准商品",不代表自动发货。
|
||||
|
||||
上游价格用 `apps/backend/scripts/sync-affiliate-dash-prices.ts` 批量导入(清单见 `apps/backend/scripts/data/affiliate-dash-prices.json`,38 条):按 `aff:<skuCode>` 补建/更新商品,并给 `sys_affiliate_purchase` 成本项生成 `scope=product` 的按件版本。金额变化时先停用旧版本再插入新版本,历史快照不变。首次导入历史基线用 `--effective-from=2026-07-10`。
|
||||
|
||||
---
|
||||
|
||||
## 三、成本中心
|
||||
|
||||
### 3.1 成本模型与适用履约方式
|
||||
|
||||
| `cost_model` | 适用履约方式 | 计价方式 | 成本来源 |
|
||||
| --- | --- | --- | --- |
|
||||
| `affiliate_purchase` | `affiliate_dash` | `per_item` | 上游 SKU 固定成本版本 |
|
||||
| `work_order_reward` | `work_order` | `per_order` | 模板规则级版本 / 工单实际报酬 |
|
||||
| `manual_actual` | `manual` | `per_order` | 订单页逐单录入的实际支出 |
|
||||
| `platform_fee` | 全部 | `per_order` / `percent_of_revenue` | 未配置 |
|
||||
| `payment_fee` | 全部 | `per_order` / `percent_of_revenue` | 未配置 |
|
||||
| `after_sales_loss` | 全部 | `per_order` | 未配置 |
|
||||
| `other` | 全部 | `per_order` | 未配置 |
|
||||
|
||||
后端判定见 `cost-center-shared.ts` 的 `costModelScopeSql()`;通用模型对所有履约方式生效,专属模型只对对应履约方式生效。
|
||||
|
||||
### 3.2 `cost_items` / `cost_versions`
|
||||
|
||||
```text
|
||||
cost_items
|
||||
id, code, name
|
||||
cost_model -- 见 §3.1
|
||||
pricing_mode -- per_item / per_order / percent_of_revenue
|
||||
unit_name, currency, status, note
|
||||
|
||||
cost_versions
|
||||
id, cost_item_id
|
||||
scope_type -- global / provider / shop / product / rule
|
||||
provider, shop_id
|
||||
canonical_product_id, work_product_rule_id
|
||||
amount_fen -- 固定金额;percent_of_revenue 时用 rate_bps
|
||||
rate_bps -- 万分比,10000 = 100%
|
||||
effective_from, effective_to
|
||||
source -- manual / upstream_sync / import
|
||||
status -- draft / active / retired
|
||||
change_reason, created_by, approved_by
|
||||
```
|
||||
|
||||
范围优先级:`rule > product > shop > provider > global`。成本版本按**业务发生时间**(`paid_at` / `accepted_at`)解析,不是回填时的 `now()`。
|
||||
|
||||
改价只新增版本;历史订单走 `cost_snapshots`,不受影响。
|
||||
|
||||
### 3.3 系统内置成本项
|
||||
|
||||
| 编码 | 成本模型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `sys_work_order_reward` | `work_order_reward` | 662 个接单模板各一条规则级版本;全局 0 元版本只作"按工单实际结算"的占位 |
|
||||
| `sys_affiliate_purchase` | `affiliate_purchase` | 上游 SKU 固定成本版本,38 个商品级 |
|
||||
| `sys_manual_service` | `manual_actual` | 人工支出登记写入 `actual` 快照;版本先为 `draft` 占位 |
|
||||
|
||||
工单报酬的三种快照:
|
||||
|
||||
- 工单**验收**后按 `work_orders.reward_amount` 写 `actual`(本地 95,870 条);
|
||||
- 未验收但已确定报酬的工单先按同一金额写 `estimated`(metadata `source=work_order_reward_committed`,本地 21,217 条);
|
||||
- 取消工单不计成本。
|
||||
|
||||
### 3.4 `cost_snapshots`
|
||||
|
||||
```text
|
||||
id
|
||||
subject_type -- order_item / work_order / manual_case
|
||||
subject_id, order_id, order_item_id, work_order_id
|
||||
cost_item_id, cost_version_id, canonical_product_id
|
||||
quantity, unit_amount_fen, total_amount_fen
|
||||
snapshot_stage -- estimated / actual / adjustment
|
||||
source -- resolved / legacy_rule / manual_input / missing
|
||||
metadata_json
|
||||
cost_basis_at -- 解析成本版本时用的业务时间
|
||||
resolved_at, superseded_at, created_at
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 改价只新增 `cost_versions`,不覆盖历史;
|
||||
- 同一主体 + 同一成本项只允许一条"当前有效"快照(`uq_cost_snapshots_active_subject_item`);
|
||||
- 预估快照代表支付时点的成本,**不因后续改价重写**;只有实际成本允许替代当前有效行;
|
||||
- 匹配不到成本时写 `cost_missing` 异常,**不能默认 0**;
|
||||
- 履约方式不存快照里,需要时 join `order_items.fulfillment_mode`。
|
||||
|
||||
### 3.5 `cost_exceptions`
|
||||
|
||||
```text
|
||||
id
|
||||
subject_type, subject_id
|
||||
order_item_id, work_order_id
|
||||
reason -- product_mapping_missing / cost_missing / manual_expense_cost_missing
|
||||
status -- open / resolved / ignored
|
||||
detail_json
|
||||
created_at, resolved_at, resolved_by
|
||||
```
|
||||
|
||||
`open` 数量必须在后台暴露。按履约方式筛选时用 `EXISTS`/`IN` 半连接 `order_items.fulfillment_mode`(本地 248,878 条,`IN` 半连接 + `idx_order_items_fulfillment_id` 索引约 170ms)。
|
||||
|
||||
---
|
||||
|
||||
## 四、订单项收入固化
|
||||
|
||||
`order_items.revenue_amount_fen` / `revenue_basis` 在迁移 `104` 里固化:
|
||||
|
||||
| `revenue_basis` | 含义 |
|
||||
| --- | --- |
|
||||
| `order_total` | 一单一品,订单实付全部归该订单项 |
|
||||
| `equal_split` | 多订单项按等额拆分,余数给最后一项 |
|
||||
| `platform_item` | 有平台明细时替换 |
|
||||
| `manual` / `unknown` | 未定 |
|
||||
|
||||
本地全量 316,585 项都是 `order_total`(当前一单一品),但模型按"订单项"设计,不把"一单一品"写死。
|
||||
|
||||
收入按 `orders.paid_at` 归属销售日(Asia/Shanghai),没有 `paid_at` 时退回 `created_at`。
|
||||
|
||||
---
|
||||
|
||||
## 五、销售事实
|
||||
|
||||
### 5.1 `sales_daily_facts`
|
||||
|
||||
```text
|
||||
stat_date -- Asia/Shanghai,按 paid_at 归属
|
||||
provider, shop_id
|
||||
canonical_product_id -- 允许为空:未归一桶
|
||||
fulfillment_mode -- work_order / affiliate_dash / legacy_platform / manual
|
||||
order_count, item_count, quantity
|
||||
gross_revenue_fen, refund_amount_fen, net_revenue_fen
|
||||
cost_amount_fen, cost_missing_count, cost_stage
|
||||
gross_profit_fen
|
||||
computed_at
|
||||
```
|
||||
|
||||
口径:
|
||||
|
||||
- 收入 = `order_items.revenue_amount_fen`,不直接拿整单金额;
|
||||
- 成本来自 `cost_snapshots` 当前有效行;没有快照的订单项计入 `cost_missing_count`;
|
||||
- 未归一商品允许 `canonical_product_id` 为空,单独成"未归一"行,保证总收入对平;
|
||||
- `cost_stage`:`missing`(全部无快照)/ `partial`(部分无)/ `estimated`(全部预估)/ `actual`(全部实际);
|
||||
- `cost_missing_count > 0` 时该行毛利不直接用于决策;
|
||||
- 退款暂未接入,`refund_amount_fen` 先按 `pay_status='refunded'` 预留。
|
||||
|
||||
### 5.2 本地重算结果(2026-10-23)
|
||||
|
||||
| 履约方式 | 事实行 | 订单项 | 收入 | 成本 | 缺失成本 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| `work_order` | 9,585 | 118,053 | ¥533.57 万 | ¥452.83 万 | 966 |
|
||||
| `affiliate_dash` | 3,163 | 41,676 | ¥508.97 万 | ¥497.41 万 | 0 |
|
||||
| `manual` | 1,849 | 102,955 | ¥1,219.20 万 | ¥0.32 万 | 102,954 |
|
||||
| `legacy_platform` | 201 | 53,901 | ¥389.12 万 | ¥0 | 53,901 |
|
||||
|
||||
---
|
||||
|
||||
## 六、履约事实
|
||||
|
||||
### 6.1 `order_items.fulfillment_mode`
|
||||
|
||||
```text
|
||||
fulfillment_mode -- work_order / affiliate_dash / legacy_platform / manual;允许 NULL
|
||||
fulfillment_decided_at -- 最后一次判定时间
|
||||
fulfillment_reason -- 判定来源:order_sync / reconcile / backfill
|
||||
```
|
||||
|
||||
判定顺序固定:**工单 → 自动发货任务 → 历史平台任务 → 人工**。当前库中三种载体没有重叠,顺序不影响结果;后续出现重叠必须告警,不能静默覆盖。
|
||||
|
||||
决策服务在 `services/fulfillment/fulfillment-mode-service.ts`:
|
||||
|
||||
- `classifyFulfillmentMode()` —— 纯函数,供单测与文档对照;
|
||||
- `decideOrderItemsFulfillmentModes()` —— 订单同步后批量落库;
|
||||
- `reconcilePendingFulfillmentModes()` —— 24 小时兜底归集;
|
||||
- `getFulfillmentModeCounts()` —— 计数核对。
|
||||
|
||||
### 6.2 回填
|
||||
|
||||
回填是**一次性脚本**,不塞进迁移:31 万行 `UPDATE` 放进迁移会拖慢部署,也无法在中途失败后按批重试。
|
||||
|
||||
```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 重写后重跑):`work_order` 118,053 / `manual` 102,955 / `legacy_platform` 53,901 / `affiliate_dash` 41,676,四项相加 = 316,585,`NULL = 0`。
|
||||
|
||||
### 6.3 决策边界(写进代码注释与测试)
|
||||
|
||||
1. `fulfillment_mode` 允许短暂为 `NULL`;订单项创建时通常还没有工单/任务,不能强制非空。
|
||||
2. 上线后新订单项超过 **24 小时**仍为 `NULL`,进入异常队列;不是要求在创建瞬间就有值。
|
||||
3. 模式可以因业务改派而变化,但每次变化必须写原因与操作人,禁止静默 `UPDATE`。
|
||||
4. **商品来源不能反推履约方式。** 当前 11,349 个人工项指向 `tpl:*` 商品,就是典型反例。
|
||||
5. `voucher_state` 只做展示与核销流程,不参与任何成本/履约判断。
|
||||
6. 三种载体同时命中的订单项必须先告警人工复核;当前本地为 0。
|
||||
|
||||
---
|
||||
|
||||
## 七、同步脚本与调度
|
||||
|
||||
| 命令 | 作用 |
|
||||
| --- | --- |
|
||||
| `npm run fulfillment:backfill` | 履约方式分批回填(只填 NULL) |
|
||||
| `npm run finance:sync -- --mode=all` | 预估快照 + 实际快照 + 销售事实重算 |
|
||||
| `npm run finance:sync -- --mode=estimated` | 只算预估快照并写缺失成本异常 |
|
||||
| `npm run finance:sync -- --mode=actual` | 只把工单快照升级为实际金额 |
|
||||
| `npm run finance:sync -- --mode=facts` | 只重算销售事实 |
|
||||
|
||||
长历史重算要分窗口:单次 `rebuildSalesDailyFacts` 超过约 400 天会被语句超时(`DATABASE_STATEMENT_TIMEOUT_MS`,默认 15 秒)打断。本地 2026-07-10 ~ 2026-10-23 分 4 段重算,正常。
|
||||
|
||||
定时任务 `services/scheduler/cost-finance-sync-job.ts` 每 5 分钟先归集待判定的订单项,再算成本与销售事实。
|
||||
|
||||
---
|
||||
|
||||
## 八、本地库原地改造(2026-10-23 已执行)
|
||||
|
||||
生产尚未执行过 `103–114`,本地库需要从旧 schema 改到新 schema。改造脚本:`scripts/rewrite-local-db-2026-10-23.sql`。
|
||||
|
||||
改造矩阵(列级 `ALTER`,不重建表、不动源表):
|
||||
|
||||
| 表 | 改造 |
|
||||
| --- | --- |
|
||||
| `product_catalog` | 删 `carrier_type` |
|
||||
| `cost_items` | `cost_type` + `carrier_type` → `cost_model`;`auto_purchase` 编码统一为 `sys_affiliate_purchase` |
|
||||
| `cost_snapshots` | 删 `carrier_type`、删未使用的 `manual_case_id` |
|
||||
| `cost_exceptions` | 删 `carrier_type` |
|
||||
| `sales_daily_facts` | `carrier_type` → `fulfillment_mode`(改名) |
|
||||
| `manual_expense_records` | `cost_item_id` 改 `NOT NULL` |
|
||||
| `work_product_rules` | 删 `legacy_cost_source` |
|
||||
| `order_items` | **不动**(履约事实、收入固化、商品指向全部保留) |
|
||||
|
||||
执行顺序:
|
||||
|
||||
```bash
|
||||
# 1. 备份(已完成)
|
||||
# backups/postgres/order_site_pre_rewrite_20261023.dump
|
||||
# 2. 删旧迁移记录 + 列级改造
|
||||
docker exec -i order_site-postgres-1 psql -U postgres -d order_site \
|
||||
-v ON_ERROR_STOP=1 < scripts/rewrite-local-db-2026-10-23.sql
|
||||
# 3. 应用新迁移 103–107
|
||||
npm run db:migrate
|
||||
# 4. 重跑履约回填(幂等)
|
||||
npm run fulfillment:backfill
|
||||
# 5. 分窗口重算销售事实
|
||||
npm run finance:sync -- --mode=facts --from=2026-07-10 --to=2026-08-08
|
||||
# ...按 30 天窗口依次执行
|
||||
```
|
||||
|
||||
改造后数据量核对:商品 745、映射 4,426、成本项 3、成本版本 702、快照 158,764、异常 248,878、销售事实 14,798、人工支出 1、订单项 316,585 —— 全部与改造前一致。
|
||||
|
||||
`check-sql-guard.ts` 对 `103`–`114` 段迁移放开字节校验(这一组在分支内整体重写);`001`–`102` 仍必须保持字节不变。
|
||||
|
||||
---
|
||||
|
||||
## 九、索引与性能
|
||||
|
||||
| 索引 | 用途 |
|
||||
| --- | --- |
|
||||
| `idx_order_items_fulfillment_mode (fulfillment_mode, order_id)` | 按履约方式筛订单项 |
|
||||
| `idx_order_items_fulfillment_id (fulfillment_mode, id)` | 异常按履约方式半连接 |
|
||||
| `idx_order_items_canonical_product` | 按标准商品查订单项 |
|
||||
| `idx_order_items_normalized_sku_lookup` | 按归一名称/系列匹配映射 |
|
||||
| `idx_channel_product_mappings_match_lookup` | 映射查找 |
|
||||
| `uq_channel_product_mappings_match_key` | 映射唯一键 |
|
||||
| `idx_cost_versions_lookup / product / rule` | 成本版本解析 |
|
||||
| `uq_cost_snapshots_active_subject_item` | 每个主体 + 成本项只允许一条有效快照 |
|
||||
| `idx_cost_exceptions_status_created` | 异常队列列表 |
|
||||
| `idx_sales_daily_facts_date / shop / product / mode` | 销售事实各维度 |
|
||||
| `idx_work_orders_rule_cover` | 成本版本页按接单规则聚合 |
|
||||
| `idx_order_items_revenue_cover` | 收入聚合 |
|
||||
| `idx_work_categories_name` | 商品目录按分类名 lateral join |
|
||||
|
||||
---
|
||||
|
||||
## 十、关联文档
|
||||
|
||||
- 业务口径: [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md)
|
||||
- 接口契约: [成本中心API契约.md](成本中心API契约.md)
|
||||
- 人工支出与备用金: [人工支出与备用金.md](人工支出与备用金.md)
|
||||
- 影子总账: [财务系统集成方案.md](财务系统集成方案.md)
|
||||
- 运维: [数据库SQL约束与运维.md](数据库SQL约束与运维.md)、[部署启动与数据库重建.md](部署启动与数据库重建.md)
|
||||
@@ -1,6 +1,6 @@
|
||||
# 接单模板匹配引擎重构设计
|
||||
|
||||
> 目标读者:后端 / 后台运营。状态:**P0 + P1 已实施;P2 / P3 待办**。最后校准:2026-10-23(代码基线 `dev @ 5d1ba115 + 迁移 103–114`;匹配业务逻辑未变)。
|
||||
> 目标读者:后端 / 后台运营。状态:**P0 + P1 已实施;P2 / P3 待办**。最后校准:2026-10-23(代码基线 `feat/finance-rewrite @ 7557de6d + 迁移 103–107`;匹配业务逻辑未变)。
|
||||
>
|
||||
> 一句话:把「订单信息 → 接单模板(价格/拼单/要求)」的匹配,从散落的多个消费点、平台判断和"店铺必填"的绑定表里,收敛为**一个平台无关的引擎**:匹配条件完全由**订单信息**表达,**店铺只是可选条件之一**,新增来源只加一个适配器 + 一组配置。
|
||||
>
|
||||
|
||||
+84
-285
@@ -1,38 +1,42 @@
|
||||
# 财务与履约口径总纲(2026-10-23)
|
||||
# 财务与履约口径总纲(2026-10-23 重写版)
|
||||
|
||||
> 状态:**现行唯一口径**。本文是财务、成本、履约分类的总入口;其他文档与本文件冲突时,以本文为准。
|
||||
> 代码基线:`dev @ 5d1ba115 + 迁移 103–114`。历史文档中的旧基线、旧三块模型、按小时人工成本等表述全部作废。
|
||||
> 最后校准:2026-10-23
|
||||
> 代码基线:`feat/finance-rewrite @ 7557de6d + 迁移 103–107`
|
||||
> 数据基线:本地生产快照克隆(数据截至 2026-10-14),已按新 schema 原地改造
|
||||
> 适用范围:订单接入、履约判定、商品归一、成本快照、销售事实、人工支出、备用金、后续总账与对账。
|
||||
|
||||
---
|
||||
|
||||
## 〇、六条硬口径
|
||||
## 〇、七条硬口径
|
||||
|
||||
1. **电子凭证不是履约方式,也不是成本类型。** 快手电子凭证只是核销码/兑换码,抖音 DYC 券(`redeem_certificates`)同理;一个有核销码的 91/快手或抖音订单,仍可能走自动发货、接单平台或人工。
|
||||
2. **旧平台通道已经下线。** `kuaishou_ct_assisted`、`kuaishou_feifei` 只保留历史数据,报表中统一标记为 `legacy_platform`,不得混入人工。
|
||||
3. **人工的定义只有一个:** 已支付订单项,没有工单、没有自动发货任务、没有历史平台任务,即为人工;有没有核销码不影响判定。
|
||||
4. **人工成本逐单、按实际金额。** 不按小时、不按固定单量、不按固定单价;必须从客服备用金账户扣款,客服只能使用本人账户。
|
||||
5. **历史人工支出不补录。** 只取少量样本做校准参考;历史缺口统一标记为“历史未采集”,不阻塞上线。
|
||||
5. **历史人工支出不补录。** 只取少量样本做校准参考;历史缺口统一标记为"历史未采集",不阻塞上线。
|
||||
6. **上线后人工账单必须 100% 可监控。** 每一笔人工单必须有处理人、实际金额、备用金支出与成本快照;缺失即进入异常队列,不允许关账。
|
||||
7. **履约方式只有一个真源。** `order_items.fulfillment_mode` 是唯一事实来源,商品表、成本项表、快照表、异常表、销售事实表都不再各自存一份"载体"。
|
||||
|
||||
---
|
||||
|
||||
## 一、履约方式与成本模型必须分开
|
||||
## 一、三个正交维度
|
||||
|
||||
当前系统把“怎么交付”和“成本怎么算”混在同一个 `carrier_type` 里,导致同一订单在订单页、成本快照、销售事实中出现不同分类。正确模型拆成三个正交维度:
|
||||
系统过去把"怎么交付"和"成本怎么算"混在同一个 `carrier_type` 里,导致同一订单在订单页、成本快照、销售事实中出现不同分类。正确模型拆成三个正交维度:
|
||||
|
||||
| 维度 | 取值 | 回答的问题 |
|
||||
| --- | --- | --- |
|
||||
| `fulfillment_mode` | `work_order` / `affiliate_dash` / `legacy_platform` / `manual` | 这单最终由谁执行 |
|
||||
| `voucher_state` | 无 / 已发码 / 已核销 / 已销毁 | 有没有快手核销码、走到哪一步 |
|
||||
| `cost_model` | `work_order_reward` / `affiliate_purchase` / `manual_actual` / `legacy_cost` / `none` | 这单成本怎么算 |
|
||||
| 维度 | 取值 | 回答的问题 | 存储位置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `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`,核销码可以有多个状态事件。
|
||||
- 一个订单项只能有一个 `fulfillment_mode`,核销码可以有多个状态事件;
|
||||
- **商品来源不由履约方式推导**:商品的来源由 `canonical_sku` 前缀表达(`aff:` / `tpl:` / `wo:` / `man:`),不能反推履约方式。
|
||||
|
||||
---
|
||||
|
||||
@@ -46,7 +50,7 @@
|
||||
| **人工 `manual`** | **102,955** | **¥1,219.20 万** | **仅 1 条 ¥320,历史基本未采集** |
|
||||
| 合计 | 316,585 | ¥2,650.87 万 | — |
|
||||
|
||||
> **数字口径必须先分清“全量”和“未归一”。** 人工全量是 102,955 项;其中 11,349 项已有 `canonical_product_id`,但这 11,349 项全部指向 `tpl:*` 接单模板商品,只说明 SKU 命中过模板,**不代表它们有工单或走过接单平台**。真正未归一的是 91,606 项。历史平台 53,901 项全部未归一。两者相加才是报表里的“未归一 145,507 项”。
|
||||
> **数字口径必须先分清"全量"和"未归一"。** 人工全量是 102,955 项;其中 11,349 项已有 `canonical_product_id`,但这 11,349 项全部指向 `tpl:*` 接单模板商品,只说明 SKU 命中过模板,**不代表它们有工单或走过接单平台**。真正未归一的是 91,606 项。历史平台 53,901 项全部未归一。两者相加才是报表里的"未归一 145,507 项"。
|
||||
|
||||
人工 102,955 项的来源与核销码事实:
|
||||
|
||||
@@ -56,7 +60,7 @@
|
||||
| 拼多多 | 2,335 | ¥41.74 万 | 无快手凭证 |
|
||||
| 抖音 | 2 | ¥62.90 | 无快手凭证 |
|
||||
|
||||
这张表直接说明:**91 人工单全部有核销码,但仍然是人工。** 把“有核销码”当成 `voucher_only` 或自动发货,是订单页漏掉人工块的直接原因。
|
||||
这张表直接说明:**91 人工单全部有核销码,但仍然是人工。** 把"有核销码"当成 `voucher_only` 或自动发货,是订单页漏掉人工块的直接原因。
|
||||
|
||||
归一潜力:人工 102,955 项中 **100,529 项有 `spec_json.skuId`(97.6%)**,共 3,561 个 SKU;剩余 2,426 项主要来自拼多多,可用 `sku_code/sku_name` 建立映射。人工商品归一后,商品编码统一用 `man:<skuId>`;不得因为复用了 `tpl:*` 商品就把履约方式改成 `work_order`。
|
||||
|
||||
@@ -64,23 +68,23 @@
|
||||
|
||||
---
|
||||
|
||||
## 三、成本中心现行规则
|
||||
## 三、成本模型规则
|
||||
|
||||
| 成本模型 | 适用履约方式 | 成本来源 | 现状 |
|
||||
| --- | --- | --- | --- |
|
||||
| `affiliate_purchase` | 自动发货 | 上游 SKU 固定成本版本 | 38 个商品,全部有 active 版本 |
|
||||
| `work_order_reward` | 接单平台 | 模板规则级版本 / 工单实际报酬 | 662 个规则版本,966 项异常 |
|
||||
| `manual_actual` | 人工 | 订单页逐单录入的实际支出 | 已实现,历史未采集 |
|
||||
| `legacy_cost` | 历史平台 | 旧平台结算数据(如可得) | 未采集,历史单列 |
|
||||
| `none` | 无成本或平台承担 | 业务确认后显式标记 | 暂不默认 |
|
||||
| `platform_fee` / `payment_fee` / `after_sales_loss` / `other` | 全部履约方式 | 通用成本项 | 未配置 |
|
||||
|
||||
硬规则:
|
||||
|
||||
- 未确认成本时保持 `cost_missing`,**绝不默认记 0**;
|
||||
- 人工成本只走 `sys_manual_service`,通过 `manual_expense_records` 写 `actual` 快照;
|
||||
- 自动发货、接单平台继续使用现有成本版本与规则版本;
|
||||
- 电子凭证不建成本项,也不建“凭证载体”成本;
|
||||
- 历史平台成本单独标记 `legacy_cost`,不要混入人工成本。
|
||||
- 电子凭证不建成本项,也不建"凭证载体"成本;
|
||||
- 计价方式只保留三种:`per_item`(按件)、`per_order`(按单)、`percent_of_revenue`(按收入比例)。**已删除 `per_hour` / `fixed_daily`**——人工成本逐单按实际金额,不按工时;
|
||||
- 历史平台成本单独标记,不混入人工成本。
|
||||
|
||||
---
|
||||
|
||||
@@ -133,7 +137,7 @@
|
||||
## 五、历史数据处理
|
||||
|
||||
- 历史人工支出**不补录**,只允许抽取样本做成本校准;
|
||||
- 历史人工单在销售事实中保留收入,成本标记为 `historical_missing`,不计入“上线后监控缺口”;
|
||||
- 历史人工单在销售事实中保留收入,成本标记为 `historical_missing`,不计入"上线后监控缺口";
|
||||
- 历史平台 `legacy_platform` 单独成行,成本未采集的标记 `legacy_missing`;
|
||||
- 自动发货、接单平台历史成本不回写、不重算版本,沿用现有快照。
|
||||
|
||||
@@ -141,36 +145,16 @@
|
||||
|
||||
## 六、实施顺序
|
||||
|
||||
### P0:统一履约事实(先做)
|
||||
| 阶段 | 内容 | 状态 |
|
||||
| --- | --- | --- |
|
||||
| P0 | 统一履约事实(`fulfillment_mode`)+ 成本模型正交 | **已完成(本地,未部署生产)** |
|
||||
| P1 | 人工商品归一(`man:<skuId>`)+ 上线后人工成本全覆盖 | 未实施 |
|
||||
| P2 | 退款冲减与异常闭环 | 未实施 |
|
||||
| P3 | 影子总账与对账 | 未实施 |
|
||||
|
||||
> 执行细节、迁移 SQL、回填脚本与代码清单见 §八;本节只保留顺序。
|
||||
P0 的具体迁移、回填、代码清单与验收 SQL 见 [成本中心与履约模型](成本中心与履约模型.md);接口契约见 [成本中心API契约](成本中心API契约.md)。
|
||||
|
||||
1. `order_items` 增加 `fulfillment_mode`、`fulfillment_decided_at`、`fulfillment_reason`;
|
||||
2. 按第四节顺序回填 316,585 个订单项;
|
||||
3. 后台、成本快照、销售事实统一读取该字段;
|
||||
4. 移除 `provider === '91kaquan' -> voucher_only` 的兜底;
|
||||
5. 验收:work_order 118,053 / affiliate_dash 41,676 / legacy_platform 53,901 / manual 102,955 四项相加等于总数。
|
||||
|
||||
### P1:人工商品与成本入口
|
||||
|
||||
1. 人工单按 `spec.skuId` 建 `man:<skuId>` 商品,PDD 用 `sku_code/sku_name` 兜底;
|
||||
2. 订单页人工录单保持逐单实际金额;
|
||||
3. 上线后新人工单必须在 24 小时内录入成本;
|
||||
4. 验收:人工单无成本记录 = 0。
|
||||
|
||||
### P2:退款与异常闭环
|
||||
|
||||
1. 订单增加退款字段;
|
||||
2. 销售事实接入退款金额与成本冲减;
|
||||
3. 人工/接单平台异常队列按履约方式拆分;
|
||||
4. 验收:退款订单收入、成本、毛利可对平。
|
||||
|
||||
### P3:影子总账与对账
|
||||
|
||||
1. 建 `finance_accounts / finance_business_events / finance_journal_*`;
|
||||
2. 先接订单支付、工单结算、押金、提现、人工支出五类事件;
|
||||
3. 每日对账:钱包余额 vs 总账余额;
|
||||
4. 验收:试算平衡、钱包差异为 0,再谈关账与报表。
|
||||
> 下一步只做 P1,不要同时改退款或总账。
|
||||
|
||||
---
|
||||
|
||||
@@ -181,246 +165,23 @@
|
||||
| 订单页人工录单,按单实际金额 | **已实施** | `74b62f34` |
|
||||
| 人工支出从客服备用金扣款 | **已实施** | `2aeb789e` |
|
||||
| 客服只能使用本人备用金账户 | **已实施** | `a7f60414` |
|
||||
| 备用金账户编号 `FA-<用户名>` | **已实施** | `fac34c84` + 迁移 `113` |
|
||||
| 备用金账户编号 `FA-<用户名>` | **已实施** | `fac34c84` + 迁移 `106` |
|
||||
| 订单页只展示平台判断 SKU | **已实施** | `c38cd8ca`、`cd9bb141` |
|
||||
| `order_items.fulfillment_mode` 持久化 | **已实施** | 迁移 `114` + 统一决策服务 |
|
||||
| 订单页 / 成本快照 / 销售事实统一读字段 | **已实施** | `admin-read-service`、`sales-facts-service`、`cost-snapshot-sync-service` |
|
||||
| 本地 316,585 项回填 | **已实施** | `fulfillment:backfill`,四项计数与 §8.2 一致 |
|
||||
| 已有成本异常载体同步 | **已实施** | 53,901 条由 `manual` 修正为 `legacy_platform` |
|
||||
| 24 小时兜底归集 | **已实施** | `cost_finance_sync` 每 5 分钟先归集 |
|
||||
| 财务与履约文档统一口径 | **本轮完成** | 本文件 + 交接总结 + 关联文档 |
|
||||
| `order_items.fulfillment_mode` 持久化 | **已实施** | 迁移 `107` + 统一决策服务 |
|
||||
| 订单页 / 成本快照 / 销售事实统一读字段 | **已实施** | `admin-order-read-helpers`、`sales-facts-service`、`cost-snapshot-sync-service` |
|
||||
| 本地 316,585 项回填 | **已实施** | `fulfillment:backfill`,四项计数与 §二 一致 |
|
||||
| 成本模型与履约方式正交(去 `carrier_type`) | **已实施(本地库已改造)** | 迁移 `103–107` 重写;`cost_items.cost_model` |
|
||||
| 12 个迁移合并为 5 个 | **已实施** | `103_product_catalog` ~ `107_order_item_fulfillment_mode` |
|
||||
| 人工商品归一 `man:<skuId>` | **未实施** | P1 |
|
||||
| 退款冲减、异常闭环 | **未实施** | 本文 §六 P2 |
|
||||
| 影子总账、对账、关账 | **未实施** | 本文 §六 P3 |
|
||||
| 退款冲减、异常闭环 | **未实施** | §六 P2 |
|
||||
| 影子总账、对账、关账 | **未实施** | §六 P3 |
|
||||
|
||||
> 分支事实:以上实现均在 `dev`;迁移 `103–114` 尚未部署生产。
|
||||
> 下一阶段只做 P1:人工商品归一 + 上线后人工成本全覆盖,不要同时改退款或总账。
|
||||
> 分支事实:以上实现均在 `feat/finance-rewrite`;迁移 `103–107` 尚未合并到 `dev`、未部署生产。
|
||||
> 生产口径仍以 `main` 为准。
|
||||
|
||||
---
|
||||
|
||||
## 八、P0 实施记录:统一履约事实
|
||||
|
||||
> 状态:**已实施(本地 `dev`,未部署生产)**。迁移 `114` 已执行,316,585 项已回填,四项计数与 §8.2 一致。
|
||||
|
||||
### 8.1 迁移 `114_order_item_fulfillment_mode.sql`
|
||||
|
||||
四张表的 `carrier_type` 约束原先都不接受 `legacy_platform`;只加 `order_items` 字段会在写销售事实/成本快照时直接失败。迁移 `114` 已同时放宽:
|
||||
|
||||
- `product_catalog.chk_product_catalog_carrier_type`
|
||||
- `cost_items.chk_cost_items_carrier_type`
|
||||
- `cost_snapshots.chk_cost_snapshots_carrier_type`
|
||||
- `sales_daily_facts.chk_sales_daily_facts_carrier`(保留 `unknown`)
|
||||
|
||||
```sql
|
||||
ALTER TABLE order_items
|
||||
ADD COLUMN IF NOT EXISTS fulfillment_mode text,
|
||||
ADD COLUMN IF NOT EXISTS fulfillment_decided_at timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS fulfillment_reason text;
|
||||
|
||||
ALTER TABLE order_items
|
||||
ADD CONSTRAINT chk_order_items_fulfillment_mode
|
||||
CHECK (
|
||||
fulfillment_mode IS NULL
|
||||
OR fulfillment_mode IN ('work_order', 'affiliate_dash', 'legacy_platform', 'manual')
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_fulfillment_mode
|
||||
ON order_items (fulfillment_mode, order_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_order_items_fulfillment_pending
|
||||
ON order_items (order_id)
|
||||
WHERE fulfillment_mode IS NULL;
|
||||
|
||||
ALTER TABLE product_catalog DROP CONSTRAINT IF EXISTS chk_product_catalog_carrier_type;
|
||||
ALTER TABLE product_catalog ADD CONSTRAINT chk_product_catalog_carrier_type
|
||||
CHECK (carrier_type IN ('affiliate_dash', 'work_order', 'legacy_platform', 'manual', 'shared'));
|
||||
|
||||
ALTER TABLE cost_items DROP CONSTRAINT IF EXISTS chk_cost_items_carrier_type;
|
||||
ALTER TABLE cost_items ADD CONSTRAINT chk_cost_items_carrier_type
|
||||
CHECK (carrier_type IN ('affiliate_dash', 'work_order', 'legacy_platform', 'manual', 'shared'));
|
||||
|
||||
ALTER TABLE cost_snapshots DROP CONSTRAINT IF EXISTS chk_cost_snapshots_carrier_type;
|
||||
ALTER TABLE cost_snapshots ADD CONSTRAINT chk_cost_snapshots_carrier_type
|
||||
CHECK (carrier_type IN ('affiliate_dash', 'work_order', 'legacy_platform', 'manual', 'shared'));
|
||||
|
||||
ALTER TABLE sales_daily_facts DROP CONSTRAINT IF EXISTS chk_sales_daily_facts_carrier;
|
||||
ALTER TABLE sales_daily_facts ADD CONSTRAINT chk_sales_daily_facts_carrier
|
||||
CHECK (carrier_type IN ('affiliate_dash', 'work_order', 'legacy_platform', 'manual', 'shared', 'unknown'));
|
||||
```
|
||||
|
||||
### 8.2 回填(只填 NULL,可重复执行)
|
||||
|
||||
> 迁移 `114` 只做 DDL;回填单独执行,不要塞进启动迁移。31 万行 `UPDATE` 放进迁移会拖慢部署,也无法在中途失败后按批重试。建议写成一次性脚本,每 5,000 行提交一次。
|
||||
|
||||
判定顺序固定:**工单 → 自动发货任务 → 历史平台任务 → 人工**。当前库中三种载体没有重叠,顺序不影响结果;后续出现重叠必须告警,不能静默覆盖。
|
||||
|
||||
```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-07-10 ~ 2026-10-14):
|
||||
|
||||
| fulfillment_mode | 订单项 | 收入 | 无成本项 |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| `work_order` | 118,053 | ¥533.57 万 | 966 |
|
||||
| `affiliate_dash` | 41,676 | ¥508.97 万 | 0 |
|
||||
| `legacy_platform` | 53,901 | ¥389.12 万 | 53,901 |
|
||||
| `manual` | 102,955 | ¥1,219.20 万 | 102,954 |
|
||||
| **合计** | **316,585** | **¥2,650.87 万** | **157,821** |
|
||||
|
||||
> 以上 SQL 已在本地库实际执行:定时任务先归集 20,000 项,回填脚本再处理 296,585 项,合计 316,585,`NULL = 0`;脚本耗时约 86 秒。随后同步 53,901 条 open 成本异常的 `carrier_type`(manual → legacy_platform),全表检查耗时约 5.4 秒。
|
||||
|
||||
生产执行顺序:
|
||||
|
||||
```bash
|
||||
# 1. 部署并等待迁移 114 完成(只做 DDL)
|
||||
npm run db:migrate:status
|
||||
|
||||
# 2. 分批回填 + 同步已有成本异常载体
|
||||
npm run fulfillment:backfill
|
||||
|
||||
# 3. 核对四项计数与 NULL=0,再触发财务重算
|
||||
npm run finance:sync -- --from=<上线前起始日> --to=<今天> --mode=all
|
||||
```
|
||||
|
||||
### 8.3 代码改造记录
|
||||
|
||||
| 文件 | 改造结果 |
|
||||
| --- | --- |
|
||||
| `apps/backend/src/db/migrations/114_order_item_fulfillment_mode.sql` | 新增三列 + 两个索引;放宽四张表的 `carrier_type` CHECK |
|
||||
| `apps/backend/src/services/fulfillment/fulfillment-mode-service.ts` | 统一判定与批量归集;`decideOrderItemsFulfillmentModes()`、`reconcilePendingFulfillmentModes()`、`syncOpenCostExceptionCarrierTypes()` |
|
||||
| `apps/backend/scripts/backfill-fulfillment-mode.ts` | 分批回填 + 同步已有成本异常载体;npm 命令 `fulfillment:backfill` |
|
||||
| `apps/backend/src/services/admin/admin-order-read-helpers.ts` | 只读 `fulfillment_mode`;删除 voucher/provider 兜底;核销码单独返回 `voucherState` |
|
||||
| `apps/backend/src/services/finance/sales-facts-service.ts` | 直接读 `COALESCE(oi.fulfillment_mode, 'manual')`,不再复制判定 |
|
||||
| `apps/backend/src/services/cost-center/cost-snapshot-sync-service.ts` | 三处临时判定全部替换为 `fulfillment_mode` |
|
||||
| `apps/backend/src/services/scheduler/cost-finance-sync-job.ts` | 每 5 分钟先归集待判定订单项,再算成本与销售事实 |
|
||||
| `apps/backend/src/services/worker-platform/sync-worker-orders-service.ts` | 订单同步结束后批量落履约方式;失败不阻断下单 |
|
||||
| 前端订单列表 / 详情 / 类型 / 显示工具 | 四种模式 + `mixed/pending`;核销码独立标签;人工按钮只认 `manualItemCount > 0` |
|
||||
|
||||
### 8.4 决策边界(写进代码注释与测试)
|
||||
|
||||
1. `fulfillment_mode` 允许短暂为 `NULL`;订单项创建时通常还没有工单/任务,不能强制非空。
|
||||
2. 上线后新订单项超过 **24 小时**仍为 `NULL`,进入异常队列;不是要求在创建瞬间就有值。
|
||||
3. 模式可以因业务改派而变化,但每次变化必须写原因与操作人(审计日志或后续事件表),禁止静默 `UPDATE`。
|
||||
4. `product_catalog.carrier_type` 只描述商品来源,**不能反推履约方式**。当前 11,349 个人工项指向 `tpl:*` 商品,就是典型反例。
|
||||
5. `voucher_state` 只做展示与核销流程,不参与任何成本/履约判断。
|
||||
6. 三种载体同时命中的订单项必须先告警人工复核;当前本地为 0。
|
||||
|
||||
### 8.5 完成情况
|
||||
|
||||
- 迁移 `114` 已在本地库执行,字段、索引、四处 CHECK 均已生效;
|
||||
- 回填后 §8.2 四项数字完全一致,`NULL = 0`;
|
||||
- 订单页、成本快照、销售事实已统一读 `fulfillment_mode`;
|
||||
- 本地联调:带已核销快手凭证的 91 人工单返回 `manual` + `voucherState=consumed`,`canClaimH5=false`;
|
||||
- 销售事实重算后四种载体收入/成本与 §8.2 完全一致;
|
||||
- 后端 `npm run check` 通过(765 项:762 通过 / 3 跳过);前端 `npm run check` 通过(143 项测试 + build);
|
||||
- 生产仍需按 §8.2 顺序执行迁移、回填、财务重算,并连续观察 7 天日检。
|
||||
|
||||
---
|
||||
|
||||
## 九、上线后运营规则(人工账单必须 100% 可监控)
|
||||
|
||||
### 9.1 角色与时限
|
||||
|
||||
| 动作 | 角色 | 时限 | 系统要求 |
|
||||
| --- | --- | --- | --- |
|
||||
| 人工单录成本 | 客服本人 | 支付后 24 小时内 | 自动带入本人备用金账户;逐单实际金额 |
|
||||
| 代录 | 管理员/财务 | 同上 | 必须明确选择客服备用金账户 |
|
||||
| 日检 | 客服主管 | 每日 10:00 | 未分类、无成本、无备用金关联均为 0 |
|
||||
| 周对账 | 财务 | 每周一 | 人工支出 vs 备用金流水金额一致 |
|
||||
| 月结 | 财务负责人 | 每月 3 日前 | 上线后区间零缺口才允许关账 |
|
||||
|
||||
### 9.2 每日必查
|
||||
|
||||
1. 上线日之后支付的订单项,超过 24 小时仍无 `fulfillment_mode`:**0**。
|
||||
2. `manual` 订单项无有效成本快照:**0**。
|
||||
3. `manual_expense_records.fund_expense_id IS NULL`(上线后):**0**。
|
||||
4. `manual_expense_records.amount_fen <> manual_expenses.amount_fen`:**0**。
|
||||
5. 人工项仍挂 `tpl:*` 商品的数量:P1 前允许存在,P1 后应改为 `man:*` 或经业务确认保留。
|
||||
|
||||
### 9.3 异常处理
|
||||
|
||||
- 发现人工单无成本:当天补录;补录仍走备用金扣款,不允许直接改余额。
|
||||
- 备用金余额不足:拒绝登记,由财务充值后再录;不允许负余额。
|
||||
- 同一订单项多笔支出:允许累加(渠道费、加急费、补差价),但每笔都必须有独立备用金流水。
|
||||
- 历史人工单:标记 `historical_missing`,不补录、不计入上线后缺口。
|
||||
|
||||
### 9.4 历史样本校准(只做参考)
|
||||
|
||||
- 每个来源抽 20–30 单,覆盖低价/中价/高价;
|
||||
- 只用于估算历史人工成本区间与毛利率,不写 `cost_snapshots`;
|
||||
- 样本结论单独记录,禁止回填成逐单成本;
|
||||
- 历史平台 53,901 项同理,只有拿到旧平台结算数据才允许按 `legacy_cost` 入账。
|
||||
|
||||
---
|
||||
|
||||
## 十、常见错误(防回归)
|
||||
|
||||
| # | 错误 | 后果 | 正确做法 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 把“有快手核销码”当成 `voucher_only` | 91 人工单全部从订单页消失 | 核销码只做状态展示 |
|
||||
| 2 | 按 `provider=91kaquan/douyin_mashida` 直接判自动发货 | 人工单被藏进自动发货 | 只看工单/任务/历史任务 |
|
||||
| 3 | 用 `product_catalog.carrier_type` 反推履约方式 | 11,349 个人工项被误认为接单平台 | 只读 `order_items.fulfillment_mode` |
|
||||
| 4 | 只加 `order_items` 字段,不改四张表的 CHECK | 写 `legacy_platform` 时运行时报错 | 按 §8.1 同时放宽 |
|
||||
| 5 | 只回填已支付历史单,不做增量兜底 | 新单长期停在 `NULL` | 24 小时定时归集 + 异常队列 |
|
||||
| 6 | 把“人工 91,606”当成“人工全量” | 与 102,955 对不上,报表互相矛盾 | 写清“全量 102,955 / 未归一 91,606” |
|
||||
| 7 | 把 `manual_expenses` 当公司人工支出表 | 两套语义混用,无法对账 | 用 `manual_expense_records` + `fund_expense_id` |
|
||||
| 8 | 用均价、工时、固定单价补历史人工 | 违背逐单实际金额,毛利失真 | 历史不补录,只做样本区间 |
|
||||
| 9 | 只改订单页,不改成本/销售事实 | 三处口径继续分叉 | 同一字段、同一决策入口 |
|
||||
| 10 | 在旧文档里保留“三块/按小时/公司直付”旧结论 | 下次开发又按旧口径实现 | 旧文只留追溯标记,现行结论只写本文件 |
|
||||
|
||||
---
|
||||
|
||||
## 十一、文档地图与维护规则
|
||||
|
||||
| 文档 | 唯一职责 | 状态 |
|
||||
| --- | --- | --- |
|
||||
| **本文件** | 履约/成本/人工口径、实施顺序、验收 SQL | **现行唯一口径** |
|
||||
| [成本中心与履约方式模型.md](成本中心与三块履约模型.md) | 商品、成本版本、快照、销售事实的数据模型 | 现行(文件名保留) |
|
||||
| [人工支出登记落地方案-2026-10-17.md](人工支出登记落地方案-2026-10-17.md) | 人工支出字段、入口、审批、权限 | 现行(P0 已实施) |
|
||||
| [客服备用金台账实施方案-2026-10-18.md](客服备用金台账实施方案-2026-10-18.md) | 备用金账户、流水、支出、编号 | 现行(P0 已实施) |
|
||||
| [财务系统集成方案.md](财务系统集成方案.md) | `finance_*` 复式总账层 | 方案(未实施) |
|
||||
| [影子总账说明.md](影子总账说明.md) | 影子总账概念、对账、转正 | 说明(未实施) |
|
||||
| [履约配置/多发货平台与电子凭证整体链路.md](履约配置/多发货平台与电子凭证整体链路.md) | 进单、履约、领取、核销链路 | 现行 |
|
||||
| [README.md](README.md) | 文档索引、分支与进度 | 现行入口 |
|
||||
|
||||
维护规则:
|
||||
|
||||
1. 履约分类只在本文定义;其他文档只链接,不复制判定 SQL。
|
||||
2. 任何数字必须注明口径:全量 / 已归一 / 未归一 / 上线后。
|
||||
3. 每次迁移后更新本文的代码基线、数据基线、§七 进度和 §8.2 数字。
|
||||
4. 新文档必须登记到 `README.md`;过期文档移入 `docs/archive/`,不得在现行文档里保留冲突结论。
|
||||
5. 不再使用“三块履约”作为分类名;`成本中心与三块履约模型.md` 只是历史文件名。
|
||||
6. 代码评审必须检查:订单页、`sales-facts-service`、`cost-snapshot-sync-service` 是否都读同一字段。
|
||||
|
||||
---
|
||||
|
||||
## 十二、上线验收查询
|
||||
## 八、上线验收查询
|
||||
|
||||
> 以下 SQL 在 `fulfillment_mode` 落地后使用;`DATE '2026-11-01'` 替换为实际上线日。
|
||||
|
||||
@@ -466,10 +227,9 @@ WHERE r.amount_fen <> me.amount_fen
|
||||
-- 6. 人工单未归一商品:P1 完成后应为 0
|
||||
SELECT COUNT(*)
|
||||
FROM order_items oi
|
||||
JOIN orders o ON o.id = oi.order_id
|
||||
WHERE oi.fulfillment_mode = 'manual'
|
||||
AND oi.canonical_product_id IS NULL
|
||||
AND COALESCE(o.paid_at, o.created_at) >= DATE '2026-11-01';
|
||||
AND oi.created_at >= DATE '2026-11-01';
|
||||
|
||||
-- 7. 核销码不得改变履约方式:带快手凭证的 91 人工单仍应为 manual
|
||||
SELECT oi.fulfillment_mode, COUNT(*)
|
||||
@@ -497,5 +257,44 @@ SELECT COUNT(*)
|
||||
FROM order_items oi
|
||||
JOIN product_catalog pc ON pc.id = oi.canonical_product_id
|
||||
WHERE oi.fulfillment_mode = 'manual'
|
||||
AND pc.carrier_type = 'work_order';
|
||||
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` 当公司人工支出表 | 两套语义混用,无法对账 | 用 `manual_expense_records` + `fund_expense_id` |
|
||||
| 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` 是否都读同一字段。
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
> 状态:**现行方案(总账层未实施)**。本文只描述 `finance_*` 总账层;当前总账表尚未建立,现有财务页仍是 `UNION ALL` 业务事件拼装。
|
||||
> 最后校准:2026-10-23
|
||||
> 代码基线:`dev @ 5d1ba115 + 迁移 103–114`
|
||||
> 代码基线:`feat/finance-rewrite @ 7557de6d + 迁移 103–107`
|
||||
> **履约方式、成本模型、人工全覆盖口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准**;本文不重复分类设计。
|
||||
> 目标:在**不破坏现有抢单、押金、结算、寄卖、提现链路**的前提下,增加可对账、可关账、可追溯的复式记账底座。
|
||||
> 结论先说:**不要直接在 `worker_wallets` / `worker_wallet_ledgers` 上继续加字段。** 现有表是业务钱包与流水,不是会计总账;正确做法是新增复式记账底座,让现有钱包成为它的“余额投影”。
|
||||
@@ -98,7 +98,7 @@
|
||||
-> 对账 / 转正
|
||||
```
|
||||
|
||||
详细设计见 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 与 [成本中心与履约方式模型.md](成本中心与三块履约模型.md)。
|
||||
详细设计见 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 与 [成本中心与履约模型.md](成本中心与履约模型.md)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ pending_material → unassigned → open → in_progress
|
||||
- 同一个商品名对应多个 `sku_code`:「指挥官密钥1个」27 个、「苏尔南钻石100颗」35 个,不能直接按 `sku_code` 做销量统计。
|
||||
- 当前 30 天订单基本是一单一品,但模型必须按订单项设计。
|
||||
|
||||
详细设计见 [成本中心与履约方式模型.md](成本中心与三块履约模型.md)。
|
||||
详细设计见 [成本中心与履约模型.md](成本中心与履约模型.md)。
|
||||
|
||||
---
|
||||
|
||||
@@ -250,7 +250,7 @@ pending_material → unassigned → open → in_progress
|
||||
- 匹配引擎:P0 + P1 已实施,P2/P3 待办。
|
||||
- 抖音三层分流:抖音侧已实施。
|
||||
6. 校正过期测试数:抖音总览中的 636/115 改为 2026-10-17 复核的 740/143。
|
||||
7. 2026-10-17 增补:新增 [成本中心与履约方式模型.md](成本中心与三块履约模型.md),把自动发货/接单平台/人工客服三块、统一成本中心、销售事实与人工 case 纳入现行设计;本报告同步补入生产只读数据与 C1~C5 风险。
|
||||
7. 2026-10-17 增补:新增 [成本中心与履约模型.md](成本中心与履约模型.md),把自动发货/接单平台/人工客服三块、统一成本中心、销售事实与人工 case 纳入现行设计;本报告同步补入生产只读数据与 C1~C5 风险。
|
||||
|
||||
仍保留为“设计/待办”的内容:
|
||||
|
||||
@@ -279,7 +279,7 @@ pending_material → unassigned → open → in_progress
|
||||
5. 余额是可变字段,缺少“由流水重算”的校验。
|
||||
6. 资金事件缺少统一的 `business_event_id` 幂等键。
|
||||
|
||||
建议:先建**影子总账 + 对账**,不要一开始替换钱包。成本金额与商品维度单独由成本中心负责,人工块由人工 case 负责;三者关系见 [成本中心与履约方式模型.md](成本中心与三块履约模型.md),总账细节见 [财务系统集成方案.md](财务系统集成方案.md) 与 [影子总账说明.md](影子总账说明.md)。
|
||||
建议:先建**影子总账 + 对账**,不要一开始替换钱包。成本金额与商品维度单独由成本中心负责,人工块由人工 case 负责;三者关系见 [成本中心与履约模型.md](成本中心与履约模型.md),总账细节见 [财务系统集成方案.md](财务系统集成方案.md) 与 [影子总账说明.md](影子总账说明.md)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user