比较提交

...
2 次代码提交
作者 SHA1 备注 提交日期
jw和Claude Opus 5 70c1f44650 新增团期调整满团名额同步产品域库存接口说明(#7178)
changelog-filename-gate / validate (push) Failing after 2s
修的是「调了不生效」:调名额此前只写订单域,而库存权威在产品域,
导致加名额放不出空位、减名额停不了售,但看板数字会变。

请求体不兼容变更:{maxParticipants,maxRooms} 两个绝对值 →
{capacityDelta,reason} 净增量且只调户数;新增阶段门(仅招募中、
资源准备中可调)与 group-batch:manage 权限校验;响应改为返回
调整前后值、已报名与余量。

后端已部署 TEST 并实测:加减名额驱动满团即停/放开继续招募、
低于已报名拒绝且零写入、物料准备起阶段门、已成团加名额仍保持成团。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 16:21:40 +08:00
jw和Claude Opus 5 a7c44c0caa docs(changelog): 团期财务总览与预支复用订单预支(#7154 / PR #7170)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 16:20:04 +08:00
共修改 2 个文件,包含 869 行新增和 0 行删除
@@ -0,0 +1,644 @@
---
schema: "hl-changelog/v2"
ticket: "7154"
title: "团期财务总览与预支复用订单预支:财务 Tab 三个只读端点 + 预支创建语义改造"
consumer: "admin"
author: "jw(GIT)"
change_type: "新增接口"
backend_status: "pending"
gateway_status: "pending"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "待部署测试服并过网关实测后改 backend_status=deployed 再推送"
updated_at: "2026-09-06"
base: "dev-v3"
---
# 团期: 财务总览与预支复用订单预支
> **服务**: hl-order-service-v3
> **PR**: #7170
> **Issue**: #7154
> **日期**: 2026-09-06
> **影响范围**: 管理后台团期详情「财务」Tab、底部操作条「预支」弹窗、财务管理→预支审批列表
---
## ⚠️ 关键变化
团期财务 Tab 与团期预支弹窗此前**在 hl-ui 里完全不存在**(全仓零调用点),本次补齐后端。
四条前端必读:
1. **`POST .../group-batch/{id}/advance` 入参整体更换**。旧契约 `{amount, remark}` 是后端造了、前端从没接过的端点(已核 hl-ui v2.1 全仓无调用点),故**不是破坏性变更**;新契约与订单级预支**逐字一致**,预支弹窗组件可整体复用。
2. **「已预支」拆成三个数,旧 `advanceTotal` 已废**。旧语义是「提交即计入」(无审批),与新口径不等价:展示用 `advanceApproved`(只计已通过),额度用 `advanceAvailable`。**不要再用一个数**。
3. **预支上限一律读后端 `advanceAvailable`,不要前端自算**。订单级弹窗现在是本地 `balanceAmount − Σ记录` 算的,依赖记录列表已拉全;团期场景该算法不可靠。
4. **「设置报账人」传的是产品侧 `productBatchId`**,从团期详情接口取,**不是**财务 Tab 路径上的 `groupBatchId`,两者不同值。该端点后端零改动。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 团期财务总览 | GET | `/v3/admin/order/group-batch/:id/finance` | 新增 | 四张金额卡 + 逐户付款 + 整团合计 |
| 2 | 团期预支记录 | GET | `/v3/admin/order/group-batch/:id/advances` | 新增 | 团期级 + 各子订单级逐笔,不分页 |
| 3 | 领款人候选 | GET | `/v3/admin/order/group-batch/:id/advance/payee-candidates` | 新增 | 预支弹窗「借款对象」下拉 |
| 4 | 发起团期预支 | POST | `/v3/admin/order/group-batch/:id/advance` | 修改 | 路径不变,入参与返回体更换;创建即进待审批 |
| 5 | 预支审批列表 | GET | `/v3/admin/order/advance-approvals/page` | 修改 | 容忍团期级行 + `scope` 筛选 + keyword 六路 |
| 6 | 整团核算汇总 | GET | `/v3/admin/order/group-batch/:id/settlement/summary` | 修改 | 出参新增两个整团预支只读字段 |
**零改动复用**(同一套审批流程,前端无需改):`PUT /v3/admin/order/advance/:id/approve`、
`PUT /v3/admin/order/advance/:id/reject`、`DELETE /v3/admin/order/advance/:id`、
`PUT /v3/admin/group-batch/:id/staff/:id/reporter-rank`。
---
## 三、接口详情
### 1. 团期财务总览 `GET /v3/admin/order/group-batch/:id/finance`
**VO**: `GroupBatchFinanceRespVO`
#### 使用场景
团期详情切到「财务」Tab 时加载。一次返回顶部四张金额卡、逐户付款表与整团合计、已退团户数、主/次报账人,Tab 全部内容一个请求搞定。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `id` | path | Long | ✓ | 正整数 | 团期 ID(订单侧 `order_group_batch.group_batch_id`) |
无 query 参数,无请求体。需 `group-batch:finance:view` 权限码(比 `group-batch:view` 更严)。
#### 出参
| 字段 | 类型 | 说明 |
|---|---|---|
| `receivableAmount` | BigDecimal | 整团应收 = Σ 活跃子订单(订单金额 + 附加费 − 优惠) |
| `receivedAmount` | BigDecimal | 整团已收 = Σ 已付(累计毛额,不冲抵退款) |
| `unpaidAmount` | BigDecimal | 整团待收 = 应收 − 已收,下限 0,即本期总尾款 |
| `advanceApproved` | BigDecimal | 已预支(只计已通过)。**第四张卡取此值,不是三个数之和** |
| `advancePending` | BigDecimal | 待审批预支,占额度但未出账,不计入「已预支」卡 |
| `advanceAvailable` | BigDecimal | 可支取余额 = 尾款池 − (已通过 + 待审批),下限 0,即预支上限 |
| `withdrawnCount` | Integer | 已退团户数(前端只拉活跃集,算不出这个数) |
| `primaryPayeeName` | String | 主报账人姓名,未设置为 `null`(后端不兜底默认导游) |
| `secondaryPayeeName` | String | 次报账人姓名,未设置为 `null` |
| `totals.totalPrice` / `.paidAmount` / `.unpaidAmount` | BigDecimal | 逐户表末行「整团合计」,**服务端算**,与顶部卡同源 |
| `items[].orderId` | String | 子订单 ID(字符串回传防精度丢失) |
| `items[].orderNo` | String | 子订单号 |
| `items[].customerName` | String | 客户姓名(不返回手机号与证件号) |
| `items[].consultantName` | String | 定制师姓名 |
| `items[].totalPrice` | BigDecimal | 本户应收 |
| `items[].paidAmount` | BigDecimal | 本户已付。⚠️ 是**全额已付**,不是真定金拆分 |
| `items[].unpaidAmount` | BigDecimal | 本户待收尾款 |
| `items[].payStatus` | String | 仅 `UNPAID` / `DEPOSIT_PAID` / `FULLY_PAID` **三值** |
| `items[].settleStatus` | String | `SETTLED` / `PENDING_BALANCE` / `WITHDRAWN`,**状态胶囊取此值** |
| `items[].settleStatusText` | String | 结清状态中文,可直接渲染 |
#### 请求示例
```http
GET /v3/admin/order/group-batch/90211/finance
```
#### 响应示例
```json
{
"code": 0,
"msg": "success",
"data": {
"receivableAmount": 40200.00,
"receivedAmount": 32900.00,
"unpaidAmount": 7300.00,
"advanceApproved": 0.00,
"advancePending": 0.00,
"advanceAvailable": 7300.00,
"withdrawnCount": 0,
"primaryPayeeName": "张领队",
"secondaryPayeeName": null,
"totals": { "totalPrice": 40200.00, "paidAmount": 32900.00, "unpaidAmount": 7300.00 },
"items": [
{ "orderId": "770145", "orderNo": "GT-26-0081", "customerName": "罗敏", "consultantName": "李雯",
"totalPrice": 12300.00, "paidAmount": 12300.00, "unpaidAmount": 0.00,
"payStatus": "FULLY_PAID", "settleStatus": "SETTLED", "settleStatusText": "已结清" },
{ "orderId": "770147", "orderNo": "GT-26-0083", "customerName": "汪洋", "consultantName": "陈璐",
"totalPrice": 12300.00, "paidAmount": 5000.00, "unpaidAmount": 7300.00,
"payStatus": "DEPOSIT_PAID", "settleStatus": "PENDING_BALANCE", "settleStatusText": "待收尾款" }
]
}
}
```
#### 空数据 / 降级响应
无活跃子订单时,六个金额字段均为 `0.00`(**不是 null**),`items` 为空数组,`withdrawnCount` 为 `0`,两个报账人为 `null`。
#### 错误响应
| 码 | 含义 |
|---|---|
| `589500` | 团期不存在或已软删 |
| `589507` | 缺 `group-batch:finance:view` 权限 |
```json
{ "code": 589500, "msg": "团期不存在", "data": null }
```
#### 业务边界
- 已取消与已软删子订单**不进任何金额、不进 `items`**,只贡献 `withdrawnCount`。
- 隐式团(`IMPLICIT_SINGLE`)按一单退化开放,字段结构不变,**前端不得因团类型走两套渲染分支**。
- 顶部「已收(定金)」卡与逐户「已付定金」列,语义都是**全额已付**,不区分定金/尾款;已结清户该列等于应收。
- 整团应收与团期详情 `totalReceivable`、看板列表该行应收**三处同源**,数值必然一致。
---
### 2. 团期预支记录 `GET /v3/admin/order/group-batch/:id/advances`
**VO**: `List<GroupBatchAdvanceItemVO>`
#### 使用场景
财务 Tab 下半部「预支记录」区块,以及预支弹窗内的记录列表。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `id` | path | Long | ✓ | 正整数 | 团期 ID |
无 query 参数,**不分页**,一次返回本期全部。
#### 出参
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | String | 预支单 ID |
| `payeeStaffId` | String | 领款人主键快照 |
| `payeeName` / `payeeRole` / `payeeRoleText` | String | 领款人姓名 / 角色码 / 角色中文 |
| `advanceType` | String | 借款类型 |
| `amount` | BigDecimal | 预支金额 |
| `purpose` / `voucherUrl` | String | 用途说明 / 凭证 URL |
| `status` / `statusText` | String | `SUBMITTED` / `APPROVED` / `REJECTED` 及其中文 |
| `rejectReason` | String | 驳回原因 |
| `createdByName` | String | 申请人姓名 |
| `createTime` / `submittedAt` / `approvedAt` | String | 创建 / 提交 / 审批时间 |
| `approvedBy` | String | 审批人姓名 |
| `scope` | String | `ORDER` 订单级 / `GROUP_BATCH` 团期级(**本次新增**) |
| `scopeName` | String | 归属维度中文,可直接渲染标签(**本次新增**) |
| `orderNo` | String | 子订单号,`scope=ORDER` 时非空;团期级为 `null`(**本次新增**) |
#### 请求示例
```http
GET /v3/admin/order/group-batch/90211/advances
```
#### 响应示例
```json
{
"code": 0,
"msg": "success",
"data": [
{ "id": "31005", "scope": "GROUP_BATCH", "scopeName": "团期预支", "orderNo": null,
"payeeName": "朝鲁门", "payeeRole": "DRIVER", "payeeRoleText": "司机",
"advanceType": "住宿押金", "amount": 5000.00, "purpose": "沿途住宿押金",
"status": "APPROVED", "statusText": "已通过", "createdByName": "李雯",
"submittedAt": "2026-08-18 22:58:26", "approvedAt": "2026-08-19 09:12:03" }
]
}
```
#### 空数据 / 降级响应
无预支时返回空数组 `[]`,前端显示空态文案「暂无预支 · 在底部「预支」发起,记录将在此显示」(文案由前端提供,后端不返回)。
#### 错误响应
| 码 | 含义 |
|---|---|
| `589500` | 团期不存在 |
| `589507` | 缺 `group-batch:finance:view` 权限 |
```json
{ "code": 589500, "msg": "团期不存在", "data": null }
```
#### 业务边界
- 返回**团期级 + 各子订单级**全部预支,按创建时间倒序。
- **本接口不返回累计金额**,累计取接口 1 的 `advanceApproved` / `advancePending` / `advanceAvailable`。
---
### 3. 领款人候选 `GET /v3/admin/order/group-batch/:id/advance/payee-candidates`
**VO**: `List<AdvancePayeeCandidateVO>`
#### 使用场景
预支弹窗「借款对象」下拉。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `id` | path | Long | ✓ | 正整数 | 团期 ID(订单侧),服务端内部换算成产品侧,前端不感知 |
#### 出参
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | Long | 候选 ID,即创建预支的 `payeeStaffId`(资源域 `staff.staff_id`) |
| `staffName` | String | 姓名 |
| `staffRole` | String | 角色代码 |
| `staffRoleText` | String | 角色中文 |
| `reporterRank` | String | `PRIMARY` / `SECONDARY` / `NONE` |
| `isDefault` | Boolean | 主报账人为 `true`,前端默认选中 |
#### 请求示例
```http
GET /v3/admin/order/group-batch/90211/advance/payee-candidates
```
#### 响应示例
```json
{
"code": 0, "msg": "success",
"data": [
{ "id": 88101, "staffName": "张领队", "staffRole": "GUIDE", "staffRoleText": "导游",
"reporterRank": "PRIMARY", "isDefault": true },
{ "id": 88102, "staffName": "朝鲁门", "staffRole": "DRIVER", "staffRoleText": "司机",
"reporterRank": "NONE", "isDefault": false }
]
}
```
#### 空数据 / 降级响应
团期未配人员时返回空数组 `[]`;此时预支无法提交(借款对象必填)。
#### 错误响应
| 码 | 含义 |
|---|---|
| `589500` | 团期不存在 |
| `589507` | 缺 `group-batch:finance:view` 权限 |
```json
{ "code": 589500, "msg": "团期不存在", "data": null }
```
#### 业务边界
- 候选 = 本团**全部**人员(与订单级「可选本单任一人员」口径一致),排序:主报账人 → 次报账人 → 其余。
- **不返回手机号**,与订单级候选保持一致。
---
### 4. 发起团期预支 `POST /v3/admin/order/group-batch/:id/advance`
**VO**: `OrderAdvanceRespVO`
#### 使用场景
财务 Tab 底部操作条「预支」→ 弹窗填写 → 「申请预支」。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `id` | path | Long | ✓ | 正整数 | 团期 ID |
| `payeeStaffId` | body | Long | ✓ | 须属本团人员 | 借款对象,取候选下拉项的 `id` |
| `advanceType` | body | String | ✓ | 字典 `advance_type` 的 `dictValue` | 借款类型 |
| `amount` | body | BigDecimal | ✓ | > 0 且 ≤ `advanceAvailable` | 预支金额 |
| `purpose` | body | String | — | ≤ 255 | 用途说明 |
| `voucherUrl` | body | String | — | ≤ 512 | 凭证 URL |
> ⚠️ 旧入参 `{amount, remark}` **已废除**。`remark` 无对应字段,改用 `purpose`。
#### 出参
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | String | 新建的预支单 ID(旧实现返回 `Void`,**现在回传**) |
| `payeeStaffId` | String | 领款人主键快照(团期级存资源域 `staff.staff_id`) |
| `payeeName` / `payeeRole` / `payeeRoleText` | String | 领款人姓名 / 角色码 / 角色中文 |
| `advanceType` | String | 借款类型 |
| `amount` | BigDecimal | 预支金额 |
| `purpose` / `voucherUrl` | String | 用途说明 / 凭证 URL |
| `status` / `statusText` | String | 创建后恒为 `SUBMITTED` / 「待审批」 |
| `createdByName` | String | 申请人姓名 |
| `createTime` / `submittedAt` | String | 创建 / 提交时间 |
#### 请求示例
```http
POST /v3/admin/order/group-batch/90211/advance
{
"payeeStaffId": 88102,
"advanceType": "住宿押金",
"amount": "3000.00",
"purpose": "沿途住宿押金",
"voucherUrl": null
}
```
#### 响应示例
```json
{
"code": 0, "msg": "success",
"data": {
"id": "31006", "payeeStaffId": "88102", "payeeName": "朝鲁门",
"payeeRole": "DRIVER", "payeeRoleText": "司机",
"advanceType": "住宿押金", "amount": 3000.00, "purpose": "沿途住宿押金",
"status": "SUBMITTED", "statusText": "待审批",
"createdByName": "李雯", "submittedAt": "2026-09-06 15:20:11"
}
}
```
#### 空数据 / 降级响应
数据字典服务不可用时,借款类型校验降级为服务端内置集合,**不影响正常类型提交**。
#### 错误响应
| 码 | 含义 |
|---|---|
| `589500` | 团期不存在 |
| `589507` | 缺 `group-batch:finance:advance` 权限 |
| `589538` | 团期状态不可发起预支(须为物料准备中 / 待出发 / 出行中) |
| `589539` | 房 / 车 / 导 / 摄四项未配齐 |
| `585003` | 预支金额必须大于 0 |
| `585004` | 预支金额超过可用余额上限 |
| `585006` | 借款类型非法 |
| `585007` | 借款对象不属于本团期人员 |
```json
{ "code": 585004, "msg": "预支金额超过可用余额上限", "data": null }
```
#### 业务边界
- **双前置闸门**:团期状态 + 四项资源全就绪,缺一即拒(此前服务端两道都没有,只有前端做了置灰)。
- **上限走团期统一池**:团期级与各子订单级**共扣一池**。团期把整团尾款预支满后,该团任一子订单再发起订单级预支同样会被 `585004` 拒——这是本次修复的超支漏洞。
- 创建后进入**站内财务审批**,走既有 `advance-approvals` 列表与 approve / reject / 撤回三端点,与订单级完全一致。
- 一期仍**只记账不出款**,实际出款走财务既有付款流程。
- 同一团期 + 同一金额 5 秒内重复提交只成功一次(幂等)。
---
### 5. 预支审批列表 `GET /v3/admin/order/advance-approvals/page`
**VO**: `PageResult<AdvanceApprovalPageItemRespVO>`
#### 使用场景
财务管理 → 预支审批。团期级预支与订单级预支**混排在同一列表、走同一审批流程**。
#### 入参
新增一个可选 query 参数,其余入参不变:
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `scope` | query | String | — | `ALL` / `ORDER` / `GROUP_BATCH` | 归属维度筛选,缺省 `ALL`(两类混排,与改造前行为一致) |
`keyword` 语义扩展:由「订单号 / 团号 / 产品名」三路扩为**六路**,另加「团期号 / 团期名 / 团期产品名」。改造前搜索框标着「团号 / 产品」却搜不出团期级预支。
#### 出参
每行新增四个字段,其余不变:
| 字段 | 类型 | 说明 |
|---|---|---|
| `scope` | String | `ORDER` / `GROUP_BATCH` |
| `scopeName` | String | 归属维度中文 |
| `batchStatus` | String | 团期状态编码,仅 `scope=GROUP_BATCH` 时非空 |
| `batchStatusName` | String | 团期状态中文 |
**既有字段按 `scope` 择一填充,前端零改动即可显示**:
| 字段 | `scope=ORDER` | `scope=GROUP_BATCH` |
|---|---|---|
| `teamNo`(团号列) | 订单团号 | **团期号** |
| `productName` | 订单产品名 | 团期产品名 |
| `departDate` / `returnDate`(行程列) | 订单出行日期 | 团期出团 / 返程日 |
| `orderAmount` / `paidAmount` | 本单应收 / 已收 | **整团应收 / 已收** |
| `orderNo` / `consultantName` | 有值 | `null`(前端显「—」) |
| `orderStatus` / `flowStatus` / `payStatus` / `settlementStatus` | 有值 | `null`,改看 `batchStatus` |
#### 请求示例
```http
GET /v3/admin/order/advance-approvals/page?status=SUBMITTED&scope=GROUP_BATCH&page=1&pageSize=20
```
#### 响应示例
```json
{
"code": 0, "msg": "success",
"data": {
"records": [
{ "id": "31006", "scope": "GROUP_BATCH", "scopeName": "团期预支",
"orderNo": null, "teamNo": "GT-26-05", "productName": "呼伦贝尔草原 5 日游",
"departDate": "2026-09-20", "returnDate": "2026-09-24", "consultantName": null,
"orderStatus": null, "batchStatus": "PENDING_DEPARTURE", "batchStatusName": "待出发",
"orderAmount": 40200.00, "paidAmount": 32900.00,
"payeeName": "朝鲁门", "advanceType": "住宿押金", "amount": 3000.00,
"status": "SUBMITTED", "statusText": "待审批" }
],
"total": 1, "page": 1, "pageSize": 20
}
}
```
#### 空数据 / 降级响应
无匹配记录时 `records` 为空数组,`total` 为 `0`。
#### 错误响应
沿用改造前,本次未新增错误码。
```json
{ "code": 589507, "msg": "无操作权限", "data": null }
```
#### 业务边界
- **前端必须容忍 `orderId` / `orderNo` 为 `null`**(团期级行不挂订单)。
- 审批通过 / 驳回 / 撤回三个端点**零改动**,对团期级行行为与订单级完全一致。
- 团期级预支走**站内财务审批(资金审批)**,与流团 / 退单户的企微 OA **业务审批**是两条线,不合并。
---
### 6. 整团核算汇总 `GET /v3/admin/order/group-batch/:id/settlement/summary`
**VO**: `GroupBatchSettlementSummaryRespVO`
#### 使用场景
整团核算页。本次新增两个只读字段,用于**核单时把整团预支从主报账人代收的尾款中扣回**。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `id` | path | Long | ✓ | 正整数 | 团期 ID。入参未变 |
#### 出参
新增两个字段,其余不变:
| 字段 | 类型 | 说明 |
|---|---|---|
| `groupAdvanceApproved` | BigDecimal | 整团已拨付预支(**只含团期级**),核算时单独成一行扣回 |
| `groupAdvancePending` | BigDecimal | 整团待审批预支,占额度未出账,仅提示 |
#### 请求示例
```http
GET /v3/admin/order/group-batch/90211/settlement/summary
```
#### 响应示例
```json
{
"code": 0, "msg": "success",
"data": {
"settledOrderCount": 3, "totalActiveOrderCount": 3,
"subOrderTotalActualCost": 21000.00, "sharedCostTotal": 6000.00,
"grandTotalCost": 27000.00,
"groupAdvanceApproved": 5000.00,
"groupAdvancePending": 0.00
}
}
```
#### 空数据 / 降级响应
无团期级预支时两个字段均为 `0.00`(不是 null)。
#### 错误响应
沿用改造前。
```json
{ "code": 589500, "msg": "团期不存在", "data": null }
```
#### 业务边界
- 🚫 **只含团期级预支,不含子订单级**。子订单级预支已进各户的核单报销单与对账,整团层再算一次会**同一笔钱扣两遍**。
- 🚫 **不计入 `grandTotalCost`**。预支是**资金拨付**不是成本,计入会与核销后的真实成本科目重复计成本。前端渲染为「整团成本 X / 其中已拨付预支 Y」的独立一行,**不参与任何成本或毛利公式**。
- 逐户报销单与逐户对账**一个字未改**。
---
## 四、契约约束与正确调用方式
1. **财务 Tab 一次请求拿全**:接口 1 已含四张卡、逐户表、整团合计、退团户数、报账人,不要再拼 `/orders`。
2. **金额一律用服务端给的值**。整团合计由服务端算并与四张卡同源;前端自行 sum 会与卡片对不上。
3. **预支上限读 `advanceAvailable`**,不要用「待收尾款 − 记录之和」本地推算。
4. **状态胶囊读 `settleStatus`**,不要用 `payStatus` 推——后者只有三值,表达的是支付事件不是结清与否,已退团户用它渲染会显示成「已付定金」。
5. **设置报账人用产品侧 `productBatchId`**(团期详情接口取),不是财务 Tab 路径上的 `groupBatchId`。
---
## 五、数据库行为
> 只写外部可观察行为。
| 动作 | 可观察结果 |
|---|---|
| 发起团期预支 | 新增一条待审批预支记录,立即出现在本团期预支记录列表与预支审批列表;团期与订单的任何金额字段**均不变动**(预支不改应收/已收/待收) |
| 审批通过 | 该记录转为「已通过」,财务 Tab 的「已预支」增加、「待审批预支」减少、「可支取余额」不变(原本就已占额度) |
| 审批驳回 / 撤回 | 该记录转为「已驳回」或从列表消失,占用的额度**当场释放**,「可支取余额」回升 |
| 上线迁移(一次性) | 存量团期原有的累计预支金额,会转成一条「已通过」的**期初结转**记录出现在预支记录列表,金额与迁移前的累计值相等;该记录无领款人与凭证,按借款类型「期初结转」可识别 |
**幂等**:同一团期 + 同一金额 5 秒内重复提交只成功一次。
**并发**:同一团期的预支申请串行处理,两个管理员同时提交不会双双突破可支取余额。
---
## 六、边界行为
| 场景 | 行为 |
|---|---|
| 团期无活跃子订单 | 六个金额字段 `0.00`,`items` 空数组 |
| 子订单已取消 | 不进金额、不进 `items`,只计入 `withdrawnCount` |
| 未设置报账人 | `primaryPayeeName` 为 `null`,后端不兜底默认导游 |
| 团期状态为招募中 / 核单中 | 发起预支返回 `589538` |
| 四项资源缺任一 | 发起预支返回 `589539` |
| 团期尾款池已被预支占满 | 团期级与该团任一子订单级预支**均**返回 `585004` |
| 数据字典服务不可用 | 借款类型降级到内置集合校验,正常类型仍可提交 |
| 审批列表出现团期级行 | `orderId` / `orderNo` / `consultantName` / 订单四态均为 `null` |
---
## 七、不影响范围
- **订单级预支全链路行为零变化**:创建 / 审批 / 驳回 / 撤回 / 本单列表五个端点与改造前逐字一致(底表 `order_id` 由非空放宽为可空,但既有查询全是等值匹配,空值行天然不命中)。
- **逐户核单报销单与逐户对账未改**:仍只含本户预支。
- **审批通过 / 驳回 / 撤回三端点未改**。
- **设置报账人端点未改**。
- 前端页面、打印导出、发票区、预支实际出款均不在本次范围。
---
## 八、测试环境已验证
> ⏳ **尚未验证**。本文档随 PR #7170 提交,待合并并部署测试服后过网关实测,届时更新
> `backend_status` / `gateway_status` / `verified_at` 并补本节实测结果。
计划实测要点:
1. 财务 Tab 四张卡与逐户表末行合计一致,且与团期详情、看板列表三处应收同源。
2. **超支被堵死**:团期级把整团尾款预支满 → 该团任一子订单再发起订单级预支被 `585004` 拒。
3. **核单零重复扣**:团期级预支 5,000 通过 + 某户订单级预支 2,000 通过 → 该户报销单含 2,000,整团 `groupAdvanceApproved` 仍是 5,000;`grandTotalCost` 不变。
4. 两道闸门各拒一次(`589538` / `589539`)。
5. 团期级预支出现在预支审批列表,「团号 / 产品」「行程」两列有值,按团期号搜得到,就地通过 / 驳回 / 撤回正常。
6. 订单级预支五端点回归无变化。
---
## 十、相关文档
- 团期模块接口文档 v2.0 §0B(预支权威契约)、§0A.3(金额口径)、卡片 GB-ADM-040 / 041 / 042 / 043
- 团期模块数据模型 §A.11(预支复用订单预支)、§A.11.10(团期层单独对账)
- 团期模块表结构 v1.2 §4-5(`order_advance` 改造 DDL)
**待回写正式稿**(本次实现与文档不一致处):
| 处 | 文档现状 | 实际 |
|---|---|---|
| §0B.9 错误码 | 「统一用 `AdvanceErrorCode`(585 段)」 | 改落 `589538` / `589539`;585 段被 v2/v3 整段重叠声明且 585001-585010 已被 order-v2 实占 |
| GB-ADM-040 出参 | `payStatus` 写五值含 `REFUNDING` / `REFUNDED` | 代码只有三值;结清状态另出 `settleStatus` 字段 |
| GB-ADM-040 出参 | 含 `advanceTotal` | 已废,改三个数 |
| GB-ADM-042 | 返回 `GroupBatchWriteResultVO`、不回传 `advanceId` | 改返 `OrderAdvanceRespVO` 并回传 `advanceId` |
| §0A.3 | 「金额缺口是本期 P0,详情恒返回 0.00」 | 已被 #6905 / #6902 实时聚合关闭 |
| §0B.6 | 「查询键列名骗人,必须进 CR checklist」 | `V20260904_001` 改名后该坑消失 |
---
## 关联 / 联系人
- **Issue**: #7154
- **PR**: #7170(base `dev-v3`)
- **后端**: jw
- **前端**: 待认领(三个新端点 + 预支弹窗入参更换 + 审批列表 `scope` 标签)
@@ -0,0 +1,225 @@
---
schema: "hl-changelog/v2"
ticket: "7178"
title: "团期调整满团名额同步产品域库存"
consumer: "admin"
author: "jw(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "修复「调了不生效」:调名额此前只写订单域,而库存权威在产品域。后端已部署 TEST 并实测:加减名额驱动产品域满团即停/放开继续招募、低于已报名拒绝且零写入、物料准备起阶段门。请求体形状已变更(净增量),前端待接。"
updated_at: "2026-09-06"
base: "dev-v3"
---
# 团期调整满团名额:同步产品域库存 + 阶段门 + 净增量入参
> **影响范围**:管理后台「团期详情 → 整团总览 → 调整满团名额」弹窗。
> 当前状态:后端已部署 TEST 并实测;前端待接入(**请求体形状已变更**)。
## ⚠️ 关键变化
1. **修复「调了不生效」**。此前调名额只写订单域,而下单侧的剩余名额由**产品域**算出,
于是加名额放不出空位、减名额停不了售,但看板数字会变。现在调整会真正驱动售卖状态。
2. **请求体不兼容变更**:由 `{maxParticipants, maxRooms}` 两个绝对值,
改为 `{capacityDelta, reason}` —— **净增量、只调户数**。
3. **新增阶段门**:仅「招募中」与「资源准备中」可调,物料准备中及之后返回 589538。
4. **新增权限校验** `group-batch:manage`(此前该接口无任何权限校验)。
5. **响应由空改为返回结果对象**,直接给出弹窗三格所需数据。
## 一、背景
调整满团名额是给运营临时增减本期可报名户数用的。此前的实现只更新了团期侧的展示值,
没有同步到真正决定「还能不能报名」的那一侧,导致这个功能实际上不起作用——
**页面上数字变了,但客户仍按老上限被卡住**。本次修复把调整落到库存权威侧,
并按新库存重算班期是否已满额。
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
| --- | --- | --- | --- | --- | --- |
| 1 | 调整团期满团名额 | PUT | `/v3/admin/order/group-batch/{groupBatchId}/capacity` | 修改接口 | 入参改为净增量并只调户数;新增阶段门与权限校验;同步产品域库存;响应改为结果对象 |
## 三、接口详情
### 1. 调整团期满团名额 `PUT /v3/admin/order/group-batch/{groupBatchId}/capacity`
**VO**: `AdjustCapacityReqVO`
#### 使用场景
管理员在「团期详情 → 整团总览」底部操作条点「调整满团名额」,
弹窗用 −/+ 步进器改满团户数,点「保存名额」时把**净变化量**提交给本接口。
典型用法:某期已满员但还有客户想报,加 2 个名额把空位放出来继续招募;
或临近出团减名额提前收口。**已成团的团期加名额后仍保持成团,不会退回招募中。**
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| groupBatchId | path | Long | 是 | 正整数 | 团期 ID |
| capacityDelta | body | Integer | 是 | 不能为 0 | 满团名额净增量(户),可正可负。新满团户数 = 当前满团户数 + 该值 |
| reason | body | String | 否 | 最长 256 字 | 调整原因,填了写进团期操作记录。**不强制必填** |
#### 出参
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| batchId | String | 团期 ID |
| beforeMaxRooms | Integer | 调整前满团户数 |
| maxRooms | Integer | 调整后满团户数,0 表示不限 |
| capacityDelta | Integer | 本次净增量 |
| enrolledRooms | Integer | 已报名户数(户 = 订单 = 房) |
| remainRooms | Integer | 调整后余量 = max(0, maxRooms − enrolledRooms);满团户数为 0(不限)时为 null |
#### 请求示例
```json
{
"capacityDelta": 3,
"reason": "客户加订,放三个空位继续招募"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"batchId": "2096510069465088002",
"beforeMaxRooms": 2,
"maxRooms": 5,
"capacityDelta": 3,
"enrolledRooms": 1,
"remainRooms": 4
},
"traceId": null,
"success": true
}
```
#### 空数据 / 降级响应
调整后满团户数为 0 时表示**不限名额**,此时 `remainRooms` 返回 null,
前端不显示余量。这是正常语义而非异常。
本接口没有空列表形态;任何失败都以错误码返回,且**失败一律零写入**——
产品侧与团期侧都不会留下半改状态。
#### 错误响应
```json
{
"code": 589538,
"message": "物料准备开始后不可再调整满团名额(仅招募中、资源准备中可调)",
"data": null,
"traceId": null,
"success": false
}
```
#### 业务边界
- **仅「招募中」与「资源准备中」可调**;物料准备中及之后返回 589538。
- **调整后不得低于已报名户数**,否则返回 589509;新值也不得为负。
- **新值为 0 表示不限名额**,此时不受已报名数约束。
- `capacityDelta` 为 0 返回 589539(等于没调)。
- 需要 `group-batch:manage` 权限,无权限返回 589507。
- **调整不改团期状态**:已成团的仍保持成团,不会退回招募中。
- 产品侧同步失败时整笔失败并返回 589540,团期侧零写入。
## 四、契约约束与正确调用方式
- **提交净增量,不要提交总数**。前端步进器算出的总数只用于展示,
提交时给差值即可。这样两人同时调也不会互相覆盖成对方的总数。
- **不再接收人数容量**。此前的 `maxParticipants` 字段已移除,人数容量由产品侧维护。
- **调整前先看能不能调**:团期进入物料准备后按钮应置灰,避免用户点了才报错。
- **弹窗三格数据**:调整前可从团期详情取「当前名额 / 已报名」;
调整成功后直接用本接口返回的 `maxRooms` / `enrolledRooms` / `remainRooms` 刷新,
不必再拉一次详情。
- **重试是安全的**。若前端超时后重试同一请求,服务端以团期侧当前值为基数重算,
不会把同一个增量叠加两次。
## 五、数据库行为
- 调整会**同时更新产品侧与团期侧的满团户数**,并按新库存重算班期是否已满额:
售罄时停止继续接单,库存恢复时重新开放报名。
- **先写产品侧,成功后才写团期侧**;产品侧写失败即整笔失败、团期侧不留任何痕迹。
- 每次成功调整写一条团期操作记录,内容形如
「满团名额:9→10(+1) · 理由:客户加订一间」,含操作人与时间;未填理由时省略后半段。
- 拒绝的三种情况(阶段不允许 / 低于已报名 / 增量为 0)**均不产生任何写入**。
- 历史上两侧数值已经不一致的团期,本次不做批量订正;下一次调整会自动把两侧拉齐。
## 六、边界行为
- 已成团的团期加名额后**仍保持成团**,只是把空位放出来继续招募,满团即停。
- 减名额减到正好等于已报名户数是允许的(余量为 0),再减一户即被拒绝。
- 满团户数为 0 表示不限,此时无论已报名多少都不触发下限校验。
- 团期未绑定产品班期时返回 589540(无处可写,不静默放过)。
## 六.6、修改前后对比
| 项 | 修改前 | 修改后 |
| --- | --- | --- |
| 是否真正影响售卖 | ❌ 只改团期侧展示值,**加名额放不出空位、减名额停不了售** | ✅ 同步到库存权威侧,满团即停 / 放开继续招募都生效 |
| 请求体 | `{maxParticipants, maxRooms}` 两个绝对值,均必填 | `{capacityDelta, reason}`,净增量、只调户数 |
| 响应 | 空(调完要再拉一次详情) | 返回调整前后值、已报名与余量 |
| 阶段限制 | **无**,任何状态都能调 | 仅招募中与资源准备中,其余 589538 |
| 权限 | **无任何校验** | `group-batch:manage` |
| 调整原因 | 无此入参 | `reason` 选填,写进操作记录 |
| 操作记录 | 只有「最大人数:20→24,最大房间:8→9」 | 「满团名额:9→10(+1) · 理由:…」 |
## 六.7、影响评估
- **请求体不兼容**:字段整体更换。上线前已核对管理后台前端仓库,
**没有任何页面在调用本接口**(原型侧本就是提交净增量,只是此前只在前端本地累加),
因此未设兼容期。若有未知调用方,需同步改造。
- **阶段门是行为变更**:此前任何状态都能调,现在物料准备后会被拒。
这是有意收紧——那之后房车已按户数配好,改名额会让资源计划失真。
- **权限是行为变更**:`group-batch:manage` 已随上一单建好并授予管理员与超级管理员,
本次直接复用,无需额外配置。
- **资源准备中调整的已知风险**:该阶段房务/车务可能已按当前户数派单订房订车,
此时加减名额**不会回头改动已派资源**,需人工复核资源计划。
- 新增字段与新响应均为增量,不改动任何既有字段的类型与含义。
## 七、不影响范围
- 下单与库存扣减的判定口径不变,仍按满团户数与人数上限;本次只是让调整真正作用到它。
- 人数容量不受影响,仍由产品侧维护。
- 团期状态机不变:调整不推进也不回退任何状态。
- 成团、取消成团、流团三个动作本次未改动。
- 小程序端不受影响,改动全部在管理后台侧。
## 八、测试环境已验证
TEST 环境已部署产品服务与订单服务并实测通过:
- 加名额 +1:产品侧满团户数随之变化,团期侧同步,返回三格数据。
- 减名额 −2:两侧同步。
- 减到正好等于已报名户数:班期转为**已满额、停止接单**。
- 再加 1 户:班期**恢复报名中**,空位重新放出。
- 减到低于已报名户数:拒绝且**零写入**(产品侧数值未变)。
- 增量为 0:拒绝。
- 物料准备中及之后调整:拒绝且零写入。
- **已成团(资源准备中)加名额:成功,班期继续招募,团期仍保持成团不回退**。
## 十、相关文档
- 工单:HL#7178
- 合并:HL PR#7181(已合入 dev-v3)
- 关联工单:HL#7158(团期手动成团),本单复用其建立的 `group-batch:manage` 权限码;
弹窗顶部「满 6 户成团 · 满 9 户满团」的成团标准数据亦由该单交付
## 关联 / 联系人
- 后端:jw
- 前端待接:弹窗步进器改为提交净增量、接收新的响应结构刷新三格;
团期进入物料准备后按钮置灰;未达标提示与「不得低于已报名数」的前端拦截保持不变