Files
order_site/docs/项目全量分析报告-2026-10-17.md
T
yml2213 43f402cf72 docs(finance): 现行财务文档收敛为 5 篇,履约口径只定义一次
原 9 篇 3,647 行,履约分类口径在总纲、模型文档、交接总结里重复三遍,
且仍留着"三块履约""按小时人工成本"等已作废表述。本次收敛为 5 篇
1,778 行:

- 财务与履约口径总纲-2026-10-23.md(300 行):唯一口径来源
  七条硬口径、三个正交维度、真实履约构成、成本模型规则、人工全覆盖、
  实施顺序、进度、验收 SQL、防回归清单
- 成本中心与履约模型.md(471 行):数据模型、迁移 103–107、履约回填、
  销售事实、本地库原地改造、索引与性能
- 成本中心API契约.md(270 行):字段替换总表、枚举、前后端模块边界
- 人工支出与备用金.md(218 行):人工支出登记字段/状态机/权限 +
  客服备用金台账
- 财务系统集成方案.md:复式总账层(未实施)

旧的 4 篇(成本中心与三块履约模型、人工支出登记落地方案、
客服备用金台账实施方案、财务履约改造交接总结)移入
docs/archive/2026-10-23/ 并加归档提示。

履约分类判定 SQL 只在总纲保留一份,其它文档改为链接。
2026-10-23 16:06:40 +08:00

