Files
order_site/docs/成本中心与三块履约模型.md
T

24 KiB
Raw Blame History

成本中心与三块履约模型

状态:现行(P0 已实施)。商品主数据、渠道映射、成本项目/版本/快照/异常已落地;销售事实仍为后续方案。 最后校准:2026-10-18 代码基线:main @ 7542d547 + 未提交的商品/成本中心 P0 数据基线:2026-10-17 生产库只读聚合(default_transaction_read_only=on,仅 SELECT,无写入) 实施顺序以 README.md §三 为准;本文负责“商品、成本、销售事实”的数据模型,不重复总账与页面设计。 关联:


〇、当前进度与边界

能力 状态
客服备用金支出(manual_expenses) 已实施;只解决客服备用金余额与用途,不等于公司人工支出登记
商品主数据 / 渠道 SKU 映射 已实施 P0;迁移 102 + 匹配键修正 105,后台「商品与成本」页可批量初始化、维护与回填历史订单项
cost_items / cost_versions / cost_snapshots / cost_exceptions 已实施 P0;迁移 103 + 修正 104,支持版本解析、预览、快照、异常队列
销售事实 / 订单毛利 未实施;sales_daily_facts 仍为草案
公司人工支出登记(manual_expense_records) 未实施
影子总账 未实施;必须在本文件的成本与销售事实之后

0.1 本期已实现边界

已实现:

  • product_catalog、channel_product_mappings,以及 order_items.canonical_product_id / normalized_quantity;
  • 按 provider + shop_id + platform_sku_code 建映射,空 shop_id 为来源级映射、具体店铺优先;provider 必须与 orders.provider 一致(91 为 91kaquan,不是匹配层的 kuaishou-91,服务层会归一别名);
  • 映射回填与 SKU 文本数量回填;
  • cost_items / cost_versions / cost_snapshots / cost_exceptions;
  • 成本版本优先级 rule > product > shop > provider > global;
  • 订单项成本预览、成本快照、缺失成本异常;
  • 后台「商品与成本」页、finance.view / finance.config 权限与审计。

本期不做:

  • sales_daily_facts 与订单毛利报表;
  • 订单进单时自动写 canonical_product_id / normalized_quantity;
  • 自动发货、工单验收、人工 case 在业务事务内自动生成成本快照;
  • 上游 cost_amount 自动同步为 cost_versions;
  • 多订单项订单的收入分摊(当前按订单总额传给成本解析器);
  • 公司人工支出登记与影子总账。

一、先纠正一个口径:三块不是“订单来源”,是“执行载体”

同一个来源的订单,可能走自动发货、接单平台或人工。正确的拆分维度是订单项最终由谁履约:

块 执行载体 成本特征 监管重点
① 自动发货 affiliate_dash 履约任务 固定成本,按皮肤/SKU,很少变化 上游成本、店铺/日期/皮肤销量
② 接单平台 work_orders + 拼单份额 变动成本,按模板/单价/固定价/拼单 发单价格、打手提现、售后追缴、毛利
③ 人工客服 目前无载体 人工工时/按单服务成本 谁处理、何时完成、证据、成本、异常

硬不变式:

一个订单项 = 订单骨架 + 买家凭证 + 一个执行载体
执行载体 ∈ { affiliate_dash, work_order, manual }

当前代码对抖音有 snapshot.classification 守卫,但 91/PDD 仍然主要靠 isConfigured=false 隐式表达“人工”。这会导致人工单在系统里没有显式状态,也没有处理记录。


二、生产数据证据(2026-10-17 只读)

2.1 最近 30 天无载体订单

“无载体”定义:已支付订单项,既没有 fulfillment_tasks,也没有 work_orders。

来源 无载体订单数 无载体金额 说明
91 42,391 约 ¥624 万 几乎全部 snapshot.isConfigured=false
PDD 2,258 约 ¥39 万 几乎全部 isConfigured=false
抖音 8 约 ¥595 仅 8 单未命中,抖音分流已基本收口

