Files
order_site/docs/财务上线初始化与验收-2026-10-26.md
T
yml2213 2b967ddd4c feat(cost-center): 商品初始化/回填增加时间范围,默认最近 30 天
- 接单模板初始化预览/执行支持 dateFrom/dateTo,只使用范围内工单证据;
  范围外模板不再先建商品
- 按工单回填商品支持时间范围;人工商品归一 UI 传时间范围
- 模板初始化重置只清理范围内导入映射/订单项,避免误伤范围外数据
- 初始化/回填默认最近 30 天,自动发货仍固定 30 天
- 文档更新初始化口径与页面操作
2026-10-26 16:26:48 +08:00

14 KiB
Raw Blame History

财务/成本上线初始化与验收(2026-10-26)

状态:现行操作手册。适用于 dev 合并后的财务/成本模块上线;历史人工成本不补录。 代码基线:迁移 103–112(本手册写于人工成本完整性与配置自动匹配修复之后)。 数据基线:生产 dump 2026-10-22(迁移 101)。历史人工支出找不回来,只保证首次配置人工履约之后的新单可记录、可监控。


〇、上线范围与边界

本次上线的是「备用金 + 人工成本 + 成本中心 + 销售事实 + 影子总账 P0」模块:

能力 本次是否上线
客服备用金账户/流水/支出/对账 是(数据需上线后初始化)
人工 SKU 配置 + 确认履约(扣积分/备用金 + 写 actual 快照) 是(配置需上线后设置)
商品目录、渠道映射、成本版本、成本快照、销售事实 是
手工退款登记(冲减收入、关闭成本异常) 是
影子总账(科目/凭证/试算平衡)、期间关账、平台对账 是(迁移 112;对账当前为手工账单导入)
平台退款 API 部分:快手行业(91)已接入;抖音/拼多多仍手工登记

明确不做:

  • 历史人工支出不补录、不按均价估算;
  • 历史人工单的 cost_missing 不进入上线后监控口径;
  • 平台自动拉单、全平台退款 API、总账接管钱包余额暂不做;
  • 人工成本监控从第一次创建人工履约配置的时间开始计算(manual_fulfillment_configs.created_at)。

一、上线顺序(必须按序)

1. 备份 + 迁移

# 生产机
cd /home/order_site
bash deploy/ubuntu-deploy.sh --backup --dry-run   # 先看计划
bash deploy/ubuntu-deploy.sh --backup             # 迁移前 pg_dump + OSS,再重建/重启

后端启动会自动执行 102–111。确认:

SELECT name FROM schema_migrations ORDER BY id DESC LIMIT 5;
-- 期望包含:111_manual_cost_integrity / 110_work_order_hall_and_shop_indexes / 109_work_order_share_participation

迁移 109/110/111 都是增量、幂等;111 会给 orders(375k) 建表达式索引,实测写阻塞约 200–300ms。

2. 初始化客服资金(备用金 + 积分)

上线后由财务/主管在后台完成:

  1. 责任主管账户:钱包管理 → 主管账户,先确认至少有一个可用主管账户(补建客服账户时必须选择责任主管)。
  2. 备用金账户:钱包管理 → 客服账户 → 「补建客服账户」(或调用 POST /api/v1/admin/fund/accounts/ensure-agents,需传 responsibleSupervisorAccountId);可直接填期初余额,也可建完再发放。
  3. 拨付备用金:钱包管理 → 对应账户 → 「发放」,把周转金拨给客服;余额不足时人工确认会直接拒绝。
  4. 积分充值:商品与成本 → 积分钱包 → 选择客服/平台 → 「发放」(南航/乐玩 1 积分 = 1 分,ROCK 1 积分 = 1 元,以 fulfillment_profiles.config_json.rechargeUnitPriceFen 为准)。
  5. 要求:客服只能使用本人备用金账户和积分账户;管理员/财务/运营代录时可在确认弹窗选择指定客服的备用金/积分账户,未选择则回退本人账户。

校验:

