# 影子总账到底是什么意思 > 状态:**现行说明(P0 已实施,2026-10-26)** > 最后校准:2026-10-26 > 代码基线:`dev`(合并 dev1023 后)+ 迁移 `103`–`111`(已合并到 `dev`,**未推送、未部署生产**) > 配套方案:[财务系统集成方案.md](财务系统集成方案.md) > **总口径以 [财务与履约口径总纲-2026-10-23.md](财务与履约口径总纲-2026-10-23.md) 为准。** > 前置:统一履约事实、商品主数据、渠道映射、成本中心与销售事实;实施顺序以总纲第六节为准。 > 一句话:**影子总账是一套“跟着真实业务跑、但暂时不接管余额”的复式会计账。** 它先把每一笔业务翻译成借贷分录,和现有钱包/订单/提现数据逐日对账;确认长期对得平之后,再让它成为正式总账。 > > 边界:影子总账解决“钱怎么记、怎么对”,不负责“商品是什么、成本是多少、人工谁处理”。商品与成本见 [成本中心与履约模型.md](成本中心与履约模型.md),人工支出见 [人工支出与备用金.md](人工支出与备用金.md)。 > 当前进度:客服备用金、商品与成本中心、成本快照、销售事实、人工确认已实施;`finance_*` 科目/凭证/期间/对账表已建,销售事实按日生成凭证、手工凭证、试算平衡、期间关账与手工账单对账已上线 P0。**尚未接管钱包余额**,平台自动拉单与正式转正未实施。人工成本现在只有一条链路:确认履约 → 扣客服积分/备用金 → 写成本快照,无登记单/审批/报销;历史人工支出找不回来、直接放弃,上线后必须全覆盖。 --- ## 一、为什么叫“影子” “影子”不是指数据是假的,而是指它的**地位**: - 现有 `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 写失败要回滚业务 | 影子期不应阻断业务;转正期才必须同事务 | --- ## 九、在本项目落地的最小步骤 > 表结构、API 与分期细节见 [财务系统集成方案.md](财务系统集成方案.md) §三–§六;全局顺序以 [README.md](README.md) §三 为准。 1. **先完成前置**:商品主数据、渠道 SKU 映射、成本中心、销售事实。 2. **建总账表**:`finance_accounts`、`finance_business_events`、`finance_journal_entries`、`finance_journal_lines`、`finance_periods`、`finance_reconciliations`。 3. **先接 4 类高频事件**:订单支付、工单结算、押金冻结/释放/没收、提现;影子期只观察,不阻断业务。 4. **日对账 + 差异清单**:钱包余额 vs 总账余额;先只报差异,不自动改账。 5. **连续对平后转正**:总账成为权威,钱包改为投影;保留回滚开关。 --- ## 十、一句话总结 **影子总账就是“先用会计的方式把业务再记一遍,但不让它管钱;等它和现有钱包、平台账单长期对得上,再让它转正”。** 它的价值不是多一张表,而是提前暴露“钱从哪里来、到哪里去、哪一笔没记、哪一笔记错了”,避免财务系统上线后才发现历史资金链路对不平。