补充:

  • 2026-09 无载体订单项约 37,708;2026-08 约 46,322。这不是历史遗留,是持续发生。
  • manual_dispatch 任务全库只有 6 条,全部为 manual_review。
  • 这些无载体订单的 claim_status 全部为 active,没有完成信号。
  • 已验收工单对应的订单,orders.order_status 仍然是 paid:订单主状态不能代表履约完成。

结论:人工块不是少量兜底,而是每天上千单、金额很大的主业务块,但基本没有进入系统任务与状态机。

2.2 接单成本占比

最近 30 天已验收工单:

来源 工单数 打手报酬 订单金额口径 报酬/订单金额
91 60,902 ¥235.9 万 ¥274.4 万 86.0%
PDD 1,289 ¥20.3 万 ¥25.4 万 80.0%
抖音 478 ¥2.14 万 ¥2.21 万 96.5%

注意:91 的 orders.total_amount 来自 maxAmount(91 给的可接受金额/结算口径),PDD 来自 order_amount,抖音来自 pay_amount。三者不是同一财务口径,不能直接相加当收入。

2.3 SKU 口径问题

最近 30 天,同一个商品名会对应多个 sku_code:

  • 「指挥官密钥1个」:27 个 sku_code
  • 「1个精致荣耀专属礼盒」:10 个
  • 「苏尔南钻石100颗」:35 个

因此:

  • 不能直接按 sku_code 做“什么皮肤卖了多少”;
  • 必须先建 product_catalog + channel_product_mappings,把平台 SKU 归一到 canonical_product_id;
  • 数量也不能只读 order_items.quantity,要从 SKU 文本解析(如「指挥官密钥5个」)。

2.4 当前订单结构

最近 30 天,三个来源的订单基本都是 1 个订单 1 个订单项。这让第一版销售统计可以做简化,但模型必须按“订单项”设计,不能把“一单一品”写死。


三、统一商品维度:先解决“什么皮肤”

3.1 商品主数据

product_catalog
  id
  canonical_sku           -- 内部标准编码,统计/成本/报表唯一口径
  product_name            -- 展示名
  category                -- 皮肤/礼包/密钥/改名卡/其他
  game                    -- 和平精英等
  status
  attributes_json

3.2 渠道 SKU 映射

channel_product_mappings
  id
  provider                -- 落库值:91kaquan / pdd / douyin_mashida
  shop_id                 -- 空串 = 来源级;具体店铺优先
  platform_sku_code       -- 平台原始 sku_code / productNo / code
  platform_sku_name
  match_type              -- sku_code / sku_code_name / sku_code_series / sku_name_series
  platform_sku_name_key   -- 归一后的精确名称或数量系列键
  canonical_product_id
  quantity_multiplier     -- 数量已在归一数量中体现,系列 SKU 固定为 1
  confidence              -- exact / rule / manual
  source                  -- manual / import / rule
  enabled

匹配优先级:

店铺 + SKU 编码
  > 来源 + SKU 编码
  > 店铺 + SKU 编码 + 精确名称
  > 来源 + SKU 编码 + 精确名称
  > 店铺 + SKU 编码 + 名称系列
  > 来源 + SKU 编码 + 名称系列
  > 店铺 + 名称系列
  > 来源 + 名称系列

匹配不到的商品进入 product_mapping_missing 异常队列,不能直接拿原始 sku_code 当报表维度。

3.2.1 接单模板批量初始化(2026-10-18 修正)

