From 43f402cf72daa3ffb90f0340139343238efc0965 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Fri, 23 Oct 2026 16:06:40 +0800 Subject: [PATCH] =?UTF-8?q?docs(finance):=20=E7=8E=B0=E8=A1=8C=E8=B4=A2?= =?UTF-8?q?=E5=8A=A1=E6=96=87=E6=A1=A3=E6=94=B6=E6=95=9B=E4=B8=BA=205=20?= =?UTF-8?q?=E7=AF=87=EF=BC=8C=E5=B1=A5=E7=BA=A6=E5=8F=A3=E5=BE=84=E5=8F=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E4=B8=80=E6=AC=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原 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 只在总纲保留一份,其它文档改为链接。 --- docs/README.md | 42 +- .../载体判定与成本中心落地-复核与草案.md | 4 +- .../人工支出登记落地方案-2026-10-17.md | 10 +- .../客服备用金台账实施方案-2026-10-18.md | 4 +- .../2026-10-23}/成本中心与三块履约模型.md | 16 +- .../财务履约改造交接总结-2026-10-23.md | 18 +- docs/人工支出与备用金.md | 218 ++++++++ docs/履约配置/多发货平台与电子凭证整体链路.md | 2 +- docs/影子总账说明.md | 4 +- docs/成本中心API契约.md | 270 ++++++++++ docs/成本中心与履约模型.md | 471 ++++++++++++++++++ docs/接单模板匹配引擎重构设计.md | 2 +- docs/财务与履约口径总纲-2026-10-23.md | 369 ++++---------- docs/财务系统集成方案.md | 4 +- docs/项目全量分析报告-2026-10-17.md | 6 +- 15 files changed, 1103 insertions(+), 337 deletions(-) rename docs/{ => archive/2026-10-23}/人工支出登记落地方案-2026-10-17.md (96%) rename docs/{ => archive/2026-10-23}/客服备用金台账实施方案-2026-10-18.md (98%) rename docs/{ => archive/2026-10-23}/成本中心与三块履约模型.md (97%) rename docs/{ => archive/2026-10-23}/财务履约改造交接总结-2026-10-23.md (86%) create mode 100644 docs/人工支出与备用金.md create mode 100644 docs/成本中心API契约.md create mode 100644 docs/成本中心与履约模型.md diff --git a/docs/README.md b/docs/README.md index 8ad95122..ca1a9d21 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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:`;历史 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 篇文档 | ## 八、维护规则 diff --git a/docs/archive/2026-10-17/载体判定与成本中心落地-复核与草案.md b/docs/archive/2026-10-17/载体判定与成本中心落地-复核与草案.md index 9e68c6a7..950a3d61 100644 --- a/docs/archive/2026-10-17/载体判定与成本中心落地-复核与草案.md +++ b/docs/archive/2026-10-17/载体判定与成本中心落地-复核与草案.md @@ -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 草案。 diff --git a/docs/人工支出登记落地方案-2026-10-17.md b/docs/archive/2026-10-23/人工支出登记落地方案-2026-10-17.md similarity index 96% rename from docs/人工支出登记落地方案-2026-10-17.md rename to docs/archive/2026-10-23/人工支出登记落地方案-2026-10-17.md index 4f477be7..ab519418 100644 --- a/docs/人工支出登记落地方案-2026-10-17.md +++ b/docs/archive/2026-10-23/人工支出登记落地方案-2026-10-17.md @@ -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) §三 为准。本文只负责“公司人工支出登记”,不重复商品、成本中心、销售事实和影子总账的完整设计。 | 本文内部阶段 | 内容 | 前置 | | --- | --- | --- | diff --git a/docs/客服备用金台账实施方案-2026-10-18.md b/docs/archive/2026-10-23/客服备用金台账实施方案-2026-10-18.md similarity index 98% rename from docs/客服备用金台账实施方案-2026-10-18.md rename to docs/archive/2026-10-23/客服备用金台账实施方案-2026-10-18.md index 921a3f09..39a18cca 100644 --- a/docs/客服备用金台账实施方案-2026-10-18.md +++ b/docs/archive/2026-10-23/客服备用金台账实施方案-2026-10-18.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 已完成“账户 + 流水 + 支出 + 对账”,不做复杂审批和总账。 diff --git a/docs/成本中心与三块履约模型.md b/docs/archive/2026-10-23/成本中心与三块履约模型.md similarity index 97% rename from docs/成本中心与三块履约模型.md rename to docs/archive/2026-10-23/成本中心与三块履约模型.md index 27947303..174e6819 100644 --- a/docs/成本中心与三块履约模型.md +++ b/docs/archive/2026-10-23/成本中心与三块履约模型.md @@ -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 diff --git a/docs/财务履约改造交接总结-2026-10-23.md b/docs/archive/2026-10-23/财务履约改造交接总结-2026-10-23.md similarity index 86% rename from docs/财务履约改造交接总结-2026-10-23.md rename to docs/archive/2026-10-23/财务履约改造交接总结-2026-10-23.md index 1499afcd..2d075d0a 100644 --- a/docs/财务履约改造交接总结-2026-10-23.md +++ b/docs/archive/2026-10-23/财务履约改造交接总结-2026-10-23.md @@ -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):文档索引与分支状态。 ## 九、交接注意 diff --git a/docs/人工支出与备用金.md b/docs/人工支出与备用金.md new file mode 100644 index 00000000..e349ad5f --- /dev/null +++ b/docs/人工支出与备用金.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) §六 diff --git a/docs/履约配置/多发货平台与电子凭证整体链路.md b/docs/履约配置/多发货平台与电子凭证整体链路.md index 527b0faf..39bfa128 100644 --- a/docs/履约配置/多发货平台与电子凭证整体链路.md +++ b/docs/履约配置/多发货平台与电子凭证整体链路.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. 目标链路 diff --git a/docs/影子总账说明.md b/docs/影子总账说明.md index af7d6149..fd7a1918 100644 --- a/docs/影子总账说明.md +++ b/docs/影子总账说明.md @@ -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_*` 表、分录、对账、转正均未实施。人工支出现在强制走客服备用金;历史人工支出不补录,上线后必须全覆盖。 diff --git a/docs/成本中心API契约.md b/docs/成本中心API契约.md new file mode 100644 index 00000000..63fea92c --- /dev/null +++ b/docs/成本中心API契约.md @@ -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` 已删除。 diff --git a/docs/成本中心与履约模型.md b/docs/成本中心与履约模型.md new file mode 100644 index 00000000..f777525a --- /dev/null +++ b/docs/成本中心与履约模型.md @@ -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:`;暂无历史工单的模板也先建商品。 +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:`,**不写 `channel_product_mappings`**。渠道映射只代表"平台 SKU 能归一到标准商品",不代表自动发货。 + +上游价格用 `apps/backend/scripts/sync-affiliate-dash-prices.ts` 批量导入(清单见 `apps/backend/scripts/data/affiliate-dash-prices.json`,38 条):按 `aff:` 补建/更新商品,并给 `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) diff --git a/docs/接单模板匹配引擎重构设计.md b/docs/接单模板匹配引擎重构设计.md index 285a823a..70c8c197 100644 --- a/docs/接单模板匹配引擎重构设计.md +++ b/docs/接单模板匹配引擎重构设计.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`;匹配业务逻辑未变)。 > > 一句话:把「订单信息 → 接单模板(价格/拼单/要求)」的匹配,从散落的多个消费点、平台判断和"店铺必填"的绑定表里,收敛为**一个平台无关的引擎**:匹配条件完全由**订单信息**表达,**店铺只是可选条件之一**,新增来源只加一个适配器 + 一组配置。 > diff --git a/docs/财务与履约口径总纲-2026-10-23.md b/docs/财务与履约口径总纲-2026-10-23.md index 10cc49c3..cb6525e7 100644 --- a/docs/财务与履约口径总纲-2026-10-23.md +++ b/docs/财务与履约口径总纲-2026-10-23.md @@ -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:`;不得因为复用了 `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:`)+ 上线后人工成本全覆盖 | 未实施 | +| 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:` 商品,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:` | **未实施** | 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` 是否都读同一字段。 diff --git a/docs/财务系统集成方案.md b/docs/财务系统集成方案.md index 1bcdaff2..39305cfd 100644 --- a/docs/财务系统集成方案.md +++ b/docs/财务系统集成方案.md @@ -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)。 --- diff --git a/docs/项目全量分析报告-2026-10-17.md b/docs/项目全量分析报告-2026-10-17.md index 7d7eff19..8ae2576a 100644 --- a/docs/项目全量分析报告-2026-10-17.md +++ b/docs/项目全量分析报告-2026-10-17.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)。 ---