42 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 | 7396 | 应付款四入口接团期住宿 + 团期住宿付款身份金额对账 + 新增撤销批准 | admin | jw(GIT) | 修改接口 | deployed | verified | verified | mmg | 9bf2fc3bdfcebb75060fc866503f8bdda870c900 | 2026-09-17 | 后端交付(hl-finance,随 hl-order-service-v3 部署)。4 个应付款读接口返回扩大(新 sourceType=GROUP_BATCH_STAY、sourceRefKey、金额对账字段、统计新字段与 OVERPAID 状态),3 个创建口新增入参 sourceRefKey 与 598812/598813 两个新错误码,草稿编辑口新增归属与额度守卫,新增 PUT /admin/finance/payments/{id}/revoke-approval。网关前缀 /admin/finance/payments 已存在,无需新路由。前端需接新 sourceType / 新字段 / 新状态 / 新错误码 / 撤销批准按钮。[mmg 2026-09-17 交付] 付款单域整模块上线:①api/finance/payable.js 16 端点+三字典(统计包络 {list,total},雪花字符串,写关去重,598xxx/100503 拦截器透 message);②finance/payable 占位页改正式模块(单菜单页内五视图:双视图统计/按团拆单/跨团合并建单/审批详情/只读详情),GBS 行 sourceRefKey 原样回传、eligible=false 禁勾显 eligibleReason、建单 submit 双态、fk-detail 按状态出操作(编辑锁来源 598808/多明细锁合计 598805/驳回必填/撤销批准请求体可省);③新建 finance/pay/payable 出纳薄壳(CashierQueuePage PAYMENT)。原型有契约无的缺口不造数留痕:申请人/计调/客人/导游/发团/联系人/期初/申请日期/收款风险查询。api spec 10 例+组件 spec 21 例,scoped checkpoint 13 项全绿。 | 2026-09-17 | dev-v3 |
finance: 应付款四入口接团期住宿 + 团期住宿付款身份金额对账 + 撤销批准
存放目录: changelogs-v2/{YYYY-MM}/(管理后台,二期 finance)
服务: hl-order-service-v3(hl-finance 模块同进程) PR: #7866 Issue: #7396 日期: 2026-09-17 影响范围: 应付款建议(按订单 / 按供应商)、应付款统计(按团 / 按供应商)、付款单创建(单笔 / 按团批量 / 按供应商批量)、草稿编辑;新增撤销批准
关键变化(给前端 mmg 的一句话)
- 团期住宿进应付款了:团期房务(整团订房 + 系统分房到户)的酒店应付,现在在四个入口都能看到,行的
sourceType为GROUP_BATCH_STAY,并带sourceRefKey(付款身份业务键)。勾选建单时sourceRefKey必须原样回传,sourceRefId回传行上的sourceId。 - 团期住宿按金额对账防重:同一付款身份(同订单、同晚、同酒店、同房型)已建单后,建议行
alreadyGenerated=true;若应付金额变了,amountChanged=true、diffAmount给出差额(正数可再建差额行,负数需先删除 / 驳回 / 撤销批准或等冲正)。 - 新增两个错误码:
598812(已付超出当前应付,需财务冲正)、598813(未付占用超出当前应付,提示里列出需处理的单号与状态)。 - 统计口径变化:已申请 / 已付改为按付款明细聚合;新增
refundedAmount/netPaidAmount/overpaidAmount/diffAmount;owedAmount不再为负;状态新增OVERPAID;按供应商统计新增unattributedPaidAmount。 - 新增撤销批准
PUT /admin/finance/payments/{id}/revoke-approval:已批准未付款的单回到待提交草稿,之后可删除或编辑重提。 - 编辑草稿收紧:有业务来源的草稿禁止改供应商 / 订单(598808);多明细草稿金额必须等于明细合计(598805);团期住宿草稿改金额受额度守卫(598812 / 598813)。
一、背景
团期房务把住宿从「逐户配房」改成「整团按日订房 + 系统分房到户」后,应付款的三个批量入口(按供应商建议、按团统计、按供应商统计)只读旧配房表,团期住宿应付在这三处恒为 0;按订单建议也没有接入。同时团期分房行 ID 会在人工微调时换新,原「按来源主键判重」可被绕过而重复申请。本次:四入口共用同一读取层;团期住宿以业务键 {orderId}:{stayDate}:{hotelId}:{roomTypeId} 作付款身份,并按金额对账防重;统计改从付款明细聚合,已付款不会再算出负欠付。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 撤销批准 | PUT | /admin/finance/payments/{id}/revoke-approval |
新增 | APPROVED → PENDING,条件更新防并发,记审核流水 UN_APPROVE |
| 2 | 应付款建议(按订单) | GET | /admin/finance/payments/suggestions |
修改 | 新增团期住宿行与孤儿行;新增 sourceRefKey 与金额对账字段 |
| 3 | 应付款建议(按供应商跨团) | GET | /admin/finance/payments/suggestions/by-supplier |
修改 | 同上;已取消但仍有活跃团期占用的订单并入候选 |
| 4 | 应付款统计(按团) | GET | /admin/finance/payments/stats/by-team |
修改 | 该付含团期住宿;已申请/已付改按明细聚合;新字段与 OVERPAID |
| 5 | 应付款统计(按供应商) | GET | /admin/finance/payments/stats/by-supplier |
修改 | 同上;新增 unattributedPaidAmount |
| 6 | 申请付款(单笔) | POST | /admin/finance/payments |
修改 | 新增入参 sourceRefKey;带来源时 orderId 必填;团期住宿金额对账 |
| 7 | 勾选批量生成(按团) | POST | /admin/finance/payments/batch-create |
修改 | 明细新增 sourceRefKey;团期住宿金额对账 |
| 8 | 按供应商跨团合并建单 | POST | /admin/finance/payments/batch-create-by-supplier |
修改 | 明细新增 sourceRefKey;团期住宿金额对账 |
| 9 | 编辑付款草稿 | PUT | /admin/finance/payments/{id} |
修改 | 有来源草稿禁改供应商/订单;多明细金额守恒;团期住宿额度守卫 |
三、接口详情
1. 撤销批准 PUT /admin/finance/payments/{id}/revoke-approval
VO: PaymentRevokeApprovalReqVO → Result<Void>
使用场景
付款单已批准(APPROVED)、出纳尚未付款时,因应付金额减少等原因需要改单:先撤销批准回到草稿(PENDING),再删除或编辑后重新提交。出纳并发付款时两者只会有一个成功。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| id | Path | Long | 是 | - | 付款单 ID |
| reason | Body | String | 否 | ≤512 字符;请求体整体可省略 | 撤销原因,记入审核流水 opinion |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| code | Integer | 200 成功 |
| data | Void | 恒为 null |
请求示例
{
"reason": "团期减房 1 间,撤销后改单"
}
响应示例
{
"code": 200,
"message": "成功",
"data": null,
"success": true
}
空数据 / 降级响应
无查询数据;成功后 GET /admin/finance/payments/{id}/review-logs 追加一条 action=UN_APPROVE、fromStatus=APPROVED、toStatus=PENDING 的记录。
错误响应
{
"code": 598802,
"message": "应付单状态非法,当前状态不允许此操作",
"data": null,
"success": false
}
{
"code": 598801,
"message": "应付单不存在",
"data": null,
"success": false
}
业务边界
- 仅
APPROVED可撤销;PENDING/SUBMITTED/REJECTED/PAID一律 598802。 - 条件更新(仅当状态仍为 APPROVED 才改):与出纳付款并发时恰好一个成功,已付款的单绝不会被改回草稿。
- 撤销后单据退出出纳待付款队列;回到 PENDING 后可删除(
DELETE /admin/finance/payments/{id})或编辑后重新提交。 - 同事务写审核流水,流水写失败整体回滚。
- 权限口径与批准接口一致。
2. 应付款建议(按订单) GET /admin/finance/payments/suggestions
VO: PaymentSuggestionRespVO{orderId, rows: List<PaymentSuggestionRowVO>}
使用场景
填单页按订单拉「该付给供应商」的建议行,勾选后带入创建。本次新增团期住宿行(sourceType=GROUP_BATCH_STAY),以及「应付已消失但仍有付款占用」的孤儿行。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| orderId | Query | Long | 是 | - | 订单 ID |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| orderId | Long | 订单 ID |
| rows[].sourceType | String | NODE 行程节点 / HOTEL_ASSIGNMENT 配房 / GROUP_BATCH_STAY 团期住宿(新增取值) |
| rows[].sourceId | Long | 来源主键;团期住宿为该付款身份下最小分房行 ID,仅定位用 |
| rows[].sourceRefKey | String | 新增。仅团期住宿有值:{orderId}:{yyyy-MM-dd}:{hotelId}:{roomTypeId},建单时原样回传 |
| rows[].resourceId | Long | 资源 ID(团期住宿为酒店 ID) |
| rows[].resourceName | String | 资源名(团期住宿为 酒店名/房型名) |
| rows[].quantity | Integer | 数量(团期住宿为该晚该房型分到本户的间数合计) |
| rows[].unitPrice | BigDecimal | 单价(团期住宿为结算价 元/间·晚;同身份单价不一致时为 null) |
| rows[].amount | BigDecimal | 当前应付(团期住宿 = Σ 结算价 × 间数;孤儿行为 0) |
| rows[].paymentMethod | String | SIGNED / COMPANY_PAID |
| rows[].paymentType | String | 付款类型字典标签预填(团期住宿同配房 = 住宿) |
| rows[].supplierId | Long | 预填供应商(按酒店反查) |
| rows[].supplierName | String | 供应商全称 |
| rows[].payeeAccountId | Long | 默认收款账户 |
| rows[].eligible | boolean | 供应商是否可付款 |
| rows[].eligibleReason | String | 不可付款原因 |
| rows[].alreadyGenerated | boolean | 是否已有活跃付款占用(团期住宿按业务键判定) |
| rows[].paidAmount | BigDecimal | 新增。已付原值(PAID 明细合计,不扣退款);仅团期住宿有值 |
| rows[].refundedAmount | BigDecimal | 新增。已确认退款;仅团期住宿有值 |
| rows[].netPaidAmount | BigDecimal | 新增。净已付 = paidAmount − refundedAmount;仅团期住宿有值 |
| rows[].unpaidAmount | BigDecimal | 新增。未付占用(PENDING/SUBMITTED/APPROVED 明细合计,含草稿);仅团期住宿有值 |
| rows[].generatedAmount | BigDecimal | 新增。已生成金额 = netPaidAmount + unpaidAmount;仅团期住宿有值 |
| rows[].amountChanged | Boolean | 新增。已有活跃占用且已生成金额 ≠ 当前应付时为 true(未申请过的行恒 false,此时看 diffAmount 即可申请额);仅团期住宿有值 |
| rows[].diffAmount | BigDecimal | 新增。当前应付 − 已生成金额(有符号);仅团期住宿有值 |
请求示例
GET /admin/finance/payments/suggestions?orderId=2100244780104425473
响应示例
{
"code": 200,
"message": "成功",
"data": {
"orderId": "2100244780104425473",
"rows": [
{
"sourceType": "GROUP_BATCH_STAY",
"sourceId": "2100244786240692225",
"sourceRefKey": "2100244780104425473:2026-11-18:2029956939232808961:2029956939333472258",
"resourceId": "2029956939232808961",
"resourceName": "测试酒店/标准间",
"quantity": 1,
"unitPrice": 519.00,
"amount": 519.00,
"paymentMethod": "COMPANY_PAID",
"paymentType": "住宿",
"supplierId": null,
"supplierName": null,
"payeeAccountId": null,
"eligible": false,
"eligibleReason": "资源未关联供应商,请手选",
"alreadyGenerated": false,
"paidAmount": 0,
"refundedAmount": 0,
"netPaidAmount": 0,
"unpaidAmount": 0,
"generatedAmount": 0,
"amountChanged": false,
"diffAmount": 519.00
}
]
},
"success": true
}
空数据 / 降级响应
无应付行返回 rows: []。供应商反查 / 可付款判定 / 字典不可用时行仍返回,eligible=false 并给出 eligibleReason,不打塌整单。
{
"code": 200,
"message": "成功",
"data": { "orderId": "2100244780104425473", "rows": [] },
"success": true
}
错误响应
{
"code": 400,
"message": "orderId 不能为空",
"data": null,
"success": false
}
业务边界
- 团期住宿只取「计划行已确认 + 结算方式为签单/公司付款 + 结算价非空」且分房行有效的数据;现付(cash)不进应付。
- 团期启用前已冻结的历史旧户只出
HOTEL_ASSIGNMENT行,不出团期住宿行,同订单不会两类并存。 - 孤儿行:某身份已有活跃付款占用但当前已无应付(如计划行被删)时,输出一行
amount=0、alreadyGenerated=true、diffAmount为负。 NODE/HOTEL_ASSIGNMENT行的新增金额字段为 null,判重口径不变。- 金额类字段均为数值(BigDecimal),ID 类字段为字符串。
3. 应付款建议(按供应商跨团) GET /admin/finance/payments/suggestions/by-supplier
VO: SupplierSuggestionRespVO{supplierId, rows: List<SupplierSuggestionRowVO>}
使用场景
按供应商列出跨团欠付明细,勾选后走按供应商合并建单。本次新增团期住宿行(改前恒无)与孤儿行。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| supplierId | Query | Long | 是 | - | 供应商 ID |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| supplierId | Long | 供应商 ID |
| rows[] | SupplierSuggestionRowVO | 继承按订单建议行全部字段(含本次新增的 sourceRefKey 与金额对账字段) |
| rows[].orderId | Long | 来源订单 ID(建单时回传) |
| rows[].teamNo | String | 来源团号 |
| rows[].orderNo | String | 来源订单号 |
请求示例
GET /admin/finance/payments/suggestions/by-supplier?supplierId=88001
响应示例
{
"code": 200,
"message": "成功",
"data": {
"supplierId": "88001",
"rows": [
{
"orderId": "2100244780104425473",
"teamNo": "26-3509",
"orderNo": "HL2026091701",
"sourceType": "GROUP_BATCH_STAY",
"sourceId": "2100244786240692225",
"sourceRefKey": "2100244780104425473:2026-11-18:2029956939232808961:2029956939333472258",
"amount": 800.00,
"alreadyGenerated": true,
"paidAmount": 800.00,
"refundedAmount": 0,
"netPaidAmount": 800.00,
"unpaidAmount": 0,
"generatedAmount": 800.00,
"amountChanged": false,
"diffAmount": 0
}
]
},
"success": true
}
空数据 / 降级响应
{
"code": 200,
"message": "成功",
"data": { "supplierId": "88001", "rows": [] },
"success": true
}
错误响应
{
"code": 400,
"message": "supplierId 不能为空",
"data": null,
"success": false
}
业务边界
- 候选订单 = 未核单且未取消的订单,另并入「已取消但仍有活跃团期住宿付款占用」的订单;已取消订单不再计应付,只呈现已付与占用(孤儿行)。
- 只保留资源反查出的供应商等于入参的行;孤儿行按付款明细上的供应商过滤。
- 核单已完成的订单不进候选(按订单建议入口仍可见)。
4. 应付款统计(按团) GET /admin/finance/payments/stats/by-team
VO: PaymentStatsByTeamReqVO → PageResult<PaymentStatsByTeamRowVO>
使用场景
按订单(团)看该付、已申请、已付、欠付。本次该付含团期住宿;已申请/已付改为按付款明细的订单归属聚合(按供应商合并付款的单能正确分摊到各团)。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| page | Query | Integer | 否 | ≥1,默认 1 | 页码 |
| pageSize | Query | Integer | 否 | 1-100,默认 20 | 每页条数 |
| keyword | Query | String | 否 | - | 团号 / 产品名模糊 |
| status | Query | String | 否 | OWED / OVERPAID / PAID |
状态筛选(页内过滤) |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| orderId | Long | 订单 ID |
| teamNo | String | 团号 |
| productName | String | 产品名 |
| customerName | String | 客人 |
| orderNo | String | 订单号 |
| departDate | LocalDate | 出团日期 |
| returnDate | LocalDate | 返团日期 |
| payableAmount | BigDecimal | 该付(含团期住宿;已取消孤儿订单为 0) |
| appliedAmount | BigDecimal | 已申请(SUBMITTED/APPROVED 明细合计,草稿不计) |
| paidAmount | BigDecimal | 已付原值(PAID 明细合计,不扣退款、永不减少) |
| owedAmount | BigDecimal | 欠付 = max(该付 − 净已付, 0),不再为负 |
| refundedAmount | BigDecimal | 新增。已确认退款 |
| netPaidAmount | BigDecimal | 新增。净已付 = paidAmount − refundedAmount |
| overpaidAmount | BigDecimal | 新增。已付超出 = max(净已付 − 该付, 0) |
| diffAmount | BigDecimal | 新增。该付 − 净已付(有符号) |
| supplierCount | Integer | 供应商数 |
| status | String | OWED 有欠付 / OVERPAID 已付超出待冲正(新增) / PAID 已付清 |
请求示例
GET /admin/finance/payments/stats/by-team?page=1&pageSize=20&keyword=26-3509
响应示例
{
"code": 200,
"message": "成功",
"data": {
"list": [
{
"orderId": "2100244780104425473",
"teamNo": "26-3509",
"payableAmount": 800.00,
"appliedAmount": 0,
"paidAmount": 800.00,
"owedAmount": 0,
"refundedAmount": 0,
"netPaidAmount": 800.00,
"overpaidAmount": 0,
"diffAmount": 0,
"supplierCount": 1,
"status": "PAID"
}
],
"total": 1,
"page": 1,
"pageSize": 20
},
"success": true
}
空数据 / 降级响应
无候选订单返回空列表、total=0。供应商反查失败只影响 supplierCount,该付照常计入。
{
"code": 200,
"message": "成功",
"data": { "list": [], "total": 0, "page": 1, "pageSize": 20 },
"success": true
}
错误响应
{
"code": 400,
"message": "每页条数最大为100",
"data": null,
"success": false
}
业务边界
- 候选订单同按供应商建议(含已取消孤儿订单并入),在 SQL 层合并,
total与分页一致。 - 已付 / 已申请只看付款明细,付款主单金额不再参与聚合;按供应商合并付款的单按明细订单分摊到各团。
- 无订单归属的付款(如合并单的出纳差额行)不进按团统计,只进按供应商统计。
status筛选在页内过滤(与改前一致)。
5. 应付款统计(按供应商) GET /admin/finance/payments/stats/by-supplier
VO: PaymentStatsBySupplierReqVO → PageResult<PaymentStatsBySupplierRowVO>
使用场景
按供应商看名下各团合计的该付、已申请、已付、欠付。本次该付含团期住宿;已申请/已付改按明细的供应商归属;新增无团归属已付额。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| page | Query | Integer | 否 | ≥1,默认 1 | 页码 |
| pageSize | Query | Integer | 否 | 1-100,默认 20 | 每页条数 |
| keyword | Query | String | 否 | - | 供应商名称模糊 |
| status | Query | String | 否 | OWED / OVERPAID / PAID |
状态筛选 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| supplierId | Long | 供应商 ID |
| supplierName | String | 供应商全称 |
| category | String | 类别标签(多类别以 / 拼接) |
| payableAmount | BigDecimal | 该付(含团期住宿) |
| appliedAmount | BigDecimal | 已申请(SUBMITTED/APPROVED 明细合计) |
| paidAmount | BigDecimal | 已付原值(PAID 明细合计) |
| owedAmount | BigDecimal | 欠付 = max(该付 − 净已付, 0) |
| refundedAmount | BigDecimal | 新增。已确认退款 |
| netPaidAmount | BigDecimal | 新增。净已付 |
| overpaidAmount | BigDecimal | 新增。已付超出 |
| diffAmount | BigDecimal | 新增。该付 − 净已付(有符号) |
| unattributedPaidAmount | BigDecimal | 新增。已付但明细无订单归属的金额;恒等式:Σ 该供应商各团已付 + 本值 = paidAmount |
| teamCount | Integer | 涉及团数 |
| status | String | OWED / OVERPAID(新增) / PAID |
请求示例
GET /admin/finance/payments/stats/by-supplier?page=1&pageSize=20&keyword=呼籁
响应示例
{
"code": 200,
"message": "成功",
"data": {
"list": [
{
"supplierId": "88001",
"supplierName": "呼籁酒店",
"category": "住宿",
"payableAmount": 1600.00,
"appliedAmount": 0,
"paidAmount": 1700.00,
"owedAmount": 0,
"refundedAmount": 0,
"netPaidAmount": 1700.00,
"overpaidAmount": 100.00,
"diffAmount": -100.00,
"unattributedPaidAmount": 100.00,
"teamCount": 2,
"status": "OVERPAID"
}
],
"total": 1,
"page": 1,
"pageSize": 20
},
"success": true
}
空数据 / 降级响应
{
"code": 200,
"message": "成功",
"data": { "list": [], "total": 0, "page": 1, "pageSize": 20 },
"success": true
}
错误响应
{
"code": 400,
"message": "页码最小为1",
"data": null,
"success": false
}
业务边界
- 已取消孤儿订单的付款占用按明细供应商建桶后并入,已付不会被丢掉。
- 供应商行为内存聚合后分页,
total= 筛选后供应商行数。 unattributedPaidAmount只来自已付款且明细无订单的行(例如按供应商合并单的存量出纳差额回填行)。
6. 申请付款(单笔) POST /admin/finance/payments
VO: PaymentCreateReqVO → PaymentIdRespVO
使用场景
手工直填或从建议清单单条勾选建付款草稿。本次:团期住宿需回传 sourceRefKey;任何单笔创建都会同时写出一条付款明细(带来源写来源明细,手工直填写系统内部的 MANUAL 明细),占用即时生效。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| supplierId | Body | Long | 是 | - | 供应商 ID |
| payeeAccountId | Body | Long | 是 | 供应商生效账户 | 收款账户 |
| amount | Body | BigDecimal | 是 | >0 | 付款金额 |
| paymentType | Body | String | 是 | ≤32,字典标签 | 付款类型 |
| reason | Body | String | 是 | ≤512 | 付款事由 |
| teamNo | Body | String | 否 | ≤32 | 团号 |
| orderId | Body | Long | 条件必填 | 带来源时必填 | 关联订单 |
| resourceId | Body | Long | 否 | - | 关联资源 |
| sourceRefType | Body | String | 否 | NODE / HOTEL_ASSIGNMENT / GROUP_BATCH_STAY;不可传 MANUAL |
来源类型,须与 sourceRefId 成对 |
| sourceRefId | Body | Long | 否 | 与 sourceRefType 成对 | 来源主键(团期住宿传建议行 sourceId) |
| sourceRefKey | Body | String | 条件必填 | ≤96;GROUP_BATCH_STAY 必填且其中订单须等于 orderId;其余类型必须为空 |
新增。建议行 sourceRefKey 原样回传 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| paymentId | Long | 新付款单 ID |
请求示例
{
"supplierId": "88001",
"payeeAccountId": "90001",
"amount": 800.00,
"paymentType": "住宿",
"reason": "团期 26-3509 11/18 住宿",
"teamNo": "26-3509",
"orderId": "2100244780104425473",
"resourceId": "2029956939232808961",
"sourceRefType": "GROUP_BATCH_STAY",
"sourceRefId": "2100244786240692225",
"sourceRefKey": "2100244780104425473:2026-11-18:2029956939232808961:2029956939333472258"
}
响应示例
{
"code": 200,
"message": "成功",
"data": { "paymentId": "2100260000000000001" },
"success": true
}
空数据 / 降级响应
无查询数据。供应商可付款判定 / 账户接口不可用时失败关闭(598803 / 598804),不落任何数据。
错误响应
{
"code": 598809,
"message": "该来源配置已生成付款单,请勿重复生成",
"data": null,
"success": false
}
{
"code": 598813,
"message": "未付占用超出当前应付 800.00 元,请先删除 / 驳回 / 撤销批准:FK-202609170003(APPROVED)",
"data": null,
"success": false
}
{
"code": 598812,
"message": "已付金额超出当前应付 400.00 元,需财务冲正",
"data": null,
"success": false
}
{
"code": 598808,
"message": "来源配置引用非法",
"data": null,
"success": false
}
业务边界
- 团期住宿判据(Σ = 同身份净已付 + 未付占用,A = 当前应付,x = 本次金额):
- 净已付 > A → 598812(message 带超出额);
- Σ > A → 598813(message 列出未付单「单号(状态)」,PENDING 删除 / SUBMITTED 驳回 / APPROVED 撤销批准即可);
- Σ = A → 598809(A 与 Σ 均为 0,即该身份不在应付范围 → 598808);
- Σ + x > A → 598813;
- 否则成功:Σ 为 0 建首笔,Σ > 0 建差额行。
NODE/HOTEL_ASSIGNMENT仍按来源主键存在性判重(598809),行为不变。- 598808 触发:来源类型不在取值域或传了 MANUAL;sourceRefType 与 sourceRefId 不成对;带来源未传 orderId;团期住宿 sourceRefKey 缺失、格式错误、日期非法或其中订单 ≠ orderId;非团期住宿传了 sourceRefKey。
- 同一订单的四个建单 / 编辑写口串行执行,抢锁超时返回
100503 资源被占用,请稍后重试。
7. 勾选批量生成(按团) POST /admin/finance/payments/batch-create
VO: PaymentBatchCreateReqVO → PaymentBatchCreateRespVO
使用场景
按订单建议清单勾选多行,按供应商拆单批量生成。本次明细新增 sourceRefKey,团期住宿走金额对账。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| orderId | Body | Long | 是 | - | 来源订单 |
| items | Body | List | 是 | 至少 1 条 | 勾选明细 |
| items[].sourceRefType | Body | String | 是 | NODE / HOTEL_ASSIGNMENT / GROUP_BATCH_STAY |
来源类型 |
| items[].sourceRefId | Body | Long | 是 | - | 来源主键(团期住宿传 sourceId) |
| items[].sourceRefKey | Body | String | 条件必填 | ≤96;团期住宿必填且订单须等于 orderId;其余类型为空 | 新增 |
| items[].supplierId | Body | Long | 是 | - | 供应商(拆单键) |
| items[].amount | Body | BigDecimal | 是 | >0 | 明细金额 |
| items[].paymentType | Body | String | 是 | ≤32 | 付款类型 |
| items[].resourceId | Body | Long | 否 | - | 资源快照 |
| items[].resourceName | Body | String | 否 | ≤128 | 资源名快照 |
| payeeAccountId | Body | Long | 否 | - | 收款账户 |
| reason | Body | String | 是 | ≤512 | 付款事由 |
| submit | Body | Boolean | 是 | - | true 直接提交 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| groups[].paymentId | Long | 付款单 ID |
| groups[].paymentNo | String | 付款单号 |
| groups[].batchNo | String | 批次号 |
| groups[].supplierId | Long | 供应商 ID |
| groups[].supplierName | String | 供应商名 |
| groups[].amount | BigDecimal | 单据金额 |
| groups[].status | String | PENDING / SUBMITTED |
请求示例
{
"orderId": "2100244780104425473",
"reason": "团期住宿",
"submit": false,
"items": [
{
"sourceRefType": "GROUP_BATCH_STAY",
"sourceRefId": "2100244786240692225",
"sourceRefKey": "2100244780104425473:2026-11-18:2029956939232808961:2029956939333472258",
"supplierId": "88001",
"amount": 400.00,
"paymentType": "住宿"
}
]
}
响应示例
{
"code": 200,
"message": "成功",
"data": {
"groups": [
{
"paymentId": "2100260000000000002",
"paymentNo": "FK-202609170004",
"batchNo": "BATCH-2100260000000000003",
"supplierId": "88001",
"supplierName": "呼籁酒店",
"amount": 400.00,
"status": "PENDING"
}
]
},
"success": true
}
空数据 / 降级响应
无查询数据;任一明细校验失败整批回滚,不落任何单据。
错误响应
{
"code": 598809,
"message": "该来源配置已生成付款单,请勿重复生成",
"data": null,
"success": false
}
{
"code": 598813,
"message": "未付占用超出当前应付 1200.00 元,请先删除 / 驳回 / 撤销批准:FK-202609170005(PENDING)",
"data": null,
"success": false
}
业务边界
- 团期住宿判据同单笔创建;同一请求内重复勾选同一身份 → 598809。
- 598812 / 598808 触发条件同单笔创建。
8. 按供应商跨团合并建单 POST /admin/finance/payments/batch-create-by-supplier
VO: PaymentBatchCreateBySupplierReqVO → PaymentBatchCreateRespVO
使用场景
按供应商建议勾选跨团明细合并成一张付款单。本次明细新增 sourceRefKey,团期住宿走金额对账,多订单按订单号升序串行加锁。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| supplierId | Body | Long | 是 | 全部明细须同属该供应商 | 供应商 |
| items | Body | List | 是 | 至少 1 条 | 勾选明细 |
| items[].orderId | Body | Long | 是 | - | 来源订单 |
| items[].sourceRefType | Body | String | 是 | NODE / HOTEL_ASSIGNMENT / GROUP_BATCH_STAY |
来源类型 |
| items[].sourceRefId | Body | Long | 是 | - | 来源主键 |
| items[].sourceRefKey | Body | String | 条件必填 | ≤96;团期住宿必填且订单须等于 items[].orderId | 新增 |
| items[].supplierId | Body | Long | 是 | 等于入参 supplierId | 供应商 |
| items[].amount | Body | BigDecimal | 是 | >0 | 明细金额 |
| items[].paymentType | Body | String | 是 | ≤32 | 付款类型 |
| payeeAccountId | Body | Long | 否 | - | 收款账户 |
| reason | Body | String | 是 | ≤512 | 付款事由 |
| submit | Body | Boolean | 是 | - | true 直接提交 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| groups[] | GroupResult | 单元素,字段同按团批量 |
请求示例
{
"supplierId": "88001",
"reason": "11 月团期住宿合并付款",
"submit": true,
"items": [
{
"orderId": "2100244780104425473",
"sourceRefType": "GROUP_BATCH_STAY",
"sourceRefId": "2100244786240692225",
"sourceRefKey": "2100244780104425473:2026-11-18:2029956939232808961:2029956939333472258",
"supplierId": "88001",
"amount": 800.00,
"paymentType": "住宿"
}
]
}
响应示例
{
"code": 200,
"message": "成功",
"data": {
"groups": [
{
"paymentId": "2100260000000000006",
"paymentNo": "FK-202609170006",
"batchNo": "BATCH-2100260000000000007",
"supplierId": "88001",
"supplierName": "呼籁酒店",
"amount": 800.00,
"status": "SUBMITTED"
}
]
},
"success": true
}
空数据 / 降级响应
无查询数据;校验失败整单回滚。
错误响应
{
"code": 598811,
"message": "勾选明细含其他供应商,请按供应商分批勾选",
"data": null,
"success": false
}
{
"code": 598809,
"message": "该来源配置已生成付款单,请勿重复生成",
"data": null,
"success": false
}
业务边界
- 合并单主单无订单归属,金额按明细订单分摊到按团统计。
- 团期住宿判据同单笔创建(598809 / 598812 / 598813 / 598808)。
9. 编辑付款草稿 PUT /admin/finance/payments/{id}
VO: PaymentUpdateReqVO → Result<Void>
使用场景
编辑 PENDING 草稿。本次新增:有业务来源的草稿不允许改供应商与订单;明细金额 / 归属跟随主单同步;团期住宿草稿改金额受额度守卫。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| id | Path | Long | 是 | - | 付款单 ID |
| supplierId | Body | Long | 是 | 有来源草稿须等于原值 | 供应商(请回传原值) |
| payeeAccountId | Body | Long | 是 | 供应商生效账户 | 收款账户 |
| amount | Body | BigDecimal | 是 | >0;多明细草稿须等于明细合计 | 付款金额 |
| paymentType | Body | String | 是 | ≤32 | 付款类型 |
| reason | Body | String | 是 | ≤512 | 付款事由 |
| teamNo | Body | String | 否 | ≤32 | 团号 |
| orderId | Body | Long | 否 | 有来源草稿须等于原值(原值为空则传空) | 关联订单 |
| resourceId | Body | Long | 否 | - | 关联资源 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| code | Integer | 200 成功 |
| data | Void | 恒为 null |
请求示例
{
"supplierId": "88001",
"payeeAccountId": "90001",
"amount": 300.00,
"paymentType": "住宿",
"reason": "差额改为 300",
"teamNo": "26-3509",
"orderId": "2100244780104425473"
}
响应示例
{
"code": 200,
"message": "成功",
"data": null,
"success": true
}
空数据 / 降级响应
无查询数据;任一校验失败主单与明细零变化。
错误响应
{
"code": 598808,
"message": "来源配置引用非法",
"data": null,
"success": false
}
{
"code": 598805,
"message": "金额无效(付款金额须大于0)",
"data": null,
"success": false
}
{
"code": 598813,
"message": "未付占用超出当前应付 1200.00 元,请先删除 / 驳回 / 撤销批准:无未付占用,本次申请金额超出可申请差额",
"data": null,
"success": false
}
业务边界
- 有来源明细(NODE / HOTEL_ASSIGNMENT / GROUP_BATCH_STAY)的草稿:改 supplierId 或 orderId → 598808;单明细同步明细金额;多明细要求 amount 等于明细合计,否则 598805。
- 团期住宿单明细草稿:同身份其他占用 + 新金额 ≤ 当前应付,否则按净已付是否超额报 598812 / 598813。
- 手工草稿(无来源):可自由改供应商 / 订单 / 团号 / 资源,系统同步其内部 MANUAL 明细的归属,资源名快照置空。
- 仅 PENDING 可编辑,否则 598802。
四、契约约束与正确调用方式
| 场景 | 做法 |
|---|---|
| 团期住宿勾选建单 | sourceRefType=GROUP_BATCH_STAY、sourceRefId=行.sourceId、sourceRefKey=行.sourceRefKey 原样回传,单笔还须带 orderId |
| 团期住宿应付增加 | 行上 amountChanged=true 且 diffAmount>0 → 以 diffAmount 为金额再建一笔(差额行) |
| 团期住宿应付减少、原单未付 | 598813 提示里列出的单:PENDING 删除 / SUBMITTED 驳回 / APPROVED 先调撤销批准再删或改 |
| 团期住宿应付减少、原单已付 | 598812,需财务冲正(后续能力),前端提示即可 |
| 编辑有来源草稿 | supplierId / orderId 回传原值,不提供修改入口 |
| 统计状态筛选 | 新增 OVERPAID 选项;owedAmount 不会再出现负数 |
| 建议行新字段 | 仅 GROUP_BATCH_STAY 行有值,其余类型为 null,前端需判空 |
五、数据库行为
| 操作 | 表 | 行为 |
|---|---|---|
| 迁移 V20260917_110 | fin_payment_item |
加列 source_ref_key VARCHAR(96) NULL、item_kind VARCHAR(16) NOT NULL DEFAULT 'FULL',加索引 idx_source_ref_key(source_ref_type, source_ref_key);幂等 |
| 迁移 V20260917_111 | fin_payment_item |
存量回填:无明细的付款单补一条明细(无来源记 MANUAL);已付单明细合计与出账流水不一致时补一条 CASHIER_ADJ;不改付款主单与资金流水,重复执行零新增 |
| 单笔创建 | fin_payment + fin_payment_item |
同事务写主单 + 1 条明细(来源明细或 MANUAL) |
| 批量创建 | fin_payment + fin_payment_item |
团期住宿明细写 source_ref_key;item_kind 为 FULL(首笔)或 ADJUST(差额) |
| 编辑草稿 | fin_payment + fin_payment_item |
同步唯一明细金额;手工单同步明细归属 |
| 撤销批准 | fin_payment + fin_payment_review_log |
条件更新 APPROVED→PENDING + 插入 UN_APPROVE 流水 |
| 出纳付款 | fin_payment_item |
主单实付与明细合计不一致时追加一条 CASHIER_ADJ 明细(新数据正常不会触发) |
| 四个读接口 | 只读 | 新读 group_batch_room_plan / group_batch_room_allocation / order_group_batch_house_legacy |
六、边界行为
- 未登录 → 401;无权限 → 403。
- 团期计划行结算价为空 → 该行不进应付(记告警日志)。
- 同一身份跨计划行单价不一致 →
unitPrice=null,amount按各行累加。 - 已核单完成的订单不进三个批量入口的候选,按订单建议仍可见。
- 抢不到同订单写锁 → 100503。
- 撤销批准与出纳付款并发 → 只有一个成功,另一方 598802 或出纳侧状态错误。
六.5、枚举
来源类型(sourceType / sourceRefType)
所属字段: PaymentSuggestionRowVO.sourceType、各创建入参 sourceRefType | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
| NODE | 行程节点 | 既有 |
| HOTEL_ASSIGNMENT | 配房 | 既有 |
| GROUP_BATCH_STAY | 团期住宿 | 新增,按业务键金额对账 |
| MANUAL | 手工 | 系统内部明细类型,前端不可传、建议行不出现 |
统计状态(status)
所属字段: PaymentStatsByTeamRowVO.status、PaymentStatsBySupplierRowVO.status | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
| OWED | 有欠付 | owedAmount > 0 |
| OVERPAID | 已付超出 | 新增,overpaidAmount > 0,待冲正 |
| PAID | 已付清 | 其余 |
六.6、修改前后对比
| 项 | 改前 | 改后 |
|---|---|---|
| 团期住宿应付 | 按订单建议之外三处恒为 0 | 四处一致 |
| 团期住宿判重 | 按分房行 ID,微调换 ID 可重复申请 | 按业务键金额对账 |
| 统计已付来源 | 付款主单金额,按主单订单归属 | 付款明细金额,按明细订单归属 |
| 统计欠付 | 可为负数 | 不为负,超出部分进 overpaidAmount |
| 统计状态 | OWED / PAID | OWED / OVERPAID / PAID |
| 单笔创建 | 不写明细,不参与防重 | 同事务写明细,立即占用 |
| 编辑草稿 | 可任意改供应商/订单/金额,明细不跟随 | 有来源禁改归属;明细跟随;团期住宿守额度 |
| 已批准单改单 | 无出口(驳回只收 SUBMITTED) | 可撤销批准回到草稿 |
| 错误码 | 无 598812 / 598813 | 新增 |
六.7、影响评估
- 是否破坏向后兼容: 部分。读接口只增字段与取值(
GROUP_BATCH_STAY、OVERPAID);写接口团期住宿必须带sourceRefKey;有来源草稿不能再改供应商 / 订单。 - 前端是否必须同步上线: 建议同步。不接也不报错,但团期住宿行无法正确建单(缺 sourceRefKey 会 598808),且看不到金额变化提示与撤销批准入口。
- 前端需做: 建议行展示
GROUP_BATCH_STAY与金额对账字段;建单回传sourceRefKey;统计加OVERPAID与新金额列;按供应商统计展示unattributedPaidAmount;已批准单加「撤销批准」按钮;598812 / 598813 文案映射;有来源草稿的供应商 / 订单置为只读。
七、不影响范围
- 行程节点、配房两类来源的判重口径不变。
- 出纳付款接口入参 / 出参不变(仅可能追加内部明细)。
- 付款单分页、详情、提交、批准、驳回、删除接口契约不变。
- C 端与小程序零影响。
八、测试环境已验证
TEST(https://api.test.1814.love:9443,hl-order-service-v3 = dev-v3@75d77eefe,含合并提交 a37bd669f;真实网关 + 管理员 token),2026-09-17:
| 场景 | 结果 |
|---|---|
| 迁移 | V20260917_110 / V20260917_111 执行成功;存量无明细单回填 7 条(6 MANUAL + 1 带来源),重复执行第①段零新增 |
| 四入口金额一致 | 纯团期户 2 间 × 400(sign):按订单建议 / 按供应商建议 / 按团统计均 800,按供应商统计应付 +800;改前按订单建议 0 行 |
| 客户自付与旧户 | 改 cash 后四处同时归 0;旧户只出 HOTEL_ASSIGNMENT 行、不双计 |
| 防重 | 单笔建单写出 1 行明细;再以单笔 / batch-create / batch-create-by-supplier 各建一次均 598809;H10 拆行与核单重绑后仍 598809 |
| 金额变化 | 增额可建差额行(ADJUST);未付超额 598813(列出单号与状态);已付超额 598812;草稿编辑超额 598813、改供应商/订单 598808、多明细改金额 598805 |
| 撤销批准 | PUT /admin/finance/payments/{id}/revoke-approval → 200、回到 PENDING、出纳队列消失、审核流水出现 UN_APPROVE;再撤 598802;与出纳付款并发 12 轮均恰一方成功 |
| 统计口径 | 已付后应付消失 → owed 0 / overpaid 800 / OVERPAID;合并付款按团各归各(800+800),出纳异额合并单 unattributedPaidAmount=100 且 800+800+100=1700 |
| 来源锁 | 同订单并发 create 与 update 8 轮均恰一个成功,Σ 不超过应付 |
GET /admin/finance/payments/suggestions?orderId=2100421247786545153 → 200,rows[0].sourceType=GROUP_BATCH_STAY,amount=800
POST /admin/finance/payments(同 sourceRefKey 第二次)→ 598809
PUT /admin/finance/payments/{id}/revoke-approval → 200;GET /admin/finance/payments/{id}/review-logs → 含 UN_APPROVE
逐条验收记录见工单 #7396 验收评论。