662 个接单模板不允许逐个手工建商品。后台「从接单模板初始化」按以下规则批量生成:

  1. 每个启用模板生成 1 个标准商品,内部编码 tpl:<rule_key>;暂时没有历史工单的模板也先建商品。
  2. 91 与快手 send-code 的历史工单直接命中模板,以 work_orders.product_rule_id 作为映射的第一手证据,不再只看原始 sku_code。
  3. 同一 provider + shop_id + sku_code 只命中一个模板时,生成 sku_code 精确映射。
  4. 同一编码被多个模板复用时,继续用“编码 + 归一名称系列”和“编码 + 精确名称”消歧;只有唯一归属才生成映射。
  5. 同一编码 + 同一名称在历史上仍命中多个模板时,不选最新、不按次数猜;跳过并列入冲突清单,前端在初始化预览中展示候选模板与涉及订单项数。
  6. 同一来源 + 店铺内名称系列唯一时,额外生成 sku_name_series 兜底,供后续出现新编码时使用;它永远排在编码类映射之后。
  7. 数量系列(1个/2个/5个/10个,秘钥/密钥等等价写法)归为同一个标准商品;数量写入 order_items.normalized_quantity,不能把“5个”再写成 quantity_multiplier=5,否则会重复乘算。
  8. 重跑初始化时只清理 source='import' 且备注为“接单模板 … 自动初始化”的映射,并重置由这些映射带出的订单项 canonical_product_id;人工映射、成本版本、快照和真实订单/工单不删除。

本地生产快照验证结果(2026-10-18):

  • 启用模板 662 个,自动生成/复用标准商品 662 个;
  • 历史工单证据 112,451 个订单项;
  • 分层映射 4,311 条,其中编码精确 2,302、编码 + 精确名称 325、编码 + 名称系列 160、名称系列兜底 1,524;
  • 可唯一归一 86,533 个有证据订单项;另有 25,918 个订单项属于 32 组真实冲突,保持未映射;
  • 全库实际归一 103,143 个订单项(包含可由名称系列兜底命中的无工单订单项)。

3.3 店铺维度

shop_id 是稳定键;shop_name 会变。报表以 shop_id 为准,展示名取最新或按时间快照。


四、统一成本中心

4.1 当前成本散落点

成本 当前来源 问题
自动发货采购成本 affiliate-dash 商品接口 cost_amount 代码已映射,但没有落库/展示/快照
工单报酬 work_product_rules.reward_amount / unit_price_fen / sharing_unit_reward 价格与匹配规则混在一张表,改价无版本
工单实际成本 work_orders.reward_amount、share_reward 实际值有了,但没有统一成本项/版本
人工成本 无 完全缺失
平台费/支付费/售后损失 分散或未建模 无法算真实毛利

4.2 成本类型

类型 说明 典型单位
auto_purchase 自动发货上游采购成本 按件
work_order_reward 接单平台打手报酬 按件/按单/按份
manual_service 人工客服成本 按单/按小时
platform_fee 平台服务费/佣金 按单/按比例
payment_fee 支付通道费 按单/按比例
after_sales_loss 售后损失/赔付 按单
shipping_other 其他履约费用 按单

4.3 成本表

cost_items
  id
  cost_key
  cost_type
  name
  unit                 -- per_item / per_order / per_hour / percent
  currency
  status

cost_versions
  id
  cost_item_id
  amount_fen           -- 固定金额;percent 类型用 rate_bps
  rate_bps
  effective_from
  effective_to
  scope_type           -- global / provider / shop / product / rule
  provider
  shop_id
  canonical_product_id
  work_product_rule_id
  source               -- manual / upstream_sync / import
  status               -- draft / active / retired
  change_reason
  created_by
  approved_by

4.4 成本快照

cost_snapshots
  id
  order_id
  order_item_id
  work_order_id
  manual_case_id
  cost_item_id
  cost_version_id
  cost_type
  quantity
  unit_amount_fen
  total_amount_fen
  snapshot_stage       -- estimated / actual / adjustment
  resolved_at
  metadata_json

规则:

  • 改价只新增 cost_versions,不覆盖历史;
  • 订单/工单/人工单创建或完成时写 cost_snapshot;
  • 匹配不到成本时写 cost_missing,不能默认 0;
  • 历史成本永远从快照读,不从当前成本表倒推。

4.5 “只改一个地方”的过渡方案

