Files
order_site/docs/影子总账说明.md
T
yml2213 0099597b0d docs: 迁移编号同步为 103–107 并校准分支状态
- 全量替换文档里的旧迁移编号:106→107、107→105、108/109/111→104、112→106、110→107;
- 删除 113/114 的残留引用(这两个编号从未落地);
- 分支状态校准为 `dev @ 465a9f65 + 迁移收敛`,本地库按「清 103–112 记录后重跑」对齐;
- 成本中心与履约模型 §一 重写迁移基线表,§八 改为迁移基线与本地库同步;
- 归档原地改造脚本的索引补登记。
2026-10-25 11:38:18 +08:00

12 KiB
Raw Blame History

影子总账到底是什么意思

状态:现行说明(未实施) 最后校准:2026-10-23 代码基线:dev @ 43f402cf + 迁移 103–107(已合并到 dev`,未推送、未部署生产) 配套方案:财务系统集成方案.md 总口径以 财务与履约口径总纲-2026-10-23.md 为准。 前置:统一履约事实、商品主数据、渠道映射、成本中心与销售事实;实施顺序以总纲第六节为准。 一句话:影子总账是一套“跟着真实业务跑、但暂时不接管余额”的复式会计账。 它先把每一笔业务翻译成借贷分录,和现有钱包/订单/提现数据逐日对账;确认长期对得平之后,再让它成为正式总账。

边界:影子总账解决“钱怎么记、怎么对”,不负责“商品是什么、成本是多少、人工谁处理”。商品与成本见 成本中心与履约模型.md,人工支出见 人工支出与备用金.md。

当前进度:客服备用金、商品与成本中心、成本快照、销售事实、人工录单已实施;finance_* 表、分录、对账、转正均未实施。人工成本现在只有一条链路:人工录单 → 扣客服备用金 → 写成本快照,无登记单/审批/报销;历史人工支出找不回来、直接放弃,上线后必须全覆盖。


一、为什么叫“影子”

“影子”不是指数据是假的,而是指它的地位:

  • 现有 worker_wallets 仍然是运行时余额的权威来源;
  • 现有 worker_wallet_ledgers 仍然继续记录打手资金动作;
  • 影子总账在后台并行记录同一批业务事件,但暂时不决定用户能提现多少、能接多少单;
  • 它像影子一样跟着真实系统走,用来验证“如果我们用会计方式记账,能不能和现有系统对得上”。
真实业务
  ├─ 现有链路:改订单 / 改工单 / 改钱包 / 改提现单   ← 仍然权威
  └─ 影子链路:业务事件 → 复式分录 → 总账余额       ← 只观察、不接管

当影子账连续对平、业务口径确认后,才进入“转正”阶段:总账成为权威,钱包变成总账的余额投影。


二、它和现在的钱包流水有什么区别

维度 现在的 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 元

借:1002 支付通道待结算       100
贷:2201 预收账款             100

此时钱包不变。这笔钱还不是收入,而是预收/待结算。

2. 工单验收,确认收入与履约成本

借:2201 预收账款             100
贷:4001 商品销售收入         100

借:5101 履约成本-打手报酬     50
贷:2001 应付打手报酬          50

此时钱包仍不一定变,因为报酬可能还在待解冻期。

3. 报酬转为可提现

借:2001 应付打手报酬          50
贷:2004 打手可提现余额        50

此时钱包 available_amount +50。

4. 抢单冻结押金 20 元

借:2004 打手可提现余额        20
贷:2003 打手押金              20

钱包:available -20,frozen_deposit +20。这只是负债内部重分类,不是费用。

5. 提现 10 元

借:2004 打手可提现余额        10
贷:1001 银行存款              10

钱包:available -10。

6. 最终核对

钱包 available = 50 - 20 - 10 = 20
钱包 frozen_deposit = 20

总账中对应的负债也应分别是 20 和 20;如果对不上,就说明有事件漏记、重复记或金额映射错了。


四、影子总账在本项目怎么跑

flowchart LR
  A["业务事务<br/>订单/工单/提现/寄卖/售后"] --> B["finance_business_events<br/>幂等事件"]
  B --> C["finance-posting-service<br/>借贷规则"]
  C --> D["finance_journal_entries"]
  D --> E["finance_journal_lines"]
  E --> F["科目余额 / 钱包投影"]
  G["现有 worker_wallets / ledgers"] --> H["每日对账"]
  F --> H
  I["平台账单 / 支付流水"] --> H
  H --> J["差异清单<br/>finance_reconciliation_items"]

1. 业务事件先落“收件箱”

每个业务动作生成唯一事件:

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 只做一件事:把事件翻译成分录。

事件: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。

因此影子期可以做成:

业务事务提交
  → 尝试写影子总账(best-effort)
  → 失败只记录 finance_business_events.status=failed
  → 定时任务重试

5.2 转正期(接管)

  • 总账成为权威;
  • 分录写入与业务状态变更必须在同一个数据库事务;
  • 分录写失败 → 业务回滚;
  • 钱包余额从总账投影读取;
  • 旧钱包字段保留一个版本用于回滚。
业务事务开始
  → 改业务状态
  → 写总账事件 + 分录
  → 更新钱包投影
  → 提交

影子期允许“账慢一点”,转正期不允许“账少一笔”。


六、历史数据怎么处理

不建议一上来把 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 §三–§六;全局顺序以 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. 连续对平后转正:总账成为权威,钱包改为投影;保留回滚开关。

十、一句话总结

影子总账就是“先用会计的方式把业务再记一遍,但不让它管钱;等它和现有钱包、平台账单长期对得上,再让它转正”。 它的价值不是多一张表,而是提前暴露“钱从哪里来、到哪里去、哪一笔没记、哪一笔记错了”,避免财务系统上线后才发现历史资金链路对不平。