-- 每个客服都应有 agent 账户;余额按实际拨付为准
SELECT au.username, fa.account_no, fa.balance_fen, fa.status
FROM admin_users au
LEFT JOIN fund_accounts fa
  ON fa.owner_admin_user_id = au.id AND fa.account_type = 'agent'
WHERE au.role = 'support'
ORDER BY au.username;

-- 三平台积分账户余额
SELECT pca.platform_key, au.username, pca.balance_quantity
FROM platform_credit_accounts pca
JOIN admin_users au ON au.id = pca.owner_admin_user_id
ORDER BY pca.platform_key, au.username;

2.5 商品初始化/回填只处理最近 30 天

入口:商品与成本 → 标准商品。接单平台/人工客服 Tab 顶部有日期范围,默认最近 30 天:

  • 「从接单模板初始化」只使用范围内工单证据;范围外模板不再先建商品;
  • 「按工单回填商品」只回填范围内订单项;
  • 「归一人工商品」只归一范围内订单项;
  • 自动发货的同步/回填固定最近 30 天。

原因:更早历史 SKU 噪声大,且人工历史成本本身不可追,重新带出会污染渠道映射和商品目录。需要调整时在页面改日期范围,不要默认全量。

3. 扫描并配置人工 SKU

入口:商品与成本 → 人工 SKU 配置。

  1. 选择日期范围(建议上线前 30 天)→ 「扫描」;没有 spec_json.skuNick/outSkuSn 的单会按 sku_code → sku_name 兜底,不再漏扫。
  2. 逐项配置:平台(南航/乐玩/ROCK/备用金现金)+ 消耗积分(0 = 成本不固定,客服确认时手填)+ 启用。
  3. 保存后会自动重新匹配该 SKU 最近 30 天的订单项,不需要再点「重新匹配」;定时任务每 5 分钟会对最近窗口内未匹配人工项做一次兜底回填;更早历史不自动回填。
  4. 未配置的 SKU 会进入成本异常队列(manual_cost_missing,detail.configMissing=true),按队列把配置补齐即可。

批量检查缺口:

-- 已配置 / 未配置的人工 SKU(按首次配置之后的订单项)
SELECT
  COUNT(*) FILTER (WHERE oi.fulfillment_config_id IS NULL) AS unconfigured_items,
  COUNT(*) AS manual_items
FROM order_items oi
WHERE oi.fulfillment_mode = 'manual'
  AND oi.created_at >= (SELECT MIN(created_at) FROM manual_fulfillment_configs);

-- 需要补配置的 SKU(按订单量排序)
SELECT
  COALESCE(NULLIF(oi.spec_json->>'skuNick',''), NULLIF(oi.spec_json->>'outSkuSn',''),
           NULLIF(oi.sku_code,''), NULLIF(oi.sku_name,'')) AS sku_key,
  MAX(oi.sku_name) AS sku_name,
  COUNT(*) AS items
FROM order_items oi
WHERE oi.fulfillment_mode = 'manual'
  AND oi.fulfillment_config_id IS NULL
  AND oi.created_at >= (SELECT MIN(created_at) FROM manual_fulfillment_configs)
GROUP BY 1
ORDER BY items DESC;

配置是全量生效的:保存后历史未匹配项也会被回填(只填空值),但历史成本仍不会补录。

4. 确认人工履约(日常)

订单列表/详情 → 「确认履约」:

  • 固定成本 SKU:零输入,直接扣对应积分/备用金;
  • 成本不固定 SKU:填写本次实际成本(分);
  • 扣款、写 manual_expenses/积分流水、写 manual_case 成本快照在同一个事务里;
  • 重复点击/并发请求只能成功一次(订单项行锁 + 唯一索引兜底),失败不会重复扣款;
  • 成功后可重复确认会被拒绝。

监控:

-- 上线后人工项:已配置但未确认(客服待办)
SELECT COUNT(*) AS pending_confirmed
FROM order_items oi
WHERE oi.fulfillment_mode = 'manual'
  AND oi.fulfillment_config_id IS NOT NULL
  AND oi.created_at >= :launch_at
  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
  );

