文件
hl-api-changelog/changelogs-v2/2026-09/07_7232_费用报销-新增接口-管理后台.md
2026-09-10 15:42:42 +08:00

20 KiB

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 7232 费用报销(申请→审批→出纳付款)+ 出纳队列接通费用线 admin yst 新增接口 deployed verified verified mmg 860ad6bc v2.1 2026-09-10 verified 2026-09-10 mmg: expense.js 八端点+EXPENSE_STATUS 五态映射;expense/expense 列表页(状态/部门/公司/关键字筛选,分类末级按名传/公司按名传/部门手录注释,驳回 rejectReason 必填,详情按 REJECTED/PAID 条件显,操作列按状态机分叉);出纳 cashier 三端点扩容 EXPENSE 线随 #7217 一并交付(commit 7278c5f9)。expense.spec 锁端点/ID 透传/状态兜底。checkpoint 全绿。2026-09-10 后端更正:§10 出纳付款登记入参字段名勘误——付款账户/金额实为统一扁平 payAccountId/amount(此前误写输出字段 fundAccountId/actualAmount,前端据此调用触发 400)。2026-09-10 mmg 跟进修复(commit 860ad6bc):buildCashierPayPayload 删 EXPENSE 分叉统一 payAccountId/amount,CashierQueuePage 清理双键+EXPENSE 手续费恒 0 不传 fee,cashier.spec 断言翻转;checkpoint 全绿(含 Vitest 全量+生产构建)。此前联调记录的 cashier/pay EXPENSE 400 实为本文档笔误所致,非后端校验 bug。 2026-09-10 dev-v3

费用报销(申请→审批→出纳付款)+ 出纳队列接通费用线

财务域新增费用报销子域:员工申请报销 → 财务审批(本期手工)→ 出纳付款 → 回写付讫。同时出纳域待付款队列 / 付款登记 / 已付款台账三个既有接口的入参枚举扩容,接通费用报销线(payType/bizType 新增 EXPENSE 值)。

  • expense 新域路径前缀:/admin/finance/expenses(8 个新端点)
  • cashier 既有域路径前缀:/admin/finance/cashier(3 个端点入参枚举扩容,无字段增删)

二、变更接口清单

# 接口 方法 路径 变更类型
1 报销分页 GET /admin/finance/expenses/page 新增
2 申请报销 POST /admin/finance/expenses 新增
3 报销详情 GET /admin/finance/expenses/{id} 新增
4 编辑草稿 PUT /admin/finance/expenses/{id} 新增
5 删除草稿 DELETE /admin/finance/expenses/{id} 新增
6 提交报销 PUT /admin/finance/expenses/{id}/submit 新增
7 批准报销 PUT /admin/finance/expenses/{id}/approve 新增
8 驳回报销 PUT /admin/finance/expenses/{id}/reject 新增
9 出纳待付款队列 GET /admin/finance/cashier/queue 修改(入参 payType 扩容 EXPENSE)
10 出纳付款登记 POST /admin/finance/cashier/pay 修改(入参 bizType 扩容 EXPENSE)
11 出纳已付款台账 GET /admin/finance/cashier/payments/page 修改(入参 bizType 扩容 EXPENSE)

三、接口详情

1. 报销分页 GET /admin/finance/expenses/page

使用场景

管理后台「费用报销」列表页,按状态 / 部门 / 公司主体 / 关键字筛选分页查询。

认证 / 幂等性 / 限流

  • 认证:管理后台管理员 Token(Authorization: Bearer <admin-token>),未登录返 401。
  • 幂等性:查询接口,天然幂等。
  • 限流:走网关默认限流,无特殊配置。

入参(Query)

字段 位置 类型 必填 约束 说明
status Query String 否 见状态枚举 按报销状态筛选,省略查全部
departmentId Query Number 否 正整数 按部门 ID 筛选
company Query String 否 — 按公司主体名称筛选
keyword Query String 否 — 关键字模糊搜索(单号 / 事由)
page Query Number 是 ≥1 页码
pageSize Query Number 是 1-100 每页条数

