changelog: #8268 团期人工确认 / #8269 确认后锁定配置 / #8271 六节点展示
changelog-filename-gate / validate (push) Failing after 1s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
这个提交包含在:
jw
2026-09-24 10:04:14 +08:00
共同撰写人 Claude Opus 5.5
父节点 fb00989545
当前提交 bfc1a3210b
共修改 3 个文件,包含 3209 行新增和 0 行删除
@@ -0,0 +1,677 @@
---
schema: "hl-changelog/v2"
ticket: "8268"
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: "2026-09-24"
status_note: "六节点定案(SRS §0.27.3 / §0.27.5 #2~#5):「配置 → 确认」由系统自动推进改为人工点「确认」。新增 POST /v3/admin/order/group-batch/{groupBatchId}/confirm(权限码 group-batch:confirm,授 GROUP_BATCH_MANAGER / ADMIN),门 = 房 / 车 / 导游领队 / 摄影四项 ready 全 true 且物资已确认,不满足返新码 589556 并逐项列出未满足项,状态不是 RESOURCE_PREPARING(含重复确认)返 589501;成功后团期进入 MATERIAL_PREPARING、时间线记 BATCH_CONFIRM(确认),事务提交后系统对全团已确认行程的户逐户补发合同与保险。四项 ready 翻真 / 成团 / 免车不再自动推进状态。连带改动:confirm-material 只在 RESOURCE_PREPARING 可调且不再顺带准入待出发(其它状态 589501);合同保险出具门改为团期已确认(MATERIAL_PREPARING 及之后),确认前手动出具返 589548,文案改为「团期确认后才能出具合同与保险」,合同保险面板 issuable 同口径;POST .../recheck-material-gate 下线(589561 不再抛出)。前端需新增「确认」按钮与 589556 逐项提示、把确认物资按钮挪到配置节点、移除物料门复判入口。"
updated_at: "2026-09-24"
base: "dev-v3"
---
# 团期状态流转: 人工「确认」取代自动推进,确认后才出合同保险(管理后台)
> **服务**: hl-order-service-v3(端口 8086/8186);权限种子在 hl-user-service
> **PR**: #8302
> **Issue**: #8268
> **日期**: 2026-09-24
> **影响范围**: 管理后台团期详情页的「确认」按钮(新增)、「确认物资」按钮、「合同保险」页签的出具按钮与可出具提示;原「复判物料门」入口下线
---
## ⚠️ 关键变化
1. **团期不会再自己从「资源准备中」走到「物料准备中」**。改前四项 ready 齐 + 合同保险出齐时系统自动推进;现在必须由团期管理员点「确认」(新端点)。
2. **合同 / 保险在确认前一律不出**。改前资源准备中四项配齐即可出具;现在确认前手动出具被 589548 拒,面板 `issuable=false`。确认后系统自动给全团已确认行程的户逐户补发。
3. **确认物资挪到确认之前**。`confirm-material` 以前只在物料准备中可调、还会顺带尝试进入待出发;现在只在资源准备中可调,且只置物资已确认,不改团期状态。
4. **`recheck-material-gate` 端点已删除**,调用会得到 404。
---
## 一、背景
六节点定案里「配置 → 确认」是一个**不可撤销的人工动作**:确认之后四项配置与物资锁定(锁定写口见 #8269 条目),系统随即对客出合同与保险。因此它不能再由系统在 ready 位翻真时悄悄推进,也需要独立于 `group-batch:manage` 的授权。
| 节点 | 持久态 | 本单之后怎么进入 |
|---|---|---|
| 配置 | `RESOURCE_PREPARING` | 成团(不变) |
| 确认 | `MATERIAL_PREPARING` | **仅**人工调用确认端点 |
| 出行·待出发 | `PENDING_DEPARTURE` | 七项硬门(不变) |
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 团期确认 | POST | `/v3/admin/order/group-batch/{groupBatchId}/confirm` | 新增接口 | 配置 → 确认;门不满足 589556 逐项回执 |
| 2 | 确认团期物资 | POST | `/v3/admin/order/group-batch/{groupBatchId}/confirm-material` | 修改(可调状态 + 行为) | 只在 `RESOURCE_PREPARING` 可调;不再顺带准入待出发 |
| 3 | 手工复判物料门 | POST | `/v3/admin/order/group-batch/{groupBatchId}/recheck-material-gate` | 删除接口 | 自动推进已删,复判入口随之下线 |
| 4 | 手动开合同 / 保险(GB-ADM-031) | POST | `/v3/admin/order/group-batch/{groupBatchId}/contracts/issue` | 修改(前置门 + 文案) | 确认前 589548,文案改 |
| 5 | 作废重开合同 / 保险(GB-ADM-031) | POST | `/v3/admin/order/group-batch/{groupBatchId}/contracts/reissue` | 修改(前置门 + 文案) | 同上 |
| 6 | 合同保险面板(GB-ADM-030) | GET | `/v3/admin/order/group-batch/{groupBatchId}/contracts` | 修改(出参语义) | `issuable` 改为「团期已确认」 |
网关无改动(均在既有 `/v3/admin/order/group-batch` 前缀下)。
---
## 三、接口详情
### 1. 团期确认 `POST /v3/admin/order/group-batch/{groupBatchId}/confirm`
**VO**: `Result<Void>`(无请求体)
#### 使用场景
团期详情页「配置」节点的「确认」按钮。房、车、导游领队、摄影四项配齐且物资已确认后,团期管理员点确认,团期进入「确认」节点,配置锁定,系统开始逐户出合同与保险。**没有撤销确认。**
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 团期主键 | 不存在返 589500 |
无请求体。
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| data | Void | 成功返回 null;团期状态已是 `MATERIAL_PREPARING`,重新拉详情即可看到 `stage=CONFIRM` |
#### 请求示例
```http
POST /v3/admin/order/group-batch/2097250563497385985/confirm HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": null,
"success": true
}
```
#### 空数据 / 降级响应
本接口无列表出参。确认成功后的逐户补发合同保险是**异步**的:确认接口立即返回 200,补发结果在「合同保险」页签逐户可见;单户出具失败不影响确认结果,也不影响其余户,可在该页签手动开(GB-ADM-031)补出。
```json
{ "code": 200, "data": null, "success": true }
```
#### 错误响应
确认门不满足(逐项列出,顺序固定为 房、车、导游领队、摄影、物资):
```json
{
"code": 589556,
"message": "团期尚不满足确认条件:车未配齐、物资未确认",
"success": false,
"data": null
}
```
状态不是「配置」(含已确认后再点一次):
```json
{
"code": 589501,
"message": "团期状态不允许当前操作",
"success": false,
"data": null
}
```
无权限(角色未授 `group-batch:confirm`):
```json
{
"code": 589507,
"message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)",
"success": false,
"data": null
}
```
#### 业务边界
- 权限码 `group-batch:confirm`,授给 `GROUP_BATCH_MANAGER` 与 `ADMIN`;其余角色 589507。与 `group-batch:manage` 分开授权。
- 判定顺序:团期存在(589500)→ 状态为 `RESOURCE_PREPARING`(589501)→ 确认门(589556)→ CAS 推进。任一步拒绝**零写入**。
- 589556 的未满足项取值只有五种:`房未配齐`、`车未配齐`、`导游领队未配齐`、`摄影未配齐`、`物资未确认`,以「、」连接。
- 不需要导游 / 摄影的团、整团免车的团,其 ready 位已由成团免闸 / 免车写口置真,确认时不会被这几项挡住。
- 并发确认只有一个成功,其余 589501;重复确认不会重复推进。
- 成功后时间线新增一条 `eventType=BATCH_CONFIRM`、`eventTypeName=确认`,带操作人。
- 补发只处理子订单状态为待出发 / 出行中(已确认行程)的户;尚未确认行程的户跳过,等其确认行程时由既有自动出具链路出;已出具的户逐项跳过;已取消户不出。
---
### 2. 确认团期物资 `POST /v3/admin/order/group-batch/{groupBatchId}/confirm-material`
**VO**: `Result<Void>`(无请求体)
#### 使用场景
「物资」页签的「确认物资」按钮。本次起它是团期确认的前置条件之一,在「配置」节点点。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 团期主键 | 不存在返 589500 |
无请求体。
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| data | Void | 成功返回 null;团期状态**不变**,物资已确认标记置真 |
#### 请求示例
```http
POST /v3/admin/order/group-batch/2097250563497385985/confirm-material HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": null,
"success": true
}
```
#### 空数据 / 降级响应
无列表出参。确认前物资可反复增删改、反复确认,每次确认都在时间线记一条「确认物资」流水(内容「确认物资清单」)。
```json
{ "code": 200, "data": null, "success": true }
```
#### 错误响应
团期不在 `RESOURCE_PREPARING`(含招募中、已确认及之后):
```json
{
"code": 589501,
"message": "团期状态不允许当前操作",
"success": false,
"data": null
}
```
#### 业务边界
- 权限码 `group-batch:manage`(不变)。
- **只在 `RESOURCE_PREPARING` 可调**;改前只在 `MATERIAL_PREPARING` 可调,两者正好相反。
- **不再推进团期状态**:改前确认物资后会顺带尝试进入待出发;现在进入待出发改由子订单确认、定时扫描与「复判待出发硬门」端点负责,七项硬门本身不变。
- 取消成团会把物资已确认标记重置(不变)。
---
### 3. 手工复判物料门 `POST /v3/admin/order/group-batch/{groupBatchId}/recheck-material-gate`
**VO**: `GroupBatchMaterialGateRespVO`(已删除)
#### 使用场景
**已下线。** 该端点原用于在「资源准备中 → 物料准备中」自动推进卡住时手工复判。自动推进已删除,这一跳只剩人工确认一条路,复判入口不再有意义。请改用新端点 `POST .../confirm`。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | - | 端点已删除 |
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| advanced | Boolean | 已删除(原:是否推进了团期) |
| currentStatus / currentStatusName | String | 已删除 |
| blockedGate / blockedOrderId / blockedReason | String / Long / String | 已删除 |
#### 请求示例
```http
POST /v3/admin/order/group-batch/2097250563497385985/recheck-material-gate HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
```
#### 响应示例
路由已不存在,经网关调用返回业务信封 `code=404`(TEST 2026-09-24 实测):
```json
{ "code": 404, "msg": "接口不存在: POST /v3/admin/order/group-batch/2102932276390383618/recheck-material-gate" }
```
#### 空数据 / 降级响应
无。端点不存在,不会返回任何业务数据。
#### 错误响应
任何调用都返回 `code=404`「接口不存在」(同上):
```json
{ "code": 404, "msg": "接口不存在: POST /v3/admin/order/group-batch/2102932276390383618/recheck-material-gate" }
```
#### 业务边界
- 原错误码 589561「团期当前状态为「{0}」,不在「资源准备中」,无需复判物料门」不再抛出,仅保留占位防号段复用。
- 前端所有调用点与按钮需移除;替代动作是「确认」。
---
### 4. 手动开合同 / 保险 `POST /v3/admin/order/group-batch/{groupBatchId}/contracts/issue`
**VO**: `GroupBatchContractIssueReqVO` → `GroupBatchIssueResultVO`
#### 使用场景
「合同保险」页签逐户开合同 / 保险。本次只改前置门与拒绝文案,入参出参结构不变。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 团期主键 | - |
| orderIds | Body | List&lt;Long&gt; | 否 | - | 为空 = 本期全部尚未出具的户 |
| target | Body | String | ✅ | `CONTRACT` / `INSURANCE` / `BOTH` | 出具目标 |
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| totalCount | Integer | 本次处理户数 |
| successCount | Integer | 成功户数 |
| failCount | Integer | 失败户数 |
| skipCount | Integer | 跳过户数(已出具 / 已签等) |
| results[].orderId | String | 子订单 ID |
| results[].target | String | `CONTRACT` / `INSURANCE` |
| results[].outcome | String | `SUCCESS` / `SKIPPED` / `FAILED` |
| results[].message | String | 失败或跳过原因 |
#### 请求示例
```json
{ "orderIds": ["770145"], "target": "CONTRACT" }
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"totalCount": 1,
"successCount": 1,
"failCount": 0,
"skipCount": 0,
"results": [
{ "orderId": "770145", "target": "CONTRACT", "outcome": "SUCCESS", "message": null }
]
},
"success": true
}
```
#### 空数据 / 降级响应
已出具的户逐项 `SKIPPED`,不算失败;单户失败只记在 `results` 里,不影响其余户(不变)。
```json
{ "code": 200, "data": { "totalCount": 1, "successCount": 0, "failCount": 0, "skipCount": 1, "results": [ { "orderId": "770145", "target": "CONTRACT", "outcome": "SKIPPED", "message": "该户合同已出具,自动跳过" } ] }, "success": true }
```
#### 错误响应
团期尚未确认(`RECRUITING` / `RESOURCE_PREPARING`,即便四项已配齐):
```json
{
"code": 589548,
"message": "团期确认后才能出具合同与保险",
"success": false,
"data": null
}
```
#### 业务边界
- 权限码 `group-batch:contract:issue`(不变)。
- 可出具的团期状态:`MATERIAL_PREPARING` / `PENDING_DEPARTURE` / `TRAVELLING` / `REVIEWING` / `SETTLED`;其余一律 589548,整单拒绝、零写入。
- 改前 `RESOURCE_PREPARING` 且四项 ready 全真时可出具,本次起不可。
---
### 5. 作废重开合同 / 保险 `POST /v3/admin/order/group-batch/{groupBatchId}/contracts/reissue`
**VO**: `GroupBatchContractIssueReqVO` → `GroupBatchIssueResultVO`
#### 使用场景
「开错」态的补救通路:先作废该户现有单据再重开。前置门与手动开完全一致。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 团期主键 | - |
| orderIds | Body | List&lt;Long&gt; | 否 | - | 为空 = 本期全部户 |
| target | Body | String | ✅ | `CONTRACT` / `INSURANCE` / `BOTH` | 重开目标 |
| reason | Body | String | 否 | - | 作废原因 |
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| totalCount / successCount / failCount / skipCount | Integer | 同手动开 |
| results[] | List | 逐户结果,同手动开 |
#### 请求示例
```json
{ "orderIds": ["770145"], "target": "CONTRACT", "reason": "方案选错" }
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"totalCount": 1,
"successCount": 1,
"failCount": 0,
"skipCount": 0,
"results": [
{ "orderId": "770145", "target": "CONTRACT", "outcome": "SUCCESS", "message": null }
]
},
"success": true
}
```
#### 空数据 / 降级响应
作废失败的户直接记 `FAILED`,不进重开,不影响其他户(不变)。
```json
{ "code": 200, "data": { "totalCount": 1, "successCount": 0, "failCount": 1, "skipCount": 0, "results": [ { "orderId": "770145", "target": "CONTRACT", "outcome": "FAILED", "message": "作废失败" } ] }, "success": true }
```
#### 错误响应
```json
{
"code": 589548,
"message": "团期确认后才能出具合同与保险",
"success": false,
"data": null
}
```
#### 业务边界
- 与手动开共用同一出具门与同一文案。
- 确认前没有任何已出具的单据可重开,调用即 589548。
---
### 6. 合同保险面板 `GET /v3/admin/order/group-batch/{groupBatchId}/contracts`
**VO**: `GroupBatchContractBoardVO`
#### 使用场景
「合同保险」页签首屏:顶部三格统计 + 逐户卡片;`issuable` 决定出具按钮是否可点。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 团期主键 | - |
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| issuable | Boolean | **语义改变**:= 团期已人工确认(`MATERIAL_PREPARING` 及之后的可出具状态);改前 = 四项配齐 |
| batchStatus | String | 团期状态存储值,`issuable=false` 时可据此提示(不变) |
| totalCount / contractIssuedCount / contractSignedCount / insuranceIssuedCount | Integer | 不变 |
| items | List | 逐户明细(不变) |
#### 请求示例
```http
GET /v3/admin/order/group-batch/2097250563497385985/contracts HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
```
无请求体。
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2097250563497385985",
"issuable": false,
"batchStatus": "RESOURCE_PREPARING",
"totalCount": 3,
"contractIssuedCount": 0,
"contractSignedCount": 0,
"insuranceIssuedCount": 0,
"items": []
},
"success": true
}
```
#### 空数据 / 降级响应
无活跃子订单时 `totalCount=0`、`items` 为空数组(不变):
```json
{ "code": 200, "data": { "issuable": false, "batchStatus": "RESOURCE_PREPARING", "totalCount": 0, "items": [] }, "success": true }
```
#### 错误响应
```json
{
"code": 589500,
"message": "团期不存在",
"success": false,
"data": null
}
```
#### 业务边界
- `RESOURCE_PREPARING` 且四项已配齐时,改前 `issuable=true`,现在 `false`;提示文案建议为「团期确认后才能出具合同与保险」。
- 其余字段口径不变。
---
## 四、契约约束与正确调用方式
### ✅ 正确 / ❌ 错误调用顺序
| 场景 | 调用 |
|------|------|
| ✅ 配置节点收尾 | 配房 / 车 / 导摄 → `confirm-material` → `confirm` |
| ❌ 先确认团期再确认物资 | `confirm` → 589556「…物资未确认」;确认后再调 `confirm-material` → 589501 |
| ❌ 确认前出具合同 | `contracts/issue`(`RESOURCE_PREPARING`)→ 589548 |
| ❌ 继续调复判物料门 | `recheck-material-gate` → 404 |
### 589556 的处理
message 的冒号之后就是未满足项清单,可直接展示给操作人;不要再去调已下线的复判端点找原因。
---
## 五、数据库行为
| 前端动作 | 外部可观察的写入 |
|----------|------------------|
| `confirm` 成功 | 团期状态 `RESOURCE_PREPARING → MATERIAL_PREPARING`(CAS,一次)+ 时间线一条「确认」(含操作人);提交后异步逐户生成合同 / 保单 |
| `confirm` 被拒(589501 / 589556 / 589507) | 零写入 |
| `confirm-material` 成功 | 物资已确认标记置真 + 时间线一条「确认物资」;团期状态不变 |
| `confirm-material` 被拒 | 零写入 |
| `contracts/issue` / `reissue` 被 589548 拒 | 零写入 |
---
## 六、边界行为
- 未登录 → 401(网关拦截)。
- 团期不存在 → 589500。
- 四项 ready 翻真、成团、整团免车**都不会再推进团期状态**;团期会停在「配置」直到有人点确认。
- 确认后补发是异步的,确认接口不等补发完成;补发整轮失败(如团期被并发流团)只影响出具,不回滚确认,可在「合同保险」页签手动补出。
- 进入待出发的七项硬门不变。
---
## 六.5、枚举 / 数据字典
### 589556 未满足项(GroupBatchService.unmetConfirmConditions)
**所属字段**: 错误 `message` 冒号后的清单 | **类型**: `String`(「、」分隔)
| 值 | 中文 | 说明 |
|----|------|------|
| `房未配齐` | 房 | 团期配房完成标志为假 |
| `车未配齐` | 车 | 团期配车完成标志为假(整团免车时为真) |
| `导游领队未配齐` | 导游领队 | 不需要导游的团成团时已置真 |
| `摄影未配齐` | 摄影 | 不需要摄影的团成团时已置真 |
| `物资未确认` | 物资 | 未调用 `confirm-material` |
### 时间线事件(GroupBatchLogEventType,新增一值)
**所属字段**: 团期状态流水 `eventType` / `eventTypeName` | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `BATCH_CONFIRM` | 确认 | 本次新增;人工确认时写入,`fromStatus=RESOURCE_PREPARING`、`toStatus=MATERIAL_PREPARING` |
| `BATCH_RESOURCE_READY` | 资源就绪·进物资准备 | 原自动推进事件,本次起不再写入;历史行照常显示 |
## 六.6、修改前后对比
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| 合同保险面板 `issuable` | 四项配齐(或已进物料准备中)即 true | 团期已确认(物料准备中及之后)才 true |
| 589548 message | 房/车/导/摄四项配齐后才能出具合同与保险 | 团期确认后才能出具合同与保险 |
| 589556 | 无 | 新增:团期尚不满足确认条件:{0} |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| 资源准备中 → 物料准备中 | 四项 ready + 逐户合同已签、保险已出 → 系统自动推进 | 只能人工 `confirm`,门 = 四项 ready + 物资已确认 |
| 确认物资可调状态 | `MATERIAL_PREPARING` | `RESOURCE_PREPARING` |
| 确认物资的副作用 | 顺带尝试进入待出发 | 不改团期状态 |
| 合同保险出具时机 | 资源准备中四项配齐即可 | 确认后;确认时对已确认行程的户自动补发 |
| 复判物料门端点 | 可用 | 删除(404) |
## 六.7、影响评估
- **是否破坏向后兼容**: 是。复判端点删除;确认物资的可调状态翻转;确认前不能出合同保险。
- **前端是否必须同步上线**: 是。缺少「确认」按钮时,新成团的团期会一直停在「配置」节点。
- **前端 workaround 清理点**: 移除「复判物料门」入口及其结果弹窗;「确认物资」按钮的显示条件由物料准备中改为资源准备中;合同保险页签按 `issuable` 置灰的提示文案改为「团期确认后才能出具合同与保险」。
---
## 七、不影响范围
- **仅影响**: 团期「配置 → 确认」这一跳、确认物资、合同保险出具门。
- **零影响**:
- 成团、取消成团、流团的入参与出参
- 进入待出发的七项硬门与「复判待出发硬门」端点
- 子订单确认行程后的逐户自动出具链路(团期已确认时照常出)
- 小程序端
- 另:内部定时任务端点 `/v3/internal/jobs/group-batch-material-gate/run` 与其定时任务同批下线,属服务间内部接口,管理后台不调用。
---
## 八、测试环境已验证
部署:hl-user-service + hl-order-service-v3 = dev-v3 @ d9fdd7fe0(2026-09-24 09:18 / 09:20),经网关 `https://api.test.1814.love` 真实鉴权实测(2026-09-24 09:22–09:45);工单 #8268 已验收关单。
| # | 场景 | 结果 |
|---|---|---|
| 1 | 四项 ready + 物资已确认,GROUP_BATCH_MANAGER 调 `POST /confirm` | 200,RESOURCE_PREPARING → MATERIAL_PREPARING,时间线 `BATCH_CONFIRM`(含操作人) |
| 2 | 门不满足调 confirm | 589556「团期尚不满足确认条件:房未配齐、车未配齐、摄影未配齐、物资未确认」,状态不变 |
| 3 | 招募中 / 已确认后重复调 confirm | 589501,不推进 |
| 4 | ROOM_MANAGER / VEHICLE_MANAGER / CUSTOMIZER / FINANCE 调 confirm | 589507;Flyway `20260923.268` 已执行,仅授 ADMIN 与 GROUP_BATCH_MANAGER |
| 5 | 成团及四项依次翻真 | 不再自动推进,停在 RESOURCE_PREPARING 直到人工确认 |
| 6 | 确认前户确认行程 / 手动出具 | 不自动出具;`GET /contracts` 的 `issuable=false`;手动出具 589548「团期确认后才能出具合同与保险」 |
| 7 | 确认后全团补发 | `total=6 success=4 skipped=2 failed=0`:已确认行程两户合同 GENERATED + 保险 INSURED;已取消户、未确认行程户不出;再次出具返回 SKIPPED |
| 8 | 配置阶段 / 确认后调 `confirm-material` | 200(`material_confirmed=1`)/ 589501 |
| 9 | 旧 `recheck-material-gate` 与内部 job 端点 | `code=404`「接口不存在」;sys_job 与 QRTZ 均无 1044 |
| 10 | 合同签齐 + 七门满足后复检 | 进入 PENDING_DEPARTURE(签署以 SQL 模拟) |
---
## 九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|----|-------|------|------------|
| — | #7105 | 合同保险出具门(四项配齐,589548) | ❌ 门条件被本单替换,码值保留 |
| — | #7526 | 物料门兜底复判(端点 + 定时任务) | ❌ 本单下线 |
| — | #7528 | 确认物资后顺带准入待出发 | ❌ 本单删除该顺带准入 |
| **本 PR #8302** | **#8268** | 人工确认 + 确认后出合同保险 | ✅ 最新 |
---
## 十、相关文档
- 关联 Issue: [wx/HL#8268](https://git.1814.love/wx/HL/issues/8268)
- 关联 PR: [wx/HL#8302](https://git.1814.love/wx/HL/pulls/8302)
- 同批六节点条目:确认后锁定配置(#8269)、六节点展示与看板七桶(#8271)
## 关联 / 联系人
### 链接
- **Issue**: [#8268](https://git.1814.love/wx/HL/issues/8268)
- **PR**: [#8302](https://git.1814.love/wx/HL/pulls/8302)
- **Merge commit**: [d9fdd7fe0](https://git.1814.love/wx/HL/commit/d9fdd7fe038952ac3ffdd31913dfcc4a2fc40574)
### 联系人
- **后端负责人**: @jw
@@ -0,0 +1,800 @@
---
schema: "hl-changelog/v2"
ticket: "8271"
title: "团期看板与详情按六节点展示:分页 / 详情 / 看板行新增 stage 与出行子状态,统计条与 opsStage 改七桶(旧八桶过渡期兼容),核团 / 验团用语统一为核单 / 结算"
consumer: "admin"
author: "jw(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: "2026-09-24"
status_note: "六节点定案(SRS §0.27.1 / §0.27.4):持久九态 batchStatus 不动,服务端派生节点 stage(RECRUIT 招募 / CONFIGURE 配置 / CONFIRM 确认 / TRIP 出行 / REVIEW 核单 / SETTLE 结算 / DISBANDED 已流团)与出行子状态 tripSubStatus(待出发 / 出行中 / 已返团),只供展示。团期分页、详情、看板行新增 stage / stageName / tripSubStatus / tripSubStatusName 四字段;既有字段 opsStage 的取值由八桶改为七桶(与 stage 逐字同值);统计条 buckets 固定 13 键 = 七桶 + 6 个旧八桶别名键(别名键不计入 total);opsStage 筛选接受七桶,旧八桶 code 过渡期按原口径兼容;导出 CSV「状态」列改为节点名。PR #8303 补充:面向用户的核团 / 验团用语统一为核单 / 结算(6 个错误码文案、时间线事件中文标签、核单状态 CHECKED 中文名),码值与存储值不变。前端需把看板页签与统计条切到七桶、详情页按 stage / tripSubStatus 展示节点;旧八桶别名在前端切换完成前保留。"
updated_at: "2026-09-24"
base: "dev-v3"
---
# 团期看板: 按六节点展示,统计条与筛选改七桶(管理后台)
> **服务**: hl-order-service-v3(端口 8086/8186)
> **PR**: #8276(代码)、#8279(文档)、#8303(核单 / 结算用语)
> **Issue**: #8271
> **日期**: 2026-09-24
> **影响范围**: 管理后台团期看板(页签、统计条、列表行、导出)、团期详情头部节点展示、团期时间线与核单 / 结算相关报错文案
---
## ⚠️ 关键变化
1. **`opsStage` 响应取值变了**:以前是八桶(`FORMED` / `PENDING_TRIP` / `TRAVELLING` / `TRIP_FINISHED` / `AUDITING` / `CHECKED` …),现在只输出七桶(`CONFIGURE` / `CONFIRM` / `TRIP` / `REVIEW` / `SETTLE` …),且与新字段 `stage` 逐字同值。按旧值做过 `switch` 的前端代码会落到默认分支。
2. **原「已成团」一桶拆成两桶**:`RESOURCE_PREPARING` → 配置(`CONFIGURE`),`MATERIAL_PREPARING` → 确认(`CONFIRM`);原「待出行 / 出行中 / 出行完毕」三桶合成一个「出行」(`TRIP`),细分看 `tripSubStatus`。
3. **统计条 `buckets` 从 8 键变 13 键**:七桶在前、6 个旧别名键在后。`total` 只等于七桶之和,**不要再对 `buckets` 整体求和**(会重复计数)。
4. **旧入参仍然能用**:页签继续传旧八桶 code 给 `opsStage` 筛选,结果与改前逐条一致(按原口径展开);统计条的旧键名计数也照旧给。
5. **用语**:「核团中 / 已验团」改为「核单 / 结算」,涉及 6 个错误码文案、时间线事件中文名、核单状态 `CHECKED` 的中文名;码值与存储值一律不变。
---
## 一、背景
团期六节点定案:看板与详情按「招募 → 配置 → 确认 → 出行 → 核单 → 结算」展示,另有分叉终态「已流团」。持久九态 `batchStatus` 不动、不新增持久列,节点与出行子状态由服务端唯一派生,**只供展示,不承担业务判断**——按钮可用性、权限仍以 `batchStatus` 与各就绪位为准。
| 节点 `stage` | `stageName` | 覆盖的 `batchStatus` | 说明 |
|---|---|---|---|
| `RECRUIT` | 招募 | `RECRUITING` | 未建团行(产品侧有班期、订单侧尚无团期)也恒为 `RECRUIT` |
| `CONFIGURE` | 配置 | `RESOURCE_PREPARING` | 配房 / 车 / 导游领队 / 摄影与物资,确认前可改 |
| `CONFIRM` | 确认 | `MATERIAL_PREPARING` | 人工确认后配置锁定,逐户出合同与保险 |
| `TRIP` | 出行 | `PENDING_DEPARTURE`、`TRAVELLING`、`TRIP_FINISHED` | 复合节点,细分见 `tripSubStatus` |
| `REVIEW` | 核单 | `REVIEWING` | 原「核团中」 |
| `SETTLE` | 结算 | `SETTLED` | 原「已验团」 |
| `DISBANDED` | 已流团 | `CANCELLED` | 分叉终态 |
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 团期分页列表(GB-ADM-001) | GET | `/v3/admin/order/group-batch` | 出参新增字段 + 出参取值变化 + 入参取值扩展 | 新增四字段;`opsStage` 出参改七桶;`opsStage` 筛选接受七桶并兼容旧八桶 |
| 2 | 团期详情(GB-ADM-002) | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 出参新增字段 + 出参取值变化 | 新增四字段;`opsStage` 改七桶 |
| 3 | 团期看板列表 | GET | `/v3/admin/order/group-batch/board` | 出参新增字段 + 出参取值变化 | 命中 / 未命中 / 孤儿三类行均带四字段;`opsStage` 改七桶 |
| 4 | 团期看板统计条(GB-ADM-009) | GET | `/v3/admin/order/group-batch/summary` | 出参取值变化 | `buckets` 13 键;`total` = 七桶之和 |
| 5 | 导出团期列表 CSV(GB-ADM-008) | GET | `/v3/admin/order/group-batch/export` | 入参取值扩展 + 导出内容变化 | `opsStage` 同分页口径;「状态」列改为节点名 |
| 6 | 团期状态流水(GB-ADM-096) | GET | `/v3/admin/order/group-batch/{groupBatchId}/status-logs` | 出参取值变化(文案) | 5 个核团 / 验团事件的 `eventTypeName` 改为核单 / 结算用语 |
路径、HTTP 方法、权限码、信封结构均不变;网关无改动。
---
## 三、接口详情
### 1. 团期分页列表 `GET /v3/admin/order/group-batch`
**VO**: `GroupBatchListReqVO` → `PageResult<GroupBatchPageItemRespVO>`
#### 使用场景
团期看板主列表(按页签筛选)。页签既可以传七桶 code,也可以继续传旧八桶 code。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| opsStage | Query | String | 否 | 七桶或旧八桶 code;空白 / 非法值忽略 | **本次改取值**:七桶 `RECRUIT` / `CONFIGURE` / `CONFIRM` / `TRIP` / `REVIEW` / `SETTLE` / `DISBANDED`;旧八桶 `FORMED` / `PENDING_TRIP` / `TRAVELLING` / `TRIP_FINISHED` / `AUDITING` / `CHECKED` 过渡期仍按原口径展开 |
| scope | Query | String | 否 | `ONGOING` / `FINISHED` / `ALL` | 班期范围;与 `opsStage` 取交集(不变) |
| productId | Query | Long | 否 | - | 按产品筛选(不变) |
| batchStatus | Query | String | 否 | 九态 code | 精确九态筛选(不变) |
| month | Query | String | 否 | `yyyy-MM` | 出发月份(不变) |
| keyword | Query | String | 否 | - | 班期编号 / 名称模糊(不变) |
| pageNo | Query | Integer | 否 | 默认 1 | 页码(不变) |
| pageSize | Query | Integer | 否 | 默认 20,最大 100 | 每页条数(不变) |
其余既有筛选参数(`deadlineFrom` / `deadlineTo` / `departFrom` / `departTo` / `sortBy` / `sortOrder`)不变。
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| records[].batchStatus | String | 持久九态,**业务判断用这个**(不变) |
| records[].batchStatusName | String | 九态中文名(不变,如「资源准备中」「出行完毕」) |
| records[].stage | String | **新增**。节点 code,取值见「一、背景」七个 |
| records[].stageName | String | **新增**。节点中文名,与 `stage` 同生同灭 |
| records[].tripSubStatus | String | **新增**。出行子状态 `PENDING_DEPARTURE` / `TRAVELLING` / `TRIP_FINISHED`;仅 `stage = TRIP` 时有值,其余为 null |
| records[].tripSubStatusName | String | **新增**。待出发 / 出行中 / 已返团;与 `tripSubStatus` 同生同灭 |
| records[].opsStage | String | **取值改变**:与 `stage` 逐字同值(七桶),旧八桶值不再输出 |
| records[].opsStageName | String | **取值改变**:与 `stageName` 同值 |
| total | Long | 命中总数(不变) |
其余分页项字段不变。
#### 请求示例
```http
GET /v3/admin/order/group-batch?opsStage=TRIP&scope=ALL&pageNo=1&pageSize=20 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
```
无请求体。
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"total": 1,
"records": [
{
"groupBatchId": "2099750660965584898",
"batchNo": "GB26100101",
"batchStatus": "PENDING_DEPARTURE",
"batchStatusName": "待出发",
"stage": "TRIP",
"stageName": "出行",
"tripSubStatus": "PENDING_DEPARTURE",
"tripSubStatusName": "待出发",
"opsStage": "TRIP",
"opsStageName": "出行"
}
]
},
"success": true
}
```
#### 空数据 / 降级响应
无命中返回空页;`batchStatus` 为 null 或不在九态内的历史脏数据行,四个新字段与 `opsStage` / `opsStageName` 同为 null,不抛错:
```json
{ "code": 200, "data": { "total": 0, "records": [] }, "success": true }
```
#### 错误响应
`opsStage` 传非法值不报错(忽略该筛选并记 warn);本接口错误形态沿用既有,如无列表权限:
```json
{
"code": 589507,
"message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)",
"success": false,
"data": null
}
```
#### 业务边界
- 七桶与旧别名 code 不相交,先按七桶解析、未命中再按别名解析。
- 旧别名按**原口径**展开,不放大为新节点:`PENDING_TRIP` 仍只筛待出发,`FORMED` = 配置 + 确认。
- 服务端严格取 `scope ∩ opsStage`:`REVIEW` / `SETTLE` 及 `TRIP` 里已返团的那部分返团日必然已过,默认 `ONGOING` 会过滤掉,点这些页签需把 `scope` 切到 `ALL`。
- 四个新字段是纯内存映射,整页无额外查询。
---
### 2. 团期详情 `GET /v3/admin/order/group-batch/{groupBatchId}`
**VO**: `GroupBatchDetailRespVO`
#### 使用场景
团期详情页头部展示当前节点(如「出行 · 待出发」)。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 团期主键 | 不变 |
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| batchStatus | String | 持久九态(不变) |
| batchStatusName | String | 九态中文名(不变) |
| stage | String | **新增**。节点 code |
| stageName | String | **新增**。节点中文名 |
| tripSubStatus | String | **新增**。仅 `stage = TRIP` 时有值 |
| tripSubStatusName | String | **新增**。与 `tripSubStatus` 同生同灭 |
| opsStage | String | **取值改变**:七桶,与 `stage` 同值 |
| opsStageName | String | **取值改变**:与 `stageName` 同值 |
其余详情字段不变。
#### 请求示例
```http
GET /v3/admin/order/group-batch/2097250563497385985 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
```
无请求体。
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2097250563497385985",
"batchStatus": "MATERIAL_PREPARING",
"batchStatusName": "物料准备中",
"stage": "CONFIRM",
"stageName": "确认",
"tripSubStatus": null,
"tripSubStatusName": null,
"opsStage": "CONFIRM",
"opsStageName": "确认"
},
"success": true
}
```
#### 空数据 / 降级响应
非出行节点 `tripSubStatus` / `tripSubStatusName` 为 null;脏数据行四字段同为 null:
```json
{ "code": 200, "data": { "batchStatus": null, "stage": null, "stageName": null, "tripSubStatus": null, "tripSubStatusName": null, "opsStage": null, "opsStageName": null }, "success": true }
```
#### 错误响应
```json
{
"code": 589500,
"message": "团期不存在",
"success": false,
"data": null
}
```
#### 业务边界
- 子状态中文名「已返团」与九态 `batchStatusName`「出行完毕」不同名,两套文案各自展示,不要互相替换。
- 前端不得用 `stage` / `tripSubStatus` 判断按钮可用性或权限。
---
### 3. 团期看板列表 `GET /v3/admin/order/group-batch/board`
**VO**: `List<GroupBatchBoardItemRespVO>`
#### 使用场景
按产品展示全部班期(含未建团行与孤儿行)的看板卡片列表。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| productId | Query | Long | ✅ | - | 产品 ID(不变) |
| scope | Query | String | 否 | `ONGOING` / `FINISHED` / `ALL`,缺省 `ALL` | 班期范围(不变) |
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| [].batchStatus | String | 持久九态(不变);未建团行按 `RECRUITING` |
| [].stage | String | **新增**。节点 code;未建团行恒 `RECRUIT` |
| [].stageName | String | **新增**。节点中文名 |
| [].tripSubStatus | String | **新增**。仅 `stage = TRIP` 时有值 |
| [].tripSubStatusName | String | **新增** |
| [].opsStage | String | **取值改变**:七桶,与 `stage` 同值 |
| [].opsStageName | String | **取值改变**:与 `stageName` 同值 |
#### 请求示例
```http
GET /v3/admin/order/group-batch/board?productId=100001&scope=ALL HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
```
无请求体。
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": [
{
"groupBatchId": "2097250563497385985",
"batchStatus": "TRAVELLING",
"stage": "TRIP",
"stageName": "出行",
"tripSubStatus": "TRAVELLING",
"tripSubStatusName": "出行中",
"opsStage": "TRIP",
"opsStageName": "出行"
},
{
"productBatchId": "2097250420299530242",
"batchStatus": "RECRUITING",
"stage": "RECRUIT",
"stageName": "招募",
"tripSubStatus": null,
"tripSubStatusName": null,
"opsStage": "RECRUIT",
"opsStageName": "招募"
}
],
"success": true
}
```
#### 空数据 / 降级响应
产品无班期时返回空数组:
```json
{ "code": 200, "data": [], "success": true }
```
#### 错误响应
```json
{
"code": 589507,
"message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)",
"success": false,
"data": null
}
```
#### 业务边界
- 命中 / 未命中 / 孤儿三类行统一带四个新字段。
- 行集合、排序、其余字段均不变。
---
### 4. 团期看板统计条 `GET /v3/admin/order/group-batch/summary`
**VO**: `GroupBatchSummaryVO`
#### 使用场景
看板顶部各页签的计数。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| productId | Query | Long | 否 | - | 不变 |
| month | Query | String | 否 | `yyyy-MM` | 不变 |
| keyword | Query | String | 否 | - | 不变 |
| scope | Query | String | 否 | `ONGOING` / `FINISHED` / `ALL` | 不变 |
本接口不接受 `opsStage`(它的作用正是给出各桶数量)。
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| total | Integer | **口径改变**:= 七桶之和(旧别名键不计入) |
| buckets | Map&lt;String, Integer&gt; | **键集合改变**:固定 13 键,无命中为 0;前 7 键为七桶,后 6 键为旧八桶别名 |
| subOrderCount | Integer | 命中团期的活跃子订单合计(不变) |
| effectiveScope | String | 实际生效的班期范围(不变) |
| filteredOutCount | Integer | 被范围过滤掉的团期数(不变) |
`buckets` 13 键口径:
| 键 | 计数口径 | 计入 `total` |
|---|---|---|
| `RECRUIT` / `CONFIGURE` / `CONFIRM` / `TRIP` / `REVIEW` / `SETTLE` / `DISBANDED` | 七桶;`TRIP` = 待出发 + 出行中 + 已返团 | ✅ |
| `FORMED` | 旧别名 = `RESOURCE_PREPARING` + `MATERIAL_PREPARING` | ❌ |
| `PENDING_TRIP` | 旧别名 = `PENDING_DEPARTURE` | ❌ |
| `TRAVELLING` | 旧别名 = `TRAVELLING` | ❌ |
| `TRIP_FINISHED` | 旧别名 = `TRIP_FINISHED` | ❌ |
| `AUDITING` | 旧别名 = `REVIEWING` | ❌ |
| `CHECKED` | 旧别名 = `SETTLED` | ❌ |
#### 请求示例
```http
GET /v3/admin/order/group-batch/summary?scope=ALL HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
```
无请求体。
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"total": 12,
"buckets": {
"RECRUIT": 3,
"CONFIGURE": 2,
"CONFIRM": 1,
"TRIP": 4,
"REVIEW": 1,
"SETTLE": 0,
"DISBANDED": 1,
"FORMED": 3,
"PENDING_TRIP": 2,
"TRAVELLING": 1,
"TRIP_FINISHED": 1,
"AUDITING": 1,
"CHECKED": 0
},
"subOrderCount": 57,
"effectiveScope": "ALL",
"filteredOutCount": 0
},
"success": true
}
```
#### 空数据 / 降级响应
无命中时 13 键全部为 0:
```json
{ "code": 200, "data": { "total": 0, "buckets": { "RECRUIT": 0, "CONFIGURE": 0, "CONFIRM": 0, "TRIP": 0, "REVIEW": 0, "SETTLE": 0, "DISBANDED": 0, "FORMED": 0, "PENDING_TRIP": 0, "TRAVELLING": 0, "TRIP_FINISHED": 0, "AUDITING": 0, "CHECKED": 0 }, "subOrderCount": 0 }, "success": true }
```
#### 错误响应
```json
{
"code": 589507,
"message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)",
"success": false,
"data": null
}
```
#### 业务边界
- 按键名读取,不要依赖 Map 下标;也不要对 `buckets` 整体求和。
- `RECRUIT` / `DISBANDED` 新旧同名同义,不重复出现。
- 状态不在九态内的行被排除,不计入任何桶。
---
### 5. 导出团期列表 CSV `GET /v3/admin/order/group-batch/export`
**VO**: `text/csv` 附件(非 `Result` 信封)
#### 使用场景
看板「导出」按钮,按当前筛选导出 CSV。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| opsStage | Query | String | 否 | 同分页口径 | **本次改取值**:七桶或旧八桶 code |
| productId | Query | Long | 否 | - | 不变 |
| month | Query | String | 否 | `yyyy-MM` | 不变 |
| keyword | Query | String | 否 | - | 不变 |
| scope | Query | String | 否 | `ONGOING` / `FINISHED` / `ALL` | 不变 |
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| 状态(CSV 第 8 列) | String | **内容改变**:由旧八桶中文名改为节点名;出行节点拼子状态,如「出行·待出发」「出行·出行中」「出行·已返团」 |
列名、列序(固定 10 列)与单次 2000 行上限不变。
#### 请求示例
```http
GET /v3/admin/order/group-batch/export?opsStage=CONFIGURE&scope=ALL HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
```
无请求体。
#### 响应示例
响应为 CSV 附件;「状态」列示例:
```json
{
"Content-Type": "text/csv; charset=utf-8",
"header": "团期号,期号,日期,出团日,满团名额,已售,剩余,状态,子订单数,整团应收",
"状态列取值示例": ["招募", "配置", "确认", "出行·待出发", "出行·出行中", "出行·已返团", "核单", "结算", "已流团"]
}
```
#### 空数据 / 降级响应
无命中时只输出表头一行(不变):
```json
{ "header": "团期号,期号,日期,出团日,满团名额,已售,剩余,状态,子订单数,整团应收", "rows": 0 }
```
#### 错误响应
```json
{
"code": 589507,
"message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)",
"success": false,
"data": null
}
```
#### 业务边界
- 按「状态」列中文做过解析的下游需同步:旧值「已成团 / 待出行 / 核团中 / 已验团」不再出现。
- 筛选口径与分页接口共用同一展开逻辑。
---
### 6. 团期状态流水 `GET /v3/admin/order/group-batch/{groupBatchId}/status-logs`
**VO**: `List<GroupBatchStatusLogItemVO>`
#### 使用场景
团期详情「操作记录 / 时间线」。本次只改 5 个事件的中文标签(PR #8303)。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 团期主键 | 不变 |
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| [].eventType | String | 事件类型值(**不变**) |
| [].eventTypeName | String | **取值改变**:见下表;标签在读取时渲染,存量行一并显示新标签 |
| [].content | String | 展示文本;本次之后新写入的核单 / 结算相关流水改用新用语,存量行原样 |
| eventType | 改前 eventTypeName | 改后 eventTypeName |
|---|---|---|
| `BATCH_TRIP_END` | 返团核团 | 发起核单 |
| `BATCH_SETTLE` | 验团结算 | 结算 |
| `BATCH_AUDIT_ALLOCATE` | 核团提交核算 | 核单提交核算 |
| `BATCH_AUDIT_REALLOCATE` | 核团重新核算 | 核单重新核算 |
| `BATCH_AUDIT_PRICE_OVERRIDE` | 核团改价 | 核单改价 |
#### 请求示例
```http
GET /v3/admin/order/group-batch/2097250563497385985/status-logs HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
```
无请求体。
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": [
{
"eventType": "BATCH_SETTLE",
"eventTypeName": "结算",
"fromStatus": "REVIEWING",
"fromStatusName": "核单中",
"toStatus": "SETTLED",
"toStatusName": "已结算",
"content": "结算归档,团期结束",
"operatorType": "ADMIN"
}
],
"success": true
}
```
#### 空数据 / 降级响应
无流水返回空数组;库里是枚举外历史值时 `eventTypeName` 为 null(不变):
```json
{ "code": 200, "data": [], "success": true }
```
#### 错误响应
```json
{
"code": 589500,
"message": "团期不存在",
"success": false,
"data": null
}
```
#### 业务边界
- 按 `eventType` 做分支的前端不受影响;按中文标签匹配的需改。
- `fromStatusName` / `toStatusName`(九态中文名「核单中」「已结算」)不变。
---
## 四、契约约束与正确调用方式
### ✅ 正确 / ❌ 错误用法对照
| 场景 | 用法 |
|------|------|
| ✅ 看板页签筛选(新) | `opsStage=CONFIGURE` / `opsStage=TRIP` |
| ✅ 看板页签筛选(过渡期旧值) | `opsStage=FORMED`,结果 = 配置 + 确认,与改前一致 |
| ✅ 页签计数 | 读 `buckets.CONFIGURE`、`buckets.TRIP` 等七桶键;总数读 `total` |
| ❌ 总数自己求和 | `Object.values(buckets).reduce(...)` → 七桶与别名重复,结果偏大 |
| ❌ 用节点判按钮 | `if (stage === 'CONFIGURE') showConfirmButton()` → 应读 `batchStatus` 与就绪位 |
| ❌ 按旧 `opsStage` 值分支 | `case 'FORMED':` → 响应已不再输出旧值 |
### 核单 / 结算用语:错误码文案变化(PR #8303,码值不变)
| code | 改前 message | 改后 message |
|---|---|---|
| 589555 | 该团期已验团归档,不可重复验团 | 该团期已结算归档,不可重复结算 |
| 589565 | 团期已验团归档,如需重新核单请先做验团反确认 | 团期已结算归档,如需重新核单请先做结算反确认 |
| 589567 | 该团期尚未进入核团:{0} | 该团期尚未进入整团核单:{0} |
| 589568 | 核团当前状态不允许该操作:{0} | 整团核单当前状态不允许该操作:{0} |
| 589572 | 所选订单不属于本团期的核团范围 | 所选订单不属于本团期的整团核单范围 |
| 589573 | 核团数据已被他人修改(当前版本 {0},提交版本 {1}),请刷新后重试 | 整团核单数据已被他人修改(当前版本 {0},提交版本 {1}),请刷新后重试 |
核单状态枚举 `CHECKED` 的中文名由「已验团」改为「已结算」(存储值不变);前端按 code 判断即可,按中文匹配的需改。
---
## 六、边界行为
- 未登录 → 401(网关拦截)。
- `batchStatus` 为 null 或不在九态内的脏数据行:四个新字段与 `opsStage` / `opsStageName` 同为 null,不抛错。
- `opsStage` 空白或非法值:忽略该筛选并记 warn,不报错。
- 未建团行恒为 `RECRUIT` / 招募。
- 旧八桶别名(筛选入参 + 统计条 6 键)在前端切到七桶之前保留,删除时另发契约变更。
---
## 六.5、枚举 / 数据字典
### stage / opsStage(GroupBatchStageBuckets.Bucket)
**所属字段**: `stage`、`opsStage`(响应)、`opsStage`(筛选入参)、`buckets` 前 7 键 | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `RECRUIT` | 招募 | ← `RECRUITING` |
| `CONFIGURE` | 配置 | ← `RESOURCE_PREPARING` |
| `CONFIRM` | 确认 | ← `MATERIAL_PREPARING` |
| `TRIP` | 出行 | ← `PENDING_DEPARTURE` / `TRAVELLING` / `TRIP_FINISHED` |
| `REVIEW` | 核单 | ← `REVIEWING` |
| `SETTLE` | 结算 | ← `SETTLED` |
| `DISBANDED` | 已流团 | ← `CANCELLED` |
### tripSubStatus(GroupBatchStageBuckets.TripSubStatus)
**所属字段**: `tripSubStatus` | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `PENDING_DEPARTURE` | 待出发 | 仍可发起流团 |
| `TRAVELLING` | 出行中 | 出发日起 |
| `TRIP_FINISHED` | 已返团 | 返团日次日起;首次录共享成本即进入核单 |
### 旧八桶别名(GroupBatchStageBuckets.LegacyBucket,过渡期)
**所属字段**: `opsStage`(筛选入参)、`buckets` 后 6 键 | **类型**: `String`
| 值 | 中文 | 说明 |
|----|------|------|
| `FORMED` | 已成团 | = `RESOURCE_PREPARING` + `MATERIAL_PREPARING` |
| `PENDING_TRIP` | 待出行 | = `PENDING_DEPARTURE` |
| `TRAVELLING` | 出行中 | = `TRAVELLING` |
| `TRIP_FINISHED` | 出行完毕 | = `TRIP_FINISHED` |
| `AUDITING` | 核团中 | = `REVIEWING` |
| `CHECKED` | 已验团 | = `SETTLED` |
---
## 六.6、修改前后对比
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| `stage` / `stageName` | 无 | 新增,七个节点 |
| `tripSubStatus` / `tripSubStatusName` | 无 | 新增,仅出行节点有值 |
| `opsStage`(响应) | 八桶:`RECRUIT` / `FORMED` / `PENDING_TRIP` / `TRAVELLING` / `TRIP_FINISHED` / `AUDITING` / `CHECKED` / `DISBANDED` | 七桶,与 `stage` 同值 |
| `opsStageName`(响应) | 招募中 / 已成团 / 待出行 / 出行中 / 出行完毕 / 核团中 / 已验团 / 已流团 | 招募 / 配置 / 确认 / 出行 / 核单 / 结算 / 已流团 |
| `buckets`(统计条) | 8 键,`total` = 8 桶之和 | 13 键(七桶 + 6 别名),`total` = 七桶之和 |
| 导出「状态」列 | 旧八桶中文名 | 节点名,出行节点拼子状态 |
| `eventTypeName`(5 个核单 / 结算事件) | 核团 / 验团用语 | 核单 / 结算用语 |
| 6 个错误码 message | 核团 / 验团用语 | 核单 / 结算用语(见「四」) |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| `opsStage=FORMED` 筛选 | 配置 + 确认 | 不变(别名按原口径) |
| `opsStage=TRIP` 筛选 | 非旧八桶 code,按非法值忽略(不筛) | 筛待出发 + 出行中 + 已返团 |
| `opsStage=CONFIGURE` / `CONFIRM` / `REVIEW` / `SETTLE` | 非法值被忽略 | 按新节点筛选 |
⚠️ 注意 `TRIP` 与旧 `TRIP_FINISHED` 的区别:旧八桶里「出行完毕」的 code 是 `TRIP_FINISHED`,不是 `TRIP`;页签若传的是 `TRIP_FINISHED`,行为不变。
## 六.7、影响评估
- **是否破坏向后兼容**: 部分。筛选入参与统计条旧键完全兼容;响应字段 `opsStage` / `opsStageName` 的取值改变,读这两个字段做分支或展示的代码会受影响。
- **前端是否必须同步上线**: 否。旧页签传旧 code、读旧键仍然工作;要展示六节点需改读 `stage` / `tripSubStatus` 与七桶键。
- **前端 workaround 清理点**: 若前端曾自己把九态折叠成阶段,可改为直接读 `stage`;切到七桶后告知后端删除旧八桶别名。
---
## 七、不影响范围
- **仅影响**: 上述 6 个查询 / 导出接口的节点相关字段与文案,及 6 个核单 / 结算错误码的 message。
- **零影响**:
- 持久九态 `batchStatus` 及其中文名 `batchStatusName`(「核单中」「已结算」原本就是这两个字)
- 所有写接口的状态流转与业务闸(节点只供展示)
- 错误码 code 值、枚举存储值、时间线 `eventType` 值
- 权限码、路径、信封结构、网关配置
- 小程序端
---
## 八、测试环境已验证
部署 `dev-v3` @ `b212cb708`,2026-09-23 17:44–17:48,`api.test.1814.love:9443`。
| 验证项 | 结果 |
|---|---|
| 分页(`pageSize=100`)与看板行(187 行)字段 | 均带 `stage` / `stageName` / `tripSubStatus` / `tripSubStatusName` 四个新字段,且 `opsStage == stage` ✓ |
| 九态 → 节点映射 | 全映射实测 ✓ |
| 统计条 `summary`(`scope=ALL`) | 13 键逐键与 DB 一致;`total = 267` = 七桶之和(13 键合计 492) ✓ |
| `opsStage` 筛选 15 个取值 | `total` 全部等于 DB 计数;`PENDING_TRIP` 只筛待出发 3 条,`FORMED = 215`,未知值忽略 ✓ |
| 低权限角色 | `ROOM_MANAGER` / `VEHICLE_MANAGER` → 589507 ✓ |
核单 / 结算用语(PR #8303:6 个错误码文案、时间线标签):
补充(PR #8303,dev-v3 @ ade8ac292,2026-09-24 09:52–09:58):api-docs 中「已验团 / 团期核团 / 验团归档 / 验团反确认 / 核团面板」旧文案 0 次;589555「该团期已结算归档,不可重复结算」、589565「团期已结算归档,如需重新核单请先做结算反确认」实测触发;`status-logs` 对 09-18 旧记录返回新标签「发起核单 / 核单改价 / 核单提交核算 / 核单重新核算 / 结算」(`content` 为写入时原文,不随之变化)。工单 #8271 已验收关单。
---
## 九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|----|-------|------|------------|
| #6917 / #6926 | #6904 | 统计条与导出、`opsStage` 筛选首版 | ⚠️ 桶取值被本次替换,旧 code 以别名保留 |
| — | #7190 | 加「出行完毕」`TRIP_FINISHED` 成八桶 | ⚠️ 同上 |
| — | #7535 | 详情 / 分页透出 `opsStage` | ⚠️ 字段保留,取值改七桶 |
| **本 PR #8276 / #8279 / #8303** | **#8271** | 六节点派生 + 七桶 + 核单 / 结算用语 | ✅ 最新 |
---
## 十、相关文档
- 关联 Issue: [wx/HL#8271](https://git.1814.love/wx/HL/issues/8271)
- 关联 PR: [wx/HL#8276](https://git.1814.love/wx/HL/pulls/8276)、[wx/HL#8279](https://git.1814.love/wx/HL/pulls/8279)、[wx/HL#8303](https://git.1814.love/wx/HL/pulls/8303)
- 同批六节点条目:团期人工确认(#8268)、确认后锁定配置(#8269)
## 关联 / 联系人
### 链接
- **Issue**: [#8271](https://git.1814.love/wx/HL/issues/8271)
- **PR**: [#8276](https://git.1814.love/wx/HL/pulls/8276)、[#8279](https://git.1814.love/wx/HL/pulls/8279)、[#8303](https://git.1814.love/wx/HL/pulls/8303)
- **Merge commit**: [678d47b58](https://git.1814.love/wx/HL/commit/678d47b584ac0237784c0bf728e86dfe6cbacb1d)、[bfbc0d337](https://git.1814.love/wx/HL/commit/bfbc0d337a92f3e2dec8b84aa54cbf1042f08698)、[ade8ac292](https://git.1814.love/wx/HL/commit/ade8ac292388354649c3e367e1171e6e55836280)
### 联系人
- **后端负责人**: @jw