目标状态:

  • 金额只在 cost_versions 改;
  • work_product_rules 只保留匹配条件 + 计价方式 + cost_item_id;
  • channel_product_mappings 引用自动发货 cost_item_id;
  • 人工成本引用 manual_service 成本项;
  • 业务表只保存 cost_snapshot_id。

过渡:

  1. 给 work_product_rules 增加可空 cost_item_id;
  2. 有 cost_item_id 走成本中心;
  3. 没有则回退旧 reward_amount/unit_price_fen,标记 legacy_cost_source;
  4. 659 条规则逐步迁移;
  5. 全部迁移后旧字段变只读/删除。

这样不会一次性打断发单,又能逐步做到“成本改动只在一个地方”。


五、销售事实与店铺日报

5.1 销售事实表

sales_daily_facts
  stat_date              -- Asia/Shanghai
  provider
  shop_id
  canonical_product_id
  sales_channel          -- auto / work_order / manual
  order_count
  item_count
  quantity               -- 归一化件数(SKU 文本解析后)
  gross_revenue_fen
  refund_fen
  net_revenue_fen
  auto_cost_fen
  work_order_cost_fen
  manual_cost_fen
  platform_fee_fen
  after_sales_loss_fen
  gross_profit_fen
  cost_missing_count

5.2 三个必须统一的字段

  1. 商品:canonical_product_id,不是原始 sku_code。
  2. 数量:normalized_quantity,不是简单的 order_items.quantity。
  3. 收入:revenue_amount_fen + revenue_basis,不是直接 orders.total_amount。

收入口径建议:

来源 当前字段 建议
91 maxAmount 明确是结算收入还是成本上限,再归一
PDD order_amount 作为订单收入候选
抖音 pay_amount 作为买家支付收入候选

5.3 退款日期

现在只有 pay_status=refunded 和 updated_at,不足以做按日净销售。建议补 refunded_at / refund_amount_fen,或从退款事件表聚合。

5.4 统计实现

第一版可以用物化视图:

orders + order_items + channel_product_mappings + cost_snapshots
  -> sales_daily_facts

数据量目前约 35 万订单、每天 4k+ 单,物化视图 + 索引足够;后续再改增量表。


六、自动发货块

订单项 -> 命中 affiliate_dash SKU 映射 -> fulfillment_task
       -> 上游商品成本 cost_amount -> cost_snapshot
       -> 履约完成 -> sales_daily_facts

要点:

  • 上游商品接口已有 price_amount / cost_amount,但当前只映射、不落库;需要同步到成本中心。
  • price_amount 是售价/积分,cost_amount 是成本,不能混用。
  • 成本变化生成新版本;变动超阈值告警/审批。
  • 店铺/日期/皮肤统计必须走 canonical_product_id,不能按原始 sku_code。
  • 统计件数要解析 SKU 文本中的数量。

七、接单平台块

订单项 -> 命中接单模板 -> work_order
       -> 创建/发布/抢单/验收 -> 工单报酬 -> cost_snapshot
       -> 押金冻结/释放/没收 -> 影子总账(负债,不直接进成本)
       -> 提现/售后/欠款 -> 影子总账

要点:

  • 工单报酬是主成本;押金是负债,不是成本。
  • 模板改价必须生成 cost_versions,不能直接改历史工单。
  • 工单实际结算金额写入 cost_snapshot(actual)。
  • 影子总账负责收入/成本/押金/提现/售后的复式记录;成本中心负责“金额是多少、用了哪个版本”。

生产数据提示:91/PDD/抖音已验收工单的报酬/订单金额分别约 86%/80%/96.5%。在收入口径未确认前,这个比例只能作为量级参考,不能直接当毛利率。


八、人工客服块(当前最需要补的)