出参

字段 类型 说明
data.records[].id String 报销单 ID(雪花 ID,Long 转字符串防 JS 精度丢失)
data.records[].expenseNo String 报销单号(FY- 前缀)
data.records[].expenseCategory String 费用分类末级名称
data.records[].departmentId String 部门 ID
data.records[].departmentName String 部门名称
data.records[].company String 公司主体名称
data.records[].amount Number 报销金额
data.records[].offsetLoanAmt Number 冲销借款金额(本期恒 0,见业务边界)
data.records[].payableAmt Number 应付金额(本期 = amount)
data.records[].reason String 报销事由
data.records[].rejectReason String 驳回原因(仅 REJECTED 状态有值,其余为 null)
data.records[].fundAccountId String 付讫资金账户 ID(仅 PAID 有值)
data.records[].status String 状态码,见状态枚举
data.records[].paidAt String 付讫时间(仅 PAID 有值)
data.records[].approvalInstanceId String 审批实例 ID(本期恒空,企微审批未接)
data.records[].createTime String 创建时间
data.total / page / pageSize Number 分页元信息

请求示例(典型)

GET /admin/finance/expenses/page?page=1&pageSize=20&status=APPROVED&departmentId=10086
Authorization: Bearer <admin-token>

响应示例(典型)

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "records": [
      {
        "id": "2094278854020399106",
        "expenseNo": "FY-20260907-0001",
        "expenseCategory": "市内交通费",
        "departmentId": "10086",
        "departmentName": "市场部",
        "company": "呼籁旅行社有限公司",
        "amount": 300.00,
        "offsetLoanAmt": 0,
        "payableAmt": 300.00,
        "reason": "客户拜访打车费",
        "rejectReason": null,
        "fundAccountId": null,
        "status": "APPROVED",
        "paidAt": null,
        "approvalInstanceId": null,
        "createTime": "2026-09-07 10:00:00"
      }
    ],
    "total": 1,
    "page": 1,
    "pageSize": 20
  }
}

空数据 / 边界响应

无匹配时 records=[]、total=0,HTTP 200,前端正常渲染空列表。

2. 申请报销 POST /admin/finance/expenses

VO: ExpenseCreateReqVO

使用场景

员工在管理后台提交一张费用报销单,落库为 PENDING 草稿(未进审批)。

认证 / 幂等性 / 限流

  • 认证:管理后台管理员 Token。
  • 幂等性:非幂等(每次创建生成新单号),重复提交产生多张单据,前端提交后应禁用按钮。
  • 限流:网关默认。

入参(Body)

字段 类型 必填 约束 说明
expenseCategory String 是 必须存在、为末级(level=2)、未停用 费用分类末级名称,按名称传不按 ID(取自 fin_expense_category 字典)
departmentId Number 是 必须存在 部门 ID(用户域组织树 wechat_department)
company String 是 必须为启用项 公司主体名称,按名称传(取自 travel_agency 启用项)
amount Number 是 >0 报销金额
reason String 是 非空 报销事由

出参

字段 类型 说明
data.id String 新建报销单 ID
data.expenseNo String 报销单号(FY- 前缀)
data.status String 创建后为 PENDING

请求示例(典型成功)

{
  "expenseCategory": "市内交通费",
  "departmentId": 10086,
  "company": "呼籁旅行社有限公司",
  "amount": 300.00,
  "reason": "客户拜访打车费"
}

请求示例(边界情况)

金额为最小粒度 / 事由最长:

{
  "expenseCategory": "办公用品费",
  "departmentId": 10086,
  "company": "呼籁旅行社有限公司",
  "amount": 0.01,
  "reason": "采购 A4 打印纸一箱"
}

请求示例(业务失败)

费用分类传了非末级 / 已停用名称 → 598703:

