feat(finance): 人工成本单事务/幂等 + 配置自动匹配 + 缺成本监控

- 确认履约改为单事务:锁订单项 → 复查快照 → 扣积分/备用金 → 写成本快照;
  快照失败整体回滚,不再出现「钱扣了、成本没记」
- manual_case 的 subject_id 统一为 order_item_id,新增订单项级唯一索引(迁移 111),
  消除 manual_expenses.id / platform_credit_entries.id 跨表撞车
- 人工 SKU 匹配增加 sku_code / sku_name 兜底;保存配置立即回填最近 30 天,
  定时任务按窗口 rematch,确认时再按 SKU 兜底匹配
- 新增 manual_cost_missing 异常队列:从首次人工配置起算,确认履约立即收口
- 补单元测试与真实 PostgreSQL 集成测试(并发、重复、强制快照失败回滚、双资金链路)
- 校准财务文档并新增上线初始化/验收手册(历史人工不补录,只保证配置后的新单)

验证:backend typecheck/lint/format/check:sql/test 843(839 pass / 4 skip);
frontend typecheck/lint/test 172;迁移 111 在 scratch 库全量执行 + 重跑幂等。
This commit is contained in:
yml2213
2026-10-26 13:04:51 +08:00
parent 73908119dc
commit a3e2bdb7c4
22 changed files with 1575 additions and 390 deletions
+11 -8
View File
@@ -1,7 +1,7 @@
# order_site 文档索引
> 最后校准:2026-10-24,代码基线 `dev @ 790de676 + 本地未提交修复`(**未推送、未部署生产**)。
> 数据基线:生产 dump `backups/postgres/order_site-20261022-092601.dump`(数据 2026-07-10 ~ 2026-10-22);本地已按真实升级路径从生产迁移 101 升到 107,并完成商品、成本快照、销售事实初始化。
> 最后校准:2026-10-26,代码基线 `dev`(合并 `dev1023` + 人工成本完整性修复,迁移到 `111`;**未推送、未部署生产**)。
> 数据基线:生产 dump `backups/postgres/order_site-20261022-092601.dump`(数据 2026-07-10 ~ 2026-10-22);本地生产副本已升级到 111,只跑了近 30 天增量。历史全量数字仅作基线,**上线验收以 [财务上线初始化与验收-2026-10-26.md](财务上线初始化与验收-2026-10-26.md) 为准**。
> 本文是 `docs/` 的唯一入口。**财务与履约口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准**;数据模型看 [成本中心与履约模型.md](成本中心与履约模型.md),接口看 [成本中心API契约.md](成本中心API契约.md)。
> 新增文档必须在下表登记状态;过期文档不再直接改写结论,统一移入 `docs/archive/`。
@@ -53,7 +53,7 @@
| 名称 | 业务含义 | 当前状态 |
| --- | --- | --- |
| `manual_expenses` | **客服备用金支出明细**:客服从自己的备用金里充值/采购/赔偿,减少客服余额 | 已实施(迁移 100);确认履约的现金支出也写这里 |
| `cost_snapshots` | **人工成本的唯一落点**:`subject_type='manual_case'`,`subject_id=manual_expenses.id` | 已实施(迁移 104) |
| `cost_snapshots` | **人工成本的唯一落点**:`subject_type='manual_case'`,`subject_id=order_item_id`(资金来源写 metadata) | 已实施(迁移 104 + 111) |
**没有独立的登记单,也没有审批与报销环节。** `manual_expense_records` / `manual_expense_events` 已随迁移 `104` 归并下线(这两张表在生产 102 上从未创建)。后续做财务系统时,人工成本的钱与账都从这条链路走,不要再另造一张人工支出表。
@@ -81,14 +81,16 @@
| 商品主数据 + 渠道 SKU 映射 | **已实施 P0/P1** | 2,017 个标准商品、4,897 条映射:`tpl:` 710、`man:` 1,195、`aff:` 49、`wo:` 63;订单项已按履约证据/人工内部 SKU 归一 |
| 成本中心 | **已实施 P0** | 迁移 `104`;710 个规则级版本 + 2 个全局占位版本;工单成本快照 151,939 条,其中 actual 129,430、estimated 22,509 |
| 销售事实 / 订单毛利 | **已实施 P0** | 迁移 `105`;375,447 项、收入 ¥3,143.61 万、成本 ¥572.95 万、毛利 ¥2,570.66 万;`sales_daily_facts.fulfillment_mode` |
| 人工成本录入(确认履约) | **已实施 P0(2026-10-24 入口收敛到订单页)** | 人工履约页已下线;确认即扣积分/备用金并写 `actual` 快照;无登记单/审批/报销 |
| 人工成本录入(确认履约) | **已实施 P0(2026-10-26 单事务 + 防重复)** | 确认即扣积分/备用金并写 `actual` 快照;钱与成本同事务、订单项级唯一;无登记单/审批/报销 |
| 人工 SKU 配置自动匹配 | **已实施(2026-10-26)** | 保存配置立即回填 + 定时 rematch + 确认时按 SKU 兜底匹配 |
| 人工缺成本监控 | **已实施(2026-10-26)** | `manual_cost_missing`:首次人工配置之后的订单项未确认即入队,确认后自动收口 |
| 履约方式统一 | **已实施 P0** | 迁移 `105`;375,447 项全部回填:接单平台 153,148、人工 115,194、历史平台 53,901、自动发货 53,204 |
| 人工商品归一 | **已实施 P1(本次快照恢复执行)** | 人工 115,194 项已归一为 1,195 个 `man:<内部SKU>` 商品;历史人工支出仍不补录 |
| 影子总账 | 未实施 | 现有财务页仍是 `UNION ALL` 业务事件拼装,不是复式总账 |
| 成本异常归档 | **已实施** | 迁移 `108`;已处理异常超 60 天搬入 `cost_exceptions_archive`,open 行不归档;`COST_EXCEPTION_RETENTION_DAYS` 可调 |
| 退款冲减 / 平台对账 / 期间关账 | 未实施 | 仅方案 |
> 分支状态(2026-10-25):`dev @ 465a9f65` 之上完成迁移收敛(10 → 5,`103`–`107`),并已与 `main` 的 worker-hall 改动合并。本地库按「清 `schema_migrations` 103–112 后重跑」对齐,升级结果与全新构建的 schema 完全一致;生产 101 → 107 的路径已验证。**未推送、未部署生产**;生产口径仍以 `main` 为准。
> 分支状态(2026-10-26):`dev` 已合并 `dev1023` 与人工成本完整性修复,迁移到 `111`;本地生产副本已验证 101 → 111(109/110 索引在生产规模数据上实测 < 0.3s),临时库全量执行与重跑幂等通过。**未推送、未部署生产**;生产口径仍以 `main` 为准。上线前按 [财务上线初始化与验收-2026-10-26.md](财务上线初始化与验收-2026-10-26.md) 初始化资金与人工 SKU。
> 财务与履约口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准。
## 三、统一实施顺序
@@ -98,8 +100,8 @@
1. 商品主数据 + 渠道 SKU 映射 已完成 P0(仅自动发货/接单平台)
2. 成本中心 + 销售事实 已完成 P0
2.5 人工成本链路收敛(确认履约 → 扣积分/备用金 → 写快照) 已完成 P0(迁移 104/107)
3. 人工商品归一与上线后人工成本全覆盖 未实施(下一阶段)
4. 退款冲减与异常队列 未实施
3. 人工商品归一与上线后人工成本全覆盖 已完成代码;生产数据需上线后配置 SKU(历史不补录)
4. 退款冲减与异常队列 已完成手工退款;平台退款 API 未实施;人工缺成本已入队
5. 影子总账(订单、工单、押金、提现、寄卖、售后) 未实施
6. 平台对账、报表、期间关账 未实施
```
@@ -126,9 +128,10 @@
| 文档 | 状态 | 用途 |
| --- | --- | --- |
| [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) | **现行唯一口径** | 履约方式、成本模型、人工全覆盖、历史策略、实施顺序、运营日检与验收 SQL |
| [成本中心与履约模型.md](成本中心与履约模型.md) | 现行 | 商品、成本版本、快照、异常、销售事实的数据模型;迁移 `103`–`107`;履约回填;迁移基线与本地库同步;索引与性能 |
| [成本中心与履约模型.md](成本中心与履约模型.md) | 现行 | 商品、成本版本、快照、异常、销售事实的数据模型;迁移 `103`–`111`;履约回填;迁移基线与本地库同步;索引与性能 |
| [成本中心API契约.md](成本中心API契约.md) | 现行 | 成本中心接口字段替换总表、枚举、模块边界(后端 5 模块 + 前端 4 个固定 Tab) |
| [人工支出与备用金.md](人工支出与备用金.md) | 现行(P0 已实施) | 确认履约流程/权限、客服备用金台账、本人账户与编号规则 |
| [财务上线初始化与验收-2026-10-26.md](财务上线初始化与验收-2026-10-26.md) | **现行上线手册** | 迁移后资金/人工 SKU 初始化、验收 SQL、日常监控 |
| [财务系统集成方案.md](财务系统集成方案.md) | 现行方案(总账未实施) | 复式记账、总账表、对账、关账 |
| [影子总账说明.md](影子总账说明.md) | 现行说明(未实施) | 影子总账是什么、和钱包流水的区别、对账与转正标准 |
| [项目全量分析报告-2026-10-17.md](项目全量分析报告-2026-10-17.md) | 历史参考 | 2026-10-17 架构与风险;财务口径以总纲为准 |
+55 -59
View File
@@ -1,7 +1,7 @@
# 人工支出与客服备用金
> 状态:**现行(P0 已实施,未部署生产)**。
> 最后校准:2026-10-24
> 状态:**现行(P0 已实施,待部署生产)**。
> 最后校准:2026-10-26(人工确认改单事务、subject_id 归一、配置自动匹配、人工缺成本入队列)
> **2026-10-24 变更**:独立「人工履约」页面已下线,订单列表/详情里的「人工录单」改为「确认履约」;`/finance/manual-order-cost*` 接口已删除。资金链路不变(扣积分/备用金 → 写 `manual_case` 快照)。
> 代码基线:`dev @ 43f402cf + 迁移 100/101/104/107`(已合并到 `dev`,**未推送、未部署生产**)
> **业务口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准**;本文只负责人工录单的字段、流程、权限与备用金台账。
@@ -16,13 +16,13 @@
```text
订单列表/详情「确认履约」
→ 扣客服积分或备用金余额(写 fund_entries / platform_credit_entries)
→ 写 cost_snapshots(subject_type='manual_case',subject_id=manual_expenses.id)
→ 写 cost_snapshots(subject_type='manual_case',subject_id=order_item_id)
```
| 表 | 业务含义 | 状态 | 关键字段 |
| --- | --- | --- | --- |
| `manual_expenses` | **客服备用金支出**:客服从自己的备用金账户里充值/采购/赔偿 | 已实施(迁移 `100`) | `account_id` / `fund_entry_id` / `category` / `payee_*` / `order_item_id` / `evidence_json` |
| `cost_snapshots` | **人工成本的唯一落点** | 已实施(迁移 `104`) | `subject_type='manual_case'` / `subject_id=manual_expenses.id` / `total_amount_fen` / `snapshot_stage='actual'` |
| `cost_snapshots` | **人工成本的唯一落点** | 已实施(迁移 `104` + 完整性兜底 `111`) | `subject_type='manual_case'` / `subject_id=order_item_id` / `total_amount_fen` / `snapshot_stage='actual'`;资金来源写 `metadata_json.fundExpenseId / creditEntryId` |
结论:
@@ -40,7 +40,7 @@
1. **人工成本逐单、按实际金额。** 不按小时、不按固定单量、不按固定单价。
2. **人工支出从客服备用金账户扣款。** 余额不足直接拒绝,不允许负余额。
3. **客服角色只能使用自己的备用金账户。** 后端越权直接 403;管理员/财务可代录到指定客服账户。
4. **历史人工支出找不回来,直接放弃。** 不补录、不做样本校准;历史缺口标记 `historical_missing`,不阻塞上线。
4. **历史人工支出找不回来,直接放弃。** 不补录、不做样本校准;历史缺口只计入 `cost_missing_count`,不阻塞上线。
5. **上线后每一笔人工单必须 100% 有成本记录**,否则不允许关账。
6. **录入必须"发生在垫付当天"才有监管价值**;事后补记只能算账,不能算监管。
7. **客服备用金是周转金台账,不是报销系统。** P0 只做"账户 + 流水 + 支出 + 对账",不做复杂审批和总账。
@@ -89,75 +89,71 @@
批量发放/对账、导出、低余额提醒、大额支出主管确认;支出凭证目前只校验数组非空,未校验对象存储 key 是否真实存在。
> 原「异常队列」指人工支出登记单的审批异常队列,随登记链下线而取消。现在的人工异常只有一类:人工录单匹配不到有效成本版本时写 `manual_expense_cost_missing`,由成本中心的异常面板统一处理。
> 原「异常队列」指人工支出登记单的审批异常队列,随登记链下线而取消。现在的人工异常统一为 `manual_cost_missing`:**首次创建人工履约配置之后**的订单项,只要还没有人工成本快照就进入队列;确认履约写入快照后自动 `resolved`。历史人工单不进入该队列。
---
## 三、人工录单
## 三、确认履约
### 3.1 落点
- 服务:`apps/backend/src/services/finance/manual-order-cost-service.ts`;备用金扣款唯一写入口 `admin-fund-service.ts` 的 `writeFundExpense`
- 接口:`apps/backend/src/routes/admin/finance.ts` 的 `POST /finance/manual-order-costs`;成本项选项 `GET /finance/manual-order-cost-items`
- 组件:`apps/frontend/src/components/admin/ManualOrderCostModal.tsx`
- 订单入口:订单列表/详情「人工录单」打开弹窗,锁定订单号、选择订单项与备用金账户、填写本单实际金额;支持深链 `/admin/orders/:orderId?manualCost=1`
- 权限:登记 `fund.expense` / `finance.config`;客服角色自动锁定本人账户
- **已删除**:迁移 `106_manual_expense_records.sql` 建的登记单表(迁移 `104` 归并下线)、`manual-expense-service.ts`、`AdminManualExpensePage.tsx`、`/admin/manual-expenses` 页面与菜单
- 服务:`apps/backend/src/services/finance/manual-fulfillment-service.ts`
- 接口:`apps/backend/src/routes/admin/finance.ts` 的 `POST /finance/manual-fulfillment/confirm`;待办 `GET /finance/manual-fulfillment/pending`
- 组件:`apps/frontend/src/components/admin/ManualFulfillmentConfirmModal.tsx`
- 订单入口:订单列表/详情「确认履约」
- 权限:`admin / operator / support`;客服只能使用本人备用金/积分账户
- 配置前置:商品与成本 → 人工 SKU 配置(`manual_fulfillment_configs`)
### 3.2 资金流与成本流(同一事务)
```text
锁定 fund_accounts 行
→ 校验余额
→ 扣减余额
→ 写 fund_entries
→ 写 manual_expenses(order_id / order_item_id 已带上)
→ 解析成本版本
→ 写 cost_snapshots(actual)
BEGIN
锁定 order_items 行(FOR UPDATE OF oi)
→ 复查是否已有 manual_case 快照(有则 409,不扣款)
→ 按 SKU 自动匹配人工履约配置(配置晚于下单也能挂上)
→ 解析成本项/成本版本(版本缺失在扣款前失败)
→ 扣积分(platform_credit_entries)或扣备用金(fund_entries + manual_expenses)
→ 写 cost_snapshots(actual,subject_id = order_item_id)
COMMIT -- 任一步失败整体回滚
```
余额不足直接拒绝,**不允许负余额**。扣钱与记成本在同一个事务里,要么都成功要么都回滚。
- 余额不足直接拒绝,**不允许负余额**;
- 重复点击/并发请求:订单项行锁 + `uq_cost_snapshots_manual_order_item` 唯一索引双重兜底,只有一个请求能成功扣款;
- 同一个订单项最多一条有效人工成本快照;不同订单项(包括现金与积分两条链路)不会因为 id 撞车互相覆盖。
### 3.3 成本快照
以 `subject_type='manual_case'`、`subject_id=manual_expenses.id` 写 `actual` 快照,金额 = 录入的实际金额。
同一订单项多笔支出可累加,不会互相覆盖(唯一键是 `subject_type + subject_id + cost_item_id`,每笔支出单一个主体)。快照 `metadata_json` 里记录处理人、收款方、付款方式、费用类型、凭证、履约方式。
没有匹配到有效成本版本时,写 `manual_expense_cost_missing` 异常(`status='open'`),**不允许静默记 0**;此时备用金已经扣了,界面会提示"已扣备用金,但未匹配到有效成本版本,已进入异常队列"。
- `subject_type='manual_case'`、`subject_id=order_item_id`、`order_item_id` 同值;
- 金额:固定成本 = `credit_quantity × 平台换算率`;成本不固定 = 客服填写的实际金额;
- `metadata_json` 记录 `fundExpenseId / creditEntryId / subjectSource / platformKey / creditQuantity / costFen / paidBy` 等;
- `uq_cost_snapshots_manual_order_item` 保证一个订单项只有一条有效快照(迁移 `111`)。
### 3.4 录入字段
```text
order_id, order_item_id -- 订单与订单项(锁定订单号,不允许改)
fund_account_id -- 备用金账户;客服只能本人
amount_fen -- 实际金额(分),> 0
expense_category -- goods_purchase / compensation / refund_advance /
-- service_fee / freight / recharge / other
payee_type, payee_name, payee_account
payment_method -- wechat / alipay / bank / cash / platform_balance / other
note -- 备注兼凭证(无附件时)
evidenceJson -- 凭证(对象存储 key / 流水号 / 聊天记录 key)
order_item_id -- 订单项(必填;弹窗内锁定)
costFen -- 仅成本不固定的 SKU 必填,> 0(分)
payeeName -- 收款方(选填,默认「人工履约」)
note -- 备注(选填)
```
费用类型到备用金分类的映射:`goods_purchase→purchase`、`compensation→compensation`、`freight→freight`、`recharge→platform_recharge`,其余 → `other`。
平台、消耗积分与固定成本都来自人工 SKU 配置,客服不选账户、不改平台。
### 3.5 凭证
### 3.5 配置与监控
凭证**至少留一项**可追溯信息(对象存储 key、流水号、聊天记录 key,或备注文字),否则后端拒绝。
- 人工 SKU 的匹配键:`spec_json.skuNick → spec_json.outSkuSn → sku_code → sku_name`;没有前两级的历史单也能按后两级配置。
- 保存配置后立即回填该 SKU 最近 30 天的订单项;定时任务每 5 分钟对最近窗口内未匹配项兜底;更早历史需显式「重新匹配」。
- 未配置 SKU 的订单项进入 `manual_cost_missing`(`detail.configMissing=true`),配置后自动匹配;确认履约后异常自动收口。
---
## 四、订单页「确认履约」
订单里的数据足够预填 90%:店铺、商品、规格、数量、用户实付、支付时间、平台单号、履约标签、状态,后台订单列表/详情已经全部具备。客服实际只需要补 **4 件事**:渠道/项目、金额、收款方、凭证(外加默认带出的「谁、什么时候」)。
弹窗规则:
- 锁定订单号,不允许改;
- 必须选择备用金账户(客服角色自动带入本人账户且不可改);
- 金额按本单实际金额填写,**没有小时单价或固定单量**;
- 录入即生效:扣备用金与写成本快照一次完成,不再有"提交审核"步骤。
- 弹窗只展示已配置履约渠道的人工项;固定成本零输入,成本不固定才要求填金额;
- 锁定订单与订单项,不允许改平台/账户;
- 确认即扣钱并写成本,没有审核/报销步骤;
- 若 SKU 还没配置,弹窗会提示先去「商品与成本 → 人工 SKU 配置」补配置;
- 重复确认会被拒绝,不会重复扣款。
---
@@ -165,16 +161,16 @@ evidenceJson -- 凭证(对象存储 key / 流水号 / 聊天记
| 指标 | 目标 |
| --- | ---: |
| 人工订单项无备用金支出单 | 0 |
| 人工订单项无成本快照 | 0 |
| 人工支出金额与备用金流水不一致 | 0 |
| 成本异常 open 中人工项 | 0(历史标记除外) |
| 人工订单项无资金流水(现金备用金支出 / 积分消耗流水) | 0 |
| 人工订单项无有效成本快照 | 0 |
| 现金人工成本金额与 manual_expenses 不一致 | 0 |
| `manual_cost_missing` open(上线后) | 0(历史人工单不计入) |
关账门槛:
- 存在人工单无成本记录:**不允许关账**;
- 存在人工单未扣备用金:**不允许关账**;
- 存在人工支出与备用金流水金额不一致:**不允许关账**。
- 存在上线后人工单无成本记录:**不允许关账**;
- 存在上线后人工单没有对应资金流水(现金或积分):**不允许关账**;
- 存在现金人工支出与备用金流水金额不一致:**不允许关账**。
核对 SQL 见 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) §八。
@@ -182,11 +178,11 @@ evidenceJson -- 凭证(对象存储 key / 流水号 / 聊天记
## 六、异常处理
- 发现人工单无成本:当天补录;补录走人工录单,仍然从备用金扣款,不允许直接改余额。
- 备用金余额不足:拒绝录入,由主管补拨后再录;不允许负余额。
- 人工录单匹配不到成本版本:备用金已扣,成本异常进队列;补齐 `sys_manual_service` 的有效版本后重新解析。
- 同一订单项多笔支出:允许累加(渠道费、加急费、补差价),但每笔都必须有独立备用金流水。
- 历史人工单:标记 `historical_missing`,找不回来、**直接放弃**,不补录、不计入上线后缺口。
- 发现人工单无成本:确认履约;扣款与写成本同事务,失败整体回滚,不会出现「扣了钱没记成本」。
- 备用金/积分不足:拒绝确认,由主管补拨/充值后再确认;不允许负余额。
- 人工 SKU 未配置:进入 `manual_cost_missing` 队列,配置后自动回填订单项,再确认即可。
- 确认后需要更正:当前不提供反审核;只能通过后续冲销/调整处理(P1),不要直接改库。
- 历史人工单:找不回来、**直接放弃**,不补录、不计入上线后监控缺口。
---
@@ -196,7 +192,7 @@ evidenceJson -- 凭证(对象存储 key / 流水号 / 聊天记
- 不补录、不回填 `cost_snapshots`;
- 不做样本抽取与均价估算;
- 不阻塞上线,历史缺口在销售事实里标记 `historical_missing`;
- 不阻塞上线,历史缺口计入销售事实的 `cost_missing_count`;
- 只保证**上线后**的新单不丢:人工单必须有备用金支出与成本快照。
历史平台 53,901 项同理,只有拿到旧平台结算数据才允许按历史成本入账。
@@ -1,6 +1,6 @@
# 多发货平台与电子凭证整体链路
> 状态:现行。最后校准:2026-10-23(代码基线 `dev @ 5d1ba115 + 迁移 `103`–`107`;履约方式已统一落库)。
> 状态:现行。最后校准:2026-10-23(代码基线 `dev`(合并 dev1023 后)+ 迁移 `103`–`111`;履约方式已统一落库)。
> 财务与履约总口径见 [财务与履约口径总纲-2026-10-23.md](../财务与履约口径总纲-2026-10-23.md)。
> 来源口径:本系统只有三个订单来源——`kuaishou-91`、`pdd`、`douyin-mashida`。**91 回调与快手 send-code 是同一个 `kuaishou-91` 来源的两条数据通道**(91=骨架权威源,快手 send-code=补全/兜底源),不是一个来源加另一个来源。
+1 -1
View File
@@ -2,7 +2,7 @@
> 状态:**现行说明(未实施)**
> 最后校准:2026-10-23
> 代码基线:`dev @ 43f402cf + 迁移 `103`–`107`(已合并到 `dev`,**未推送、未部署生产**)
> 代码基线:`dev`(合并 dev1023 后)+ 迁移 `103`–`111`(已合并到 `dev`,**未推送、未部署生产**)
> 配套方案:[财务系统集成方案.md](财务系统集成方案.md)
> **总口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准。**
> 前置:统一履约事实、商品主数据、渠道映射、成本中心与销售事实;实施顺序以总纲第六节为准。
+6 -1
View File
@@ -2,7 +2,7 @@
> 状态:**现行**。后端服务与前端页面都必须严格按此实现,不得各自发挥。
> 最后校准:2026-10-25
> 代码基线:`dev @ 465a9f65` + 迁移 `103`–`107`(已合并到 `dev`,**未推送、未部署生产**)
> 代码基线:`dev`(合并 dev1023 后)+ 迁移 `103`–`111`(已合并到 `dev`,**未推送、未部署生产**)
> 详细业务口径见 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md);数据模型见 [成本中心与履约模型.md](成本中心与履约模型.md)。
## 一、字段替换总表
@@ -204,6 +204,11 @@ AND (
}
```
`reason` 取值:`product_mapping_missing`(商品未归一)、`cost_missing`(非人工项缺成本版本)、
`manual_cost_missing`(人工项尚未确认履约/未配置渠道)。`manual_cost_missing` 只监控
「第一次创建人工履约配置之后」的订单项;确认履约写入 `manual_case` 快照后自动 `resolved`。
人工成本快照的 `subjectId` / `subject_id` 统一为 `order_item_id`。
## 七、销售事实接口
`GET /api/v1/admin/sales-profit` 的分组维度字段名从 `carrierType` 改为 `fulfillmentMode`:
+26 -14
View File
@@ -1,9 +1,10 @@
# 成本中心与履约模型
> 状态:**现行**。商品主数据、成本中心、成本快照、销售事实、履约事实均已落地。
> 最后校准:2026-10-25
> 代码基线:`dev @ 465a9f65 + 迁移合并`(**未推送、未部署生产**)
> 数据基线:生产 dump `order_site-20261022-092601.dump`(2026-07-10 ~ 2026-10-22);本地已升级到迁移 107,并完成商品、映射、成本快照与销售事实初始化
> 最后校准:2026-10-26
> 代码基线:`dev`(合并 dev1023 + 人工成本完整性修复,迁移到 `111`)
> 数据基线:生产 dump `order_site-20261022-092601.dump`(2026-07-10 ~ 2026-10-22);本地只跑了近 30 天增量,历史数字仅作基线。
> 上线初始化/验收见 [财务上线初始化与验收-2026-10-26.md](财务上线初始化与验收-2026-10-26.md)。
> **业务口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准**;本文只负责数据模型、迁移与运维。
> 接口字段见 [成本中心API契约.md](成本中心API契约.md)。
@@ -36,9 +37,9 @@ orders ──┬── order_items ──── fulfillment_mode (唯一履约
原 `dev` 分支上,商品、成本中心、人工履约用 10 个迁移(`103`–`112`,1,275 行)分批落地,字段反复摇摆。其中:
- `108`(下线人工支出登记)与 `109`(隔离历史快照主体)在「生产 102 → 当前」的唯一升级路径上是**空操作**;
- `111` 是给 `104` 自身种子打的补丁(draft 占位改 active 占位);
- `112` 的 `resolution_note` 列本就属于异常表。
- 原 `108`(下线人工支出登记)与原 `109`(隔离历史快照主体)在「生产 102 → 当前」的唯一升级路径上是**空操作**;
- 原 `111` 是给 `104` 自身种子打的补丁(draft 占位改 active 占位);
- 原 `112` 的 `resolution_note` 列本就属于异常表。
2026-10-25 收敛为 5 个(合计 1,151 行):
@@ -50,9 +51,18 @@ orders ──┬── order_items ──── fulfillment_mode (唯一履约
| `106_order_refunds.sql` | `order_refunds` 退款表 | 38 |
| `107_manual_fulfillment.sql` | 人工 SKU 登记/配置 + 三平台积分钱包 + 备用金账户编号可读化 | 292 |
后续增量(不参与「10 合 5」的收敛):
| 迁移 | 内容 |
| --- | --- |
| `108_cost_exception_archive.sql` | 成本异常 60 天归档 |
| `109_work_order_share_participation.sql` | 拼单多次参与份额模型(与本模块无关,同批上线) |
| `110_work_order_hall_and_shop_indexes.sql` | 大厅置顶/店铺筛选索引(与本模块无关) |
| `111_manual_cost_integrity.sql` | 人工成本单事务兜底:订单项级唯一、`subject_id=order_item_id`、人工 SKU 匹配索引、`manual_cost_missing` 监控所需索引 |
**合并原则**:同一批上线的结构性 DDL 才合并;数据回填与业务口径注释跟着所属表走。`103` 保持独立(商品主数据是成本与销售事实的共同上游),`106` 保持独立(退款是独立业务线)。
**关于历史人工快照的负数 `subject_id`(重要坑,已固化在 `104` 的列注释里)**:人工成本快照的主体从「人工支出登记单 id」改成「客服备用金支出单 id」(`manual_expenses.id`),两套 id 空间各自从 1 开始会撞车。撞上唯一索引 `uq_cost_snapshots_active_subject_item` 后,`INSERT ... ON CONFLICT DO NOTHING` 会静默跳过——**钱从备用金扣了、成本快照却没写成**。历史孤儿快照的主体一律用负数,与正数的 `manual_expenses.id` 彻底隔离;新建链路不再产生负数主体。
**人工成本快照主体(2026-10-26 定稿)**:`manual_case` 的 `subject_id` 统一为 `order_item_id`,不再使用 `manual_expenses.id` / `platform_credit_entries.id`(两张表各自 BIGSERIAL,必然撞 id)。资金来源只放 `metadata_json.fundExpenseId / creditEntryId`。迁移 `111` 增加订单项级唯一索引 `uq_cost_snapshots_manual_order_item`,一个订单项最多一条有效人工快照;`subject_id` 为正数的历史行归一到订单项,负数历史孤儿快照按迁移 `104` 约定保留、不参与新链路。
### 1.1 字段替换对照
@@ -256,8 +266,9 @@ resolved_at, superseded_at, created_at
- 预估快照代表支付时点的成本,**不因后续改价重写**;只有实际成本允许替代当前有效行;
- 匹配不到成本时写 `cost_missing` 异常,**不能默认 0**;
- 履约方式不存快照里,需要时 join `order_items.fulfillment_mode`;
- 人工单的快照主体是 `subject_type='manual_case'` + `subject_id=manual_expenses.id`(客服备用金支出单),**没有独立的登记单表**;
- 负数 `subject_id` 表示历史孤儿快照(约定固化在迁移 `104` 的列注释里),金额保留供毛利参考,不与新链路争主体 id。
- 人工单的快照主体是 `subject_type='manual_case'` + `subject_id=order_item_id`;资金流水 id 只写 `metadata_json`,**没有独立的登记单表**;
- 负数 `subject_id` 表示历史孤儿快照(迁移 `104` 约定),迁移 `111` 保留不动,不参与新链路;
- 一个订单项最多一条有效人工快照(`uq_cost_snapshots_manual_order_item`)。
### 3.5 `cost_exceptions`
@@ -265,7 +276,7 @@ resolved_at, superseded_at, created_at
id
subject_type, subject_id
order_item_id, work_order_id
reason -- product_mapping_missing / cost_missing / manual_expense_cost_missing
reason -- product_mapping_missing / cost_missing / manual_cost_missing
status -- open / resolved / ignored
detail_json
created_at, resolved_at, resolved_by
@@ -386,7 +397,8 @@ 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`。
> 上述计数是 2026-10-23 历史回填的结果,仅作迁移正确性参照;当前本地生产副本只跑了近 30 天增量,**不要用这些数字判断上线是否完成**。
> 本地验收(2026-10-23 重写后重跑):`work_order` 118,053 / `manual` 102,955 / `legacy_platform` 53,901 / `affiliate_dash` 41,676,四项相加 = 316,585,`NULL = 0`。
### 6.3 决策边界(写进代码注释与测试)
@@ -419,11 +431,11 @@ WHERE oi.id = d.id
生产尚未执行过 `103` 及之后的迁移。本地库与代码对齐有两条路径:
1. **全新升级**:从生产 dump 恢复到 `101` / `102`,直接 `npm run db:migrate` 升到 `107`;
1. **全新升级**:从生产 dump 恢复到 `101` / `102`,直接 `npm run db:migrate` 升到 `111`;
2. **已有本地库**:清掉 `schema_migrations` 里 `103`–`112` 的记录后重跑。所有迁移都是 `IF NOT EXISTS`,数据步骤幂等:
```sql
DELETE FROM schema_migrations WHERE name ~ '^(10[3-9]|11[0-2])_';
DELETE FROM schema_migrations WHERE name ~ '^(10[3-9]|11[0-1])_';
```
2026-10-25 实测:按第 2 条路径升级后的库,与全新构建的库 **schema 完全一致**(`pg_dump --schema-only` 仅随机 token 不同),种子数据(3 个系统成本项、2 个全局占位版本、4 个人工履约档案)也一致。
@@ -486,7 +498,7 @@ npm run revenue:backfill -- --batch-size=2000 --max-batches=100
归档不删除审计痕迹,但热表查询与页面上不会再出现这些历史行;需要追溯时查归档表。
> **2026-10-25 已执行一次性清理**:`--days=0` 口径归档 151,939 条 resolved 异常(152 批,3.9 秒)。
> **2026-10-25 已执行一次性清理(历史库)**:`--days=0` 口径归档 151,939 条 resolved 异常(152 批,3.9 秒)。该数字只作历史记录。
> 热表 260,253 → 108,314 行(全部为 open),`VACUUM FULL` 后体积 155 MB → 51 MB(heap 38 / idx 13),
> 归档表 61 MB;主键重叠校验 0 条。生产环境不要在业务时段跑 `VACUUM FULL`,日常由 autovacuum 复用空间即可。
+1 -1
View File
@@ -1,6 +1,6 @@
# 接单模板匹配引擎重构设计
> 目标读者:后端 / 后台运营。状态:**P0 + P1 已实施;P2 / P3 待办**。最后校准:2026-10-23(代码基线 `dev @ 43f402cf + 迁移 `103`–`107`;匹配业务逻辑未变)。
> 目标读者:后端 / 后台运营。状态:**P0 + P1 已实施;P2 / P3 待办**。最后校准:2026-10-23(代码基线 `dev`(合并 dev1023 后)+ 迁移 `103`–`111`;匹配业务逻辑未变)。
>
> 一句话:把「订单信息 → 接单模板(价格/拼单/要求)」的匹配,从散落的多个消费点、平台判断和"店铺必填"的绑定表里,收敛为**一个平台无关的引擎**:匹配条件完全由**订单信息**表达,**店铺只是可选条件之一**,新增来源只加一个适配器 + 一组配置。
>
@@ -0,0 +1,233 @@
# 财务/成本上线初始化与验收(2026-10-26)
> 状态:现行操作手册。适用于 `dev` 合并后的财务/成本模块上线;**历史人工成本不补录**。
> 代码基线:迁移 `103`–`111`(本手册写于人工成本完整性与配置自动匹配修复之后)。
> 数据基线:生产 dump `2026-10-22`(迁移 `101`)。历史人工支出找不回来,只保证**首次配置人工履约之后**的新单可记录、可监控。
---
## 〇、上线范围与边界
本次上线的是「备用金 + 人工成本 + 成本中心 + 销售事实」模块,不是复式总账:
| 能力 | 本次是否上线 |
| --- | --- |
| 客服备用金账户/流水/支出/对账 | 是(数据需上线后初始化) |
| 人工 SKU 配置 + 确认履约(扣积分/备用金 + 写 actual 快照) | 是(配置需上线后设置) |
| 商品目录、渠道映射、成本版本、成本快照、销售事实 | 是 |
| 手工退款登记(冲减收入、关闭成本异常) | 是 |
| 平台退款 API、影子总账、对账中心、期间关账 | **否**(后续 P3) |
明确不做:
- 历史人工支出不补录、不按均价估算;
- 历史人工单的 `cost_missing` 不进入上线后监控口径;
- 人工成本监控从**第一次创建人工履约配置的时间**开始计算(`manual_fulfillment_configs.created_at`)。
---
## 一、上线顺序(必须按序)
### 1. 备份 + 迁移
```bash
# 生产机
cd /home/order_site
bash deploy/ubuntu-deploy.sh --backup --dry-run # 先看计划
bash deploy/ubuntu-deploy.sh --backup # 迁移前 pg_dump + OSS,再重建/重启
```
后端启动会自动执行 `102`–`111`。确认:
```sql
SELECT name FROM schema_migrations ORDER BY id DESC LIMIT 5;
-- 期望包含:111_manual_cost_integrity / 110_work_order_hall_and_shop_indexes / 109_work_order_share_participation
```
> 迁移 `109`/`110`/`111` 都是增量、幂等;`111` 会给 `orders(375k)` 建表达式索引,实测写阻塞约 200–300ms。
### 2. 初始化客服资金(备用金 + 积分)
上线后由财务/主管在后台完成:
1. **备用金账户**:钱包管理 → 客服账户 → 「补建客服账户」(或调用 `POST /api/v1/admin/fund/accounts/ensure-agents`)。
2. **拨付备用金**:钱包管理 → 对应账户 → 「发放」,把周转金拨给客服;余额不足时人工确认会直接拒绝。
3. **积分充值**:商品与成本 → 积分钱包 → 选择客服/平台 → 「发放」(南航/乐玩 1 积分 = 1 分,ROCK 1 积分 = 1 元,以 `fulfillment_profiles.config_json.rechargeUnitPriceFen` 为准)。
4. 要求:客服只能使用**本人**备用金账户和积分账户;管理员代录也使用自己的账户(当前实现如此)。
校验:
```sql
-- 每个客服都应有 agent 账户;余额按实际拨付为准
SELECT au.username, fa.account_no, fa.balance_fen, fa.status
FROM admin_users au
LEFT JOIN fund_accounts fa
ON fa.owner_admin_user_id = au.id AND fa.account_type = 'agent'
WHERE au.role = 'support'
ORDER BY au.username;
-- 三平台积分账户余额
SELECT pca.platform_key, au.username, pca.balance_quantity
FROM platform_credit_accounts pca
JOIN admin_users au ON au.id = pca.owner_admin_user_id
ORDER BY pca.platform_key, au.username;
```
### 3. 扫描并配置人工 SKU
入口:商品与成本 → 人工 SKU 配置。
1. 选择日期范围(建议上线前 30 天)→ 「扫描」;没有 `spec_json.skuNick/outSkuSn` 的单会按 `sku_code → sku_name` 兜底,不再漏扫。
2. 逐项配置:平台(南航/乐玩/ROCK/备用金现金)+ 消耗积分(0 = 成本不固定,客服确认时手填)+ 启用。
3. **保存后会自动重新匹配该 SKU 最近 30 天的订单项**,不需要再点「重新匹配」;定时任务每 5 分钟会对最近窗口内未匹配人工项做一次兜底回填;更早历史不自动回填。
4. 未配置的 SKU 会进入成本异常队列(`manual_cost_missing`,`detail.configMissing=true`),按队列把配置补齐即可。
批量检查缺口:
```sql
-- 已配置 / 未配置的人工 SKU(按首次配置之后的订单项)
SELECT
COUNT(*) FILTER (WHERE oi.fulfillment_config_id IS NULL) AS unconfigured_items,
COUNT(*) AS manual_items
FROM order_items oi
WHERE oi.fulfillment_mode = 'manual'
AND oi.created_at >= (SELECT MIN(created_at) FROM manual_fulfillment_configs);
-- 需要补配置的 SKU(按订单量排序)
SELECT
COALESCE(NULLIF(oi.spec_json->>'skuNick',''), NULLIF(oi.spec_json->>'outSkuSn',''),
NULLIF(oi.sku_code,''), NULLIF(oi.sku_name,'')) AS sku_key,
MAX(oi.sku_name) AS sku_name,
COUNT(*) AS items
FROM order_items oi
WHERE oi.fulfillment_mode = 'manual'
AND oi.fulfillment_config_id IS NULL
AND oi.created_at >= (SELECT MIN(created_at) FROM manual_fulfillment_configs)
GROUP BY 1
ORDER BY items DESC;
```
> 配置是全量生效的:保存后历史未匹配项也会被回填(只填空值),但历史成本仍不会补录。
### 4. 确认人工履约(日常)
订单列表/详情 → 「确认履约」:
- 固定成本 SKU:零输入,直接扣对应积分/备用金;
- 成本不固定 SKU:填写本次实际成本(分);
- 扣款、写 `manual_expenses`/积分流水、写 `manual_case` 成本快照在**同一个事务**里;
- 重复点击/并发请求只能成功一次(订单项行锁 + 唯一索引兜底),失败不会重复扣款;
- 成功后可重复确认会被拒绝。
监控:
```sql
-- 上线后人工项:已配置但未确认(客服待办)
SELECT COUNT(*) AS pending_confirmed
FROM order_items oi
WHERE oi.fulfillment_mode = 'manual'
AND oi.fulfillment_config_id IS NOT NULL
AND oi.created_at >= :launch_at
AND NOT EXISTS (
SELECT 1 FROM cost_snapshots cs
WHERE cs.subject_type = 'manual_case'
AND cs.order_item_id = oi.id
AND cs.superseded_at IS NULL
);
-- 人工成本缺失异常(队列)
SELECT status, COUNT(*) FROM cost_exceptions
WHERE reason = 'manual_cost_missing'
GROUP BY status;
```
`manual_cost_missing` 只在「首次配置之后」生成;确认履约写入快照后自动置为 `resolved`。
### 5. 历史回填(可选,不阻塞上线)
本次上线不要求补历史人工成本;如需历史商品归一/成本快照/销售事实用于报表,另跑:
```bash
npm run revenue:backfill -- --dry-run # 应为 unknown=0
npm run fulfillment:backfill # 只填 NULL
npm run finance:sync -- --mode=all --from=2026-07-10 --to=2026-10-22
```
长区间按 30 天窗口分段执行(默认 `DATABASE_STATEMENT_TIMEOUT_MS=15000`)。
注意:人工历史成本按既定口径**不补录**,历史人工毛利仍不可用。
---
## 二、上线验收 SQL(替换 `:launch_at` 为实际上线时间)
```sql
-- 1. 履约方式覆盖:四项相加 = order_items 总数,NULL = 0
SELECT COALESCE(fulfillment_mode, '(null)') AS mode, COUNT(*)
FROM order_items GROUP BY 1 ORDER BY 2 DESC;
-- 2. 上线后人工项必须有履约配置(允许配置跟进期)
SELECT COUNT(*) FROM order_items
WHERE fulfillment_mode = 'manual'
AND created_at >= :launch_at
AND fulfillment_config_id IS NULL;
-- 3. 上线后人工项必须有成本快照(关账门槛)
SELECT COUNT(*) FROM order_items oi
WHERE oi.fulfillment_mode = 'manual'
AND oi.created_at >= :launch_at
AND NOT EXISTS (
SELECT 1 FROM cost_snapshots cs
WHERE cs.subject_type = 'manual_case'
AND cs.order_item_id = oi.id
AND cs.superseded_at IS NULL
);
-- 4. 人工成本快照金额 = 对应备用金支出(仅现金路径)
SELECT cs.id, me.expense_no, me.amount_fen, cs.total_amount_fen
FROM cost_snapshots cs
JOIN manual_expenses me ON me.order_item_id = cs.order_item_id
WHERE cs.subject_type = 'manual_case'
AND cs.superseded_at IS NULL
AND cs.created_at >= :launch_at
AND cs.total_amount_fen <> me.amount_fen;
-- 5. 一个订单项最多一条有效人工快照(应为 0)
SELECT order_item_id, COUNT(*)
FROM cost_snapshots
WHERE subject_type = 'manual_case' AND superseded_at IS NULL
GROUP BY 1 HAVING COUNT(*) > 1;
-- 6. 销售事实与实时聚合对平(同一日期区间)
-- 净收入 = Σ订单项收入 − 已确认退款;成本 = Σ有效快照
WITH r AS (SELECT MIN(stat_date) a, MAX(stat_date) b FROM sales_daily_facts)
SELECT
(SELECT SUM(net_revenue_fen) FROM sales_daily_facts) AS facts_revenue,
(SELECT SUM(cost_amount_fen) FROM sales_daily_facts) AS facts_cost;
-- 与独立聚合对比,允许 0 差异。
-- 7. 人工成本异常:上线后应能被确认动作收口
SELECT reason, status, COUNT(*) FROM cost_exceptions
WHERE created_at >= :launch_at
GROUP BY 1,2 ORDER BY 3 DESC;
```
---
## 三、常见问题
| 现象 | 原因 | 处理 |
| --- | --- | --- |
| 确认时报「还没有配置履约渠道」 | SKU 未配置,或配置的 `sku_key` 与订单项不匹配 | 在人工 SKU 配置里按兜底键配置;保存后自动回填 |
| 确认时报「当前账号还没有备用金账户」 | 该客服未补建账户 | 钱包管理 → 补建客服账户 |
| 确认时报「积分不足」 | 未充值/充值到别的客服 | 积分钱包 → 给本人账户发放 |
| 确认成功但列表仍有「待确认」 | 前端缓存 | 刷新;后端以快照为准 |
| `manual_cost_missing` 不减少 | 未确认履约,或确认失败回滚 | 看订单详情确认;异常只能由快照自动收口 |
---
## 四、已知边界(不要对外承诺)
1. 没有复式总账、平台对账、期间关账;现有「收支明细」是业务事件拼装,不是会计账。
2. 平台退款 API 未接入,退款只支持后台手工登记(整单/指定商品,可撤销)。
3. 平台服务费/支付通道费/寄卖货款成本确认时点仍待定;相关成本项配置为空时不参与毛利。
4. 历史人工成本、历史平台成本、历史未归一商品不补齐;历史区间毛利只能作为参考。
5. 管理员/财务代录目前仍使用**本人**备用金/积分账户,不能指定客服账户。
+31 -15
View File
@@ -1,9 +1,10 @@
# 财务与履约口径总纲(2026-10-23 重写版)
> 状态:**现行唯一口径**。本文是财务、成本、履约分类的总入口;其他文档与本文件冲突时,以本文为准。
> 最后校准:2026-10-24
> 代码基线:`dev @ 790de676 + 本地未提交修复`(**未推送、未部署生产**)
> 数据基线:生产 dump `order_site-20261022-092601.dump`(2026-07-10 ~ 2026-10-22);本地已升级到迁移 107,并完成履约方式、商品、成本快照与销售事实初始化
> 最后校准:2026-10-26
> 代码基线:`dev`(合并 dev1023 + 人工成本完整性修复,迁移到 `111`)
> 数据基线:§二/§七 的数字来自 2026-10-22 生产 dump 的**历史快照**,只用于理解历史缺口,**不再作为上线验收依据**;
> 上线初始化与验收以 [财务上线初始化与验收-2026-10-26.md](财务上线初始化与验收-2026-10-26.md) 为准。
> 适用范围:订单接入、履约判定、商品归一、成本快照、销售事实、人工录单、备用金、后续总账与对账。
---
@@ -41,7 +42,9 @@
---
## 二、当前真实履约构成(本地库,2026-07-10 ~ 2026-10-22)
## 二、历史履约构成(2026-10-22 dump,仅作历史基线)
> 本节数字是 2026-10-22 的历史快照。当前库只跑了近 30 天增量,**不要拿本节数字做上线验收**;上线验收用新手册的 SQL。
| 履约方式 | 订单项 | 收入 | 当前成本情况 |
| --- | ---: | ---: | --- |
@@ -131,8 +134,8 @@
## 五、历史数据处理
- 历史人工支出**找不回来,直接放弃**:不补录、不做样本回填、不做均价估算;
- 历史人工单在销售事实中保留收入,成本标记为 `historical_missing`,不计入"上线后监控缺口";
- 历史平台 `legacy_platform` 单独成行,成本未采集的标记 `legacy_missing`;
- 历史人工单在销售事实里保留收入,成本缺口计入 `cost_missing_count`;**不做特殊 stage、不补录**,不计入「上线后监控缺口」;
- 历史平台 `legacy_platform` 单独成行,成本缺口同样计入 `cost_missing_count`;`cost_stage` 由快照覆盖情况决定(actual / estimated / partial / missing);
- 自动发货、接单平台历史成本不回写、不重算版本,沿用现有快照。
---
@@ -165,8 +168,11 @@ P0 的具体迁移、回填、代码清单与验收 SQL 见 [成本中心与履
| `order_items.fulfillment_mode` 持久化 | **已实施** | 迁移 `105` + 统一决策服务 |
| 订单页 / 成本快照 / 销售事实统一读字段 | **已实施** | `admin-order-read-helpers`、`sales-facts-service`、`cost-snapshot-sync-service` |
| 本地 316,585 项回填 | **已实施** | `fulfillment:backfill`,四项计数与 §二 一致 |
| 成本模型与履约方式正交(去 `carrier_type`) | **已实施(本地库已改造)** | 迁移 `103`–`107` 重写;`cost_items.cost_model` |
| 10 个迁移收敛为 5 个 | **已实施** | `103_product_catalog` ~ `107_manual_fulfillment` |
| 成本模型与履约方式正交(去 `carrier_type`) | **已实施(本地库已改造)** | 迁移 `103`–`108`;`cost_items.cost_model` |
| 10 个迁移收敛为 5 个 | **已实施** | `103_product_catalog` ~ `107_manual_fulfillment`(后续新增 `108`–`111` 增量) |
| 人工确认原子性/防重复 | **已实施(2026-10-26)** | 单事务 + 订单项行锁 + `uq_cost_snapshots_manual_order_item`(迁移 `111`);集成测试覆盖并发/回滚 |
| 人工 SKU 配置自动匹配 | **已实施(2026-10-26)** | 保存配置立即回填 + 定时任务 rematch + 确认时按 SKU 兜底匹配 |
| 人工缺成本监控 | **已实施(2026-10-26)** | `manual_cost_missing`,从首次人工配置时间起算,确认后自动收口 |
| 人工成本链路收敛为「人工录单 → 扣备用金 → 写快照」 | **已实施(本地库已改造)** | 迁移 `104`;登记单与审批报销已下线,口径固化在成本中心迁移 |
| 人工商品归一 `man:<skuId>` | **已实施(本地库已归一)** | `manual-product-normalization-service` + `POST /cost-center/manual-products/normalize`;1,116 个 `man:` 商品、91,606 个人工订单项归一,人工未归一 = 0 |
| 人工商品归一后的销售事实 | **已实施** | 94 天分 30 天窗口重算:26,507 行;人工 ¥1,219.20 万收入全部按 `man:` 商品归集 |
@@ -175,7 +181,7 @@ P0 的具体迁移、回填、代码清单与验收 SQL 见 [成本中心与履
| 影子总账、对账、关账 | **未实施** | §六 P3 |
> 历史平台仍有 53,901 个订单项(¥389.12 万)未归一,不属于 P1 范围;人工历史成本按既定口径不补录,因此人工毛利当前是“收入已归商品、成本仅新单有”的状态。
> 分支事实:以上实现已快进合并到 `dev`;迁移 `103`–`107` **未推送远端、未部署生产**。生产口径仍以 `main` 为准。
> 分支事实:以上实现已合并到 `dev`;迁移 `103`–`111` **未推送远端、未部署生产**。生产口径仍以 `main` 为准;上线前按 [财务上线初始化与验收-2026-10-26.md](财务上线初始化与验收-2026-10-26.md) 初始化资金与人工 SKU。
---
@@ -209,20 +215,25 @@ WHERE oi.fulfillment_mode = 'manual'
WHERE cs.order_item_id = oi.id AND cs.superseded_at IS NULL
);
-- 4. 人工订单项没有备用金支出单:上线后应为 0
-- 4. 人工订单项没有资金流水(现金 manual_expenses 或积分 creditEntryId):上线后应为 0
SELECT COUNT(*)
FROM order_items oi
WHERE oi.fulfillment_mode = 'manual'
AND oi.created_at >= DATE '2026-11-01'
AND NOT EXISTS (SELECT 1 FROM manual_expenses me WHERE me.order_item_id = oi.id)
AND NOT EXISTS (
SELECT 1 FROM manual_expenses me
WHERE me.order_item_id = oi.id
SELECT 1 FROM cost_snapshots cs
WHERE cs.subject_type = 'manual_case'
AND cs.order_item_id = oi.id
AND cs.superseded_at IS NULL
AND cs.metadata_json ? 'creditEntryId'
);
-- 5. 人工成本快照金额与备用金支出金额不一致:应为 0
-- 5. 现金人工成本快照金额与备用金支出金额不一致:应为 0
-- subject_id 已统一为 order_item_id,资金来源按 order_item_id 关联。
SELECT cs.id, me.expense_no, me.amount_fen, cs.total_amount_fen
FROM cost_snapshots cs
JOIN manual_expenses me ON me.id = cs.subject_id
JOIN manual_expenses me ON me.order_item_id = cs.order_item_id
WHERE cs.subject_type = 'manual_case'
AND cs.superseded_at IS NULL
AND cs.total_amount_fen <> me.amount_fen
@@ -256,7 +267,12 @@ WHERE oi.fulfillment_mode = 'manual'
AND ft.executor_key IN ('kuaishou_ct_assisted', 'kuaishou_feifei')
);
-- 9. 人工项误挂接单模板商品:P1 完成后应为 0
-- 9. 人工成本缺失异常:上线后应随确认履约自动收口
SELECT status, COUNT(*) FROM cost_exceptions
WHERE reason = 'manual_cost_missing'
GROUP BY status;
-- 10. 人工项误挂接单模板商品:P1 完成后应为 0
SELECT COUNT(*)
FROM order_items oi
JOIN product_catalog pc ON pc.id = oi.canonical_product_id
+3 -2
View File
@@ -1,8 +1,9 @@
# 财务系统集成方案
> 状态:**现行方案(总账层未实施)**。本文只描述 `finance_*` 总账层;当前总账表尚未建立,现有财务页仍是 `UNION ALL` 业务事件拼装。
> 最后校准:2026-10-23
> 代码基线:`dev @ 43f402cf + 迁移 `103`–`107`(已合并到 `dev`,**未推送、未部署生产**)
> 最后校准:2026-10-26
> 代码基线:`dev`(迁移到 `111`,**未推送、未部署生产**)
> 说明:本文是后续 P3 总账/对账/关账方案,**尚未实施**;本次上线范围(备用金、人工成本、成本中心、销售事实)与初始化步骤见 [财务上线初始化与验收-2026-10-26.md](财务上线初始化与验收-2026-10-26.md)。
> **履约方式、成本模型、人工全覆盖口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准**;本文不重复分类设计。
> 目标:在**不破坏现有抢单、押金、结算、寄卖、提现链路**的前提下,增加可对账、可关账、可追溯的复式记账底座。
> 结论先说:**不要直接在 `worker_wallets` / `worker_wallet_ledgers` 上继续加字段。** 现有表是业务钱包与流水,不是会计总账;正确做法是新增复式记账底座,让现有钱包成为它的“余额投影”。
+15
View File
@@ -219,6 +219,21 @@ docker compose ps
docker compose logs -f backend
```
## 财务/成本上线后的初始化(迁移 102–111)
代码与迁移只负责建表;**备用金账户、积分余额、人工 SKU 配置不会自动创建**。
首次上线后按 [财务上线初始化与验收-2026-10-26.md](财务上线初始化与验收-2026-10-26.md) 执行:
1. 钱包管理 → 补建客服备用金账户 → 按需拨付;
2. 商品与成本 → 积分钱包 → 给客服发放三平台积分;
3. 商品与成本 → 人工 SKU 配置 → 扫描并逐项配置(保存后自动回填订单项);
4. 用手册 §二 的验收 SQL 检查:配置覆盖、人工成本快照、`manual_cost_missing`、销售事实对平。
> 历史人工成本不补录;人工成本监控从第一次创建人工履约配置的时间开始。
> 定时任务 `cost-finance-sync` 每 5 分钟运行,负责履约归集、人工 SKU 回填、成本快照与销售事实。
---
## 服务器日常更新
```bash