口径确认(2026-10-17):业务明确人工块当前不以 SLA 监管为目标,第一优先级是 人工支出可追溯 ——「主要是把流程搞清楚:哪个人处理了哪些东西、付了多少钱。现在人工完全监管不到, 很多地方需要人工付款,也不知道付款项目、金额、订单号」。 因此本章顺序调整为:① 人工支出登记 → ② 处理人可见(轻量 case) → ③ SLA 与监管指标(延后)。 DDL 见 drafts/2026-10-17-人工支出登记-DDL草案.sql。

2026-10-18 进度:客服备用金支出已实施(fund.manual_expenses),解决“客服手里有多少钱、这笔钱花到哪”; 但公司口径的人工支出登记(manual_expense_records)仍未实施。后者通过 fund_expense_id 关联前者,不重复做客服余额系统。

8.1 问题本质

不是“客服效率低”,而是人工单没有进入系统状态机,且人工付款完全没有台账:

  • 没有 case;
  • 没有处理人;
  • 没有开始/完成时间;
  • 没有交付证据;
  • 没有人工成本;
  • 订单完成状态无法回写;
  • 付款项目、金额、关联订单号、付款人、凭证全部缺失 —— 这是当前最直接的损失点。

8.2 载体判定

建议在订单项上增加显式字段:

carrier_type: affiliate_dash / work_order / manual
carrier_status: pending / active / completed / failed / cancelled
carrier_decided_at
carrier_reason

判定:

  1. 命中自动发货 -> affiliate_dash
  2. 命中接单模板 -> work_order
  3. 都没有 -> manual
  4. 宽限期后仍无载体 -> 自动创建人工 case

8.3 人工 case 模型

落地版见 drafts/2026-10-17-人工支出登记-DDL草案.sql:已去掉 due_at/优先级等 SLA 字段,先只保留「谁在处理哪个订单项 + 结果 + 证据 + 支出登记」。下表为完整形态(阶段③)。

manual_fulfillment_cases
  id
  case_no
  order_id
  order_item_id
  provider
  shop_id
  canonical_product_id
  quantity
  priority
  status
  assignee_admin_user_id
  assigned_at
  due_at
  started_at
  completed_at
  outcome              -- delivered / refunded / failed / cancelled
  delivery_reference
  evidence_json
  cost_snapshot_id
  notes

manual_case_events
  id
  case_id
  event_type
  actor_type
  actor_id
  payload_json
  created_at

状态:

pending_assign -> assigned -> processing -> waiting_customer
               -> pending_review -> completed / failed / cancelled

8.4 监管规则(阶段③,延后)

  • 每个无载体订单必须有 case;
  • 完成必须填 outcome + 交付凭证/参考号;
  • 失败必须填原因并决定重试/退款/升级;
  • 高价值订单主管复核;
  • 超时自动升级;
  • 所有状态变更写 manual_case_events,不可改;
  • 客服不能私下改价/退款,必须走审批。

8.5 人工成本

  • 按单固定成本:简单,先落地;
  • 按小时:agent_hourly_rate × (completed_at - started_at);
  • 建议两者都记录:固定服务成本 + 工时,便于后续校准;
  • 完成时写 cost_snapshot;缺失进入 cost_missing。

8.6 监管指标(阶段③,延后)

  • 待处理量、平均等待、最长挂起;
  • 客服处理量、平均处理时长、完成率;
  • 退款率、投诉率、返工率;
  • 单均人工成本;
  • 无凭证完成率;
  • 同一买家/客服异常重复;
  • 高价值订单抽样复核。

8.7 与现有任务系统的关系

现有 manual_dispatch 任务只有 6 条,且缺少 assignee/SLA/证据/成本字段。建议:

  • 新人工 case 作为主模型;
  • 如果存在对应 manual_dispatch 任务,完成时桥接现有完成接口;
  • 不把所有人工作业强行塞进 fulfillment_tasks。

九、三块统一数据流

