Files
order_site/docs/人工支出登记落地方案-2026-10-17.md
T

287 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 人工支出登记落地方案(2026-10-18 修订)
> 状态:**现行方案(未实施)**
> 最后校准:2026-10-18
> 代码基线:`main @ 7542d547`
> 依据:业务口径「人工块当前不以 SLA 监管为目标,先把流程搞清楚:哪个客服处理了哪些东西、付了多少钱、什么项目、关联哪个订单」。
> 数据来源:本地只读克隆(生产快照,数据截至 2026-10-14)+ 现有代码盘点。
> 重要:**客服备用金支出已经实施**(`manual_expenses`),但本文的**公司人工支出登记**(`manual_expense_records`)尚未实施;两张表不是一回事,不能直接复用或改名。
> 相关文档:[成本中心与三块履约模型.md](成本中心与三块履约模型.md) §8 · [财务系统集成方案.md](财务系统集成方案.md) §八 · [客服备用金台账实施方案](客服备用金台账实施方案-2026-10-18.md) ·
> DDL 草案 [drafts/2026-10-17-人工支出登记-DDL草案.sql](drafts/2026-10-17-人工支出登记-DDL草案.sql)
---
## 〇、先分清两张表
| 表 | 业务含义 | 当前状态 | 关键字段 |
| --- | --- | --- | --- |
| `manual_expenses` | **客服备用金支出**:客服从自己的备用金账户里充值/采购/赔偿 | **已实施** | `account_id` / `fund_entry_id` / `category` / `payee_*` / `evidence_json` |
| `manual_expense_records` | **公司人工支出/垫付登记**:按订单/成本项记录公司实际付出去的钱 | 仅草案 | `order_id` / `order_item_id` / `cost_item_id` / `paid_by_admin_user_id` / `status` / `cost_snapshot_id` |
结论:
1. 客服侧已经收口的是“备用金余额与用途”,解决“客服手里有多少钱、这笔钱花到哪”。
2. 财务侧还缺的是“公司口径的实际支出/垫付”,解决“公司为哪个订单、哪个成本项、付给谁、谁垫的、有没有审批和凭证”。
3. 两张表通过 `fund_expense_id` 关联:如果这笔公司支出来源于客服备用金,就填;公司直付则留空。
4. **不要**把 `manual_expenses` 直接改名为 `manual_expense_records`,也不要让两张表共用索引/约束名。
---
## 一、结论先行
1. **订单里的数据足够预填 90%**:店铺、商品、规格、数量、用户实付、支付时间、平台单号、履约标签、状态,后台订单列表/详情已经全部具备。客服实际只需要补 **4 件事**:渠道/项目、金额、收款方、凭证(外加默认带出的「谁、什么时候」)。
2. **现有财务页没有“支出”这一侧**。财务页现有 7 个账目类别(订单收款、订单退款、打手报酬、打手提现、打手充值、打手资金回收、售后追缴退还)**全部是收款或打手侧资金**;客服备用金支出已经落在 `fund` 模块,但还没有汇总进财务页,也没有公司口径的人工支出/垫付登记。
3. **落地成本低、风险小**:不需要改订单主表,不需要新角色体系(现有 `admin/finance/operator/support` 四角色 + `finance.*` 权限即可覆盖),只需新增「公司人工支出登记」表 + 订单侧入口 + 财务侧新类别。
4. **登记必须“发生在垫付当天”**才有监管价值;事后补记只能算账,不能算监管。
5. **本轮只补公司口径**:客服备用金台账已完成,不重复做一套客服余额系统;通过 `fund_expense_id` 关联即可。
---
## 二、现状盘点(逐字段,来自代码而非猜测)
### 2.1 订单已经带了什么(可直接预填)
订单列表行数据由 `mapAdminOrderListItem` 组装(`apps/backend/src/services/admin/admin-order-read-helpers.ts:17`),字段与截图中看到的元素一一对应:
| 界面元素(截图) | 数据字段 | 来源 | 支出登记用途 |
| --- | --- | --- | --- |
| 快手 / 91卡券 | `provider` / `platform` | 订单表 | 自动带出,决定默认渠道选项 |
| 2629001926827116 | `platformOrderId` | 订单表 | **对账主键**,必带 |
| 小鱼儿游戏服务店 | `shopName` / `shopId` | 订单表 | 统计维度(店铺) |
| 和平精英…雪山精灵兔 | `itemTitle` / `items[]` | 订单项 + 快照 | 商品/皮肤维度 |
| 指挥客密钥1个、(2进1)暗夜风云/蓝调溯影 | `skuNick` / `skuCode` | 订单项 | 规格维度(注意口径问题见 §7.4) |
| 数量 1 · 关联工单 1 | `totalQuantity` / `workOrderCount` | 订单项 / 工单 | 数量与载体提示 |
| ¥ 10.30 CNY | `totalAmount` / `totalAmountFen` | 订单表 | **用户实付(= 收入)**,用于算毛利 |
| 2026-10-17 22:24:21 | `paidAt` / `createdAt` | 订单表 | 支出时间不能早于支付时间 |
| 打手履约 / ⚡自动直发 / 🎫电子凭证 | `fulfillmentType` + `workOrderCount` | 派生 | 提示该单走哪条链路 |
| 已支付 | `payStatus` | 订单表 | 只有已支付才允许登记支出 |
| 待准备资源 / 未开始 | `resourceStatus` / `customerStatus` | 任务/工单派生 | 提示当前进度 |
| (未在列表显示)操作人 | 后台会话 `userId` / `username` / `role` | `admin_sessions` | 默认「登记人」 |
补充事实:克隆库里后台账号 **29 个,其中 `support`(客服)20 个**,`finance`(财务)角色独立存在 —— 登记人群与审批人是现成的。
> 说明:截图上的标签文案在代码里是「待准备资源」(`resourceStatus = pending_prepare`)、「未开始」(`customerStatus = not_started`),见 `apps/frontend/src/components/admin/StatusTag.tsx:39,48`。
### 2.2 订单里**没有**的(必须人工补,这也是登记表的全部价值)
| # | 缺失信息 | 为什么订单里不可能有 |
| --- | --- | --- |
| 1 | **实际走哪个渠道/找谁履约** | 订单只记录**平台来源**(快手/PDD/抖音),不记录我方线下找的上游、打手、代购 |
| 2 | **实际付了多少钱** | 订单只有**用户付给我们的钱**,没有**我们付出去的钱** |
| 3 | **收款方**(微信号/支付宝/银行卡/账号/昵称) | 完全在系统外 |
| 4 | **凭证**(转账截图、聊天记录、订单截图) | 完全在系统外 |
| 5 | **实际垫付人** | 可能与登录账号不同(主管代付、他人代垫) |
| 6 | **备注/原因**(为什么赔、加急、补差价) | 无结构可依 |
### 2.3 现有财务流水的类别缺口(关键发现)
`AdminFinancePage.tsx:39` 的账目类别:
```text
订单收款(order_paid) · 订单退款(order_refund) · 打手报酬(worker_reward) · 打手提现(withdraw_paid)
打手充值(recharge) · 打手资金回收(worker_recovery) · 售后追缴退还(after_sales_refund)
```
**没有任何一类描述“我方公司支出”**。后端 `admin-finance-service.ts` 的 `allowedCategories` 同样只有这 7 类,且方向判定只把 `order_paid / recharge / worker_recovery` 视为流入。客服备用金页已有自己的 `manual_expenses` 明细,但那是 fund 口径,不会自动进入财务页;即使今天记了公司人工支出,财务页也没有地方落。
**结论**:人工支出登记必须同时补「财务流水类别」,否则只是把数据存进了另一张看不见的表。
---
## 三、登记模型:以订单为入口,人工只做最小输入
### 3.1 字段级分工(自动带出 vs 人工填写)
| 字段 | 谁提供 | 必填 | 说明 |
| --- | --- | --- | --- |
| 订单号 / 订单项 | 自动(订单) | 是(无订单支出除外) | 从订单详情/列表发起时锁定 |
| 客服备用金来源 | 人工关联 | 否 | 若这笔钱从客服备用金出,关联 `manual_expenses.id`;公司直付留空 |
| 平台 / 店铺 / 商品 / 规格 / 数量 | 自动(订单) | 是 | 只读展示,避免手抄错 |
| 用户实付 | 自动(订单) | 是 | 只读,用于毛利 |
| 支付时间 | 自动(订单) | 是 | 校验支出时间不早于它 |
| **付款项目(渠道/成本项)** | **人工选** | **是** | 对应 `cost_items`,例如「上游拿货-A 渠道」「代练打手」「平台充值」;没有可选项时走"新建项目"审批流 |
| **支出金额** | **人工填** | **是** | 单位分;> 阈值触发二级审批 |
| **收款方类型 + 名称** | **人工填** | **是** | 打手 / 上游 / 客户 / 平台 / 其他 |
| **收款账号** | 人工填 | 否 | 微信/支付宝/银行卡号,用于事后核对 |
| **付款方式** | 人工选 | 是 | 微信 / 支付宝 / 银行 / 现金 / 平台余额 |
| **付款时间** | 人工填 | 是 | 默认当前时间,可回填(当天) |
| **凭证** | **人工传** | **是** | 转账截图/聊天记录,支持多张 |
| 登记人 + 登记时间 | 自动(会话) | — | 默认登录账号,可改(代付场景) |
| 备注 / 原因 | 人工填 | 否 | 赔偿、差价、加急等必须写 |
| 报销状态 | 流程 | — | 待审 / 已通过 / 已驳回 / 已报销 |
### 3.2 两种登记形态都要支持
| 形态 | 场景 | 订单号 | 占比预估 |
| --- | --- | --- | --- |
| **有订单支出** | 客服看到订单 → 去别的渠道拿货/找打手 → 登记 | 必填 | 绝大多数 |
| **无订单支出** | 平台充值、批量拿货、客户赔偿、运费、办公 | 允许为空 | 少数但必须支持,否则这部分钱永远查不到 |
无订单支出**必须单独可见**(财务页筛选项 + 异常队列统计),不能混进订单支出里被平均掉。
### 3.3 一对多关系(必须允许)
- **一个订单项可以有多笔支出**:渠道费 + 加急费 + 补差价 → 逐笔登记,汇总到订单损益。
- **一笔转账覆盖多个订单**(例如一次拿货 5 单)→ 建议拆成 5 条明细,用 `expense_batch_no` 归组(可选字段),避免把成本摊错订单。
- **实际支出 vs 标准成本**:`cost_snapshots` 记的是**标准成本**(成本中心算出来的),人工支出记的是**实际发生**。二者要能对比,差异就是"人工议价空间/异常"的信号 —— 这是后续做成本优化的关键数据,不要在实现时用其中一个覆盖另一个。
---
## 四、交互落地(三个入口,一套数据)
### 4.1 订单列表(就是截图这一页)
- **行操作**加一个「登记支出」按钮(放在「详情」旁边;现在该位置只在自动直发时显示 H5 链接,不冲突)。
- **新增一列/一个标签**:本单已登记支出合计(无则留空)。让主管扫一眼就能看出哪些单垫了钱、垫了多少。
- **筛选条件**增加:`有/无支出登记`、`支出待审批` —— 这是主管抽查与财务对账的入口。
### 4.2 订单详情页
`AdminOrderDetailPage.tsx` 现有四张卡片:基础信息 / 商品明细 / 任务 / 原始载荷。建议在**商品明细与任务之间**插入:
```text
┌ 人工支出 ────────────────────────────────┐
│ 合计 ¥XX.XX(已通过) / 待审 ¥XX.XX │
│ ┌────────┬──────┬────────┬──────┬──────┐ │
│ │项目 │金额 │收款方 │付款人 │状态 │ │
│ ├────────┼──────┼────────┼──────┼──────┤ │
│ │上游拿货 │¥8.50 │微信-xx │客服A │待审 │ │
│ └────────┴──────┴────────┴──────┴──────┘ │
│ [+ 登记支出] │
│ 毛利预览:收入 ¥10.30 − 标准成本 ¥0 − 支出 ¥8.50 = ¥1.80 │
└──────────────────────────────────────────┘
```
「毛利预览」是这张卡片真正的管理价值:**一眼看到这单是不是亏的**。
### 4.3 财务页
- 账目类别新增:`人工支出(manual_expense)`、`人工垫付报销(manual_expense_reimburse)`。
- 新增「支出登记」页签:按 状态 / 客服 / 店铺 / 项目 / 日期 筛选,支持批量审批。
- 汇总口径:**谁付了多少钱**(按登记人)、**钱花在哪**(按项目/店铺)、**还有多少没报销**。
### 4.4 时间要求(监管的生命线)
**垫付当天登记**。建议规则:
- 超过 24 小时补登记 → 标记「迟登记」,进异常队列;
- 超过 7 天 → 必须主管审批才能登记;
- 未登记但有订单证据(如工单备注提到垫付)→ 系统提示补登。
理由:事后补记的金额与项目失真最快,而"当天登记"是可执行、可考核的硬约束。
---
## 五、状态机与权限(复用现有体系,不新增角色)
```text
draft ──提交──> submitted ──审批──> approved ──报销──> reimbursed
│
└──驳回──> rejected ──修改后重提──> submitted
(预留)approved/reimbursed ──退款或仲裁──> reversed(反向登记,不改原记录)
```
| 动作 | 谁可以 | 现有能力 |
| --- | --- | --- |
| 提交登记 | 客服 / 运营 / 管理员 | `requireAdminRoles(['admin','operator','support'])`(`routes/admin/orders.ts:14` 已是这个组合) |
| 审批 / 驳回 | 财务(或管理员) | 角色 `finance`;权限点 `finance.review` 已存在 |
| 维护付款项目 | 财务 | `finance.config` 已存在 |
| 查看支出 | 财务 / 管理员 | `finance.view` 已存在 |
| 导出 | 财务 | `finance.export` 已存在 |
> 注意:**订单路由目前只做角色校验,没有权限点校验**(`routes/admin/orders.ts` 全篇没有 `requirePermission`)。新增支出接口时建议按财务口径加权限点,避免客服看到全公司支出。
**审计双留痕**(现有约定即可满足):
1. 路由层 `createJsonHandler({ audit })` → 写 `admin_audit_logs`(`admin-audit-service.ts:11`);
2. 业务层写 `manual_expense_events`(只追加,记录状态流转与操作人)。
**不可变约束**:`approved` 之后禁止 UPDATE 金额/项目/收款方;要改只能反向登记(`reversal_of_id`),这是为退款/仲裁预留的,也是财务能信这张表的前提。
---
## 六、校验与异常队列(不默认 0、不静默通过)
| # | 规则 | 不通过时 |
| --- | --- | --- |
| 1 | 金额 > 0 | 拒绝提交 |
| 2 | 单笔 ≥ ¥5,000(阈值待定) | 需管理员二级审批 |
| 3 | 凭证必传(至少 1 张) | 拒绝提交 |
| 4 | 订单必须存在且 `pay_status = paid` | 拒绝;无订单支出走独立形态 |
| 5 | 付款项目必选 | 允许暂存但**不得 approved**;进「未归类」队列 |
| 6 | 同订单 + 同项目 + 同金额 + 同收款方,24h 内重复 | 提示疑似重复,需填原因才可提交 |
| 7 | 付款时间不得晚于当前、不得早于订单支付时间 | 拒绝 |
| 8 | 迟登记(> 24h) | 允许但标记,进异常队列 |
| 9 | 收款方为空/无意义(如「微信」) | 提示补全 |
**异常队列必须在财务页可见**:未归类数、迟登记数、无订单支出占比、待审超期数。否则又会变成"看不见的账"。
---
## 七、与已有系统的衔接
### 7.1 载体判定(人工块显性化)
人工支出登记本身就是「这单是人工在做」的强证据。回填规则补充一条:**存在 approved 人工支出的订单项,其 `carrier_type` 判定为 `manual`**(与任务/工单存在性并列,冲突时以现有优先级为准并记异常)。
### 7.2 成本中心
- `cost_item_id` 即"付款项目",与 `cost_items`(`cost_type = 'manual_service'` 或 `'auto_purchase'`)打通;
- `approved` 时写 `cost_snapshots`(`source = 'manual_input'`),与自动发货/工单的标准成本并列进入订单损益;
- 匹配不到项目 → `cost_exceptions`,不默认 0。
### 7.3 影子总账(分录)
```text
approved(垫付) 借:5102 履约成本-人工客服 贷:2005 其他应付-垫付人
reimbursed(报销) 借:2005 其他应付-垫付人 贷:1001 银行存款
reversed(预留) 反向登记 + 反向凭证,原凭证不动
```
幂等事件键:`manual_expense:{expense_no}:approved|reimbursed|reversed`。
### 7.4 报表口径的两个坑(已在别处确认,实施时必须遵守)
1. **数量**:`order_items.quantity` 不可信(316,585 项里 316,578 项 = 1,而名称带「N个」的有 104,710 项,按名称解析总量是它的 **7.3 倍**)→ 支出若要按件摊,必须用 `normalized_quantity`。
2. **商品**:同一商品名对应多个 `sku_code`(「苏尔南钻石100颗」47 个码)→ 必须经 `channel_product_mappings` 归一到 `canonical_product_id`。
---
## 八、在全局路线中的位置
> 全局顺序以 [README.md](README.md) §三 为准。本文只负责“公司人工支出登记”,不重复商品、成本中心、销售事实和影子总账的完整设计。
| 本文内部阶段 | 内容 | 前置 |
| --- | --- | --- |
| **A. 登记表 + 入口** | `manual_expense_records` / `manual_expense_events`、订单详情「登记支出」卡片、订单列表入口与标签 | 成本中心基础表已建;`cost_item_id` 可为空,但空值进异常队列 |
| **B. 审批 + 财务页** | 状态机、审批/驳回/报销、财务页新类别、「支出登记」页签、异常队列 | A 完成 |
| **C. 成本快照 + 毛利** | `approved` 写 `cost_snapshots`,订单损益预览 | 成本中心 `cost_versions` / `cost_snapshots` 可用 |
| **D. 总账入账** | `approved` / `reimbursed` / `reversed` 生成影子总账事件 | 影子总账已落地 |
**客服备用金支出已经完成 A 的一部分(记录钱和用途),但没有审批、成本项、成本快照和总账事件。** 因此本轮不是重做客服台账,而是把公司口径补上并桥接。
---
## 九、待业务确认(实施前需拍板)
| # | 问题 | 建议默认 |
| --- | --- | --- |
| 1 | 审批人是谁? | 财务(`finance` 角色)审批,管理员兜底 |
| 2 | 单笔二级审批阈值? | ¥5,000 起需管理员 |
| 3 | 区分「垫付」与「公司直付」吗? | 需要:垫付要报销,公司直付只记账 |
| 4 | 必须支持无订单支出吗? | **必须**(平台充值/赔偿/运费无处可记) |
| 5 | 允许一单多笔支出吗? | **必须** |
| 6 | 凭证必须吗? | **必须**(且支持多张) |
| 7 | 迟登记如何处理? | 24h 内正常,超时标记,超 7 天需审批 |
| 8 | 是否与现有 7 类财务流水合并展示? | 合并展示、分类筛选 |
| 9 | 客服备用金支出是否自动生成公司人工支出? | **不自动**;提供“从备用金支出带入”,人工确认后生成,避免重复入账 |
| 10 | 实际付款人必须等于登录人吗? | 允许代付;实际付款人必填,默认登录人,便于按人日结对账 |
| 11 | `cost_item_id` 为空能否提交? | 可暂存/提交,但不得 `approved`;进“未归类”异常队列 |
---
## 十、一句话小结
**订单里已经有"卖了什么、卖了多少钱、什么时候卖"的全部事实;缺的只是"我们为了这单付出去多少钱、付给谁、谁垫的、有没有凭证"。把这一件事做成当天可填、必附凭证、不可篡改、能进财务页的登记表,人工块就从"系统盲区"变成"可查账的成本项"。**