{
  "expenseCategory": "交通费",
  "departmentId": 10086,
  "company": "呼籁旅行社有限公司",
  "amount": 300.00,
  "reason": "打车"
}
{"code":598703,"message":"费用分类非法(不存在/非末级/已停用)","success":false,"data":null}

金额 ≤0 → 598704;部门或公司主体非法 → 598705。

3. 报销详情 GET /admin/finance/expenses/{id}

入参

字段 位置 类型 必填 说明
id Path String 是 报销单 ID(雪花字符串)

出参

字段与分页行一致(id/expenseNo/expenseCategory/departmentId/departmentName/company/amount/offsetLoanAmt/payableAmt/reason/rejectReason/fundAccountId/status/paidAt/approvalInstanceId/createTime),见 §1 出参表。

错误响应

{"code":598701,"message":"报销单不存在","success":false,"data":null}

4. 编辑草稿 PUT /admin/finance/expenses/{id}

VO: ExpenseUpdateReqVO(字段与 ExpenseCreateReqVO 完全一致,见 §2 入参表)

业务边界

  • 仅 PENDING 状态可编辑,其余状态调用返回 598702。
  • 编辑不改单号,状态保持 PENDING。

请求示例

{
  "expenseCategory": "市内交通费",
  "departmentId": 10086,
  "company": "呼籁旅行社有限公司",
  "amount": 350.00,
  "reason": "客户拜访打车费(含返程)"
}

错误响应

{"code":598702,"message":"报销单状态非法(仅PENDING可编辑删除、提交须PENDING、审批须SUBMITTED)","success":false,"data":null}

5. 删除草稿 DELETE /admin/finance/expenses/{id}

业务边界

  • 仅 PENDING 状态可删除,其余状态调用返回 598702。
  • 删除为软删除,单据不再出现在任何列表。

出参

字段 类型 说明
data Boolean true 表示删除成功

6. 提交报销 PUT /admin/finance/expenses/{id}/submit

业务边界

  • 仅 PENDING 状态可提交,提交后流转 PENDING → SUBMITTED,单据进入锁定态(不可编辑 / 删除)。
  • 无 Body 入参。

出参

字段 类型 说明
data.id String 报销单 ID
data.status String 提交后为 SUBMITTED

7. 批准报销 PUT /admin/finance/expenses/{id}/approve

业务边界

  • 仅 SUBMITTED 状态可批准,流转 SUBMITTED → APPROVED,进入出纳待付款队列。
  • 本期为手工审批(不接企微审批流),approvalInstanceId 恒空。
  • 无 Body 入参。

出参

字段 类型 说明
data.id String 报销单 ID
data.status String 批准后为 APPROVED

8. 驳回报销 PUT /admin/finance/expenses/{id}/reject

入参(Body)

字段 类型 必填 约束 说明
rejectReason String 是 非空 驳回原因(回显在详情 rejectReason 字段)

业务边界

  • 仅 SUBMITTED 状态可驳回,流转 SUBMITTED → REJECTED,不进出纳队列。

请求示例

{"rejectReason": "缺少打车发票附件,请补充后重新提交"}

出参

字段 类型 说明
data.id String 报销单 ID
data.status String 驳回后为 REJECTED

9. 出纳待付款队列 GET /admin/finance/cashier/queue(修改:payType 扩容)

变更点

入参 payType 枚举由仅 NONBIZ 扩容为 NONBIZ / EXPENSE。传 EXPENSE 时返回费用报销线(fin_expense 表 status=APPROVED 的单据);其余字段、结构、分页契约不变。

入参(Query,增量)

字段 位置 类型 必填 约束 说明
payType Query String 否 NONBIZ / EXPENSE 付款类型筛选;省略 / 空 = 既有行为

出参(队列行,费用线字段填充规则)