flowchart LR
  O["订单项"] --> C["载体判定"]
  C -->|命中自动| A["affiliate_dash 任务"]
  C -->|命中模板| W["work_order"]
  C -->|都没有| M["manual_case"]

  A --> CS["cost_snapshots"]
  W --> CS
  M --> CS

  CS --> F["sales_daily_facts / 毛利"]
  A --> L["影子总账"]
  W --> L
  M --> L

  P["product_catalog + channel_product_mappings"] --> F
  V["cost_items + cost_versions"] --> CS

十、分期落地

P0:商品与销售事实(先能做报表)

  • product_catalog
  • channel_product_mappings
  • sales_daily_facts(可先物化视图)
  • 数量归一、收入口径、退款日期补齐

P1:成本中心影子期

  • cost_items / cost_versions / cost_snapshots
  • 自动发货先接固定成本
  • 工单先接现有规则金额
  • 人工先接按单/按小时成本
  • 缺失成本进异常队列

P2:公司人工支出登记 + 轻量处理记录

  • 载体判定(carrier_type)
  • 无载体自动建轻量 case(谁在处理哪个订单项)
  • 公司人工支出登记:付款项目 / 金额 / 订单号 / 付款人 / 收款人 / 付款方式 / 凭证 / 审批
  • 通过 fund_expense_id 关联客服备用金支出,避免重复记账
  • 人工支出进成本快照与影子总账(借:履约成本-人工,贷:其他应付-垫付人)
  • 延后(阶段③):SLA、超时升级、主管复核阈值、监管指标

P3:影子总账

  • 订单收入、工单成本、押金、提现、寄卖、售后
  • 日对账、差异清单
  • 连续对平后转正

十一、业务口径确认结果(2026-10-17)

11.1 已确认

# 问题 结论 依据
1 91 的 orders.total_amount 是收入还是成本上限? 收入:total_amount 就是用户付款金额,与快手发码配合,计入收入 业务确认;数据核验:91 已支付 312,569 笔中 235,271 笔含快手 paymentFen,其中 235,268 笔与 total_amount 完全一致(100.00%),仅 3 笔差 ¥1.80~2.00
2 打手报酬是成本还是代收代付? 支出/成本:工单结算后打手会提现,我方实际付款给他们 → 按总额法(收入记用户实付全额,报酬记履约成本) 业务确认
3 人工块的目标是什么? 先做流程可见与支出可追溯(谁处理了哪些、付了多少钱、什么项目、关联订单号、凭证),SLA/监管指标延后 业务确认
4 退款/仲裁怎么冲减? 暂未接入,先预留:字段、反向冲正关系与事件类型先留好,本轮不实现逻辑 业务确认

11.2 仍待确认

  1. 收入确认时点:付款 / 发货 / 验收 / 售后期结束?(金额口径已定 = 用户实付)
  2. 自动发货的 cost_amount 是否稳定、是否含平台积分/汇率?
  3. 人工支出按单还是按小时归集?(先按单登记,工时字段后续再加)
  4. 平台服务费/支付通道费由谁承担、结算周期与账单口径?
  5. 寄卖货款的采购成本确认时点?

十二、分析基线与克隆现状(2026-10-17 更新)

生产只读聚合(SET default_transaction_read_only = on)已完成,无写入、无改表、无改数据。

现有基线:本机已存在可反复使用的生产快照克隆(本地 docker order_site-postgres-1,orders 316,597 行,数据截至 2026-10-14), 因此后续 SQL 迭代一律打本地克隆,不必再连生产;需要更新时再取新快照。

本机备份目录已收敛:backups/ 及子目录 700、两份 dump 600,并删除了一个残留临时 dump(137 MB)。 如需长期保留留档,建议只保留一份最新 dump,并确认磁盘加密。


十三、一句话结论

自动发货解决“固定成本 + 店铺/日期/皮肤销量”,接单平台解决“变动成本 + 影子总账”,人工客服解决“支出可追溯(谁付的、什么项目、多少钱、哪个订单)+ 处理人可见 + 人工成本”;三者共用一套商品主数据、成本中心和销售事实表,成本改价只发生在 cost_versions 一个地方。