-- 人工成本缺失异常(队列)
SELECT status, COUNT(*) FROM cost_exceptions
WHERE reason = 'manual_cost_missing'
GROUP BY status;

manual_cost_missing 只在「首次配置之后」生成;确认履约写入快照后自动置为 resolved。

5. 历史回填(退款功能上线前必须跑 revenue:backfill)

存量订单要能退款,必须先回填订单项收入。 迁移 104 只加列不回填;revenue_basis='unknown' 的订单项 revenue_amount_fen=0,订单详情「退款登记」会因可退金额为 0 而置灰,手工退款也会失败。

npm run revenue:backfill -- --dry-run     # 待回填量,应为 0
npm run revenue:backfill                  # 分批回填;中断可重跑
npm run fulfillment:backfill              # 只填 NULL(可选,报表用)
npm run finance:sync -- --mode=all --from=2026-07-10 --to=2026-10-22

验证:

SELECT COUNT(*) FROM order_items WHERE revenue_basis = 'unknown'; -- 退款上线前应为 0

长区间按 30 天窗口分段执行(默认 DATABASE_STATEMENT_TIMEOUT_MS=15000)。 注意:人工历史成本按既定口径不补录,历史人工毛利仍不可用。


二、上线验收 SQL(替换 :launch_at 为实际上线时间)

-- 1. 履约方式覆盖:四项相加 = order_items 总数,NULL = 0
SELECT COALESCE(fulfillment_mode, '(null)') AS mode, COUNT(*)
FROM order_items GROUP BY 1 ORDER BY 2 DESC;

-- 2. 上线后人工项必须有履约配置(允许配置跟进期)
SELECT COUNT(*) FROM order_items
WHERE fulfillment_mode = 'manual'
  AND created_at >= :launch_at
  AND fulfillment_config_id IS NULL;

-- 3. 上线后人工项必须有成本快照(关账门槛)
SELECT COUNT(*) FROM order_items oi
WHERE oi.fulfillment_mode = 'manual'
  AND oi.created_at >= :launch_at
  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
  );

-- 4. 人工成本快照金额 = 对应备用金支出(仅现金路径)
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.created_at >= :launch_at
  AND cs.total_amount_fen <> me.amount_fen;

-- 5. 一个订单项最多一条有效人工快照(应为 0)
SELECT order_item_id, COUNT(*)
FROM cost_snapshots
WHERE subject_type = 'manual_case' AND superseded_at IS NULL
GROUP BY 1 HAVING COUNT(*) > 1;

-- 6. 销售事实与实时聚合对平(同一日期区间)
--    净收入 = Σ订单项收入 − 已确认退款;成本 = Σ有效快照
WITH r AS (SELECT MIN(stat_date) a, MAX(stat_date) b FROM sales_daily_facts)
SELECT
  (SELECT SUM(net_revenue_fen) FROM sales_daily_facts) AS facts_revenue,
  (SELECT SUM(cost_amount_fen) FROM sales_daily_facts) AS facts_cost;
-- 与独立聚合对比,允许 0 差异。

-- 7. 人工成本异常:上线后应能被确认动作收口
SELECT reason, status, COUNT(*) FROM cost_exceptions
WHERE created_at >= :launch_at
GROUP BY 1,2 ORDER BY 3 DESC;

-- 7.1 存量订单收入回填(退款功能上线前应为 0)
SELECT COUNT(*) AS unknown_revenue_items
FROM order_items WHERE revenue_basis = 'unknown';

-- 8. 总账借贷平衡(应为差额 0)
SELECT
  COALESCE(SUM(debit_fen), 0) AS debit_fen,
  COALESCE(SUM(credit_fen), 0) AS credit_fen,
  COALESCE(SUM(debit_fen), 0) - COALESCE(SUM(credit_fen), 0) AS diff_fen
FROM finance_journal_lines l
JOIN finance_journal_entries e ON e.id = l.entry_id AND e.status = 'posted'
WHERE e.occurred_at >= :launch_at;

