- 确认履约改为单事务:锁订单项 → 复查快照 → 扣积分/备用金 → 写成本快照; 快照失败整体回滚,不再出现「钱扣了、成本没记」 - 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 库全量执行 + 重跑幂等。
19 KiB
财务与履约口径总纲(2026-10-23 重写版)
状态:现行唯一口径。本文是财务、成本、履约分类的总入口;其他文档与本文件冲突时,以本文为准。 最后校准:2026-10-26 代码基线:
dev(合并 dev1023 + 人工成本完整性修复,迁移到111) 数据基线:§二/§七 的数字来自 2026-10-22 生产 dump 的历史快照,只用于理解历史缺口,不再作为上线验收依据; 上线初始化与验收以 财务上线初始化与验收-2026-10-26.md 为准。 适用范围:订单接入、履约判定、商品归一、成本快照、销售事实、人工录单、备用金、后续总账与对账。
〇、八条硬口径
- 电子凭证不是履约方式,也不是成本类型。 快手电子凭证只是核销码/兑换码,抖音 DYC 券(
redeem_certificates)同理;一个有核销码的 91/快手或抖音订单,仍可能走自动发货、接单平台或人工。 - 旧平台通道已经下线。
kuaishou_ct_assisted、kuaishou_feifei只保留历史数据,报表中统一标记为legacy_platform,不得混入人工。 - 人工的定义只有一个: 已支付订单项,没有工单、没有自动发货任务、没有历史平台任务,即为人工;有没有核销码不影响判定。
- 人工成本逐单、按实际金额。 不按小时、不按固定单量、不按固定单价;必须从客服备用金账户扣款,客服只能使用本人账户。
- 人工成本只走「人工录单 → 扣备用金 → 写成本快照」一条路。 没有登记单、没有审批、没有报销环节,录入即计入毛利。
- 历史人工支出找不回来,直接放弃。 历史缺口统一标记为"历史未采集",不补录、不做样本回填;只保证上线后的新单不丢。
- 上线后人工账单必须 100% 可监控。 每一笔人工单必须有处理人、实际金额、备用金支出与成本快照;缺失即进入异常队列,不允许关账。
- 履约方式只有一个真源。
order_items.fulfillment_mode是唯一事实来源,商品表、成本项表、快照表、异常表、销售事实表都不再各自存一份"载体"。
一、三个正交维度
系统过去把"怎么交付"和"成本怎么算"混在同一个 carrier_type 里,导致同一订单在订单页、成本快照、销售事实中出现不同分类。正确模型拆成三个正交维度:
| 维度 | 取值 | 回答的问题 | 存储位置 |
|---|---|---|---|
fulfillment_mode |
work_order / affiliate_dash / legacy_platform / manual |
这单最终由谁执行 | order_items.fulfillment_mode(唯一真源) |
voucher_state |
无 / 已发码 / 已核销 / 已销毁 | 有没有核销码、走到哪一步 | 订单读取时单独计算 |
cost_model |
affiliate_purchase / work_order_reward / manual_actual / platform_fee / payment_fee / after_sales_loss / other |
这单成本怎么算 | cost_items.cost_model |
硬规则:
voucher_state不参与fulfillment_mode判定;fulfillment_mode必须在履约决策时写入order_items,不能每次查询临时推断;- 成本快照、销售事实、后台订单页只读同一份
fulfillment_mode; - 一个订单项只能有一个
fulfillment_mode,核销码可以有多个状态事件; - 商品来源不由履约方式推导:商品的来源由
canonical_sku前缀表达(aff:/tpl:/wo:/man:),不能反推履约方式。
二、历史履约构成(2026-10-22 dump,仅作历史基线)
本节数字是 2026-10-22 的历史快照。当前库只跑了近 30 天增量,不要拿本节数字做上线验收;上线验收用新手册的 SQL。
| 履约方式 | 订单项 | 收入 | 当前成本情况 |
|---|---|---|---|
接单平台 work_order |
153,148 | ¥680.27 万 | 151,939 条快照,成本 ¥572.95 万;1,209 项缺成本(取消/0 报酬等) |
自动发货 affiliate_dash |
53,204 | ¥627.20 万 | 商品已归一;上游采购成本版本未配置,全部进入 cost_missing |
历史平台 legacy_platform |
53,901 | ¥389.12 万 | 已下线通道,历史成本未采集,商品与成本均保持缺口 |
人工 manual |
115,194 | ¥1,447.01 万 | 商品已归一为 1,195 个 man:*;历史实际支出未采集,全部进入 cost_missing |
| 合计 | 375,447 | ¥3,143.61 万 | 成本 ¥572.95 万,毛利 ¥2,570.66 万 |
平台分布:91kaquan 368,343 项 / ¥3,023.996 万;pdd 5,563 项 / ¥105.051 万;douyin_mashida 1,541 项 / ¥14.560 万(2026-10-12 ~ 2026-10-22)。
商品前缀只表示归一来源,不反推履约方式。
tpl:710 个接单模板商品、man:1,195 个人工内部 SKU、aff:49 个自动发货 SKU、wo:63 个工单长尾商品;渠道映射共 4,897 条(接单模板 4,748 + 工单长尾 149),自动发货与人工不写渠道映射。
成本缺口口径:cost_missing 共 223,508 项,其中人工 115,194、自动发货 53,204、历史平台 53,901、接单平台 1,209。人工与历史平台属于明确不补录的历史缺口;自动发货需配置上游采购成本版本;工单 1,209 项主要是取消/0 报酬,不属于商品未归一。
异常队列:product_mapping_missing 仅剩历史平台 53,901 条,属于已下线通道的预期缺口;工单、自动发货、人工的商品归一均为 0 缺失。
三、成本模型规则
| 成本模型 | 适用履约方式 | 成本来源 | 现状 |
|---|---|---|---|
affiliate_purchase |
自动发货 | 上游 SKU 固定成本版本 | 49 个商品,本次快照恢复未配置采购成本版本 |
work_order_reward |
接单平台 | 模板规则级版本 / 工单实际报酬 | 710 个规则版本;1,209 项取消/0 报酬缺口 |
manual_actual |
人工 | 订单页人工录单的实际支出(直接扣备用金) | 已实现,历史未采集 |
platform_fee / payment_fee / after_sales_loss / other |
全部履约方式 | 通用成本项 | 未配置 |
硬规则:
- 未确认成本时保持
cost_missing,绝不默认记 0; - 人工成本只走
sys_manual_service,由订单页「人工录单」写actual快照(主体 =manual_expenses.id); - 自动发货、接单平台继续使用现有成本版本与规则版本;
- 电子凭证不建成本项,也不建"凭证载体"成本;
- 计价方式只保留三种:
per_item(按件)、per_order(按单)、percent_of_revenue(按收入比例)。已删除per_hour/fixed_daily——人工成本逐单按实际金额,不按工时; - 历史平台成本单独标记,不混入人工成本。
四、上线后人工账单全覆盖规则
4.1 判定
每个已支付订单项按以下顺序判定,命中即停止:
有 work_orders -> work_order
有 affiliate_dash 任务 -> affiliate_dash
有 ct_assisted/feifei -> legacy_platform
其余 -> manual
核销码只写入 voucher_state,不改变上述结果。
4.2 人工单必须有的数据
每一笔 manual 订单项必须具备:
- 处理人(客服/操作人);
- 实际付款金额(分);
- 费用项目;
- 收款方与付款方式;
- 备用金账户与备用金支出单(
manual_expenses.id); - 凭证或备注;
- 成本快照(
manual_case/actual)。
录入路径只有一条:订单页「人工录单」→ 扣客服备用金 → 写 actual 快照。没有单独的登记单,也没有审批与报销环节;钱与成本在同一次操作里落地。
4.3 监控指标(上线后每日检查)
| 指标 | 目标 |
|---|---|
已支付订单项无 fulfillment_mode |
0 |
| 人工订单项无备用金支出单 | 0 |
| 人工订单项无成本快照 | 0 |
| 人工支出金额与备用金流水不一致 | 0 |
| 成本异常 open 中人工项 | 0(历史标记除外) |
4.4 关账门槛
- 存在人工单无成本记录:不允许关账;
- 存在人工单未扣备用金:不允许关账;
- 存在人工支出与备用金流水金额不一致:不允许关账。
五、历史数据处理
- 历史人工支出找不回来,直接放弃:不补录、不做样本回填、不做均价估算;
- 历史人工单在销售事实里保留收入,成本缺口计入
cost_missing_count;不做特殊 stage、不补录,不计入「上线后监控缺口」; - 历史平台
legacy_platform单独成行,成本缺口同样计入cost_missing_count;cost_stage由快照覆盖情况决定(actual / estimated / partial / missing); - 自动发货、接单平台历史成本不回写、不重算版本,沿用现有快照。
六、实施顺序
| 阶段 | 内容 | 状态 |
|---|---|---|
| P0 | 统一履约事实(fulfillment_mode)+ 成本模型正交 |
已完成(本地,未部署生产) |
| P0.5 | 人工成本链路收敛为「人工录单 → 扣备用金 → 写快照」(下线登记/审批/报销) | 已完成(本地,未部署生产) |
| P1 | 人工商品归一(man:<skuId>)+ 上线后人工成本全覆盖 |
已完成(本地,未部署生产) |
| P2 | 退款冲减与异常闭环 | 已完成(手动登记先行,平台退款 API 待接入) |
| P3 | 影子总账与对账 | 未实施 |
P0 的具体迁移、回填、代码清单与验收 SQL 见 成本中心与履约模型;接口契约见 成本中心API契约。
下一步只做 P3,不要同时改总账与平台对接。
七、当前实施进度(2026-10-24 更新)
| 能力 | 状态 | 证据 |
|---|---|---|
| 订单页人工录单,按单实际金额 | 已实施 | 74b62f34 |
| 人工支出从客服备用金扣款 | 已实施 | 2aeb789e |
| 客服只能使用本人备用金账户 | 已实施 | a7f60414 |
备用金账户编号 FA-<用户名> |
已实施 | fac34c84 + 迁移 107_manual_fulfillment |
| 订单页只展示平台判断 SKU | 已实施 | c38cd8ca、cd9bb141 |
order_items.fulfillment_mode 持久化 |
已实施 | 迁移 105 + 统一决策服务 |
| 订单页 / 成本快照 / 销售事实统一读字段 | 已实施 | admin-order-read-helpers、sales-facts-service、cost-snapshot-sync-service |
| 本地 316,585 项回填 | 已实施 | fulfillment:backfill,四项计数与 §二 一致 |
成本模型与履约方式正交(去 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: 商品归集 |
| 退款冲减、异常闭环 | 已实施(手动登记先行) | 迁移 106_order_refunds;订单页「退款登记」(整单/指定商品、可撤销);销售事实按有效退款冲减;全额退完后自动关闭该订单项成本异常并写 resolution_note |
| 平台退款 API 接入 | 未实施 | 预留 order_refunds.source='api'/'platform' 与 external_ref |
| 影子总账、对账、关账 | 未实施 | §六 P3 |
历史平台仍有 53,901 个订单项(¥389.12 万)未归一,不属于 P1 范围;人工历史成本按既定口径不补录,因此人工毛利当前是“收入已归商品、成本仅新单有”的状态。 分支事实:以上实现已合并到
dev;迁移103–111未推送远端、未部署生产。生产口径仍以main为准;上线前按 财务上线初始化与验收-2026-10-26.md 初始化资金与人工 SKU。
八、上线验收查询
以下 SQL 在
fulfillment_mode落地后使用;DATE '2026-11-01'替换为实际上线日。
-- 1. 履约方式覆盖:四项相加应等于订单项总数
SELECT fulfillment_mode, COUNT(*)
FROM order_items
GROUP BY fulfillment_mode
ORDER BY 2 DESC;
-- 2. 上线后超过 24 小时仍未分类:应为 0
SELECT COUNT(*)
FROM order_items oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.fulfillment_mode IS NULL
AND COALESCE(o.paid_at, o.created_at) < now() - interval '24 hours'
AND COALESCE(o.paid_at, o.created_at) >= DATE '2026-11-01';
-- 3. 人工单无成本:上线后应为 0
SELECT COUNT(*)
FROM order_items oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.fulfillment_mode = 'manual'
AND COALESCE(o.paid_at, o.created_at) >= DATE '2026-11-01'
AND NOT EXISTS (
SELECT 1 FROM cost_snapshots cs
WHERE cs.order_item_id = oi.id AND cs.superseded_at IS NULL
);
-- 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 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
-- 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.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
AND me.created_at >= DATE '2026-11-01';
-- 6. 人工单未归一商品:P1 完成后应为 0
SELECT COUNT(*)
FROM order_items oi
WHERE oi.fulfillment_mode = 'manual'
AND oi.canonical_product_id IS NULL
AND oi.created_at >= DATE '2026-11-01';
-- 7. 核销码不得改变履约方式:带快手凭证的 91 人工单仍应为 manual
SELECT oi.fulfillment_mode, COUNT(*)
FROM order_items oi
JOIN orders o ON o.id = oi.order_id
WHERE o.provider = '91kaquan'
AND EXISTS (
SELECT 1 FROM kuaishou_industry_vouchers v
WHERE v.oid = o.platform_order_id
)
GROUP BY oi.fulfillment_mode;
-- 8. 历史平台不得混入人工:应为 0
SELECT COUNT(*)
FROM order_items oi
WHERE oi.fulfillment_mode = 'manual'
AND EXISTS (
SELECT 1 FROM fulfillment_tasks ft
WHERE ft.order_item_id = oi.id
AND ft.executor_key IN ('kuaishou_ct_assisted', 'kuaishou_feifei')
);
-- 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
WHERE oi.fulfillment_mode = 'manual'
AND pc.canonical_sku LIKE 'tpl:%';
九、常见错误(防回归)
| # | 错误 | 后果 | 正确做法 |
|---|---|---|---|
| 1 | 把"有快手核销码"当成 voucher_only |
91 人工单全部从订单页消失 | 核销码只做状态展示 |
| 2 | 按 provider=91kaquan/douyin_mashida 直接判自动发货 |
人工单被藏进自动发货 | 只看工单/任务/历史任务 |
| 3 | 用 product_catalog 的来源反推履约方式 |
11,349 个人工项被误认为接单平台 | 只读 order_items.fulfillment_mode |
| 4 | 在商品/成本项/快照/异常四张表各存一份载体 | 四处口径分叉 | 载体只存 order_items.fulfillment_mode |
| 5 | 只回填已支付历史单,不做增量兜底 | 新单长期停在 NULL |
24 小时定时归集 + 异常队列 |
| 6 | 把"人工 91,606"当成"人工全量" | 与 102,955 对不上,报表互相矛盾 | 写清"全量 102,955 / 未归一 91,606" |
| 7 | 给人工成本再造一张「登记单 + 审批 + 报销」表 | 两套资金语义混用,客服和财务都要录两遍 | 人工录单直接扣备用金并写快照,manual_expenses 是唯一资金台账 |
| 8 | 用均价、工时、固定单价补历史人工 | 违背逐单实际金额,毛利失真 | 历史不补录,只做样本区间 |
| 9 | 只改订单页,不改成本/销售事实 | 三处口径继续分叉 | 同一字段、同一决策入口 |
| 10 | 在旧文档里保留"三块/按小时/公司直付"旧结论 | 下次开发又按旧口径实现 | 旧文只留追溯标记,现行结论只写本文件 |
十、文档地图与维护规则
| 文档 | 唯一职责 | 状态 |
|---|---|---|
| 本文件 | 履约/成本/人工口径、实施顺序、验收 SQL | 现行唯一口径 |
| 成本中心与履约模型.md | 商品、成本版本、快照、销售事实的数据模型与迁移 | 现行 |
| 成本中心API契约.md | 成本中心后端/前端接口字段与模块边界 | 现行 |
| 人工支出与备用金.md | 人工录单、客服备用金台账与权限 | 现行(P0 已实施) |
| 财务系统集成方案.md | finance_* 复式总账层(未实施) |
方案 |
| README.md | 文档索引、分支与进度 | 现行入口 |
维护规则:
- 履约分类只在本文定义;其他文档只链接,不复制判定 SQL。
- 任何数字必须注明口径:全量 / 已归一 / 未归一 / 上线后。
- 每次迁移后更新本文的代码基线、数据基线、§七 进度和 §二 数字。
- 新文档必须登记到
README.md;过期文档移入docs/archive/,不得在现行文档里保留冲突结论。 - 不再使用"三块履约"作为分类名。
- 代码评审必须检查:订单页、
sales-facts-service、cost-snapshot-sync-service是否都读同一字段。