- 新服务 manual-product-normalization-service:skuNick → outSkuSn → sku_code 兜底,按内部 SKU 生成 man:<sku> 商品 - 订单项按 id 分批(2 万/批)回填,避免 15s 语句超时;同步补齐人工成本快照的商品维度 - 新增 POST /cost-center/manual-products/normalize(支持 dryRun/日期窗口),成本中心「标准商品」加「归一人工商品」按钮 - 每日 cost_finance_sync 接入最近 N 天的增量归一 - 渠道映射未归一队列增加人工单提示:人工单不能用平台 sku_code 建映射 - 文档同步:P1 标记完成,补充归一接口契约与销售事实重算结果
17 KiB
财务与履约口径总纲(2026-10-23 重写版)
状态:现行唯一口径。本文是财务、成本、履约分类的总入口;其他文档与本文件冲突时,以本文为准。 最后校准:2026-10-23 代码基线:
dev @ 43f402cf + 迁移 103–109(已合并到dev,未推送、未部署生产) 数据基线:本地生产快照克隆(数据截至 2026-10-14),已按新 schema 原地改造 适用范围:订单接入、履约判定、商品归一、成本快照、销售事实、人工录单、备用金、后续总账与对账。
〇、八条硬口径
- 电子凭证不是履约方式,也不是成本类型。 快手电子凭证只是核销码/兑换码,抖音 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-07-10 ~ 2026-10-14)
| 履约方式 | 订单项 | 收入 | 当前成本情况 |
|---|---|---|---|
接单平台 work_order |
118,053 | ¥533.57 万 | 基本完整,缺 966 项 |
自动发货 affiliate_dash |
41,676 | ¥508.97 万 | 完整 |
历史平台 legacy_platform |
53,901 | ¥389.12 万 | 历史成本未采集,单列 |
人工 manual |
102,955 | ¥1,219.20 万 | 仅 1 条 ¥320,历史基本未采集 |
| 合计 | 316,585 | ¥2,650.87 万 | — |
数字口径必须先分清"全量"和"未归一"。 人工全量是 102,955 项;其中 11,349 项已有
canonical_product_id,但这 11,349 项全部指向tpl:*接单模板商品,只说明 SKU 命中过模板,不代表它们有工单或走过接单平台。真正未归一的是 91,606 项。历史平台 53,901 项全部未归一。两者相加才是报表里的"未归一 145,507 项"。
人工 102,955 项的来源与核销码事实:
| 来源 | 订单项 | 收入 | 核销码事实 |
|---|---|---|---|
| 91/快手 | 100,618 | ¥1,177.45 万 | 全部有快手凭证记录:CONSUMED 87,310 / DESTROYED 13,247 / UNUSED 61 |
| 拼多多 | 2,335 | ¥41.74 万 | 无快手凭证 |
| 抖音 | 2 | ¥62.90 | 无快手凭证 |
这张表直接说明:91 人工单全部有核销码,但仍然是人工。 把"有核销码"当成 voucher_only 或自动发货,是订单页漏掉人工块的直接原因。
归一潜力:人工 102,955 项中 100,529 项有 spec_json.skuId(97.6%),共 3,561 个 SKU;剩余 2,426 项主要来自拼多多,可用 sku_code/sku_name 建立映射。人工商品归一后,商品编码统一用 man:<skuId>;不得因为复用了 tpl:* 商品就把履约方式改成 work_order。
成本现状:人工 102,955 项里只有 1 条实际成本快照(¥320),其余 102,954 项没有成本;历史平台 53,901 项全部没有成本。这两块是当前毛利失真的主要原因,但历史人工不补录,只做样本校准。
三、成本模型规则
| 成本模型 | 适用履约方式 | 成本来源 | 现状 |
|---|---|---|---|
affiliate_purchase |
自动发货 | 上游 SKU 固定成本版本 | 38 个商品,全部有 active 版本 |
work_order_reward |
接单平台 | 模板规则级版本 / 工单实际报酬 | 662 个规则版本,966 项异常 |
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 关账门槛
- 存在人工单无成本记录:不允许关账;
- 存在人工单未扣备用金:不允许关账;
- 存在人工支出与备用金流水金额不一致:不允许关账。
五、历史数据处理
- 历史人工支出找不回来,直接放弃:不补录、不做样本回填、不做均价估算;
- 历史人工单在销售事实中保留收入,成本标记为
historical_missing,不计入"上线后监控缺口"; - 历史平台
legacy_platform单独成行,成本未采集的标记legacy_missing; - 自动发货、接单平台历史成本不回写、不重算版本,沿用现有快照。
六、实施顺序
| 阶段 | 内容 | 状态 |
|---|---|---|
| P0 | 统一履约事实(fulfillment_mode)+ 成本模型正交 |
已完成(本地,未部署生产) |
| P0.5 | 人工成本链路收敛为「人工录单 → 扣备用金 → 写快照」(下线登记/审批/报销) | 已完成(本地,未部署生产) |
| P1 | 人工商品归一(man:<skuId>)+ 上线后人工成本全覆盖 |
已完成(本地,未部署生产) |
| P2 | 退款冲减与异常闭环 | 未实施 |
| P3 | 影子总账与对账 | 未实施 |
P0 的具体迁移、回填、代码清单与验收 SQL 见 成本中心与履约模型;接口契约见 成本中心API契约。
下一步只做 P2,不要同时改总账。
七、当前实施进度(2026-10-24 更新)
| 能力 | 状态 | 证据 |
|---|---|---|
| 订单页人工录单,按单实际金额 | 已实施 | 74b62f34 |
| 人工支出从客服备用金扣款 | 已实施 | 2aeb789e |
| 客服只能使用本人备用金账户 | 已实施 | a7f60414 |
备用金账户编号 FA-<用户名> |
已实施 | fac34c84 + 迁移 106_fund_account_no_readable |
| 订单页只展示平台判断 SKU | 已实施 | c38cd8ca、cd9bb141 |
order_items.fulfillment_mode 持久化 |
已实施 | 迁移 107 + 统一决策服务 |
| 订单页 / 成本快照 / 销售事实统一读字段 | 已实施 | admin-order-read-helpers、sales-facts-service、cost-snapshot-sync-service |
| 本地 316,585 项回填 | 已实施 | fulfillment:backfill,四项计数与 §二 一致 |
成本模型与履约方式正交(去 carrier_type) |
已实施(本地库已改造) | 迁移 103–107 重写;cost_items.cost_model |
| 12 个迁移合并为 5 个 | 已实施 | 103_product_catalog ~ 107_order_item_fulfillment_mode |
| 人工成本链路收敛为「人工录单 → 扣备用金 → 写快照」 | 已实施(本地库已改造) | 迁移 108_drop_manual_expense_records;下线登记单与审批报销 |
人工商品归一 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: 商品归集 |
| 退款冲减、异常闭环 | 未实施 | §六 P2 |
| 影子总账、对账、关账 | 未实施 | §六 P3 |
历史平台仍有 53,901 个订单项(¥389.12 万)未归一,不属于 P1 范围;人工历史成本按既定口径不补录,因此人工毛利当前是“收入已归商品、成本仅新单有”的状态。 分支事实:以上实现已快进合并到
dev;迁移103–111未推送远端、未部署生产。生产口径仍以main为准。
八、上线验收查询
以下 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. 人工订单项没有备用金支出单:上线后应为 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
);
-- 5. 人工成本快照金额与备用金支出金额不一致:应为 0
SELECT cs.id, me.expense_no, me.amount_fen, cs.total_amount_fen
FROM cost_snapshots cs
JOIN manual_expenses me ON me.id = cs.subject_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. 人工项误挂接单模板商品: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是否都读同一字段。