feat: 新增客服备用金台账与后台用户中文显示名

- 新增备用金账户、流水、支出、对账与权限
- 后台用户支持中文显示名并同步备用金姓名
- 审计、登录记录、任务及工单操作人优先展示中文名
- 补充备用金与人工支出方案文档
This commit is contained in:
yml2213
2026-10-18 13:55:24 +08:00
parent 7bacb7d5eb
commit 7542d547bf
55 changed files with 6557 additions and 105 deletions
+1
View File
@@ -44,6 +44,7 @@
| --- | --- | --- |
| [项目全量分析报告-2026-10-17.md](项目全量分析报告-2026-10-17.md) | 现行 | 当前架构、业务链路、风险与改进路线 |
| [载体判定与成本中心落地-复核与草案-2026-10-17.md](载体判定与成本中心落地-复核与草案-2026-10-17.md) | 现行(复核+草案) | 只读克隆量化载体分布与人工块、对上一轮结论的复核与修正、两份 SQL 草案说明 |
| [人工支出登记落地方案-2026-10-17.md](人工支出登记落地方案-2026-10-17.md) | 现行(方案) | 以订单为入口的支出登记:字段级预填分工、交互入口、状态机与权限、校验与异常队列、分阶段落地 |
| [成本中心与三块履约模型.md](成本中心与三块履约模型.md) | 现行(方案) | 自动发货/接单平台/人工客服三块、统一成本中心、销售事实与人工监管 |
| [财务系统集成方案.md](财务系统集成方案.md) | 现行(方案) | 财务系统边界、复式记账模型、分期落地 |
| [影子总账说明.md](影子总账说明.md) | 现行(方案) | 影子总账的定义、运行方式、对账与转正标准 |
@@ -117,6 +117,8 @@ CREATE TABLE IF NOT EXISTS manual_expense_records (
cost_snapshot_id BIGINT REFERENCES cost_snapshots(id) ON DELETE RESTRICT,
-- 预留:退款/仲裁接入后用反向登记冲正,不修改已通过的原始记录
reversal_of_id BIGINT REFERENCES manual_expense_records(id) ON DELETE RESTRICT,
-- 可选:一笔转账覆盖多个订单时用于归组(拆成多条明细,同一 batch)
expense_batch_no TEXT NOT NULL DEFAULT '',
note TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
@@ -131,6 +133,9 @@ CREATE INDEX IF NOT EXISTS idx_manual_expenses_status ON manual_expense_records
CREATE INDEX IF NOT EXISTS idx_manual_expenses_cost_item ON manual_expense_records (cost_item_id);
-- 付款人日结对账用
CREATE INDEX IF NOT EXISTS idx_manual_expenses_payer ON manual_expense_records (paid_by_admin_user_id, paid_at DESC);
-- 批量归组查询用
CREATE INDEX IF NOT EXISTS idx_manual_expenses_batch
ON manual_expense_records (expense_batch_no) WHERE expense_batch_no <> '';
DO $$
BEGIN
@@ -0,0 +1,259 @@
# 人工支出登记落地方案(2026-10-17)
> 状态:现行(方案,未实施);基线:`main @ 7bacb7d5`;**本文只做分析与设计,不含代码改动**。
> 依据:业务口径「人工块当前不以 SLA 监管为目标,先把流程搞清楚:哪个客服处理了哪些东西、付了多少钱、什么项目、关联哪个订单」。
> 数据来源:本地只读克隆(生产快照,数据截至 2026-10-14)+ 现有代码盘点。
> 相关文档:[成本中心与三块履约模型.md](成本中心与三块履约模型.md) §8 · [财务系统集成方案.md](财务系统集成方案.md) §八 ·
> DDL 草案 [drafts/2026-10-17-人工支出登记-DDL草案.sql](drafts/2026-10-17-人工支出登记-DDL草案.sql)
---
## 一、结论先行
1. **订单里的数据足够预填 90%**:店铺、商品、规格、数量、用户实付、支付时间、平台单号、履约标签、状态,后台订单列表/详情已经全部具备。客服实际只需要补 **4 件事**:渠道/项目、金额、收款方、凭证(外加默认带出的「谁、什么时候」)。
2. **现有财务流水没有"支出"这一侧**。财务页现有 7 个账目类别(订单收款、订单退款、打手报酬、打手提现、打手充值、打手资金回收、售后追缴退还)**全部是收款或打手侧资金**;我方真实付出的钱(人工垫付、上游拿货、赔偿、运费)**一类都没有** —— 这就是"付了多少钱、什么项目"查不到的根本原因。
3. **落地成本低、风险小**:不需要改订单主表,不需要新角色体系(现有 `admin/finance/operator/support` 四角色 + `finance.*` 权限即可覆盖),只需新增「支出登记」表 + 订单侧入口 + 财务侧一个新类别。
4. **登记必须"发生在垫付当天"**才有监管价值;事后补记只能算账,不能算监管。
---
## 二、现状盘点(逐字段,来自代码而非猜测)
### 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` 视为流入。也就是说:即使今天记了人工支出,财务页也没有地方落。
**结论**:人工支出登记必须同时补「财务流水类别」,否则只是把数据存进了另一张看不见的表。
---
## 三、登记模型:以订单为入口,人工只做最小输入
### 3.1 字段级分工(自动带出 vs 人工填写)
| 字段 | 谁提供 | 必填 | 说明 |
| --- | --- | --- | --- |
| 订单号 / 订单项 | 自动(订单) | 是(无订单支出除外) | 从订单详情/列表发起时锁定 |
| 平台 / 店铺 / 商品 / 规格 / 数量 | 自动(订单) | 是 | 只读展示,避免手抄错 |
| 用户实付 | 自动(订单) | 是 | 只读,用于毛利 |
| 支付时间 | 自动(订单) | 是 | 校验支出时间不早于它 |
| **付款项目(渠道/成本项)** | **人工选** | **是** | 对应 `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(垫付) 借:5101 履约成本-人工 贷: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`。
---
## 八、分阶段落地
| 阶段 | 内容 | 产出价值 | 量级 |
| --- | --- | --- | --- |
| **P0** | 支出登记表 + 订单详情「登记支出」卡片 + 订单列表入口与标签 | **能记录**:钱花在哪、谁付的、有凭证 | 1~2 天 |
| **P1** | 审批流 + 财务页新类别 + 「支出登记」页签 + 异常队列 | **能审、能统计**:谁付了多少、还有多少没报销 | 2~3 天 |
| **P2** | 成本项归类 + `cost_snapshots` + 订单损益毛利预览 | **能算清**:每单真实毛利、渠道对比 | 3~5 天 |
| **P3** | 影子总账入账 + 退款/仲裁冲正(字段已预留) | **能对账**:总账与业务账一致 | 后续 |
先做 P0 的理由:业务当前最痛的是"查不到",而不是"算不准"。能记录就已经解决 80% 的问题,且 P0 不依赖成本中心与总账。
---
## 九、待业务确认(实施前需拍板)
| # | 问题 | 建议默认 |
| --- | --- | --- |
| 1 | 审批人是谁? | 财务(`finance` 角色)审批,管理员兜底 |
| 2 | 单笔二级审批阈值? | ¥5,000 起需管理员 |
| 3 | 区分「垫付」与「公司直付」吗? | 需要:垫付要报销,公司直付只记账 |
| 4 | 必须支持无订单支出吗? | **必须**(平台充值/赔偿/运费无处可记) |
| 5 | 允许一单多笔支出吗? | **必须** |
| 6 | 凭证必须吗? | **必须**(且支持多张) |
| 7 | 迟登记如何处理? | 24h 内正常,超时标记,超 7 天需审批 |
| 8 | 是否与现有 7 类财务流水合并展示? | 合并展示、分类筛选 |
---
## 十、一句话小结
**订单里已经有"卖了什么、卖了多少钱、什么时候卖"的全部事实;缺的只是"我们为了这单付出去多少钱、付给谁、谁垫的、有没有凭证"。把这一件事做成当天可填、必附凭证、不可篡改、能进财务页的登记表,人工块就从"系统盲区"变成"可查账的成本项"。**
@@ -0,0 +1,440 @@
# 客服备用金台账实施方案(2026-10-18)
> 状态:方案,未实施。
> 背景:两位主管向客服发放备用金,客服用于充值、采购、赔偿等;目前全部走人工,无法监控每笔资金用途和每个客服余额。
> 结论:这不是报销系统,而是**客服备用金(周转金)台账**。先做“账户 + 流水 + 对账”,不做复杂审批和总账。
---
## 一、目标与非目标
### 1.1 目标
系统必须能回答四个问题:
1. 公司现在有多少钱在主管和客服手里?
2. 每个客服当前余额是多少?
3. 每一笔钱花到哪、为什么花、有没有凭证?
4. 系统余额和客服实际余额对不对得上?
### 1.2 非目标(P0 不做)
- 不做垫付/直付的复杂资金性质区分;
- 不做多级审批流;
- 不做成本中心、订单毛利、复式总账;
- 不做平台账户独立钱包(当前口径:充值后立即用于订单/消耗,直接记支出);
- 不做多币种、发票、税务。
---
## 二、业务模型
```mermaid
flowchart LR
C["公司资金"] -->|拨付| S["主管资金池<br/>2 位主管"]
S -->|发放| A["客服备用金账户<br/>每人一个余额"]
A -->|支出| P["平台 / 供应商 / 客户"]
A -->|缴回| S
P -->|付款截图| E["支出记录"]
```
### 2.1 五类流水,缺一不可
| 流水类型 | 钱的方向 | 对客服余额 | 是否算支出 | 关键字段 |
| --- | --- | --- | --- | --- |
| 期初 | 上线初始化 | 增加 | 否 | 金额、主管确认 |
| 发放 | 主管 → 客服 | 增加 | 否 | 金额、主管、客服、时间、凭证 |
| 支出 | 客服 → 平台/供应商/客户 | 减少 | 是 | 金额、用途、收款方、平台账号、订单号(可选)、付款截图 |
| 缴回 | 客服 → 主管 | 减少 | 否 | 金额、主管、客服、时间、凭证 |
| 调整 | 主管/财务 → 客服 | 增加或减少 | 视原因 | 差异金额、原因、审批人、凭证 |
> 只有“支出”没有“发放/缴回/期初”,余额永远算不出来。
### 2.2 余额公式
```text
客服余额 = 期初 + 累计发放 + 累计调整 - 累计支出 - 累计缴回
主管池余额 = 期初 + 公司拨付 + 客服缴回 - 发放给客服 - 调整
```
余额只能由流水产生,不允许直接手工修改。
---
## 三、账户设计
### 3.1 主管资金池
- 两位主管各一个账户;
- 公司给主管拨付、主管发给客服、客服缴回,都记入主管池;
- 每个客服指定一个责任主管;
- 发放可以两位主管都操作,但每笔必须记录发放人;
- 对账由责任主管确认,避免“两个人都以为对方对过了”。
### 3.2 客服备用金账户
- 每个客服一个账户;
- 客服只能看到自己的余额和流水;
- 主管能看到自己名下客服的余额和流水;
- 财务/管理员能看到全部;
- 余额不足时不能继续支出,先由主管补拨。
### 3.3 平台账户
当前口径为 A:充值后立即用于订单/消耗,因此不单独建平台钱包。支出记录中保留“平台名称 + 平台账号”,用于追溯钱充到了哪里。
如果以后出现“充值后平台还有余额、以后继续用”的情况,再增加平台账户层,不混入客服余额。
---
## 四、数据模型
P0 四张表即可。
### 4.1 `fund_accounts` 资金账户
| 字段 | 说明 |
| --- | --- |
| id | 主键 |
| account_no | 账户编号,唯一 |
| account_type | `supervisor` / `agent` |
| owner_admin_user_id | 主管或客服的后台用户 ID |
| owner_name | 姓名快照 |
| responsible_supervisor_id | 客服的责任主管;主管账户为空 |
| balance_fen | 当前余额,分 |
| status | `active` / `frozen` / `closed` |
| created_at / updated_at | 时间 |
约束:
- 同一用户同一类型只能有一个账户;
- 余额不得为负;
- 冻结账户不能发放、不能支出,但允许缴回和调整。
### 4.2 `fund_entries` 资金流水
| 字段 | 说明 |
| --- | --- |
| id | 主键 |
| entry_no | 流水号,唯一 |
| account_id | 所属账户 |
| entry_type | `opening` / `funding` / `issue` / `expense` / `return` / `adjustment` |
| amount_fen | 有符号金额,正数流入、负数流出 |
| balance_after_fen | 记账后余额快照 |
| transfer_no | 转账批次号;发放/缴回的两条流水共用 |
| counterparty_account_id | 对方账户;支出时为空 |
| expense_id | 支出明细 ID;仅 `expense` 使用 |
| reconciliation_id | 对账调整关联 |
| occurred_at | 资金实际发生时间 |
| created_by_admin_user_id | 操作人 |
| evidence_json | 凭证文件 key、备注 |
| note | 说明 |
| created_at | 创建时间 |
约束:
- `amount_fen <> 0`;
- 余额只能通过流水变化;
- 发放、缴回必须同时写两条流水:主管负数、客服正数,或反过来;
- 支出必须同时写一条客服负数流水和一条 `manual_expenses`;
- 已记账流水不可修改、不可删除;更正走调整或反向流水。
### 4.3 `manual_expenses` 支出明细
| 字段 | 说明 |
| --- | --- |
| id | 主键 |
| expense_no | 支出编号,唯一 |
| account_id | 客服备用金账户 |
| amount_fen | 支出金额,分 |
| category | `platform_recharge` / `purchase` / `compensation` / `freight` / `other` |
| payee_type | `platform` / `supplier` / `customer` / `other` |
| payee_name | 收款方名称 |
| payee_account | 收款账号 |
| platform_name | 充值平台名称 |
| platform_account | 平台账号 |
| order_id | 关联订单,可空 |
| order_item_id | 关联订单项,可空 |
| paid_at | 付款时间 |
| evidence_json | 付款截图、凭证 |
| note | 用途/原因 |
| fund_entry_id | 对应资金流水,唯一 |
| created_by_admin_user_id | 登记人 |
| created_at / updated_at | 时间 |
约束:
- 金额 > 0;
- 用途、收款方、付款时间、凭证必填;
- 订单号可选;赔偿类建议关联订单;
- 同一客服 + 同金额 + 同收款方 24 小时内重复,提示疑似重复;
- 付款时间不能晚于当前时间;超过 24 小时补记标记“迟登记”。
### 4.4 `fund_reconciliations` 对账
| 字段 | 说明 |
| --- | --- |
| id | 主键 |
| account_id | 客服或主管账户 |
| period_start / period_end | 对账区间 |
| system_balance_fen | 系统余额 |
| actual_balance_fen | 实际余额 |
| difference_fen | 差异 |
| status | `pending` / `confirmed` / `discrepancy` |
| reason | 差异原因 |
| evidence_json | 盘点/截图 |
| confirmed_by_admin_user_id | 确认人 |
| confirmed_at | 确认时间 |
| created_at | 创建时间 |
规则:
- 差异为 0 才能直接确认;
- 差异不为 0 必须填写原因,并走调整流水;
- 调整必须由主管/财务/管理员操作,不能由客服自行改余额。
---
## 五、业务流程
### 5.1 上线初始化
1. 建立两位主管账户;
2. 建立每个客服账户,指定责任主管;
3. 主管确认每个客服的期初余额;
4. 期初余额写 `opening` 流水,备注“上线期初余额”;
5. 期初确认后,所有余额变化必须走流水。
### 5.2 公司 → 主管
管理员记录“拨付”,主管池增加。若公司层暂不管理,至少给主管池录期初余额。
### 5.3 主管 → 客服
主管选择客服、填写金额、付款方式、时间、凭证,提交后:
- 主管池减少;
- 客服余额增加;
- 生成两条 `issue` 流水,共用一个 `transfer_no`。
### 5.4 客服支出
客服填写金额、用途、收款方/平台、平台账号、订单号(可选)、付款时间、截图、备注,提交后:
- 客服余额减少;
- 生成一条 `expense` 流水;
- 生成一条 `manual_expenses` 明细。
### 5.5 客服缴回
客服或主管发起缴回,主管确认后:
- 客服余额减少;
- 主管池增加;
- 生成两条 `return` 流水。
### 5.6 对账
1. 系统显示当前余额;
2. 主管/客服盘点实际现金、微信、支付宝等余额;
3. 输入实际余额,系统计算差异;
4. 差异为 0 直接确认;不为 0 填写原因并走调整;
5. 调整后重新对平。
建议频率:每笔流水当天记,每天生成余额快照,每周主管与客服对一次账。
---
## 六、权限设计
新增权限点:
- `fund.view`:查看账户和流水;
- `fund.issue`:发放/缴回;
- `fund.expense`:登记支出;
- `fund.reconcile`:发起/确认对账;
- `fund.adjust`:调整余额;
- `fund.export`:导出。
角色建议:
| 角色 | 权限 |
| --- | --- |
| 管理员 | 全部 |
| 财务 | 查看全部、对账、调整、导出 |
| 主管(operator 或新增 `fund_manager`) | 查看名下客服、发放、缴回、对账 |
| 客服(support) | 登记自己的支出、查看自己的余额和流水 |
服务层还要做归属校验:
- 客服只能操作自己的账户;
- 主管只能操作自己名下客服;
- 发放人不能给自己发放;
- 调整必须由主管/财务/管理员操作;
- 所有写操作写审计日志。
---
## 七、页面设计
### 7.1 管理端“备用金”页
建议五个页签:
1. **总览**:公司总备用金、主管池合计、客服余额合计、本月支出、待对账人数、异常笔数。
2. **客服余额**:客服、责任主管、当前余额、本月发放、本月支出、本月缴回、最后交易、最后对账、状态。
3. **主管资金池**:主管、池余额、本月拨入、本月发放、本月收回、名下客服数、差异。
4. **支出明细**:时间、客服、金额、用途、收款方/平台、订单号、凭证、状态;支持筛选。
5. **对账与异常**:负余额、无凭证、长期未动、疑似重复、对账差异、超限额。
### 7.2 客服端“我的备用金”
- 当前余额;
- 发放/支出/缴回流水;
- “登记支出”按钮;
- “申请补拨”按钮;
- “申请缴回”按钮;
- 对账状态。
### 7.3 订单详情(可选)
订单详情增加“本单相关支出”小卡片,展示已关联的支出合计和明细。订单号不是必填,所以这个卡片只对已关联订单的支出生效。
---
## 八、接口设计
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| GET | `/admin/fund/overview` | 总览 |
| GET | `/admin/fund/accounts` | 账户列表 |
| GET | `/admin/fund/accounts/:id` | 账户详情 + 流水 |
| POST | `/admin/fund/accounts/:id/funding` | 公司 → 主管拨付 |
| POST | `/admin/fund/accounts/:id/issue` | 主管 → 客服发放 |
| POST | `/admin/fund/accounts/:id/return` | 客服 → 主管缴回 |
| POST | `/admin/fund/accounts/:id/expenses` | 登记支出 |
| GET | `/admin/fund/expenses` | 支出明细 |
| GET | `/admin/fund/expenses/:id` | 支出详情 |
| POST | `/admin/fund/reconciliations` | 发起对账 |
| POST | `/admin/fund/reconciliations/:id/confirm` | 确认对账 |
| POST | `/admin/fund/reconciliations/:id/adjust` | 差异调整 |
实现要求:
- 所有金额用“分”存储,前端按元展示;
- 发放、缴回、支出必须在数据库事务中完成;
- 账户行使用 `SELECT ... FOR UPDATE`,避免并发导致余额错乱;
- 所有写接口使用现有 `createJsonHandler({ audit })` 写审计日志;
- 所有写接口做权限点和归属校验。
---
## 九、校验与异常
| 规则 | 处理 |
| --- | --- |
| 金额 <= 0 | 拒绝 |
| 余额不足 | 拒绝,提示先补拨 |
| 账户冻结 | 禁止发放/支出,允许缴回/调整 |
| 支出无凭证 | 拒绝 |
| 发放/缴回无凭证 | 允许备注+交接说明,但标记待补 |
| 收款方为空 | 拒绝 |
| 付款时间晚于当前 | 拒绝 |
| 付款时间早于当天 | 允许,标记迟登记 |
| 同客服+同金额+同收款方 24h 内重复 | 提示,需备注原因 |
| 对账差异未填原因 | 拒绝确认 |
| 调整无审批人 | 拒绝 |
异常队列:
- 负余额;
- 无凭证;
- 超过 7 天未对账;
- 余额长期不动;
- 单笔/单日支出异常;
- 同一凭证被多笔复用;
- 对账差异。
---
## 十、分期落地
### P0(先能记、能看、能对)
- 四张表 + 索引;
- 主管池、客服账户;
- 期初、发放、支出、缴回、调整五类流水;
- 支出明细 + 凭证上传;
- 总览、客服余额、主管池、支出明细、对账页面;
- 权限点 + 审计;
- 上线期初余额;
- 每周对账。
### P1(更好用)
- 异常队列和提醒;
- 余额上限、低余额提醒;
- 批量发放/批量对账;
- 导出;
- 大额支出主管确认;
- 移动端快速登记。
### P2(算得更清)
- 订单关联支出汇总;
- 支出分类与成本中心打通;
- 单均人工成本、店铺/客服维度报表。
### P3(财务总账)
- 复式记账;
- 与订单、钱包、提现、人工支出统一对账;
- 期间关账。
---
## 十一、上线步骤
1. 业务确认:两位主管、客服名单、责任主管、支出分类、期初余额截止时间。
2. 开发:迁移、后端台账服务、接口、前端页面、权限。
3. 初始化:建立账户,录入期初余额,主管逐人确认。
4. 试点:选 2 位主管 + 2~3 位客服,跑 1 周。
5. 校准:根据试点调整字段、分类、对账频率。
6. 全量:全部客服上线,关闭线下 Excel/微信群记账。
7. 运营:每周对账,每月复盘余额和支出异常。
---
## 十二、测试与验收
### 12.1 必测
- 发放:主管池减少、客服余额增加,两条流水金额相反;
- 支出:客服余额减少,支出明细与流水一一对应;
- 缴回:客服余额减少、主管池增加;
- 余额不足:拒绝支出;
- 并发发放/支出:余额不超扣、不出现负数;
- 权限:客服不能看别人,主管不能看非名下客服,客服不能调整;
- 对账差异:必须填原因并生成调整流水;
- 审计:所有写操作有日志。
### 12.2 验收标准
- 每个客服的当前余额可查;
- 每笔支出可追溯到人、金额、用途、收款方、时间、凭证;
- 主管池余额与发放/缴回流水对得上;
- 系统余额与客服实际余额的差异有记录、有原因、有审批;
- 不存在无流水导致的余额变化;
- 不存在负余额(除非明确开启允许负余额)。
---
## 十三、待确认
1. 两位主管的钱是公司资金,还是主管个人先垫?P0 先按公司资金池记,个人垫资在备注中说明。
2. 客服账号是否都是现有 `admin_users` 的 `support` 角色?如果是,直接绑定 `owner_admin_user_id`。
3. 客服余额是“一个总余额”,还是需要分微信/支付宝/现金多个钱包?P0 建议一个总余额 + 流水记付款方式。
4. 期初余额的截止时间和确认人。
5. 支出分类是否就用:平台充值、采购/拿货、客户赔偿、运费、其他。
6. 对账频率:每周一次是否足够,是否需要每日快照。
7. 大额支出是否需要主管事前确认;阈值多少。