字段 类型 费用线(payType=EXPENSE)取值
id String 报销单 ID
bizNo String 报销单号(= expenseNo)
payType String EXPENSE
payTypeName String 费用
unitId / unitName String 槽位复用:部门 ID / 部门名称
category / categoryName String 费用分类(末级名称)
amount Number 报销金额
fee Number 手续费(费用线为 0)
actualAmount Number 应付金额(= payableAmt)
operatorName String 申请人
createTime String 创建时间
occurDate String 发生日期
status String 队列状态
remark String 报销事由(= reason)

请求示例

GET /admin/finance/cashier/queue?payType=EXPENSE
Authorization: Bearer <admin-token>

10. 出纳付款登记 POST /admin/finance/cashier/pay(修改:bizType 扩容)

变更点

入参 bizType 枚举新增 EXPENSE 值。bizType=EXPENSE 时 bizId 传报销单 ID,登记付款后回写 fin_expense 状态为 PAID、记录 fundAccountId / paidAt(回写列名,非入参字段),同事务记资金流水。

入参(Body,关键字段)

⚠️ 出纳付款登记为全 bizType 统一扁平入参(NONBIZ / EXPENSE / PAYMENT / PREPAY 共用同一套字段),付款账户 / 金额字段名固定为 payAccountId / amount。下方为全量必填/关键字段:

字段 类型 必填 约束 说明
bizType String 是 新增 EXPENSE 业务类型;费用报销传 EXPENSE
bizId String 是 对应业务单据 ID bizType=EXPENSE 时传报销单 ID
payAccountId String 是 必须存在且 ACTIVE 出账资金账户 ID(fin_fund_account)
amount Number 是 >0 付款金额(出纳按实际打款金额录入,可与应付不一致)
payDate String 是 yyyy-MM-dd 付款日期(可回溯补录)
payMethod String 否 字典 fin_pay_way 付款方式(CASH/BANK_TRANSFER/WECHAT/ALIPAY)
fee Number 否 ≥0 手续费(挂出账流水)
voucherNo String 否 — 付款凭证号
voucherUrl String 否 — 付款凭证影像 URL

请求示例(bizType=EXPENSE)

{
  "bizType": "EXPENSE",
  "bizId": "2097885645098659841",
  "payAccountId": "2095340438490583041",
  "amount": 300.00,
  "payDate": "2026-09-10",
  "payMethod": "BANK_TRANSFER"
}

成功响应

{
  "code": 200,
  "message": "成功",
  "data": { "flowId": "2097946971271495682", "flowNo": "LS202609100002", "balanceAfter": 114594.0, "bizId": "2097885645098659841" },
  "success": true
}

业务边界

  • 出纳实际打款金额与 payableAmt 不一致时后端打 WARN 审计日志但不硬拦(允许出纳按实际打款登记)。
  • 并发防重:同事务记资金流水 + 条件更新回写,对同一报销单重复付款返回 598602。

错误响应

重复付款:

{"code":598602,"message":"业务单状态非已批准,不可付款","success":false,"data":null}

付款类型非法(598607 文案已扩为含 EXPENSE):

{"code":598607,"message":"付款类型非法,本期支持 NONBIZ/EXPENSE","success":false,"data":null}

11. 出纳已付款台账 GET /admin/finance/cashier/payments/page(修改:bizType 扩容)

变更点

入参新增 bizType 筛选,支持 EXPENSE;传空 / 省略 = 既有 NONBIZ 行为。其余字段、结构不变。

入参(Query,增量)

字段 位置 类型 必填 约束 说明
bizType Query String 否 NONBIZ / EXPENSE 业务类型筛选;空 = NONBIZ(既有行为)

请求示例

GET /admin/finance/cashier/payments/page?page=1&pageSize=20&bizType=EXPENSE
Authorization: Bearer <admin-token>

六.5、枚举 / 数据字典

status(报销单状态,代码枚举)

所属字段: ExpensePageReqVO.status / ExpenseRespVO.status | 类型: String

值 中文 说明
PENDING 待提交 草稿态,可编辑可删除
SUBMITTED 审批中 已提交,锁定(不可编辑 / 删除)
APPROVED 已批准 审批通过,待出纳付款
REJECTED 已驳回 审批驳回,rejectReason 有值
PAID 已付讫 出纳付款完成,终态只读