317 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# order_site 项目全量分析报告(2026-10-17)
> 状态:**历史参考(2026-10-17 全量分析快照)**。架构与风险结论可参考;财务、履约分类、人工支出、备用金等口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准。
> 本文中的“三块履约”“按小时人工成本”“公司直付”等表述已过期,不得作为实现依据。
> 代码基线:`main @ 6850bc64`(分析当时;当前基线见 README)
> 复核范围:后端、前端、数据库迁移、部署脚本、现行文档
> 复核方式:源码走查 + 类型检查 + Lint + 格式检查 + 全量测试 + 生产构建 + 2026-10-17 生产库只读聚合(`default_transaction_read_only=on`)
> 说明:本次没有做全量数据克隆;历史数据量引用自生产库只读聚合,已在文中标注。
---
## 一、结论速览
| # | 结论 | 影响 |
| --- | --- | --- |
| 1 | 系统已是**三来源订单履约 + 众包接单 + 寄卖仓库 + 凭证核销**平台,不再是单一“快手自动发货”工具 | 文档与新人认知必须先统一来源模型 |
| 2 | 来源只有三个:`kuaishou-91`、`pdd`、`douyin-mashida`;**91 回调与快手 send-code 是同一个 `kuaishou-91` 来源的两条数据通道** | 代码已正确使用 `kuaishou-91` 适配器;文档曾把二者写成两个来源,现已校正 |
| 3 | 工程纪律处于同类项目上游:严格 TS、99 个渐进迁移、SQL 护栏、回调验签、日志脱敏、生产配置校验、按需部署 | 具备继续承载财务系统的工程基础 |
| 4 | 最大风险集中在四处:**资金门禁、Webhook 失败恢复、公开接口凭据、并发锁序** | 这四处会直接影响钱、单、凭证与账号安全 |
| 5 | 财务系统不能直接长在 `worker_wallet_ledgers` 上;现有表是“业务钱包流水”,不是会计总账 | 需要先建复式记账与对账底座,再接现有资金动作 |
| 6 | 前端与数据库事务层的测试仍偏薄;部分后端测试是源码字符串断言 | 财务系统必须补真实 PostgreSQL 集成测试与金额守恒测试 |
---
## 二、来源模型:三个来源,不是一个来源加另一个来源
```mermaid
flowchart LR
A["kuaishou-91<br/>91 回调(订单骨架)"] --> O[("orders / order_items")]
B["kuaishou-91<br/>快手 send-code(补全/兜底)"] --> O
C["pdd"] --> O
D["douyin-mashida"] --> O
O --> M["product-match 引擎<br/>3 个来源适配器"]
M --> W[("work_orders")]
O --> F[("fulfillment_tasks")]
```
| sourceKey | 数据通道 | 角色 |
| --- | --- | --- |
| `kuaishou-91` | 91 回调 | 订单骨架权威源 |
| `kuaishou-91` | 快手 send-code | 商品/SKU/店铺补全源与兜底源 |
| `pdd` | 拼多多同步 | 两段式原始订单暂存后消费 |
| `douyin-mashida` | 抖音码速达推送 | 抖店订单、DYC 券、H5/兑换页 |
代码事实:
- 来源键定义:`apps/backend/src/services/product-match/types.ts:7`
- 适配器:`apps/backend/src/services/product-match/adapters/kuaishou-91.ts`
- 注册表:`apps/backend/src/services/product-match/adapters/registry.ts:69`
- 快手 send-code 的权威注释:`apps/backend/src/services/worker-platform/sync-work-orders-from-send-code.ts:5`
**结论:91 与快手 send-code 必须继续共用一个 `kuaishou-91` 适配器。** 拆开会导致同一店铺键空间、同一订单骨架被重复匹配,直接破坏“一个订单项一个执行载体”的不变式。
---
## 三、架构与运行形态
| 层 | 现状 |
| --- | --- |
| 后端 | Node 22(生产)/ Express 5 / TypeScript 6;`routes → services → repositories → pg` |
| 前端 | React 19 / Vite 8 / Ant Design 6 / TanStack Query 5 |
| 数据库 | PostgreSQL 16,`node-pg-migrate`,启动时自动迁移 |
| 网关 | Caddy,统一压缩、静态缓存、反代 |
| 部署 | Docker Compose;`ubuntu-deploy.sh` 按 Git 改动只重建变化的一端 |
| 运行 | 单实例;限流、调度器、SSE 订阅、列表缓存均在进程内存 |
| 存储 | 生产阿里云 OSS;开发 MinIO |
后端入口与路由挂载:`apps/backend/src/app.ts:109`。抖音消息推送必须在全局 body parser 之前挂载,以保留原始报文验签:`apps/backend/src/app.ts:42`。
前端是三个入口共用一套构建产物:
- 后台:`HashRouter`
- 打手端:独立域名 `worker.khhao.com`
- 买家侧:`/claim`、`/h5`、`/redeem` 独立路径路由
---
## 四、当前规模(实测)
| 指标 | 后端 | 前端 |
| --- | --- | --- |
| 源码文件 / 行数 | 371 / 77,811 | 179 / 52,812 |
| CSS | — | 7,448 行 |
| 测试文件 / 行数 | 109 / 16,249 | 26 / 2,221 |
| 测试/源码比 | 约 20.9% | 约 4.2% |
| 接口声明 | 270(后台 197、打手 51) | — |
| 测试结果(本次) | 740 项:737 pass / 3 skipped / 0 fail | 143 pass / 0 fail |
| 生产构建 | 通过 | 通过(主 chunk 524.69 kB,有 >500 kB 警告) |
数据库:**99 个迁移、53 张表**。
提交历史:1,069 commits,作者高度集中(`git shortlog` 前两位 824 / 250),bus factor 约等于 1。
---
## 五、核心业务链路
### 5.1 进单与匹配
1. 三个来源统一调用 `upsertOrderFromSource`。
2. 按 `(provider, platform, platformOrderId)` 做 `pg_advisory_xact_lock` 去重。
3. `resolveOrderItemForFulfillment` 解析履约载体:`affiliate_dash` 或 `manual_dispatch`。
4. `syncWorkerOrdersForSourceOrder` 用 `product-match` 引擎匹配接单模板,生成工单。
关键点:
- 匹配引擎是纯函数,来源差异只在适配器。
- 并列最高分返回 `ambiguous`,不猜。
- 抖音 `sku_code` 恒为内部 SKU;引流标题只进 `snapshot.productTitle`,永不参与匹配。
- 抖音命中 `affiliate_dash` 后不建工单(`apps/backend/src/services/worker-platform/sync-worker-orders-service.ts:65`)。
### 5.2 工单与结算
状态机:
```text
pending_material → unassigned → open → in_progress
→ pending_acceptance → accepted
→ problem → open / cancelled
```
- 抢单:条件更新 `WHERE status='open' AND assigned_worker_id IS NULL`。
- 结算:`acceptWorkOrderAndSettle` 锁工单与份额,释放/延迟解冻押金,抵扣售后欠款,可能升级等级。
- 拼单:母单可长期 `open`,份额 `joined → submitted → accepted/cancelled`。
- 赠送单:确认加好友后取消/脱离不再退押金。
### 5.3 寄卖仓库
- 提报:`consignment_submissions`,冻结发布押金。
- 入库:`consignment_inventories`,审核通过生成 `INV…`。
- 匹配:品类强隔离、三态比对、低配不充高配、unknown 不判精确。
- 履约:`internal_stock` 内部现货,或 `worker_deposit` 指派打手。
- 结算:验收后释放发布押金,补发货款 `cost_price_fen || asking_price_fen`。
- 撤销:打手押金与卖家押金可 `auto / refund / forfeit / keep_frozen`,显式覆盖必须填原因。
### 5.4 凭证与券
| 类型 | 表 | 核销方式 |
| --- | --- | --- |
| 快手行业电子凭证 | `kuaishou_industry_vouchers` | 快手 OpenAPI |
| 抖音 DYC 券 | `redeem_certificates` | `coupons.verifyV3` |
两套表、两套状态机,**不可混用**。发布/指派时按来源分流:`publish-voucher-service.ts`。
### 5.5 三块履约与成本(2026-10-17 只读数据增补)
真正决定成本和监管方式的是**执行载体**,不是订单来源:
| 块 | 载体 | 成本 | 生产数据 |
| --- | --- | --- | --- |
| 自动发货 | `affiliate_dash` 任务 | 固定,按皮肤/SKU | 上游已有 `cost_amount`,但未落库 |
| 接单平台 | `work_orders` | 变动,按模板/单价/拼单 | 最近 30 天已验收 62,669 单 |
| 人工客服 | 目前无载体 | 人工工时/按单 | 最近 30 天 **44,657 个已支付订单项无任务也无工单** |
关键数据:
- 91 无载体订单 42,391、PDD 2,258、抖音 8;全部 `claim_status=active`。
- `manual_dispatch` 任务全库只有 6 条,说明人工块基本没进任务系统。
- 已验收工单的 `orders.order_status` 仍为 `paid`,订单主状态不能代表履约完成。
- 最近 30 天已验收工单:91 报酬/订单金额约 86.0%、PDD 80.0%、抖音 96.5%。**口径已确认(2026-10-17):`total_amount` 即用户付款金额、计收入,打手报酬计支出(总额法)**;91 的 `total_amount` 与快手 `paymentFen` 数据核验 100% 一致。详见 [载体判定与成本中心落地-复核与草案.md](archive/2026-10-17/载体判定与成本中心落地-复核与草案.md) §九。
- 同一个商品名对应多个 `sku_code`:「指挥官密钥1个」27 个、「苏尔南钻石100颗」35 个,不能直接按 `sku_code` 做销量统计。
- 当前 30 天订单基本是一单一品,但模型必须按订单项设计。
详细设计见 [成本中心与履约模型.md](成本中心与履约模型.md)。
---
## 六、工程质量亮点
1. **严格类型**:`strict`、`noUncheckedIndexedAccess`、`exactOptionalPropertyTypes`。
2. **测试纪律**:后端 740 项,覆盖匹配引擎、押金策略、退款、路径判定、日志脱敏、SQL 形状。
3. **SQL 护栏**:`check:sql` 检查历史迁移不可修改、热点索引存在、禁止 `COUNT(*) OVER()`。
4. **生产配置校验**:生产强制 session secret 长度、显式 CORS 白名单、存储凭据完整。
5. **回调验签**:91、快手、affiliate-dash、抖音四条外部链路均有验签;91/affiliate 有时间戳容差。
6. **账号安全**:后台/打手口令 scrypt + 盐;会话落库 + `session_version`;打手设备数限制。
7. **日志脱敏**:敏感 key、URL 参数、JSON 字段统一脱敏,并有单测。
8. **部署与备份**:健康检查、优雅关闭、日志轮转、迁移前备份、按需重建。
9. **零 TODO/FIXME**:源码中没有遗留标记;文档密度高。
---
## 七、风险清单(按当前代码复核)
### P0 · 资金与一致性
| # | 发现 | 证据 | 影响 |
| --- | --- | --- | --- |
| 1 | **发单金额高于用户付款只有前端提醒,后端无强制校验** | 前端 `apps/frontend/src/pages/admin/panels/work-order-payment.ts:17`;后端发布只校验 `>0` `apps/backend/src/services/worker-platform/admin-work-order-management-service.ts:228` | 直接调 API 可以发出亏损单;改价、发布、指派三入口没有统一门禁 |
| 2 | **寄卖审核可由请求体改 `workerId`,并按改后归属结算货款** | `apps/backend/src/services/worker-platform/consignment-admin-service.ts:606`;供货方反查优先库存归属 `apps/backend/src/repositories/worker-platform/consignment-supplier-repo.ts:67` | 钱可能结给错误的人;还会按手机号自动绑定 |
| 3 | **寄卖审核详情全量返回游戏账号密码 `credentialsJson`,只需 `consignment.view`** | `apps/backend/src/services/worker-platform/consignment-admin-service.ts:183` | 查看权限即可拿到卖家凭证,权限粒度过粗 |
| 4 | **抢单与指派加锁顺序相反** | 抢单先锁钱包 `apps/backend/src/repositories/worker-platform/work-order-grab-repo.ts:25`;指派先锁工单 `:156`,再锁钱包 `:171` | 并发下可形成死锁,接口 40P01 / 500 |
| 5 | **自动工单硬编码零押金,模板押金字段不生效** | `apps/backend/src/services/worker-platform/sync-worker-orders-service.ts:245` | 自动发单没有押金约束;`deposit_threshold_amount` 也只读写/展示 |
| 6 | **发布工单时凭证核销失败不回滚;指派却会拦截** | 发布 `apps/backend/src/services/worker-platform/admin-work-order-management-service.ts:250`;指派 `apps/backend/src/services/worker-platform/admin-work-order-assignment-service.ts:75` | 同一条业务规则两套行为;抖音券缺失时工单仍可能进入大厅 |
### P1 · 可靠性与公开面
| # | 发现 | 证据 | 影响 |
| --- | --- | --- | --- |
| 7 | **Webhook 先去重落库、后处理业务;失败后平台重推被永久跳过** | 抖音 `apps/backend/src/services/platforms/douyin-mashida/notify-service.ts:95`;affiliate-dash `apps/backend/src/services/platforms/affiliate-dash/notify-service.ts:70` | 建单/发券/同步任一步失败,事件已写入 `webhook_events`,重推返回 duplicate,订单可能永久丢失 |
| 8 | **抖音回调同步串行调用上游,单次超时 20 秒** | `apps/backend/src/services/platforms/douyin-mashida/source-config-service.ts:136` | 回调响应可能超过平台超时,与 #7 叠加后无法恢复 |
| 9 | **`/collect/*`、`/redeem/*` 只凭订单号,无第二因子** | `apps/backend/src/routes/collect.ts:22`、`:32`;`apps/backend/src/routes/redeem.ts:24`、`:34` | 可查询/写入待补全订单资料;知道订单号即可触发买家券核销 |
| 10 | **`/collect/upload` 可匿名上传任意场景文件到 OSS** | `apps/backend/src/routes/collect.ts:42`、`:49`;场景名只做字符串清洗 `apps/backend/src/services/file-storage/file-storage-service.ts:360` | 不校验订单存在,换 `orderNo` 即可绕过限流桶,存在存储滥用风险 |
| 11 | **`/files/object` 无鉴权;claim token 默认永久有效** | `apps/backend/src/routes/files.ts:12`;默认 `apps/backend/src/config/defaults.ts:65`,生产示例同样为 0 `.env.server.example:27` | 链接或对象 key 泄露即长期可访问 |
| 12 | **无 CSP/HSTS/X-Frame-Options;后台 token 存 localStorage** | `deploy/caddy/Caddyfile` 只有缓存头;`apps/frontend/src/utils/admin-auth.ts:1`、`:101` | 任一 XSS 即可接管后台会话 |
| 13 | **先到先匹配,后到的快手补全数据不会纠正既有工单** | 已有工单直接 `already_exists` 跳过 `apps/backend/src/services/worker-platform/sync-worker-orders-service.ts:97` | 价格/模板可能停留在首次粗匹配结果 |
| 14 | **单实例耦合**:限流桶、调度器、SSE、列表缓存均在进程内 | `apps/backend/src/middleware/rate-limit.ts:26`;`services/realtime/realtime-event-service.ts` | 直接扩容会出现重复定时任务、限流失效、缓存不一致 |
### P2 · 可维护性与运维
| # | 发现 | 证据 |
| --- | --- | --- |
| 15 | 巨型文件:`WorkOrdersPanel.tsx` 3,686 行、`AdminTaskDetailPage.tsx` 2,015 行、`work-order-query-repo.ts` 1,499 行、`consignment-repo.ts` 1,478 行 | 实测 |
| 16 | 前端无组件渲染测试、无 E2E;部分后端测试是源码字符串断言 | 测试目录与用例实现 |
| 17 | 无 CI;质量门禁只靠本机 pre-push,可被 `SKIP_CHECKS=1` 绕过 | `.githooks/pre-push` |
| 18 | `dev` 分支已完全落后 `main`,实际是单分支开发 | `git log main..dev` 为空 |
| 19 | 迁移启动时自动执行且无 down;多副本会竞争迁移 | `apps/backend/src/startup/bootstrap.ts:32` |
| 20 | `uncaughtException` 只记录不退出 | `apps/backend/src/index.ts:57` |
| 21 | 本地 Node 24,生产镜像 Node 22;前端主 chunk 524.69 kB 警告 | 构建输出、`deploy/docker/backend.Dockerfile:11` |
| 22 | 备份会把 `apps/backend/data` 配置打包上传 OSS,可能含 PDD RSA 私钥/平台 Cookie | `deploy/backup-db.sh:119` |
### 三块履约与成本增补(2026-10-17)
| # | 发现 | 证据 | 影响 |
| --- | --- | --- | --- |
| C1 | **人工块没有载体、没有 case、没有完成状态** | 最近 30 天 44,657 个已支付订单项无任务也无工单;`manual_dispatch` 仅 6 条;已验收工单的订单仍 `order_status=paid` | 人工履约不可监管、不可计量、不可对账 |
| C2 | **成本数据散落且无版本/快照** | 自动发货 `cost_amount` 只映射不落库;工单价格在 `work_product_rules`;人工无成本表 | 改价无法追溯,历史毛利不可重算,三块成本口径不一致 |
| C3 | **订单金额口径不统一** | 91 `totalAmount=maxAmount`(`platforms/ninetyone/order-service.ts:48`);PDD `order_amount`;抖音 `pay_amount` | 直接汇总 `orders.total_amount` 会把不同财务口径相加 |
| C4 | **SKU 与数量口径不稳定** | 同一商品名对应 27/35 个 `sku_code`;`order_items.quantity` 对「5个」类商品不可靠 | 店铺/日期/皮肤销量统计会漏计、重计或错并 |
| C5 | **无订单项级金额字段** | `order_items` 仅有 `sku_code/sku_name/quantity/spec_json/item_snapshot_json` | 多商品订单无法直接分摊收入,需要快照/分摊规则 |
---
## 八、文档现状与本次整理
本次已完成:
1. 新增 [docs/README.md](README.md):文档唯一入口、状态约定、三来源口径。
2. 新增 [docs/archive/README.md](archive/README.md):历史文档说明。
3. 将 6 份过期阶段性文档移入 `docs/archive/2026-10-08/`,不删除内容,保留追溯。
4. 统一以下文档的来源口径:
- `README.md`
- `docs/接单模板匹配引擎重构设计.md`
- `docs/抖音-码速达/00-总览.md`
- `docs/抖音-码速达/10-三层分流设计.md`
- `docs/抖音-码速达/12-按SKU分类改造总设计.md`
- `docs/履约配置/多发货平台与电子凭证整体链路.md`
- `docs/91卡券/5.当前项目对接配置.md`
- `docs/91卡券/6.开发设计.md`
5. 校正“设计稿/未实施”状态:
- 匹配引擎:P0 + P1 已实施,P2/P3 待办。
- 抖音三层分流:抖音侧已实施。
6. 校正过期测试数:抖音总览中的 636/115 改为 2026-10-17 复核的 740/143。
7. 2026-10-17 增补:新增 [成本中心与履约模型.md](成本中心与履约模型.md),把自动发货/接单平台/人工客服三块、统一成本中心、销售事实与人工 case 纳入现行设计;本报告同步补入生产只读数据与 C1~C5 风险。
仍保留为“设计/待办”的内容:
- `work_product_match_entries` 新表尚未落地(只在设计中),当前仍靠 `compile.ts` 编译旧模板/绑定。
- 匹配触发点收敛、寄卖建议价/大厅改价统一走 `pricing.ts` 尚未完成。
- 抖音“待人工队列”仍未做。
- `product_catalog` / `channel_product_mappings` / `cost_items` / `cost_versions` / `cost_snapshots` / `sales_daily_facts` / `manual_fulfillment_cases` 均未落地。
---
## 九、财务系统集成结论(摘要)
现有财务能力是**业务钱包 + 流水**,不是会计总账:
- `worker_wallets`:可用余额、冻结押金、累计入账、累计结算。
- `worker_wallet_ledgers`:冻结/释放/扣减/结算等流水。
- `worker_finance_requests`:充值/提现申请。
- 售后欠款、寄卖发布押金、工单报酬、货款结算分散在多个 repo。
缺口:
1. 没有复式记账(借贷不平无法发现)。
2. 没有会计期间/月结/关账。
3. 没有平台侧对账(91、快手、PDD、抖音、affiliate-dash)。
4. 没有订单级收入/成本/毛利。
5. 余额是可变字段,缺少“由流水重算”的校验。
6. 资金事件缺少统一的 `business_event_id` 幂等键。
建议:先建**影子总账 + 对账**,不要一开始替换钱包。成本金额与商品维度单独由成本中心负责,人工块由人工 case 负责;三者关系见 [成本中心与履约模型.md](成本中心与履约模型.md),总账细节见 [财务系统集成方案.md](财务系统集成方案.md) 与 [影子总账说明.md](影子总账说明.md)。
---
## 十、建议路线
### 立即(1–3 天)
1. 后端统一“发单金额 ≤ 用户付款”校验,改价/发布/指派共用。
2. 修复 Webhook 去重时序,改为 `processing / succeeded / failed` 或失败删除。
3. 统一抢单/指派锁序,或对 40P01 做有限重试。
4. 寄卖审核禁止直接改 `worker_id`;确需改派走独立接口 + 原因 + 复核。
### 短期(1–2 周)
1. 发布与凭证核销统一为“失败即不发布”,或明确异步补偿。
2. `/collect`、`/redeem` 增加一次性 token/二次校验;上传前校验订单存在。
3. 生产强制 `CLAIM_TOKEN_TTL_HOURS > 0`;私有文件短期签名;补安全响应头。
4. 先落商品主数据 + 销售事实:`product_catalog`、`channel_product_mappings`、`sales_daily_facts`,解决店铺/日期/皮肤销量与数量归一。
5. 再落成本中心影子期:`cost_items / cost_versions / cost_snapshots`,自动发货接上游固定成本,工单接现有规则金额,人工**逐单按实际金额**(旧稿的“按单/按小时”已作废,见 [总纲](财务与履约口径总纲-2026-10-23.md))。
### 中期(1 个月)
1. 人工 case:载体判定、无载体自动建 case、分配/SLA/证据/主管复核、人工成本快照。
2. 财务影子总账:`finance_journal_entries / lines` + 业务事件映射。
3. CI + PostgreSQL 集成测试;补金额守恒、并发抢单、Webhook 失败恢复测试。
4. 财务对账:平台账单/支付流水/钱包流水三方核对。
5. 订单级损益:收入、履约成本、打手报酬、人工成本、平台费、退款。
6. 拆分巨型文件;补前端组件测试与 1–2 条 E2E。
---
## 十一、一句话总结
**代码与工程纪律足以支撑下一阶段的财务系统;但财务系统必须建在“商品主数据 + 成本中心 + 不可变复式分录 + 业务事件幂等 + 对账”之上,而不是继续给 `worker_wallets` 加字段。** 先修资金门禁、Webhook 恢复、锁序与公开接口;再把人工块纳入 case 监管,最后接财务总账,风险最低。