diff --git a/README.md b/README.md index 7e07eb10..334a6109 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # order_site -快手自动发货与履约管理系统。 +多渠道订单履约、打手众包接单与寄卖仓库系统。 -前后端分离架构:接收外部订单、按规则匹配发货平台、生成统一领取链接,并提供后台做平台配置、任务追踪和人工复核。 +前后端分离架构:接收 `kuaishou-91`、`pdd`、`douyin-mashida` 三个来源的订单,按规则匹配自动发货或生成打手工单,提供统一领取/兑换链接,并支持平台配置、财务结算、审计与人工复核。 ## 技术栈 @@ -26,28 +26,43 @@ deploy/ caddy/ Caddy 反向代理 docker/ 前后端 Dockerfile mac-dev.sh / ubuntu-deploy.sh -docs/ 平台接口、履约链路、部署说明 +docs/ 文档索引、架构分析、财务方案、平台接口、部署说明 docker-compose.dev.yml 本地 Docker 开发环境 docker-compose.yml 生产 / 服务器 Compose ``` +## 来源模型(现行) + +系统只有三个订单来源: + +| sourceKey | 数据通道 | 说明 | +| --- | --- | --- | +| `kuaishou-91` | 91 回调 + 快手 send-code | **同一个来源的两条通道**:91 是订单骨架权威源,快手 send-code 是数据补全源与兜底源;共享店铺键空间与同一套匹配上下文 | +| `pdd` | 拼多多订单同步 | 两段式 `pdd_raw_orders → orders` | +| `douyin-mashida` | 抖音码速达推送 | 抖店订单、DYC 券、H5/兑换页 | + +不要把 91 与快手 send-code 拆成两个来源或两个适配器。代码中的唯一来源键是 `kuaishou-91`(`apps/backend/src/services/product-match/types.ts`)。 + ## 业务边界(简图) ```text -91 卡券进单 +kuaishou-91(91 回调 + 快手 send-code) → 本系统建单 / 匹配商品 / 创建履约任务 → 返回统一 claimUrl 用户打开领取页 → 可选:快手行业电子凭证自动核销 - → kuaishou-cloud / kuaishou-feifei / 人工 完成发货 + → affiliate-dash / 人工 完成发货 后台 → 订单任务、平台配置、审计、人工复核 接单平台(打手众包) → 工单发布到抢单大厅,打手抢单代练 → 提交验收 / 问题单处置 / 任务时限超时自动判定 +抖音码速达 + → 抖店订单推送 → 建单 → DYC 券 → 领取/兑换页 → 核销 ``` 更完整的链路说明见:`docs/履约配置/多发货平台与电子凭证整体链路.md`。 +文档总索引见:`docs/README.md`。 ## 本地开发 diff --git a/docs/91卡券/5.当前项目对接配置.md b/docs/91卡券/5.当前项目对接配置.md index 10508901..c42140b0 100644 --- a/docs/91卡券/5.当前项目对接配置.md +++ b/docs/91卡券/5.当前项目对接配置.md @@ -1,5 +1,7 @@ # 91卡券接入当前项目接口配置 +> 来源口径(2026-10-17):`provider='91kaquan'` 是订单通道标识;在商品匹配层,91 回调与快手 send-code 合并为**同一个来源 `kuaishou-91`**。不要把二者当成两个来源或两个适配器。 + ## 1. 对接目标 - `91卡券` 作为外部售卖与自动发货通道。 @@ -69,7 +71,7 @@ https://你的域名/api/v1/open/91 ### 5.2 当前项目内部映射(现状) -代码中已按独立来源落地: +代码中已按 `kuaishou-91` 来源的订单通道落地: - `provider = '91kaquan'` - `platform = 'kuaishou'` diff --git a/docs/91卡券/6.开发设计.md b/docs/91卡券/6.开发设计.md index 949b34af..e87b6a63 100644 --- a/docs/91卡券/6.开发设计.md +++ b/docs/91卡券/6.开发设计.md @@ -2,6 +2,8 @@ ## 1. 目标 +> 来源口径(2026-10-17):`91卡券` 是 **`kuaishou-91` 来源的订单骨架通道**;快手 send-code 是同一来源的数据补全与兜底通道。二者共享匹配上下文与店铺键空间,**不能拆成两个来源或两个适配器**。 + 基于以下对外接口文档完成当前项目接入: - [3.异步卡密下单.md](./3.异步卡密下单.md) @@ -19,8 +21,8 @@ ## 2. 设计原则 -- `91卡券` 作为**独立订单来源**。 -- `91卡券` 不是新的履约执行器,而是外部售卖、订单输入和自动发货通道。 +- `91卡券` 是 `kuaishou-91` 来源的**订单骨架通道**,不是新的履约执行器;快手 send-code 是同来源的补全/兜底通道。 +- 外部售卖、订单输入和自动发货通道仍由 91 承担;匹配层的来源键统一为 `kuaishou-91`。 - 当前项目真正的交付物仍然是 `claimUrl`。 - 查询接口返回 `20` 的判断标准是“领取链接已生成并可发”,不是“快手最终兑换完成”。 - `buyNum > 1` 时,必须拆成多个 task,并返回多个 card。 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..29226dd1 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,74 @@ +# order_site 文档索引 + +> 最后校准:2026-10-17,代码基线 `main @ 6850bc64`。 +> 本文是 `docs/` 的唯一入口。新增文档必须在上表登记状态;过期文档不再直接改写结论,统一移入 `docs/archive/`。 + +## 一、先记住三条口径 + +### 1. 来源只有三个,不是四个 + +| sourceKey | 包含的数据通道 | 说明 | +| --- | --- | --- | +| `kuaishou-91` | 91 回调 + 快手 send-code | **同一个来源的两条通道**:91 是订单骨架权威源,快手 send-code 是数据补全源与兜底源;共享店铺键空间与同一套匹配上下文 | +| `pdd` | 拼多多订单同步 | 两段式 `pdd_raw_orders → orders` | +| `douyin-mashida` | 抖音码速达推送 | 抖店订单、DYC 券、H5/兑换页 | + +**不要把 91 与快手 send-code 拆成两个来源、两个适配器或两条业务线。** 代码中的唯一来源键是 `kuaishou-91`(`apps/backend/src/services/product-match/types.ts`),适配器是 `adapters/kuaishou-91.ts`。 + +### 2. 一个订单 = 骨架 + 买家凭证 + 一个执行载体 + +- 骨架:`orders` + `order_items` +- 买家凭证:快手电子凭证(`kuaishou_industry_vouchers`)或抖音券(`redeem_certificates`) +- 执行载体:`affiliate_dash` 自动发货任务,或 `work_orders` 工单,二者业务上互斥 + +### 3. 文档状态 + +| 状态 | 含义 | +| --- | --- | +| 现行 | 与当前代码一致,改动代码时必须同步更新 | +| 参考 | 厂商协议、外部接口文档,按平台版本更新 | +| 历史 | 只用于追溯,不得作为实现依据;位于 `docs/archive/` | + +## 二、现行文档 + +| 文档 | 状态 | 用途 | +| --- | --- | --- | +| [项目全量分析报告-2026-10-17.md](项目全量分析报告-2026-10-17.md) | 现行 | 当前架构、业务链路、风险与改进路线 | +| [财务系统集成方案.md](财务系统集成方案.md) | 现行(方案) | 财务系统边界、复式记账模型、分期落地 | +| [影子总账说明.md](影子总账说明.md) | 现行(方案) | 影子总账的定义、运行方式、对账与转正标准 | +| [接单模板匹配引擎重构设计.md](接单模板匹配引擎重构设计.md) | 现行(P0/P1 已实施) | `kuaishou-91 / pdd / douyin-mashida` 三来源匹配引擎 | +| [履约配置/多发货平台与电子凭证整体链路.md](履约配置/多发货平台与电子凭证整体链路.md) | 现行 | 进单、履约、领取、电子凭证的整体链路 | +| [affiliate-dash-发货平台/affiliate-dash对接.md](affiliate-dash-发货平台/affiliate-dash对接.md) | 现行 | affiliate-dash 自动履约接口与字段 | +| [履约配置/cloudtentacles发货平台.md](履约配置/cloudtentacles发货平台.md) | 历史参考 | 已下线通道的第三方接口样例,不代表现行实现 | +| [91卡券/6.开发设计.md](91卡券/6.开发设计.md) | 现行 | `kuaishou-91` 来源的 91 侧接入设计 | +| [抖音-码速达/00-总览.md](抖音-码速达/00-总览.md) | 现行 | 抖音码速达接入总览与增量记录 | +| [抖音-码速达/12-按SKU分类改造总设计.md](抖音-码速达/12-按SKU分类改造总设计.md) | 现行 | 抖音三层分流与载体唯一 | +| [部署启动与数据库重建.md](部署启动与数据库重建.md) | 现行 | 本地/生产启动、迁移、重建 | +| [数据库备份与恢复.md](数据库备份与恢复.md) | 现行 | 备份、恢复、OSS | +| [数据库SQL约束与运维.md](数据库SQL约束与运维.md) | 现行 | SQL 护栏与生产观测 | + +## 三、厂商协议与外部资料 + +这些目录是平台方文档或抓取资料,**不作为本项目实现说明**: + +| 目录 | 内容 | +| --- | --- | +| `91卡券/` | 91 卡券公开接口、签名、加密示例(项目接入结论见 `91卡券/6.开发设计.md`) | +| `行业电子凭证/` | 快手电子凭证接口、售后、测试指南 | +| `发货平台-2/` | 早期发货平台协议,现行为 `affiliate_dash`;保留作历史接口对照 | +| `抖音-码速达/接口API/` | 抖音码速达出网接口 | +| `抖音-码速达/消息推送/` | 抖音码速达推送消息字段与签名 | + +## 四、历史文档 + +| 位置 | 说明 | +| --- | --- | +| [archive/README.md](archive/README.md) | 2026-10-08 及更早的阶段分析、寄卖方案、性能快照、一次性迁移检查脚本 | + +## 五、维护规则 + +1. 改代码影响业务口径时,先改现行文档,再提交代码。 +2. 文档头部必须写「状态 + 最后校准日期 + 代码基线」。 +3. 旧分析不要在原文件上继续堆结论;新建当期报告,旧文件移入 `docs/archive/<日期>/`。 +4. 行号只用于当期基线;跨版本引用优先写文件与函数名。 +5. 新增来源时只加一个 `ProductMatchSourceKey` + 一个适配器;**91 + 快手 send-code 永远算一个 `kuaishou-91`**。 diff --git a/docs/ affiliate_dash_发货平台/affiliate-dash对接.md b/docs/affiliate-dash-发货平台/affiliate-dash对接.md similarity index 100% rename from docs/ affiliate_dash_发货平台/affiliate-dash对接.md rename to docs/affiliate-dash-发货平台/affiliate-dash对接.md diff --git a/docs/寄卖子系统技术分析报告.md b/docs/archive/2026-10-08/寄卖子系统技术分析报告.md similarity index 100% rename from docs/寄卖子系统技术分析报告.md rename to docs/archive/2026-10-08/寄卖子系统技术分析报告.md diff --git a/docs/寄卖库存优化现状与进度追踪.md b/docs/archive/2026-10-08/寄卖库存优化现状与进度追踪.md similarity index 100% rename from docs/寄卖库存优化现状与进度追踪.md rename to docs/archive/2026-10-08/寄卖库存优化现状与进度追踪.md diff --git a/docs/寄卖系统优化方案.md b/docs/archive/2026-10-08/寄卖系统优化方案.md similarity index 100% rename from docs/寄卖系统优化方案.md rename to docs/archive/2026-10-08/寄卖系统优化方案.md diff --git a/docs/寄卖系统迁移079上库前检查.sql b/docs/archive/2026-10-08/寄卖系统迁移079上库前检查.sql similarity index 100% rename from docs/寄卖系统迁移079上库前检查.sql rename to docs/archive/2026-10-08/寄卖系统迁移079上库前检查.sql diff --git a/docs/近期性能与稳定性专项优化分析报告.md b/docs/archive/2026-10-08/近期性能与稳定性专项优化分析报告.md similarity index 100% rename from docs/近期性能与稳定性专项优化分析报告.md rename to docs/archive/2026-10-08/近期性能与稳定性专项优化分析报告.md diff --git a/docs/项目分析报告-发单系统与寄售仓库.md b/docs/archive/2026-10-08/项目分析报告-发单系统与寄售仓库.md similarity index 100% rename from docs/项目分析报告-发单系统与寄售仓库.md rename to docs/archive/2026-10-08/项目分析报告-发单系统与寄售仓库.md diff --git a/docs/archive/README.md b/docs/archive/README.md new file mode 100644 index 00000000..900cbce6 --- /dev/null +++ b/docs/archive/README.md @@ -0,0 +1,22 @@ +# 历史文档归档 + +> 这里的文档只用于追溯当时的分析过程,**不是现行实现说明**。 +> 代码、数据库结构与线上行为以仓库当前 `main` 和 `docs/README.md` 中标记为「现行」的文档为准。 + +## 2026-10-08 + +| 文档 | 归档原因 | +| --- | --- | +| `项目分析报告-发单系统与寄售仓库.md` | 基线为 `ef491660`,测试数、迁移数、表数与当前实现已明显不一致;由《项目全量分析报告-2026-10-17》替代 | +| `寄卖子系统技术分析报告.md` | 同上,且部分行号已漂移;寄卖代码仍可参考,但结论需按现行代码复核 | +| `寄卖系统优化方案.md` | 阶段性优化方案,部分事项已完成,不能作为现行路线图 | +| `寄卖库存优化现状与进度追踪.md` | 阶段性进度追踪,完成状态已过时 | +| `近期性能与稳定性专项优化分析报告.md` | 2026-10-01 / 10-02 的生产快照与专项复盘,保留作性能证据,不代表当前容量 | +| `寄卖系统迁移079上库前检查.sql` | 迁移 079 的一次性上库前检查脚本,生产已执行;保留作审计追溯 | + +## 使用建议 + +- 需要当前架构与风险:看 `docs/项目全量分析报告-2026-10-17.md`。 +- 需要财务系统设计:看 `docs/财务系统集成方案.md`。 +- 需要某个平台协议:看对应的厂商文档目录(`91卡券/`、`行业电子凭证/`、`发货平台-2/`、`抖音-码速达/接口API/`、`抖音-码速达/消息推送/`)。 +- 历史文档中的代码行号、测试数量、迁移数量不要直接引用。 diff --git a/docs/履约配置/cloudtentacles发货平台.md b/docs/履约配置/cloudtentacles发货平台.md index bda9dfcb..2507703a 100644 --- a/docs/履约配置/cloudtentacles发货平台.md +++ b/docs/履约配置/cloudtentacles发货平台.md @@ -1,5 +1,5 @@ > ⚠ 本仓库当前**未接入** cloudtentacles 通道:该履约通道已下线,`apps/backend/src/services/platforms/` 下已无对应适配器,`fulfillment/executors/registry.ts:18-19` 对历史通道只保留只读 legacy stub(`legacy-retired-executor.ts`)。本文是第三方(cloudtentacles)官方接口样例,仅作外部参考。 -> 现行自动履约通道是 affiliate-dash(`apps/backend/src/services/platforms/affiliate-dash/`,走 `/api/client/v1`);通道现状见 `docs/ affiliate_dash_发货平台/affiliate-dash对接.md`。 +> 现行自动履约通道是 affiliate-dash(`apps/backend/src/services/platforms/affiliate-dash/`,走 `/api/client/v1`);通道现状见 `docs/affiliate-dash-发货平台/affiliate-dash对接.md`。 查询SKU列表 ``` diff --git a/docs/履约配置/多发货平台与电子凭证整体链路.md b/docs/履约配置/多发货平台与电子凭证整体链路.md index c07f6feb..5719bf6e 100644 --- a/docs/履约配置/多发货平台与电子凭证整体链路.md +++ b/docs/履约配置/多发货平台与电子凭证整体链路.md @@ -1,12 +1,15 @@ # 多发货平台与电子凭证整体链路 +> 状态:现行。最后校准:2026-10-17(`main @ 6850bc64`)。 +> 来源口径:本系统只有三个订单来源——`kuaishou-91`、`pdd`、`douyin-mashida`。**91 回调与快手 send-code 是同一个 `kuaishou-91` 来源的两条数据通道**(91=骨架权威源,快手 send-code=补全/兜底源),不是一个来源加另一个来源。 + ## 1. 当前结论 当前系统需要把四类能力拆开理解: | 模块 | 定位 | 说明 | | --- | --- | --- | -| 91 卡券 | 订单来源 + 发链接通道 | 91 调用我方下单接口,我方生成领取链接;91 查询订单时拿到领取链接,并发送给用户。 | +| `kuaishou-91` | 订单来源 | 91 回调提供订单骨架;快手 send-code 提供商品/SKU 补全与兜底。两者共享 `kuaishou-91` 来源与同一店铺键空间。 | | affiliate-dash | 现行发货平台(自动履约) | 当前唯一自动履约通道,order_site 作为其商户走开放接口 `/api/client/v1`,发放皮肤、道具。 | | kuaishou-cloud / kuaishou-feifei | 已下线发货平台 | 历史乐玩 / 飞飞通道已下线,executor 仅保留只读 legacy stub(`legacy-retired-executor.ts`),不再发货。 | | 快手电子凭证 | 领取流程里的自动核销能力 | 用来替代用户在领取页手动输入核销码的步骤,不替代 91,也不替代发货平台。 | @@ -14,7 +17,7 @@ 因此整体设计不是“新增订单来源”,而是: ```text -91 负责进单和把领取链接发给用户 +kuaishou-91(91 回调 + 快手 send-code)负责进单与补全 我方领取页负责统一承接用户 kuaishou-cloud / kuaishou-feifei 通道已下线,affiliate-dash 负责真实发货 快手电子凭证负责把领取页里的核销码输入步骤自动化 @@ -23,7 +26,8 @@ kuaishou-cloud / kuaishou-feifei 通道已下线,affiliate-dash 负责真实 ## 2. 目标链路 ```text -91 卡券下单 +kuaishou-91 进单 + (91 回调 = 订单骨架权威源;快手 send-code = 商品/SKU 补全与兜底) -> 我方创建内部订单和履约任务 -> 按商品选择发货平台 -> affiliate-dash(自动履约) diff --git a/docs/影子总账说明.md b/docs/影子总账说明.md new file mode 100644 index 00000000..3c46c9aa --- /dev/null +++ b/docs/影子总账说明.md @@ -0,0 +1,322 @@ +# 影子总账到底是什么意思 + +> 状态:**财务系统设计说明** +> 代码基线:`main @ 6850bc64` +> 配套方案:[财务系统集成方案.md](财务系统集成方案.md) +> 一句话:**影子总账是一套“跟着真实业务跑、但暂时不接管余额”的复式会计账。** 它先把每一笔业务翻译成借贷分录,和现有钱包/订单/提现数据逐日对账;确认长期对得平之后,再让它成为正式总账。 + +--- + +## 一、为什么叫“影子” + +“影子”不是指数据是假的,而是指它的**地位**: + +- 现有 `worker_wallets` 仍然是运行时余额的权威来源; +- 现有 `worker_wallet_ledgers` 仍然继续记录打手资金动作; +- 影子总账在后台**并行**记录同一批业务事件,但暂时不决定用户能提现多少、能接多少单; +- 它像影子一样跟着真实系统走,用来验证“如果我们用会计方式记账,能不能和现有系统对得上”。 + +```text +真实业务 + ├─ 现有链路:改订单 / 改工单 / 改钱包 / 改提现单 ← 仍然权威 + └─ 影子链路:业务事件 → 复式分录 → 总账余额 ← 只观察、不接管 +``` + +当影子账连续对平、业务口径确认后,才进入“转正”阶段:总账成为权威,钱包变成总账的余额投影。 + +--- + +## 二、它和现在的钱包流水有什么区别 + +| 维度 | 现在的 `worker_wallet_ledgers` | 影子总账 | +| --- | --- | --- | +| 记账方式 | 单边流水:`+50` / `-20` | 复式分录:每笔有借方和贷方,必须平衡 | +| 覆盖范围 | 主要是打手资金 | 订单收入、成本、打手报酬、押金、提现、寄卖货款、平台费 | +| 余额 | `worker_wallets` 直接维护可变余额 | 由分录汇总/投影得出,可独立重算 | +| 目的 | 让打手看到余额与流水 | 让财务能出报表、对账、关账、算毛利 | +| 可修改性 | 余额可更新,流水是业务记录 | 已过账分录不可改,只能反向冲正 | +| 维度 | 主要按 worker / work_order | 同时按 order / work_order / worker / shop / provider / settlement | +| 失败影响 | 直接影响打手余额 | 影子期不影响业务;转正后与业务同事务 | + +### 最关键的区别:单边 vs 复式 + +假设打手被冻结 20 元押金: + +- 现在只记一条:`frozen_deposit +20`,`available -20`; +- 复式账要记两条: + - 借:打手可提现余额 20 + - 贷:打手押金负债 20 + +这样财务才能回答:这 20 元不是“消失了”,而是从“可提现负债”重分类成了“押金负债”。押金退回到哪、没收进哪个收入科目,也都有依据。 + +--- + +## 三、一个具体例子 + +假设一笔订单: + +- 买家支付 100 元; +- 工单验收,打手报酬 50 元; +- 打手抢单时冻结押金 20 元; +- 后来提现 10 元。 + +### 1. 买家支付 100 元 + +```text +借:1002 支付通道待结算 100 +贷:2201 预收账款 100 +``` + +此时钱包不变。这笔钱还不是收入,而是预收/待结算。 + +### 2. 工单验收,确认收入与履约成本 + +```text +借:2201 预收账款 100 +贷:4001 商品销售收入 100 + +借:5101 履约成本-打手报酬 50 +贷:2001 应付打手报酬 50 +``` + +此时钱包仍不一定变,因为报酬可能还在待解冻期。 + +### 3. 报酬转为可提现 + +```text +借:2001 应付打手报酬 50 +贷:2004 打手可提现余额 50 +``` + +此时钱包 `available_amount +50`。 + +### 4. 抢单冻结押金 20 元 + +```text +借:2004 打手可提现余额 20 +贷:2003 打手押金 20 +``` + +钱包:`available -20`,`frozen_deposit +20`。这只是负债内部重分类,不是费用。 + +### 5. 提现 10 元 + +```text +借:2004 打手可提现余额 10 +贷:1001 银行存款 10 +``` + +钱包:`available -10`。 + +### 6. 最终核对 + +```text +钱包 available = 50 - 20 - 10 = 20 +钱包 frozen_deposit = 20 +``` + +总账中对应的负债也应分别是 20 和 20;如果对不上,就说明有事件漏记、重复记或金额映射错了。 + +--- + +## 四、影子总账在本项目怎么跑 + +```mermaid +flowchart LR + A["业务事务
订单/工单/提现/寄卖/售后"] --> B["finance_business_events
幂等事件"] + B --> C["finance-posting-service
借贷规则"] + C --> D["finance_journal_entries"] + D --> E["finance_journal_lines"] + E --> F["科目余额 / 钱包投影"] + G["现有 worker_wallets / ledgers"] --> H["每日对账"] + F --> H + I["平台账单 / 支付流水"] --> H + H --> J["差异清单
finance_reconciliation_items"] +``` + +### 1. 业务事件先落“收件箱” + +每个业务动作生成唯一事件: + +```text +order:123:paid +work_order:456:settled +work_order:456:deposit_freeze +withdrawal:789:approved +consignment:abc:payout +``` + +写入 `finance_business_events`,`business_event_id` 唯一。重复投递直接忽略,保证幂等。 + +### 2. 按规则生成借贷分录 + +`finance-posting-service` 只做一件事:把事件翻译成分录。 + +```text +事件:work_order:456:settled +分录: + 借 5101 履约成本 50 + 贷 2001 应付打手报酬 50 +维度:orderId / workOrderId / workerId / shopId / provider +``` + +规则必须集中管理,不能散落在各个业务 service 里。 + +### 3. 写凭证与分录 + +- `finance_journal_entries`:凭证头(事件、时间、期间、状态)。 +- `finance_journal_lines`:借贷行(科目、金额、维度)。 +- 已过账凭证不可 UPDATE / DELETE;冲正用反向凭证。 + +### 4. 生成余额投影 + +有两种方式: + +- **实时投影**:分录写入后同步更新 `finance_account_balances` / `worker_wallets`; +- **定时重算**:按期间从分录汇总,生成余额快照。 + +影子期建议两者都做:实时投影用于展示,定时重算用于校验。 + +### 5. 每日对账 + +至少做四组对账: + +| 对账 | 左 | 右 | +| --- | --- | --- | +| 钱包余额 | `worker_wallets.available_amount` | 总账中打手可提现余额 | +| 押金余额 | `worker_wallets.frozen_deposit_amount` | 总账中打手押金负债 | +| 待解冻 | `worker_wallets.pending_unfreeze_amount` | 总账中待解冻明细 | +| 平台应收 | 平台账单/结算单 | 总账中应收账款 | + +差异写入 `finance_reconciliation_items`,必须能定位到具体订单、工单、提现单或事件。 + +--- + +## 五、影子期和转正期的区别 + +这是最容易混淆的地方。 + +### 5.1 影子期(观察) + +- 现有钱包仍是权威; +- 影子总账写入失败,**不应阻断业务**; +- 失败事件进入重试/死信队列; +- 每日对账发现漏记、重复、金额不一致; +- 目标是把“事件覆盖率”和“对账差异”压到 0。 + +因此影子期可以做成: + +```text +业务事务提交 + → 尝试写影子总账(best-effort) + → 失败只记录 finance_business_events.status=failed + → 定时任务重试 +``` + +### 5.2 转正期(接管) + +- 总账成为权威; +- 分录写入与业务状态变更必须在**同一个数据库事务**; +- 分录写失败 → 业务回滚; +- 钱包余额从总账投影读取; +- 旧钱包字段保留一个版本用于回滚。 + +```text +业务事务开始 + → 改业务状态 + → 写总账事件 + 分录 + → 更新钱包投影 + → 提交 +``` + +**影子期允许“账慢一点”,转正期不允许“账少一笔”。** + +--- + +## 六、历史数据怎么处理 + +不建议一上来把 32 万+ 历史订单全部重放。更稳妥的做法是: + +### 方案 A:期初余额 + 未来流水(推荐) + +1. 选一个切换日(例如 2026-11-01)。 +2. 把切换日各科目余额作为“期初分录”导入: + - 银行存款 + - 平台应收 + - 应付打手 + - 打手押金 + - 打手可提现余额 + - 应付卖家货款 +3. 切换日之后的所有业务走影子总账。 +4. 历史报表继续由旧系统/旧查询提供,或后续再做历史回放。 + +优点:风险低、上线快、不会因为历史脏数据卡住。 +缺点:历史订单级损益需要另行回放。 + +### 方案 B:有限回放(用于验证) + +选最近 1–3 个月做回放: + +- 从 `orders`、`work_orders`、`worker_wallet_ledgers`、`worker_finance_requests`、`consignment_*` 重建事件; +- 与现有钱包/报表逐日比对; +- 验证 posting 规则,不要求覆盖全部历史。 + +### 方案 C:全量回放(不推荐先行) + +历史数据可能存在: + +- 已删除/改写的业务记录; +- 旧口径押金; +- 多渠道补单; +- 手工调账没有事件来源。 + +全量回放很容易得到“永远对不平”的结果,反而拖慢项目。 + +--- + +## 七、转正标准 + +建议至少满足: + +1. 所有核心事件类型已接入:订单支付/退款、工单结算、押金冻结/释放/没收、提现、充值、寄卖押金/货款、售后扣款/欠款。 +2. 连续 30 天: + - 借贷不平凭证数 = 0; + - 钱包余额与总账重算差异 = 0; + - 押金/待解冻/提现冻结差异 = 0; + - 平台应收差异可解释; + - 事件漏记率低于约定阈值(例如 0.1%)。 +3. 财务/运营完成抽样核对与签字。 +4. 有明确回滚开关:关闭总账投影读,回到旧钱包字段。 +5. 关账、冲正、手工分录权限与审计已就绪。 + +--- + +## 八、常见误区 + +| 误区 | 实际 | +| --- | --- | +| 影子总账 = 数据库备份 | 不是。备份是数据副本,影子总账是另一套会计模型 | +| 有 `worker_wallet_ledgers` 就是总账 | 不是。它是单边业务流水,不能出借贷平衡报表 | +| 先把钱包表改成借贷 | 风险极高。应先并行建账,再切换读模型 | +| 影子账可以先随便改 | 不行。已过账分录必须不可变,否则无法审计 | +| 对账就是总账 | 不是。对账是校验手段,总账是记账体系 | +| 历史数据必须全部重算 | 不一定。期初余额 + 未来流水通常更稳 | +| 影子期 Ledger 写失败要回滚业务 | 影子期不应阻断业务;转正期才必须同事务 | + +--- + +## 九、在本项目落地的最小步骤 + +1. **定科目与口径**:收入确认时点、打手报酬是成本还是代付、平台费归属。 +2. **建表**:`finance_accounts`、`finance_business_events`、`finance_journal_entries`、`finance_journal_lines`、`finance_periods`、`finance_reconciliations`。 +3. **接 4 个高频事件**:订单支付、工单结算、押金冻结/释放/没收、提现。 +4. **做日对账**:钱包余额 vs 总账余额;先只报差异,不自动改账。 +5. **补事件**:寄卖押金/货款、售后欠款、充值。 +6. **做后台页面**:凭证簿、试算平衡、差异清单。 +7. **连续对平后切换**:总账成为权威,钱包改为投影。 + +--- + +## 十、一句话总结 + +**影子总账就是“先用会计的方式把业务再记一遍,但不让它管钱;等它和现有钱包、平台账单长期对得上,再让它转正”。** 它的价值不是多一张表,而是提前暴露“钱从哪里来、到哪里去、哪一笔没记、哪一笔记错了”,避免财务系统上线后才发现历史资金链路对不平。 diff --git a/docs/抖音-码速达/00-总览.md b/docs/抖音-码速达/00-总览.md index 60945dfb..d079c46e 100644 --- a/docs/抖音-码速达/00-总览.md +++ b/docs/抖音-码速达/00-总览.md @@ -1,17 +1,17 @@ # 抖音码速达 · 总览(进度 / 代码 / 配置 / 上线) > 更新:2026-10-15 **状态:最小接入已跑通,且按 SKU 的三层分流已上线**(真实网关签名验证 / 真实推送订阅 / 真实 orderDetail 建单 / 真实 coupons.syncV2 同步成功 / 工单发布顺带核销抖音券),两端自检全绿 -> 本文是这套内容的**入口**,具体细节在 04~11,最新增量见 [§九点六](#九点六2026-10-14--10-15-增量)。 +> 本文是这套内容的**入口**,具体细节在 04~12,最新增量见 [§九点六](#九点六2026-10-14--10-15-增量)。 --- ## 一、一句话现状 -按 [05-最小接入方案分析.md](05-最小接入方案分析.md) 落地的**最小闭环六步全部完成**,后端 636 测试 / 前端 115 测试全绿,迁移 `094` 已在本地临时库实测通过(建表 + 幂等 + 唯一约束行为均验证)。 +按 [05-最小接入方案分析.md](05-最小接入方案分析.md) 落地的**最小闭环六步全部完成**;2026-10-17 复核:后端 740 项测试(737 pass / 3 skipped)、前端 143 项测试全绿,迁移 `094` / `095` 已在本地临时库实测通过。 **没做的**:兑换后的实际交付物(卡密/充值)、`order.searchList` 对账、兑换页二次校验(手机后 4 位)。 -> ✅ **原「按 SKU 的三层分类」已上线**(2026-10-14):抖音与 91/快手 同构(命中映射 → `affiliate_dash` 自动发货;未命中 → 接单平台建工单;都不中 → 无载体=待人工),载体唯一有硬守卫。总设计 [12-按SKU分类改造总设计.md](12-按SKU分类改造总设计.md)、分层实测 [10-三层分流设计.md](10-三层分流设计.md)、运营动作 [11-接单模板待补清单.md](11-接单模板待补清单.md)。 +> ✅ **原「按 SKU 的三层分类」已上线**(2026-10-14):抖音与 `kuaishou-91` 来源(91 回调 + 快手 send-code)同构(命中映射 → `affiliate_dash` 自动发货;未命中 → 接单平台建工单;都不中 → 无载体=待人工),载体唯一有硬守卫。总设计 [12-按SKU分类改造总设计.md](12-按SKU分类改造总设计.md)、分层实测 [10-三层分流设计.md](10-三层分流设计.md)、运营动作 [11-接单模板待补清单.md](11-接单模板待补清单.md)。 **只走真实公网联调**(离线模拟已按要求删除): @@ -143,50 +143,33 @@ ## 五、文档索引 -| 文档 | 内容 | 是否入库 | +| 文档 | 内容 | 状态 | | --- | --- | --- | -| [README.md](README.md) | 抓取说明 + 目录 | ✅ 已提交(`5978e923`) | +| [README.md](README.md) | 抓取说明 + 目录 | ✅ 已入库 | | [01-计费规则.md](01-计费规则.md) | 平台计费 | ✅ | | [02-接口调用规范与签名.md](02-接口调用规范与签名.md) | 出网签名规则 | ✅ | | [03-消息推送规范与签名.md](03-消息推送规范与签名.md) | 推送与验签 | ✅ | | `接口API/`、`消息推送/`、`data/` | 全量抓取快照(46 个文件 / 488K) | ✅ | -| [04-结合本项目的深度分析.md](04-结合本项目的深度分析.md) | 深读平台后结合仓库的分析 | ❌ 仅本地 | -| [05-最小接入方案分析.md](05-最小接入方案分析.md) | **实施方案**(决策与取舍都在这) | ❌ 仅本地 | -| [06-待确认问题清单.md](06-待确认问题清单.md) | 问题清单(Q1~Q4 已答,其余边做边测) | ❌ 仅本地 | -| [07-最小接入实现说明.md](07-最小接入实现说明.md) | 实现说明 + 联调步骤 + 坑 | ❌ 仅本地 | -| [08-公网联调.md](08-公网联调.md) | **真实公网联调**(ngrok + 一键切地址 + 前置自检 + checklist + 坑) | ❌ 仅本地 | -| 本文 `00-总览.md` | 进度索引 | ❌ 仅本地 | +| [04-结合本项目的深度分析.md](04-结合本项目的深度分析.md) | 深读平台后结合仓库的分析 | ✅ 已入库(历史分析) | +| [05-最小接入方案分析.md](05-最小接入方案分析.md) | **实施方案**(决策与取舍都在这) | ✅ 已入库(历史方案,已实施) | +| [06-待确认问题清单.md](06-待确认问题清单.md) | 问题清单(Q1~Q4 已答,其余边做边测) | ✅ 已入库(历史清单) | +| [07-最小接入实现说明.md](07-最小接入实现说明.md) | 实现说明 + 联调步骤 + 坑 | ✅ 已入库(现行) | +| [08-公网联调.md](08-公网联调.md) | **真实公网联调**(ngrok + 一键切地址 + 前置自检 + checklist + 坑) | ✅ 已入库(现行) | +| 本文 `00-总览.md` | 进度索引 | ✅ 已入库(现行) | --- -## 六、提交范围建议 +## 六、落地提交记录 -只提交本次抖音相关的 22 个文件(**不要 `git add -A`**,同一分支有并行会话在改寄售): +| commit | 内容 | +| --- | --- | +| `5a73fb74` | 抖音券核销后台界面(列表/核销/冲正/同步/对账/作废) | +| `319e0822` | 领取消息只发给有自动发货领取单元的单,人工单不再假发货 | +| `4b081d88` | 抖音按 SKU 三层分类接入,对齐 `kuaishou-91` | +| `10464fea` | 抽离接单模板匹配引擎(来源适配器/引擎/价格收口) | +| `6850bc64` | 工单发布并核销支持抖音券(按来源分流) | -```bash -git add apps/backend/src/app.ts \ - apps/backend/src/config/app-config-keys.ts \ - apps/backend/src/routes/admin/platform-config.ts \ - apps/backend/src/routes/admin/platform-config/douyin-mashida.ts \ - apps/backend/src/routes/douyin-mashida.ts \ - apps/backend/src/routes/redeem.ts \ - apps/backend/src/db/migrations/094_redeem_certificates.sql \ - apps/backend/src/repositories/redeem-certificate-repo.ts \ - apps/backend/src/services/admin/platform-config/douyin-mashida-service.ts \ - apps/backend/src/services/platforms/douyin-mashida \ - apps/backend/src/services/redeem \ - apps/frontend/src/App.tsx \ - apps/frontend/src/styles/client.css \ - apps/frontend/src/pages/redeem \ - apps/frontend/src/services/redeem.ts \ - apps/frontend/src/types/redeem.ts \ - apps/frontend/src/utils/redeem.ts \ - apps/frontend/tests/redeem.test.ts -``` - -建议 commit message:`feat(douyin-mashida): 接入抖音码速达最小闭环(消息建单/券码同步/固定兑换页/核销回执)` - -> 04~07 与本文档默认**不入库**(分析类内容)。若要留档,建议单独一次 `docs(douyin-mashida): ...` 提交。 +> 本文档与 04~12 均已入库;历史分析文档按状态标注,不再使用“仅本地/不入库”的口径。 --- @@ -268,7 +251,7 @@ git add apps/backend/src/app.ts \ ### 1)按 SKU 三层分流上线(`4b081d88`) -抖音与 91/快手 同构,**只动抖音,91 不变**: +抖音与 `kuaishou-91` 同构,**只动抖音,kuaishou-91 不变**: | 层 | 判据 | 载体 | | --- | --- | --- | @@ -283,7 +266,7 @@ git add apps/backend/src/app.ts \ ### 2)接单模板匹配引擎重构(`10464fea`,P0+P1) -按 [接单模板匹配引擎重构设计.md](../接单模板匹配引擎重构设计.md) 抽出 `services/product-match/`(适配器按**来源**划分:`kuaishou-91` / `pdd` / `douyin-mashida`)。生产回放 6000 条订单项:91/PDD 决策 **0 差异**,抖音 5 条全为修正。旧 `work-product-match-mappers.ts` 变为门面,签名不变。 +按 [接单模板匹配引擎重构设计.md](../接单模板匹配引擎重构设计.md) 抽出 `services/product-match/`(适配器按**来源**划分:`kuaishou-91` / `pdd` / `douyin-mashida`,其中 `kuaishou-91` 同时覆盖 91 回调与快手 send-code 两条数据通道)。生产回放 6000 条订单项:`kuaishou-91`/PDD 决策 **0 差异**,抖音 5 条全为修正。旧 `work-product-match-mappers.ts` 变为门面,签名不变。 ### 3)工单「发布并核销」支持抖音券(本次) @@ -300,7 +283,7 @@ publishAdminWorkOrder / assignAdminWorkOrder / 寄售履约 │ ├─ 委托 verifyCertificate(redeem/certificate-service.ts)→ coupons.verifyV3 │ │ (幂等 / invalid 拒核 / 未同步拒核 / 平台券码优先 / 失败对账自愈) │ └─ 券快照 merge 进工单 material_json.douyinMashidaVoucher + 事件 publish_voucher_consumed - └─ 其它来源(91/快手) + └─ 其它来源(kuaishou-91) └─ 原快手电子凭证路径(listKuaishouIndustryVouchersByOid + consumeKuaishouIndustryVoucher) ``` @@ -325,7 +308,7 @@ publishAdminWorkOrder / assignAdminWorkOrder / 寄售履约 --- -## 十、自检结果(本次) +## 十、自检结果(2026-10-15 当时) | 项 | 结果 | | --- | --- | diff --git a/docs/抖音-码速达/04-结合本项目的深度分析.md b/docs/抖音-码速达/04-结合本项目的深度分析.md index ffa518f4..4008474d 100644 --- a/docs/抖音-码速达/04-结合本项目的深度分析.md +++ b/docs/抖音-码速达/04-结合本项目的深度分析.md @@ -2,8 +2,9 @@ > 分析对象:新平台「抖音码速达 - 开放平台」(下文简称 **码速达**),协议数据见同目录 > [README.md](README.md) / [02-接口调用规范与签名.md](02-接口调用规范与签名.md) / [03-消息推送规范与签名.md](03-消息推送规范与签名.md)。 -> 分析主体:本仓库 `order_site`(快手自动发货与履约管理系统)。 -> 写作日期:2026-10-08 | 分支:`dev` | 结论均标注「事实 / 推断」,「事实」都带代码位置。 +> 分析主体:本仓库 `order_site`(多渠道订单履约与打手众包平台)。 +> 写作日期:2026-10-08 | 分支:`dev` | 状态:**接入前历史分析**;现行实现与结论以 [00-总览.md](00-总览.md)、[07-最小接入实现说明.md](07-最小接入实现说明.md) 为准。 +> 结论均标注「事实 / 推断」,「事实」都带代码位置;跨版本行号可能已漂移。 --- diff --git a/docs/抖音-码速达/05-最小接入方案分析.md b/docs/抖音-码速达/05-最小接入方案分析.md index 05fd4f0c..a5c09b00 100644 --- a/docs/抖音-码速达/05-最小接入方案分析.md +++ b/docs/抖音-码速达/05-最小接入方案分析.md @@ -3,7 +3,7 @@ > 目标:**用最小代价跑通一条真实闭环**(收单 → 发券 → 用户兑换 → 回执核销),先不做对账轮询、售后、商品映射后台、充值回执。 > 协议依据:[README.md](README.md)、[02-接口调用规范与签名.md](02-接口调用规范与签名.md)、[03-消息推送规范与签名.md](03-消息推送规范与签名.md)、[接口API/](接口API)。 > 本仓库依据:[04-结合本项目的深度分析.md](04-结合本项目的深度分析.md)(下称「深度分析」)。 -> 日期:2026-10-08 | 分支:`dev` +> 日期:2026-10-08 | 分支:`dev` | 状态:**已实施的历史方案**;现行实现见 [07-最小接入实现说明.md](07-最小接入实现说明.md) 与 [00-总览.md](00-总览.md)。 --- diff --git a/docs/抖音-码速达/06-待确认问题清单.md b/docs/抖音-码速达/06-待确认问题清单.md index 1840a437..eb61f8b4 100644 --- a/docs/抖音-码速达/06-待确认问题清单.md +++ b/docs/抖音-码速达/06-待确认问题清单.md @@ -2,7 +2,7 @@ > 用途:把「最小接入」开工前必须问清楚的问题**按对象拆好**,可直接复制给对方;每条都标注了「为什么问 / 卡住什么 / 什么算回答了」。 > 上下文:[05-最小接入方案分析.md](05-最小接入方案分析.md)(最小闭环:收单 → 发券 → 固定链接兑换 → 核销 + 发链接)。 -> 整理日期:2026-10-08 | 分支:`dev` +> 整理日期:2026-10-08 | 分支:`dev` | 状态:**历史问题清单**;Q1~Q4 已答复,未决项以 [00-总览.md](00-总览.md) 和现行实现为准。 --- diff --git a/docs/抖音-码速达/07-最小接入实现说明.md b/docs/抖音-码速达/07-最小接入实现说明.md index fb619844..be78061c 100644 --- a/docs/抖音-码速达/07-最小接入实现说明.md +++ b/docs/抖音-码速达/07-最小接入实现说明.md @@ -1,8 +1,8 @@ -# 抖音码速达 · 最小接入实现说明(本地文档) +# 抖音码速达 · 最小接入实现说明 > 对应方案:[05-最小接入方案分析.md](05-最小接入方案分析.md)。本文记录**已经落地了什么、怎么配、怎么联调、有哪些坑**。 -> 状态:后端已实现并通过 `npm run check`(630 测试 / 627 通过 / 0 失败);数据库迁移 `094` 未执行过(本地/生产启动时自动 up)。 -> 文档本身**未入库**,与 04/05/06 一样只保留在本地。 +> 状态:已实现;2026-10-17 复核后端 740 项测试(737 pass / 3 skipped)、前端 143 项全绿,生产构建通过。迁移 `094` / `095` 已入库并在本地临时库验证,生产以启动时自动迁移记录为准。 +> 本文已入库;04/05/06 属接入前历史分析,现行结论以 [00-总览.md](00-总览.md) 与本文为准。 --- diff --git a/docs/抖音-码速达/10-三层分流设计.md b/docs/抖音-码速达/10-三层分流设计.md index a66a292f..7a4d0c8a 100644 --- a/docs/抖音-码速达/10-三层分流设计.md +++ b/docs/抖音-码速达/10-三层分流设计.md @@ -1,6 +1,6 @@ # 抖音码速达 · 三层分流设计(皮肤自动发货 / 接单平台 / 人工) -> 状态:**设计稿,未实施**(2026-10-14) +> 状态:**已实施(抖音侧,2026-10-14);后续项见 §8 未决问题**。最后校准:2026-10-17(`main @ 6850bc64`)。 > 依据:生产库只读实测(接单模板 662 条)+ 全链路代码走查 > 关联:[09-皮肤领取流程.md](09-皮肤领取流程.md)、[11-接单模板待补清单.md](11-接单模板待补清单.md) @@ -13,11 +13,11 @@ 1. 内部 SKU 命中 `AFFILIATE_DASH_SKU_MAPPING` → `affiliate_dash` 皮肤自动发货; 2. 其余 → 一律 `manual_dispatch`(人工)。 -而 91/快手 是**三条路**:皮肤自动发货 / **接单平台自动发单(按接单模板建工单给打手)** / 人工兜底。本次目标:让抖音复用同一套分类,把中间那层补上。 +而 `kuaishou-91` 来源(91 回调 + 快手 send-code 两条数据通道)是**三条路**:皮肤自动发货 / **接单平台自动发单(按接单模板建工单给打手)** / 人工兜底。本次目标:让抖音复用同一套分类,把中间那层补上。 --- -## 二、91/快手 现在的三层机制(代码事实) +## 二、kuaishou-91 现在的三层机制(代码事实) ```mermaid flowchart TD @@ -62,7 +62,7 @@ flowchart TD ### 关键结论:层① 与层② 是**同一个单子的两种执行载体**(业务上严格互斥) -⚠️ 先纠正一个容易搞错的关系:**91 单、快手、工单不是三条独立的线,本质是一个单子**。 +⚠️ 先纠正一个容易搞错的关系:**91 回调、快手 send-code、工单不是三条独立的线,本质是一个单子**。前两者是同一个 `kuaishou-91` 来源的两条数据通道,工单只是这个单子的执行载体之一。 `sync-work-orders-from-send-code.ts:5-6` 的注释就是权威口径:「**91 回调是订单骨架权威源,快手 send-code 是数据补全源与兜底源**」。 @@ -188,7 +188,7 @@ flowchart TD ## 五、改动清单(文件级) -> ✅ **实施状态(2026-10-14)**:第 **1、2、3、4** 项已落地并测试通过(类型检查 + 后端全量 712 例);第 **7、8、9、10** 项待实施。详见 [12-按SKU分类改造总设计.md](12-按SKU分类改造总设计.md)「§1.1 已实施 / §1.2 待实施」。 +> ✅ **实施状态(2026-10-14)**:第 **1、2、3、4** 项已落地并测试通过;2026-10-17 复核后端全量 **740 例**(737 pass / 3 skipped)。第 **7、8、9、10** 项待实施。详见 [12-按SKU分类改造总设计.md](12-按SKU分类改造总设计.md)「§1.1 已实施 / §1.2 待实施」。 > - 第 4 项的实际落法:抖音**未命中时不再兜底 `manual_dispatch`**(覆盖 `unmatchedExecutorKey`),因此「未命中 = 无执行载体」,由发单层先判接单模板 → 命中即工单、都不中即待人工;载体唯一由 `snapshot.classification.channel === 'affiliate_dash'` 守卫。 | # | 文件 | 改动 | 风险 | diff --git a/docs/抖音-码速达/12-按SKU分类改造总设计.md b/docs/抖音-码速达/12-按SKU分类改造总设计.md index 62f9f165..a2dc8a1d 100644 --- a/docs/抖音-码速达/12-按SKU分类改造总设计.md +++ b/docs/抖音-码速达/12-按SKU分类改造总设计.md @@ -1,15 +1,15 @@ -# 抖音码速达 · 按 SKU 分类改造总设计(对齐 91/快手 三层) +# 抖音码速达 · 按 SKU 分类改造总设计(对齐 kuaishou-91 三层) -> 状态:**设计稿,未实施**(2026-10-14) -> 依据:全链路代码走查 + 生产库只读实测(模板 662 条 / 91 订单 32.9 万 / 抖音 11 笔) +> 状态:**抖音侧已实施(2026-10-14);P2 / P3 后续项未实施**。最后校准:2026-10-17(`main @ 6850bc64`)。 +> 依据:全链路代码走查 + 生产库只读实测(模板 662 条 / `kuaishou-91` 订单 32.9 万 / 抖音 11 笔) > 前置:[10-三层分流设计.md](10-三层分流设计.md)、[11-接单模板待补清单.md](11-接单模板待补清单.md) -> 前提:**抖音侧实现可自由重构**(用户确认,无历史兼容包袱) +> 前提:抖音侧实现可自由重构(无历史兼容包袱);**91 回调与快手 send-code 是同一个 `kuaishou-91` 来源的两条数据通道,不拆成两个来源** --- ## 一、目标与结论摘要 -**目标**:抖音与 91/快手 用同一套「按 SKU 分类」逻辑,产出三种结果: +**目标**:抖音与 `kuaishou-91` 用同一套「按 SKU 分类」逻辑,产出三种结果: | 层 | 触发条件 | 产物 | | --- | --- | --- | @@ -37,7 +37,7 @@ | 6 | 券 `cert_link` 按基址形态决定(`/h5` 一律按订单号拼;其它形态有 token 用 token、否则回退 `certLink`)——**不再依赖是否命中自动发货** | `apps/backend/src/services/redeem/certificate-service.ts` | | 7 | **抖音券写入工单 `material_json.douyinMashidaVoucher`**(对齐快手把凭证挂工单):① 发券后回填;② 建工单时券已存在则直接带上(覆盖「补模板后重新匹配」) | 同上 + `apps/backend/src/services/worker-platform/sync-worker-orders-service.ts` | -**验证**:`npm run typecheck` 通过;后端全量测试 **717 例 / 714 通过 / 0 失败 / 3 跳过**;本次新增 16 例(分类判定、sku_code 不覆写、券链接 5 例、券回填 4 例、载体守卫 3 例、91 回归)。 +**验证(2026-10-14 当时)**:`npm run typecheck` 通过;后端全量测试 **717 例 / 714 通过 / 0 失败 / 3 跳过**;本次新增 16 例(分类判定、sku_code 不覆写、券链接 5 例、券回填 4 例、载体守卫 3 例、91 回归)。2026-10-17 复核为 740 项(737 pass / 3 skipped)。 ### 1.2 业务口径(2026-10-14 确认)与后续 @@ -53,7 +53,7 @@ ## 二、现状全链路梳理 -### 2.1 91/快手 全链路 +### 2.1 kuaishou-91 全链路 ```mermaid flowchart TD @@ -94,7 +94,7 @@ flowchart TD #### 一个单子的构成(关键认知) -**91 与快手是互相配合的两个数据源,工单不是独立的第三条线**——`sync-work-orders-from-send-code.ts:5-6` 注释即权威口径:「91 回调是订单骨架权威源,快手 send-code 是数据补全源与兜底源」。 +**91 回调与快手 send-code 是同一个 `kuaishou-91` 来源的两条数据通道,工单不是独立的第三条线**——`sync-work-orders-from-send-code.ts:5-6` 注释即权威口径:「91 回调是订单骨架权威源,快手 send-code 是数据补全源与兜底源」。两者共享同一店铺键空间与同一套匹配上下文,**不是两个来源**。 | 组成 | 说明 | | --- | --- | @@ -138,7 +138,7 @@ flowchart TD ### 2.3 逐项差异对照 -| 维度 | 91/快手 | 抖音(现状) | 改造方向 | +| 维度 | `kuaishou-91` | 抖音(现状) | 改造方向 | | --- | --- | --- | --- | | 分类键 | 中文商品名(`productNo`) | 内部 SKU(`code`) | 统一为规范化 **canonical SKU** | | `sku_code` 落库 | 命中映射后被覆写成英文 | 同左(未命中时才是内部 SKU) | **不再覆写**,英文 sku 只落 snapshot | diff --git a/docs/抖音-码速达/README.md b/docs/抖音-码速达/README.md index 4fa3cc1c..a9bd8fc0 100644 --- a/docs/抖音-码速达/README.md +++ b/docs/抖音-码速达/README.md @@ -29,7 +29,7 @@ | [01-计费规则.md](01-计费规则.md) | 免费/基础/高级/增值 四档功能单价与各接口分级 | | [02-接口调用规范与签名.md](02-接口调用规范与签名.md) | 网关、公共参数、签名算法、返回结构、JAVA/.NET/PHP 请求示例 | | [03-消息推送规范与签名.md](03-消息推送规范与签名.md) | 推送参数、data 提交方式、签名与验签、消息设置、示例代码 | -| 04 ~ 06 分析文档 | 【本地文档,暂未入库】结合本项目的深度分析、最小接入方案分析、待确认问题清单 | +| [04-结合本项目的深度分析.md](04-结合本项目的深度分析.md) ~ [12-按SKU分类改造总设计.md](12-按SKU分类改造总设计.md) | 本项目接入分析、最小方案、实现说明、三层分流与按 SKU 改造;04~06 为历史分析,07/00/12 为现行实现 | | `接口API/` | 16 个接口逐条文档(请求参数、返回参数与示例、签名原串示例) | | `消息推送/` | 14 条消息逐条文档(topic、data 字段、示例 JSON、签名原串示例) | | `data/raw_data.json` | 页面内嵌原始数据(api / message / fee / menu 四块,完整未删减) | diff --git a/docs/接单模板匹配引擎重构设计.md b/docs/接单模板匹配引擎重构设计.md index 95d077bc..08fdcdac 100644 --- a/docs/接单模板匹配引擎重构设计.md +++ b/docs/接单模板匹配引擎重构设计.md @@ -1,8 +1,10 @@ # 接单模板匹配引擎重构设计 -> 目标读者:后端 / 后台运营。状态:**设计草案(未实施)**。日期:2026-10-14。 +> 目标读者:后端 / 后台运营。状态:**P0 + P1 已实施;P2 / P3 待办**。最后校准:2026-10-17(`main @ 6850bc64`)。 > -> 一句话:把「订单信息 → 接单模板(价格/拼单/要求)」的匹配,从当前散落在 4 个消费者、3 处平台判断、1 张"店铺必填"的绑定表里的实现,收敛为**一个平台无关的引擎**:匹配条件完全由**订单信息**表达,**店铺只是可选条件之一**,新增来源只加一个适配器 + 一组配置。 +> 一句话:把「订单信息 → 接单模板(价格/拼单/要求)」的匹配,从散落的多个消费点、平台判断和"店铺必填"的绑定表里,收敛为**一个平台无关的引擎**:匹配条件完全由**订单信息**表达,**店铺只是可选条件之一**,新增来源只加一个适配器 + 一组配置。 +> +> **来源口径(2026-10-17 确认)**:全系统只有三个匹配来源——`kuaishou-91`、`pdd`、`douyin-mashida`。其中 **91 回调与快手 send-code 是同一个 `kuaishou-91` 来源的两条数据通道**:91 是订单骨架权威源,快手 send-code 是补全/兜底源;它们共享店铺键空间,**不是两个来源,也不要拆成两个适配器**。 --- @@ -12,8 +14,7 @@ ```mermaid graph LR - A["91 回调"] --> ORD[("orders / order_items")] - B["快手 send-code"] --> ORD + A["kuaishou-91
91 回调(骨架)+ 快手 send-code(补全)"] --> ORD[("orders / order_items")] C["PDD 同步"] --> ORD D["抖音订单"] --> ORD ORD --> CTX["匹配上下文构造
resolveKuaishouWorkProductMatchContext"] @@ -25,7 +26,9 @@ graph LR OTH["寄卖建议价 / 模板改价同步大厅"] -.-> CFG ``` -### 1.2 四个消费者、各自实现 +### 1.2 四个匹配消费点(不是四个来源) + +> 这里的“四个”是**代码里曾经各自实现匹配的消费点**,不是订单来源数量。订单来源始终只有 `kuaishou-91 / pdd / douyin-mashida` 三个。 | 消费者 | 位置 | 现在怎么匹配 | 问题 | | --- | --- | --- | --- | @@ -40,7 +43,7 @@ graph LR 2. **商品绑定表要求店铺非空**:`work_product_rule_mappings` 有 CHECK `jsonb_array_length(seller_ids_json) > 0`(迁移 034),且打分函数第一关就是店铺命中(`:179-185`)。→ **不配店铺就无法使用"商品绑定"这一层**,而它恰恰是唯一表达 SKU 精确/数量系列的地方。 3. **上下文只有一个平台实现**:`resolveKuaishouWorkProductMatchContext` 只读 `snapshot.kuaishouSendCode`(`:147-169`)。其它平台靠别名兜(`out_sku_sn / externalSkuName / goodsId / spec`,`:241-257、303-316`)。 4. **兜底算法会被静默启用**:`:103 useLegacyFallback = !context.sellerId || 开关`,后台 `legacyFallbackEnabled=false`(生产实测值)也拦不住。 -5. **匹配入口三处触发且不重算**:`syncWorkerOrdersForSourceOrder` 被 91 回调(`platforms/ninetyone/order-service.ts:187`)、订单通用入口(`order/order-service.ts:211/241`)、快手 send-code(`sync-work-orders-from-send-code.ts:141/224`)分别调用;服务内发现已有工单即 `already_exists` 跳过(`sync-worker-orders-service.ts:111-121`)→ **先到先匹配,后到的补全数据不会纠正错误结果**(抖音 3 张单即此形态)。 +5. **匹配入口三处触发且不重算**:`syncWorkerOrdersForSourceOrder` 被 91 回调(`platforms/ninetyone/order-service.ts:187`)、订单通用入口(`order/order-service.ts:211/241`)、快手 send-code(`sync-work-orders-from-send-code.ts:141/224`)分别调用;服务内发现已有工单即 `already_exists` 跳过(`sync-worker-orders-service.ts:97`)→ **先到先匹配,后到的补全数据不会纠正错误结果**。注意:91 与快手 send-code 属于同一个 `kuaishou-91` 来源,这里是同一来源的两条到达通道,不是两个来源。 ### 1.4 平台耦合清单 @@ -55,7 +58,9 @@ graph LR ### 1.5 订单信息实际有什么(生产实测 2026-10-14) -| 来源 provider | 订单项数 | 有 shop_id | 店铺数 | 有 kuaishouSendCode | +> 下表的 `provider=91kaquan` 是订单通道标识;匹配层仍统一归到 `sourceKey=kuaishou-91`。不要把 `provider` 当成匹配来源数量。 + +| 订单 provider | 订单项数 | 有 shop_id | 店铺数 | 有 kuaishouSendCode | | --- | --- | --- | --- | --- | | 91kaquan | 329,677 | 100% | 8 | 252,375(76.6%) | | pdd | 4,443 | 100% | 4 | 0 | @@ -330,7 +335,7 @@ apps/backend/src/services/product-match/ - 抖音 5 条命中变化,全部是修正: - `指挥官密钥1个 / 5个`(订单 334081 / 334084 / 334088):`碎梦浮光珍宝礼盒 0.85 元/个、拼单关` → **`指挥官密钥 9.5 元/个、拼单随订单开 5 份`**; - `突击兵密钥5个`(333380 / 333932):由引流标题误命中的 `指挥官密钥` → **`突击兵`**(与快手侧同商品的判定一致)。 -- `npm run check`:format / sql guard / lint / typecheck / 727 用例(0 失败)/ build 全绿。 +- `npm run check`(2026-10-14 当时):format / sql guard / lint / typecheck / 727 用例(0 失败)/ build 全绿;2026-10-17 复核后端 740 项(737 pass / 3 skipped)、前端 143 项。 ### 8.3 待办(P2 / P3) diff --git a/docs/财务系统集成方案.md b/docs/财务系统集成方案.md new file mode 100644 index 00000000..25f428cd --- /dev/null +++ b/docs/财务系统集成方案.md @@ -0,0 +1,441 @@ +# 财务系统集成方案 + +> 状态:**方案(待实施)** +> 代码基线:`main @ 6850bc64` +> 目标:在**不破坏现有抢单、押金、结算、寄卖、提现链路**的前提下,为 order_site 增加一套可对账、可关账、可追溯的财务系统。 +> 结论先说:**不要直接在 `worker_wallets` / `worker_wallet_ledgers` 上继续加字段。** 现有表是业务钱包与流水,不是会计总账;正确做法是新增复式记账底座,让现有钱包成为它的“余额投影”。 +> +> 如果只想知道“影子总账”是什么、为什么先跑影子账:先读 [影子总账说明.md](影子总账说明.md)。 + +--- + +## 一、现有财务能力盘点 + +### 1.1 已有表 + +| 表 | 作用 | 现状 | +| --- | --- | --- | +| `worker_wallets` | 打手资金汇总 | `available_amount` / `frozen_deposit_amount` / `pending_unfreeze_amount` / `frozen_withdraw_amount` / `total_credited_amount` / `total_settled_amount` | +| `worker_wallet_ledgers` | 打手资金流水 | `ledger_type` / `amount` / `balance_after` / `frozen_after` / `audit_status` / `payload_json` | +| `worker_deposit_unfreezes` | 押金/报酬延迟解冻队列 | 到点后由调度任务入账 | +| `worker_finance_requests` | 充值/提现申请 | `request_type` / `status` / `amount` / `frozen_amount` | +| `worker_withdrawal_accounts` | 提现账户 | 多渠道、二维码、黑名单 | +| `worker_after_sales_cases` / `worker_after_sales_debts` | 售后追责与欠款 | 欠款可从后续报酬抵扣 | +| `worker_wallet_ledgers.payload_json` | 寄卖发布押金等扩展信息 | `kind` / `consignmentSubmissionId` 等 | +| `consignment_submissions` / `consignment_inventories` | 寄卖发布押金与货款 | 押金冻结/释放/没收、供货货款结算 | + +### 1.2 已有后台财务页 + +`apps/backend/src/services/admin/admin-finance-service.ts` 目前用一条大 `UNION ALL` 把订单收款、退款、打手报酬、提现、充值、售后追缴拼成“财务事件”,输出日汇总与流水。 + +这不是总账: + +- 没有借贷方向,无法验证每笔业务是否平衡。 +- 没有会计科目、期间、关账。 +- 没有平台侧账单与对账差异。 +- 没有订单级收入/成本/毛利。 +- 没有业务事件幂等键,重放可能重复统计。 +- 余额是可变字段,无法从流水独立重算并校验。 + +### 1.3 现有资金动作(必须全部纳入财务) + +| 业务动作 | 代码入口 | 财务性质 | +| --- | --- | --- | +| 订单支付 | `services/order/order-service.ts` | 收入/预收 + 平台应收 | +| 订单退款 | 订单状态回流、售后 | 冲减收入/应收 | +| 工单验收结算 | `repositories/worker-platform/work-order-settlement-repo.ts` | 履约成本 + 应付打手 | +| 抢单冻结押金 | `work-order-grab-repo.ts` | 打手余额 → 押金负债 | +| 验收释放押金 | `work-order-settlement-repo.ts` | 押金负债 → 打手余额 | +| 取消/超时没收押金 | `work-order-cancel-repo.ts` | 押金负债 → 违约金收入 | +| 拼单份额结算 | `work-order-settlement-repo.ts` | 按份额拆分成本/押金 | +| 售后扣款/欠款抵扣 | `after-sales-repo.ts` | 冲减应付打手 / 其他应收 | +| 充值审核通过 | `worker-finance-request-service.ts` | 银行存款 → 打手余额 | +| 提现申请/审核 | 同上 | 打手余额 → 银行存款 | +| 寄卖发布押金 | `consignment-deposit-repo.ts` | 打手余额 → 押金负债 | +| 寄卖货款结算 | `consignment-deposit-repo.ts` | 采购成本/存货 → 应付卖家 | +| 平台服务费/通道费 | 目前未单独建模 | 销售费用/财务费用 | + +--- + +## 二、建设原则 + +1. **复式记账**:每笔业务至少两行分录,借方合计 = 贷方合计;不平不允许提交。 +2. **不可变**:已过账分录禁止 UPDATE / DELETE;冲正用反向分录。 +3. **业务事件幂等**:每个业务动作有唯一 `business_event_id`,重复投递只入账一次。 +4. **金额只用整数分**:数据库 `BIGINT`,禁止浮点。 +5. **钱包是投影**:`worker_wallets` 的余额必须能由总账分录重算出来;差异告警。 +6. **同一事务**:业务状态变更与会计分录在同一数据库事务内提交,避免“货发了、账没记”。 +7. **期间可关账**:关账后禁止补记;必须用调整分录并留原因。 +8. **对账优先**:先做影子账 + 对账,再切换钱包读模型,不做一次性大爆炸迁移。 + +--- + +## 三、目标架构 + +```mermaid +flowchart LR + BIZ["业务动作
订单/工单/寄卖/提现/售后"] --> OUTBOX["finance_business_events
幂等事件"] + OUTBOX --> POST["finance-posting-service
借贷规则"] + POST --> JE["finance_journal_entries"] + JE --> JL["finance_journal_lines"] + JL --> ACC["finance_accounts
科目表"] + JL --> PROJ["余额投影
worker_wallets / 平台应收应付"] + EXT["平台账单 / 支付流水"] --> REC["finance_reconciliations"] + PROJ --> REC + REC --> REPORT["试算平衡 / 利润表 / 订单损益"] +``` + +### 3.1 科目表(建议) + +| 编码 | 科目 | 类型 | 正常方向 | +| --- | --- | --- | --- | +| 1001 | 银行存款 | 资产 | 借 | +| 1002 | 支付通道待结算 | 资产 | 借 | +| 1101 | 应收账款-平台 | 资产 | 借 | +| 1201 | 预付账款-供货平台 | 资产 | 借 | +| 1301 | 其他应收-打手欠款 | 资产 | 借 | +| 2001 | 应付打手报酬 | 负债 | 贷 | +| 2002 | 应付卖家货款 | 负债 | 贷 | +| 2003 | 打手押金 | 负债 | 贷 | +| 2004 | 打手可提现余额 | 负债 | 贷 | +| 2101 | 应付平台服务费 | 负债 | 贷 | +| 2201 | 预收账款 | 负债 | 贷 | +| 3001 | 本年利润 | 权益 | 贷 | +| 4001 | 商品销售收入 | 收入 | 贷 | +| 4101 | 平台服务费收入 | 收入 | 贷 | +| 4201 | 违约金/没收押金收入 | 收入 | 贷 | +| 5001 | 商品采购成本 | 成本 | 借 | +| 5101 | 履约成本-打手报酬 | 成本 | 借 | +| 5201 | 支付通道费 | 费用 | 借 | +| 5301 | 售后损失 | 费用 | 借 | +| 5401 | 平台服务费 | 费用 | 借 | + +> 科目可按实际业务裁剪,但“资产/负债/权益/收入/成本费用”五类与借贷方向必须先定死。 + +### 3.2 核心表 + +```sql +-- 科目 +CREATE TABLE finance_accounts ( + id BIGSERIAL PRIMARY KEY, + account_code TEXT NOT NULL UNIQUE, + account_name TEXT NOT NULL, + account_type TEXT NOT NULL CHECK (account_type IN ('asset','liability','equity','revenue','expense')), + normal_side TEXT NOT NULL CHECK (normal_side IN ('debit','credit')), + parent_id BIGINT REFERENCES finance_accounts(id), + status TEXT NOT NULL DEFAULT 'active', + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL +); + +-- 业务事件收件箱:幂等闸门 +CREATE TABLE finance_business_events ( + id BIGSERIAL PRIMARY KEY, + business_event_id TEXT NOT NULL UNIQUE, + event_type TEXT NOT NULL, + source_type TEXT NOT NULL, + source_id TEXT NOT NULL, + occurred_at TIMESTAMPTZ NOT NULL, + payload_json JSONB NOT NULL DEFAULT '{}'::jsonb, + status TEXT NOT NULL DEFAULT 'received' + CHECK (status IN ('received','posted','failed','ignored')), + attempts INTEGER NOT NULL DEFAULT 0, + last_error TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL, + processed_at TIMESTAMPTZ +); + +-- 结算单(打手报酬 / 卖家货款 / 平台服务费) +CREATE TABLE finance_settlements ( + id BIGSERIAL PRIMARY KEY, + settlement_no TEXT NOT NULL UNIQUE, + counterparty_type TEXT NOT NULL CHECK (counterparty_type IN ('worker','supplier','platform','other')), + counterparty_id TEXT NOT NULL DEFAULT '', + period_key TEXT NOT NULL, + amount_fen BIGINT NOT NULL DEFAULT 0, + status TEXT NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending','approved','paid','cancelled')), + approved_at TIMESTAMPTZ, + paid_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL +); + +CREATE TABLE finance_settlement_items ( + id BIGSERIAL PRIMARY KEY, + settlement_id BIGINT NOT NULL REFERENCES finance_settlements(id) ON DELETE CASCADE, + source_type TEXT NOT NULL, + source_id TEXT NOT NULL, + amount_fen BIGINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL +); + +-- 凭证头 +CREATE TABLE finance_journal_entries ( + id BIGSERIAL PRIMARY KEY, + entry_no TEXT NOT NULL UNIQUE, + business_event_id TEXT NOT NULL UNIQUE REFERENCES finance_business_events(business_event_id), + entry_type TEXT NOT NULL, + occurred_at TIMESTAMPTZ NOT NULL, + posted_at TIMESTAMPTZ NOT NULL, + period_key TEXT NOT NULL, + currency TEXT NOT NULL DEFAULT 'CNY', + memo TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL DEFAULT 'posted' CHECK (status IN ('draft','posted','reversed')), + reversal_of_id BIGINT REFERENCES finance_journal_entries(id), + created_by TEXT NOT NULL DEFAULT 'system', + created_at TIMESTAMPTZ NOT NULL +); + +-- 凭证行 +CREATE TABLE finance_journal_lines ( + id BIGSERIAL PRIMARY KEY, + entry_id BIGINT NOT NULL REFERENCES finance_journal_entries(id) ON DELETE RESTRICT, + line_no INTEGER NOT NULL, + account_id BIGINT NOT NULL REFERENCES finance_accounts(id), + debit_fen BIGINT NOT NULL DEFAULT 0, + credit_fen BIGINT NOT NULL DEFAULT 0, + order_id BIGINT, + work_order_id BIGINT, + worker_id BIGINT, + shop_id TEXT NOT NULL DEFAULT '', + provider TEXT NOT NULL DEFAULT '', + settlement_id BIGINT REFERENCES finance_settlements(id), + memo TEXT NOT NULL DEFAULT '', + dimensions_json JSONB NOT NULL DEFAULT '{}'::jsonb, + CHECK (debit_fen >= 0 AND credit_fen >= 0), + CHECK ((debit_fen > 0) <> (credit_fen > 0)), + UNIQUE (entry_id, line_no) +); + +-- 会计期间 +CREATE TABLE finance_periods ( + period_key TEXT PRIMARY KEY, -- 2026-10 + starts_at TIMESTAMPTZ NOT NULL, + ends_at TIMESTAMPTZ NOT NULL, + status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','closed')), + closed_at TIMESTAMPTZ, + closed_by TEXT NOT NULL DEFAULT '' +); + +-- 对账批次 / 明细 +CREATE TABLE finance_reconciliations ( + id BIGSERIAL PRIMARY KEY, + channel TEXT NOT NULL, -- 91 / kuaishou / pdd / douyin / affiliate_dash / alipay + period_key TEXT NOT NULL, + expected_fen BIGINT NOT NULL DEFAULT 0, + actual_fen BIGINT NOT NULL DEFAULT 0, + diff_fen BIGINT NOT NULL DEFAULT 0, + status TEXT NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending','matched','diff','resolved')), + created_at TIMESTAMPTZ NOT NULL, + resolved_at TIMESTAMPTZ +); + +CREATE TABLE finance_reconciliation_items ( + id BIGSERIAL PRIMARY KEY, + reconciliation_id BIGINT NOT NULL REFERENCES finance_reconciliations(id) ON DELETE CASCADE, + source_type TEXT NOT NULL, + source_id TEXT NOT NULL, + external_no TEXT NOT NULL DEFAULT '', + expected_fen BIGINT NOT NULL DEFAULT 0, + actual_fen BIGINT NOT NULL DEFAULT 0, + diff_fen BIGINT NOT NULL DEFAULT 0, + resolution TEXT NOT NULL DEFAULT '', + created_at TIMESTAMPTZ NOT NULL +); +``` + +### 3.3 业务事件 → 分录规则(建议) + +> 冲正不修改原凭证:新建一条 `business_event_id = <原ID>:reverse` 的反向凭证,并让 `reversal_of_id` 指向原凭证;原凭证保持 `posted` 不变。 + +| 事件 | 借方 | 贷方 | 备注 | +| --- | --- | --- | --- | +| 订单支付 | 1002 支付通道待结算 / 1101 应收平台 | 2201 预收账款 | 收入确认时点需业务拍板 | +| 履约完成/验收 | 2201 预收账款 | 4001 商品销售收入 | 或按交付时点确认 | +| 工单验收结算 | 5101 履约成本 | 2001 应付打手报酬 | 按份额逐笔 | +| 打手报酬转可提现 | 2001 应付打手报酬 | 2004 打手可提现余额 | 与现有 `reward_settlement` 对齐 | +| 抢单冻结押金 | 2004 打手可提现余额 | 2003 打手押金 | 不产生损益 | +| 释放押金 | 2003 打手押金 | 2004 打手可提现余额 | 不产生损益 | +| 没收押金 | 2003 打手押金 | 4201 违约金收入 | 需原因与审计 | +| 提现审核通过 | 2004 打手可提现余额 | 1001 银行存款 | 先冻结后扣减 | +| 充值审核通过 | 1001 银行存款 | 2004 打手可提现余额 | 需支付流水号 | +| 售后扣款 | 2001/2004 | 1301 其他应收-打手欠款 | 欠款不足转 `worker_after_sales_debts` | +| 寄卖发布押金 | 2004 打手可提现余额 | 2003 打手押金 | 与普通押金分维度 | +| 寄卖验收货款 | 5001 商品采购成本 | 2002 应付卖家货款 | 货款金额取 `cost_price_fen || asking_price_fen` | +| 支付卖家货款 | 2002 应付卖家货款 | 1001 银行存款 | 需结算单 | +| 平台服务费 | 5401 平台服务费 | 2101 应付平台服务费 | 从平台账单导入 | + +> 具体收入确认时点(付款 / 发货 / 验收 / 售后期结束)必须由业务与财务共同拍板;这会直接影响利润表。 + +--- + +## 四、与现有代码的接入方式 + +### 4.1 唯一记账入口 + +新增 `apps/backend/src/services/finance/finance-posting-service.ts`: + +```ts +postBusinessEvent({ + businessEventId, // 业务唯一键,如 work_order:123:settle + eventType, // work_order_settled + sourceType, // work_order + sourceId, // 123 + occurredAt, + lines: [...], // 借贷行 +}) +``` + +约束: + +- 在业务事务内调用;使用同一个 `PoolClient`。 +- `finance_business_events` 先 `INSERT ... ON CONFLICT DO NOTHING`。 +- 已存在则直接返回,不重复入账。 +- 校验借贷平衡;不平抛错并回滚业务事务。 +- 写入 `finance_journal_entries` / `finance_journal_lines`。 +- 更新钱包投影(或由投影任务异步更新,但同一事务更安全)。 + +### 4.2 需要改造的写入点 + +| 位置 | 改造 | +| --- | --- | +| `services/order/order-service.ts` | 订单支付/退款发事件 | +| `repositories/worker-platform/work-order-settlement-repo.ts` | 验收结算、押金释放、报酬入账发事件 | +| `work-order-grab-repo.ts` | 抢单冻结押金发事件 | +| `work-order-cancel-repo.ts` | 取消/超时没收押金发事件 | +| `after-sales-repo.ts` | 售后扣款/欠款抵扣发事件 | +| `worker-finance-request-service.ts` | 充值/提现审核发事件 | +| `consignment-deposit-repo.ts` | 发布押金、货款结算发事件 | +| `admin-finance-service.ts` | 改为读总账/试算平衡,不再用 `UNION ALL` 拼报表 | + +### 4.3 钱包投影一致性 + +第一阶段保留现有钱包更新逻辑,同时写总账;第二阶段再切换: + +1. **影子期**:业务照旧改钱包,同时写总账;每日比较“钱包余额”与“总账重算余额”。 +2. **校验期**:差异为 0 连续 N 天后,后台展示“总账余额”为准。 +3. **切换期**:钱包读接口改为读投影;旧字段保留一个版本用于回滚。 +4. **清理期**:删除重复的余额更新代码,只保留总账 + 投影。 + +--- + +## 五、后台页面与 API + +### 5.1 页面 + +| 页面 | 内容 | +| --- | --- | +| 财务总览 | 收入、成本、毛利、押金负债、可提现余额、待结算 | +| 凭证簿 | 按期间/事件/科目/订单筛选;查看借贷行 | +| 科目表 | 科目树、启用/停用、期初余额 | +| 结算中心 | 打手报酬、卖家货款、平台服务费结算单 | +| 对账中心 | 平台账单导入、差异明细、处理状态 | +| 期间关账 | 月末检查、关账、反关账(需高权限) | +| 订单损益 | 单笔订单收入、采购成本、打手报酬、退款、毛利 | +| 试算平衡 / 利润表 | 按期间导出 | + +### 5.2 API + +建议新增前缀 `/api/v1/admin/finance`: + +```text +GET /accounts +POST /accounts +GET /journal?period=&eventType=&orderId=&workOrderId= +GET /journal/:id +GET /trial-balance?period= +GET /profit-loss?period= +GET /order-profit/:orderId +GET /settlements +POST /settlements/:id/approve +GET /reconciliations +POST /reconciliations/import +POST /reconciliations/:id/resolve +GET /periods +POST /periods/:key/close +``` + +权限建议新增: + +- `finance.post`:手工分录 +- `finance.close`:关账 +- `finance.reconcile`:对账处理 +- 现有 `finance.view / review / config / export` 继续使用 + +所有写操作必须写 `admin_audit_logs`;关账、手工分录、冲正必须填原因。 + +--- + +## 六、分期落地 + +### P0:影子总账(1–2 周) + +- 建 `finance_accounts / business_events / journal_entries / journal_lines / periods`。 +- 先接 4 类高频事件:订单支付、工单结算、押金冻结/释放/没收、提现。 +- 写总账但不改现有钱包逻辑。 +- 每日对账:钱包余额 vs 总账余额。 +- 补金额守恒与幂等测试。 + +### P1:寄卖与售后(1 周) + +- 接寄卖发布押金、货款结算、售后欠款/抵扣。 +- 建 `finance_settlements` / `finance_settlement_items`。 +- 后台增加凭证簿与试算平衡。 + +### P2:平台对账(2–3 周) + +- 接 91 / 快手 / PDD / 抖音 / affiliate-dash 账单或导出。 +- 建 `finance_reconciliations` / `finance_reconciliation_items`。 +- 差异必须能定位到订单/工单/提现单。 +- 对账差异不自动改账,必须人工确认后生成调整分录。 + +### P3:报表与关账(2 周) + +- 利润表、订单损益、现金流量简表。 +- 期间关账、反关账、期初余额。 +- 导出与权限审计。 + +### P4:钱包切换(1 周,需灰度) + +- 钱包读模型切到总账投影。 +- 旧字段保留一个版本;差异告警。 +- 回滚方案:关闭投影读开关,回到旧钱包字段。 + +--- + +## 七、必须补的测试 + +1. 每笔凭证借方合计 = 贷方合计。 +2. 同一 `business_event_id` 重复投递只入账一次。 +3. 冲正后原凭证 + 反向凭证净额为 0。 +4. 钱包余额 = 总账重算余额。 +5. 抢单冻结 / 验收释放 / 取消没收的押金守恒。 +6. 拼单多份额:份额报酬 + 押金 + 欠款抵扣 = 钱包变化。 +7. 提现:申请冻结、审核通过、拒绝解冻三条路径。 +8. 寄卖:发布押金、驳回释放、验收货款、撤销处置。 +9. 售后:扣款、欠款、后续报酬抵扣。 +10. 关账后禁止补记;调整分录必须留原因。 +11. 对账:平台金额、订单金额、钱包金额三方差异可定位。 + +--- + +## 八、需要业务拍板的问题 + +| # | 问题 | 影响 | +| --- | --- | --- | +| 1 | 收入确认时点:付款 / 发货 / 验收 / 售后期结束? | 决定利润表与预收账款 | +| 2 | 平台结算周期与账单口径(91、快手、PDD、抖音、affiliate-dash) | 决定对账与应收 | +| 3 | 打手报酬是“成本”还是“代收代付”? | 决定收入总额法/净额法 | +| 4 | 寄卖货款的采购成本确认时点 | 决定存货/成本 | +| 5 | 提现通道费、平台服务费由谁承担 | 决定费用科目与结算 | +| 6 | 退款、拒付、仲裁的冲减规则 | 决定售后与收入冲回 | +| 7 | 是否需要发票/税务 | 决定发票表与税额 | +| 8 | 是否多币种 | 当前 CNY,若否则需汇率与汇兑损益 | + +--- + +## 九、一句话结论 + +**财务系统应作为“总账层”接入,而不是继续扩展业务钱包。** 先以影子总账 + 幂等业务事件 + 每日对账落地,确认钱包与总账长期一致后,再切换余额读模型;这样既能保护现有抢单/押金/寄卖链路,也能为后续平台结算、订单损益和关账留出正确底座。 diff --git a/docs/项目全量分析报告-2026-10-17.md b/docs/项目全量分析报告-2026-10-17.md new file mode 100644 index 00000000..746b26e3 --- /dev/null +++ b/docs/项目全量分析报告-2026-10-17.md @@ -0,0 +1,279 @@ +# order_site 项目全量分析报告(2026-10-17) + +> 状态:**现行** +> 代码基线:`main @ 6850bc64`(工作区干净) +> 复核范围:后端、前端、数据库迁移、部署脚本、现行文档 +> 复核方式:源码走查 + 类型检查 + Lint + 格式检查 + 全量测试 + 生产构建 +> 说明:本次没有连接生产数据库;历史数据量引用自既有生产快照,已在文中标注。 + +--- + +## 一、结论速览 + +| # | 结论 | 影响 | +| --- | --- | --- | +| 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
91 回调(订单骨架)"] --> O[("orders / order_items")] + B["kuaishou-91
快手 send-code(补全/兜底)"] --> O + C["pdd"] --> O + D["douyin-mashida"] --> O + O --> M["product-match 引擎
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`。 + +--- + +## 六、工程质量亮点 + +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` | + +--- + +## 八、文档现状与本次整理 + +本次已完成: + +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。 + +仍保留为“设计/待办”的内容: + +- `work_product_match_entries` 新表尚未落地(只在设计中),当前仍靠 `compile.ts` 编译旧模板/绑定。 +- 匹配触发点收敛、寄卖建议价/大厅改价统一走 `pricing.ts` 尚未完成。 +- 抖音“待人工队列”仍未做。 + +--- + +## 九、财务系统集成结论(摘要) + +现有财务能力是**业务钱包 + 流水**,不是会计总账: + +- `worker_wallets`:可用余额、冻结押金、累计入账、累计结算。 +- `worker_wallet_ledgers`:冻结/释放/扣减/结算等流水。 +- `worker_finance_requests`:充值/提现申请。 +- 售后欠款、寄卖发布押金、工单报酬、货款结算分散在多个 repo。 + +缺口: + +1. 没有复式记账(借贷不平无法发现)。 +2. 没有会计期间/月结/关账。 +3. 没有平台侧对账(91、快手、PDD、抖音、affiliate-dash)。 +4. 没有订单级收入/成本/毛利。 +5. 余额是可变字段,缺少“由流水重算”的校验。 +6. 资金事件缺少统一的 `business_event_id` 幂等键。 + +建议:先建**影子总账 + 对账**,不要一开始替换钱包。详见 [财务系统集成方案.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. 财务影子总账落地:`finance_journal_entries / lines` + 业务事件映射。 + +### 中期(1 个月) + +1. CI + PostgreSQL 集成测试;补金额守恒、并发抢单、Webhook 失败恢复测试。 +2. 财务对账:平台账单/支付流水/钱包流水三方核对。 +3. 订单级损益:收入、履约成本、打手报酬、平台费、退款。 +4. 拆分巨型文件;补前端组件测试与 1–2 条 E2E。 + +--- + +## 十一、一句话总结 + +**代码与工程纪律足以支撑下一阶段的财务系统;但财务系统必须建在“不可变复式分录 + 业务事件幂等 + 对账”之上,而不是继续给 `worker_wallets` 加字段。** 先修资金门禁、Webhook 恢复、锁序与公开接口,再接财务总账,风险最低。