状态机:PENDING →(submit)→ SUBMITTED →(approve)→ APPROVED →(出纳付款)→ PAID;SUBMITTED →(reject)→ REJECTED。

payType / bizType(出纳域,枚举值扩容)

所属字段: cashier/queue 入参 payType、cashier/pay 入参 bizType、cashier/payments/page 入参 bizType | 类型: String

值 中文 说明
NONBIZ 非业务付款 既有值,行为不变
EXPENSE 费用(报销) 新增值,费用报销线

expenseCategory(费用分类,数据字典 fin_expense_category)

  • 按末级名称(level=2)传值,按名称不按 ID。
  • 仅正常(未停用)项可选;传非末级 / 不存在 / 已停用名称返回 598703。

company(公司主体,数据字典 travel_agency)

  • 按名称传值,仅启用项可选;非法返回 598705。

错误码(新段 5987xx + 出纳段扩充)

错误码 含义 触发场景
598701 报销单不存在 详情 / 编辑 / 删除 / 提交 / 审批传错 ID
598702 报销单状态非法 非 PENDING 编辑删除、非 PENDING 提交、非 SUBMITTED 审批
598703 费用分类非法 分类不存在 / 非末级 / 已停用
598704 金额无效 amount ≤ 0
598705 部门或公司主体非法 departmentId 不存在或 company 非启用项
598706 单号取号撞号重试耗尽 极端并发下取号失败(重试后仍撞号)
598607 付款类型非法(文案扩充) 出纳付款 / 台账传非 NONBIZ/EXPENSE 值

六.6、修改前后对比

项目 修改前 修改后
费用报销接口 无 新增 /admin/finance/expenses 下 8 端点(分页 / 申请 / 详情 / 编辑 / 删除 / 提交 / 批准 / 驳回)
出纳队列 payType 仅 NONBIZ 扩容 NONBIZ / EXPENSE
出纳付款 bizType 仅 NONBIZ 扩容 NONBIZ / EXPENSE
出纳台账筛选 无 bizType 入参 新增 bizType 入参(空 = NONBIZ)
598607 文案 仅提示 NONBIZ 扩为「付款类型非法,本期支持 NONBIZ/EXPENSE」

六.7、影响评估

  • 是否破坏向后兼容:否。expense 8 端点为纯新增;出纳 3 端点为入参枚举扩容,省略新值时行为与之前完全一致。
  • 前端是否必须同步上线:否。旧功能不受影响;费用报销页面与出纳队列费用线 Tab 可按节奏上线。
  • 前端 workaround 清理点:无(全新能力)。

七、不影响范围

  • 小程序端(C 端):费用报销为纯管理后台能力,/mp/** 零改动。
  • 出纳 NONBIZ 线:不传 payType/bizType=EXPENSE 时行为与之前完全一致。
  • 员工借款 / 冲销:本期未建借款域,offsetLoanAmt 恒 0、payableAmt = amount,留 TODO 后续接入。

八、测试环境已验证

192.168.100.236 测试服行为级 E2E 16 PASS / 0 FAIL:

  • 全链路走通:申请 → 提交 → 批准 → 出纳付款 → 回写 PAID。
  • 结存勾稽正确:付款前资金账户余额 114894 − 打款 300 = 114594。
  • 并发防重生效:重复付款返回 598602。
  • 四类校验生效:598702(状态非法)/ 598703(分类非法)/ 598704(金额无效)/ 598705(部门或公司非法)。

当前状态

  • 后端:已部署并已验证。
  • 前端:待处理(费用报销页面 + 出纳队列费用线 Tab)。

十、相关文档

关联 / 联系人

  • Issue: #7232
  • PR: #7247
  • 后端负责人: @yst
  • 当前状态: 后端已就绪(TEST 已验证),前端待接入。