文件
hl-api-changelog/changelogs-v2/2026-09/17_7396_应付款四入口接团期住宿与付款身份金额对账-修改接口-管理后台.md
T
2026-09-17 18:28:13 +08:00

42 KiB
原始文件 Blame 文件历史

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 的一句话)

  1. 团期住宿进应付款了:团期房务(整团订房 + 系统分房到户)的酒店应付,现在在四个入口都能看到,行的 sourceType 为 GROUP_BATCH_STAY,并带 sourceRefKey(付款身份业务键)。勾选建单时 sourceRefKey 必须原样回传,sourceRefId 回传行上的 sourceId。
  2. 团期住宿按金额对账防重:同一付款身份(同订单、同晚、同酒店、同房型)已建单后,建议行 alreadyGenerated=true;若应付金额变了,amountChanged=true、diffAmount 给出差额(正数可再建差额行,负数需先删除 / 驳回 / 撤销批准或等冲正)。
  3. 新增两个错误码:598812(已付超出当前应付,需财务冲正)、598813(未付占用超出当前应付,提示里列出需处理的单号与状态)。
  4. 统计口径变化:已申请 / 已付改为按付款明细聚合;新增 refundedAmount / netPaidAmount / overpaidAmount / diffAmount;owedAmount 不再为负;状态新增 OVERPAID;按供应商统计新增 unattributedPaidAmount。
  5. 新增撤销批准 PUT /admin/finance/payments/{id}/revoke-approval:已批准未付款的单回到待提交草稿,之后可删除或编辑重提。
  6. 编辑草稿收紧:有业务来源的草稿禁止改供应商 / 订单(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 验收评论。


十、相关文档

  • Issue: #7396
  • PR: #7866
  • 核查 SQL: docs/finance/7396-payment-item-reconcile-check.sql(HL 仓库)

关联 / 联系人

链接

  • Issue: #7396
  • PR: #7866
  • 关联: #7327(团期核单与月报)、#7398(退款 / 冲正,598812 的后续出口)

联系人

  • 后端负责人: @jw
  • 前端负责人: @mmg