287 lines
19 KiB
Markdown
287 lines
19 KiB
Markdown
# 人工支出登记落地方案(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`;进“未归类”异常队列 |
|
||
|
||
---
|
||
|
||
## 十、一句话小结
|
||
|
||
**订单里已经有"卖了什么、卖了多少钱、什么时候卖"的全部事实;缺的只是"我们为了这单付出去多少钱、付给谁、谁垫的、有没有凭证"。把这一件事做成当天可填、必附凭证、不可篡改、能进财务页的登记表,人工块就从"系统盲区"变成"可查账的成本项"。**
|