-- 9. 已关账期间不应再有新凭证
SELECT e.period_key, COUNT(*)
FROM finance_journal_entries e
JOIN finance_periods p ON p.period_key = e.period_key AND p.status = 'closed'
WHERE e.created_at >= :launch_at
GROUP BY 1;

三、常见问题

现象 原因 处理
确认时报「还没有配置履约渠道」 SKU 未配置,或配置的 sku_key 与订单项不匹配 在人工 SKU 配置里按兜底键配置;保存后自动回填
确认时报「当前账号还没有备用金账户」 该客服未补建账户 钱包管理 → 补建客服账户
确认时报「积分不足」 未充值/充值到别的客服 积分钱包 → 给本人账户发放
确认成功但列表仍有「待确认」 前端缓存 刷新;后端以快照为准
manual_cost_missing 不减少 未确认履约,或确认失败回滚 看订单详情确认;异常只能由快照自动收口
订单详情「退款登记」置灰 订单项 revenue_amount_fen=0,通常是存量订单未跑收入回填 执行 npm run revenue:backfill;刷新后「可退」应大于 0

四、总账、期间关账与平台对账(迁移 112)

入口:后台「财务 → 总账与对账」(/admin/finance/ledger)。

4.1 影子总账

  • 销售事实按日同步:收入(借 1101 / 贷 4001)、退款(借 4101 / 贷 1101)、成本(借 5001 / 贷 2001);
  • 未关账期间按日整体重建:退款/收入回填导致销售事实变化时,旧日凭证会被替换;已关账期间保持不动;
  • 财务可手工录入调整凭证(借贷科目 + 金额 + 摘要),必须借贷平衡;
  • 试算平衡按日期区间展示;总账不接管 worker_wallets / fund_accounts 余额;
  • 定时任务 cost-finance-sync 每 5 分钟同步最近窗口;也可手动「同步销售事实到总账」。

4.2 期间关账

  • 期间按 YYYY-MM;关账前会先补齐该期间凭证,并校验试算平衡;
  • 关账后该期间禁止新增凭证;反关账需 finance.config 权限并写审计;
  • 关账不修改历史凭证,只是阻断新写入。

4.3 平台对账

  • 支持粘贴 JSON 账单导入(销售/退款行),按 provider + platform_order_id 匹配订单/退款;
  • 明细状态:matched / amount_mismatch / missing_order / missing_refund / duplicate;
  • 差异批次可标记解决(记录备注);平台自动拉单后续复用同一匹配器。

账单行示例:

[
  { "platformOrderId": "123456", "entryType": "sale", "amountFen": 990 },
  { "platformOrderId": "123456", "entryType": "refund", "amountFen": 100, "externalRef": "RF-1" }
]

4.4 平台退款 API

  • 快手行业(91):POST /api/v1/admin/kuaishou-industry/orders/:orderId/refunds/approve-and-record,传 platformRefundId、orderItemId、amountFen; 先调用快手「同意退款」,成功后再落 order_refunds.source='platform' 并冲减销售事实;
  • 抖音/拼多多:暂无退款 API,返回 501,仍用「手工退款登记」;
  • 平台退款必须传订单项;参数校验在调用平台之前完成,避免平台已同意、本地记账失败。

五、已知边界(不要对外承诺)

  1. 总账是影子账:销售事实按日凭证 + 手工凭证;尚未覆盖每一笔钱包/寄卖/提现业务,也没有接管余额;「收支明细」仍是业务事件拼装。
  2. 平台对账当前是手工账单 JSON 导入,未做平台自动拉单;平台退款 API 仅接入快手(91),抖音/拼多多仍手工登记。
  3. 平台服务费/支付通道费/寄卖货款成本确认时点仍待定;相关成本项配置为空时不参与毛利。
  4. 历史人工成本、历史平台成本、历史未归一商品不补齐;历史区间毛利只能作为参考。
  5. 管理员/财务代录已支持指定客服备用金/积分账户;客服只能使用本人账户。