changelog(7149): 按 CHANGELOG_TEMPLATE 重写——三接口自含入参/出参/示例/错误/边界,补对比与影响评估(门禁)
changelog-filename-gate / validate (push) Successful in 2s

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01XYL5S9SsBtkg7aGyFAbrrQ
这个提交包含在:
API Changelog Bot
2026-09-06 16:11:30 +08:00
共同撰写人 Claude Fable 5.1
父节点 c1d7fed7b6
当前提交 7b9b60b9ac
@@ -12,270 +12,407 @@ frontend_owner: "mmg"
frontend_ref: ""
target_release: ""
verified_at: "2026-09-06"
status_note: "后端已合 dev-v3 并部署测试服、网关实测通过;前端需改团期子订单提需求弹窗:房型大类必填+两个新错误码展示+招募中即显示提需求入口。"
status_note: "后端已合 dev-v3 并部署测试服、网关实测通过;前端需改团期子订单提需求弹窗:房型大类必填、两个新错误码直接展示 message、招募中即显示提需求入口、物料准备中入口置灰。"
updated_at: "2026-09-06"
base: "dev-v3"
---
# 团期模块:子订单支付后即可提房车、物资准备起冻结、房型间数必填
# 团期模块:子订单支付后即可提房车需求、物资准备起冻结、团单房型与间数必填
> **服务**: `hl-order-service-v3`
> **Issue**: #7149
> **PR**: #7177
> **PR**: #7177(squash 合入 dev-v3 `a52365278`)
> **日期**: 2026-09-06
> **影响范围**: 团期子订单房型与用车需求提交/修改、团期冻结逻辑、业务允许入口
> **影响范围**: 管理后台团期子订单详情「住宿安排 / 用车安排」提需求弹窗与「订单调整」提交
## ⚠️ 关键变化
团期子订单定制师的需求操作权限、冻结时机与校验规则同时调整:
三条行为改造,请求 / 响应结构一律不变:
1. **支付后即可提需求**:团期子订单(`product_batch_id` 非空)在定制师端已支付(`orderStatus=CUSTOMIZING`)后,团期处于招募中(`RECRUITING`)或资源准备中(`RESOURCE_PREPARING`)即可提交/修改房车需求,无需等待团期成团。前端需在招募中即显示提需求入口。
1. **支付后即可提需求**:团期子订单(详情里 `groupOrder=true` / `productBatchId` 非空)在客户已支付(`orderStatus=CUSTOMIZING`)后即可提 / 改用房、用车需求,团期处于「招募中 `RECRUITING`」或「资源准备中 `RESOURCE_PREPARING`」都放行,不再等团期成团。改前团期非 `RESOURCE_PREPARING` 一律 589501「团期状态不允许当前操作」。
2. **物资准备起冻结**:团期进入「物料准备中 `MATERIAL_PREPARING`」及之后(`PENDING_DEPARTURE / TRAVELLING / REVIEWING / SETTLED`)提 / 改需求被拒,新错误码 **589536「团期已进入物资准备,需求已冻结,请联系团期管理员」**(文案里的「物资准备」就是状态芯片的「物料准备中」,同义)。唯一例外:该户该资源最新一版需求被团期管理员打回(`REJECTED_TO_CONSULTANT`)时可以重提一次,重提后再改仍 589536。团期 `CANCELLED` / 查不到团期仍 589501。
3. **团单房型大类与房间数必填**:团期子订单每个非自订晚(`customerSelfBooked` 非 `true`)的每段,按段首候选 `candidates[0].rooms[]` 逐行要求 `roomCategory` 非空且 `roomCount ≥ 1`(旧结构无 `rooms[]` 时按段级 `roomCategory` + `roomCount` 判),缺失返回新错误码 **582099「团期订单第{N}晚第{M}段需填写房型大类与房间数」**(M 从 1 起),拒绝时零副作用。核心订单不受影响,房型仍选填。**团单不再接受「加晚次空白占位」段**——每晚要么填齐房型行,要么标 `customerSelfBooked=true`。
2. **物资准备起冻结**:团期进入物料准备中(`MATERIAL_PREPARING`)及之后后,定制师不得再提交或修改房车需求。新增错误码 **589536**;唯一例外:最新版需求被打回可重提一次。前端需在物料准备中后置灰入口或弹提示。
3. **房型与间数必填**:团期子订单的每个非自订晚的每段,房型大类(`roomCategory`)与房间数(`roomCount`)必填且≥1。新增错误码 **582099**;核心订单无此约束。前端需改房型大类为必填。
前端要做:① 团期子订单提需求弹窗把「房型大类」改必填并提示;② 589536 / 582099 直接展示后端 `message`;③ 团期子订单在招募中即显示「提交房型需求 / 用车需求」入口;进入物料准备中后入口置灰或提示已冻结(被打回的户除外);④ 团单去掉「先提交空白占位」交互。
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 提交/修改房型需求 | PUT | `/v3/admin/order/{id}/hotel-requirement` | 行为与守卫修改 | 团期子订单新增权限闸门+房型间数必填校验 |
| 2 | 调整订单统一提交 | POST | `/v3/admin/order/{id}/adjustment/submit` | 行为与守卫修改 | hotelRequirement与vehicleRequirement走同一闸门 |
| 3 | 提交/修改用车需求 | PUT | `/v3/admin/order/{id}/vehicle-requirement` | 行为与守卫修改 | 团期子订单新增权限闸门 |
| 4 | 团期汇总查询 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement-summary` | 复用不改 | 响应roomCategory不再出现未知 |
## 变更接口
(同上)
| 1 | 提交 / 修改用房需求(兼容期 @Deprecated) | PUT | `/v3/admin/order/{id}/hotel-requirement` | 行为修改 | 团期闸门放宽 + 冻结 + 团单房型间数必填 |
| 2 | 订单调整统一提交 | POST | `/v3/admin/order/{id}/adjustment/submit` | 行为修改 | `updates.hotelRequirement` / `updates.vehicleRequirement` 走同一闸门与必填校验 |
| 3 | 提交 / 修改用车需求 | PUT | `/v3/admin/order/{id}/vehicle-requirement` | 行为修改 | 团期闸门放宽 + 冻结,不加必填 |
## 三、接口详情
### 1. 提交/修改房型需求 `PUT /v3/admin/order/{id}/hotel-requirement`
### 1. 提交 / 修改用房需求 `PUT /v3/admin/order/{id}/hotel-requirement`
**VO**: HotelRequirementReqVO转HotelRequirementRespVO
**VO**: `HotelRequirementReqVO → HotelRequirementRespVO`(`hl-order-service-v3/src/main/java/com/hulalv/order/requirement/controller/admin/vo/`)
#### 使用场景
定制师提交/修改/调整房型需求。后端按是否存在active行与当前状态自动三分支:无active→INIT_SUBMIT、PENDING→PENDING_EDIT、DONE→DONE_ADJUST。本端点为@Deprecated兼容期。
定制师在子订单详情「住宿安排」提交或修改用房需求。后端按当前生效版本自动分支:无生效版本 → `INIT_SUBMIT`(新版本);生效版本为 `PENDING / PENDING_REVIEW` → `PENDING_EDIT`(同版本覆盖);`DONE` 等 → `DONE_ADJUST`(版本 +1)。团期子订单新版本状态恒为 `PENDING_REVIEW`(等团期管理员确认),不进房务抢单池。本端点为兼容期入口(#4515 标 @Deprecated),新客户端走接口 2。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| id | Path | Long | 是 | 正整数 | 订单ID |
| days | Body | Array | 是 | 长度等于tripNights | 每晚用房安排 |
| days[].dayNumber | Body | Integer | 是 | 1到tripNights | 第几晚 |
| days[].customerSelfBooked | Body | Boolean | 否 | true/false/null | 客户自订标记 |
| days[].segments | Body | Array | 条件必填 | 非自订晚≥1段 | 分住段列表 |
| days[].segments[].roomCategory | Body | String | 条件必填 | 字典room_category;团期必填 | 房型大类 |
| days[].segments[].roomCount | Body | Integer | 条件必填 | 大于0;团期必填 | 房间数 |
| days[].segments[].candidates | Body | Array | 是 | ≥1家 | 候选酒店列表 |
| specialTags | Body | Array | 否 | 字典值 | 特殊诉求标签 |
| remark | Body | String | 否 | 最长500字 | 备注 |
| `id` | Path | String(Long) | 是 | 订单 ID | 团期子订单 `productBatchId` 非空 |
| `days` | Body | Array | 是 | 长度 = `tripNights`,`dayNumber` 1..tripNights 不重复 | 逐晚安排 |
| `days[].dayNumber` | Body | Integer | 是 | 1..tripNights | 第几晚 |
| `days[].customerSelfBooked` | Body | Boolean | 否 | `true` = 客人自订 | 自订晚不校验房型,`segments` 可空 |
| `days[].segments` | Body | Array | 非自订晚 ≥1 | 缺段 582098 | 当晚分住段 |
| `days[].segments[].candidates` | Body | Array | 是(≥1) | 候选酒店,房控择一 | `hotelId / hotelName` 可空(无酒店候选) |
| `days[].segments[].candidates[].rooms` | Body | Array | 新结构 | 房型行 | 团单按 `candidates[0].rooms` 逐行校验 |
| `…rooms[].roomCategory` | Body | String | **团单是** | 字典 `room_category`(STANDARD/SINGLE/TWIN/QUEEN/KING/SUITE/FAMILY/YURT/SPECIAL) | 核心订单选填不变 |
| `…rooms[].roomCount` | Body | Integer | **团单是,≥1** | 有行即 >0(582016) | 房数 |
| `…rooms[].roomTypeId / roomTypeName / protocolPrice / remark` | Body | Long / String / Decimal / String | 否 | — | 真实房型与快照,可空 |
| `days[].segments[].roomCategory / roomCount` | Body | String / Integer | 兼容 | 旧结构无 `rooms[]` 时团单必填 | 段级兼容字段 |
| `days[].segments[].budget / remark` | Body | Decimal / String(≤200) | 否 | 预算由后端按协议价覆盖 | — |
| `specialTags` | Body | String[] | 否 | 字典 `house_special_demand` | 特殊诉求 |
| `remark` | Body | String(≤500) | 否 | — | 备注 |
#### 出参
#### 出参 `Result<HotelRequirementRespVO>`
| 字段 | 类型 | 说明 |
|---|---|---|
| data.requirementId | String | 需求行ID |
| data.version | Integer | 版本号 |
| data.status | String | 需求状态 |
| data.submittedAt | String | 提交时间 |
| `data.requirementId` | String | 需求行 ID |
| `data.version` | Integer | 版本号(INSERT-only 单调递增) |
| `data.isActive` | Boolean | 是否当前生效版本 |
| `data.status` | String | 团单恒 `PENDING_REVIEW`;核心为 `PENDING` |
| `data.submittedAt` | String | 首提时间 |
| `data.claimerId / claimerName / claimedAt` | String / String / String | 团单恒 null(不进抢单池) |
| `data.branchTaken` | String | `INIT_SUBMIT / PENDING_EDIT / DONE_ADJUST` |
| `data.previousVersion` | Integer | `DONE_ADJUST` 时上一版本号,否则 null |
| `data.assignmentDeletedCount` | Integer | `DONE_ADJUST` 时软删配房行数,否则 null |
#### 请求示例
提交房型需求,第1晚TWIN房2间,第2晚自订。应答会返回requirementId和version。
```json
{
"days": [
{ "dayNumber": 1, "segments": [ { "remark": "第1晚", "candidates": [ { "hotelName": "网关实测酒店", "rooms": [ { "roomTypeName": "测试房型", "roomCategory": "KING", "roomCount": 1 } ] } ] } ] },
{ "dayNumber": 2, "segments": [ { "remark": "第2晚", "candidates": [ { "hotelName": "网关实测酒店", "rooms": [ { "roomTypeName": "测试房型", "roomCategory": "TWIN", "roomCount": 2 } ] } ] } ] }
],
"remark": "#7149 网关实测"
}
```
#### 响应示例
成功返回code=200,data包含requirementId、version、status=PENDING_REVIEW。
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"requirementId": "2096504560393551873",
"version": 1,
"isActive": true,
"status": "PENDING_REVIEW",
"submittedAt": "2026-09-06 15:44:21",
"claimerId": null,
"claimerName": null,
"claimedAt": null,
"branchTaken": "INIT_SUBMIT",
"previousVersion": null,
"assignmentDeletedCount": null
}
}
```
#### 空数据 / 降级响应
本接口无空列表语义;`days` 为空数组或长度与 `tripNights` 不符返回 582011「天数长度与订单住宿晚数不一致」。第 2 晚 `customerSelfBooked=true` 且无 `segments` 时正常返回 200(自订晚不进房务分母,团单房型校验跳过)。
#### 错误响应
冻结场景返回code=589536,消息「团期已进入物资准备,需求已冻结,请联系团期管理员」。房型缺失返回code=582099,消息「团期订单第X晚第Y段需填写房型大类与房间数」。
```json
{ "code": 582099, "message": "团期订单第2晚第1段需填写房型大类与房间数", "success": false, "data": null }
```
```json
{ "code": 589536, "message": "团期已进入物资准备,需求已冻结,请联系团期管理员", "success": false, "data": null }
```
其它沿用:589501「团期状态不允许当前操作」(团期 `CANCELLED` / 查不到)、582098「第{N}晚缺少用房需求,请填写房间需求或标记为客户自订」、582016「房间数必须大于 0」、582019「候选方案必须至少含 1 个房型行」、582017「订单状态不允许提交需求」(未支付)。HTTP 始终 200,错误在 `code` / `message`。
#### 业务边界
- 权限守卫:允许RECRUITING/RESOURCE_PREPARING,拒绝MATERIAL_PREPARING及之后(589536)、CANCELLED(589501)。
- 房型间数必填:仅团期子订单非自订晚,缺失返582099,DB零副作用。
- 自订晚customerSelfBooked=true允许segments为空,不受约束。
- 打回例外:被打回后可重提一次,重提后再改仍返589536。
- 团期闸门(房车共用):`RECRUITING / RESOURCE_PREPARING` 放行;`MATERIAL_PREPARING / PENDING_DEPARTURE / TRAVELLING / REVIEWING / SETTLED` 及未知状态 589536;`CANCELLED` / 查不到团期 589501;核心订单不查团期。
- 冻结期例外:该户用房需求最新一版为 `REJECTED_TO_CONSULTANT`(团期管理员打回)时放行重提,新版本回到 `PENDING_REVIEW`;之后再改仍 589536,要再改只能再次被打回。
- 团单房型间数必填按「段首候选 rooms 行」判,与全团需求汇总 `requirement-summary` 同口径;旧结构(无 `rooms[]`)按段级 `roomCategory + roomCount` 合成一行判。
- 校验顺序:结构校验(582098 / 582016 / 582019)→ 订单状态(582017)→ 团期闸门(589536 / 589501)→ 团单房型间数(582099);任一拒绝均在写库之前,无新版本、`room_control_status` 不变。
- 成功后 `order_main.room_control_status=PENDING_REVIEW`(既有行为),团期成团时定制师不再产生「房型需求 · 待提交」待办。
### 2. 调整订单统一提交 `POST /v3/admin/order/{id}/adjustment/submit`
### 2. 订单调整统一提交 `POST /v3/admin/order/{id}/adjustment/submit`
**VO**: AdjustmentSubmitReqVO转AdjustmentSubmitRespVO
**VO**: `AdjustmentSubmitReqVO → AdjustmentSubmitRespVO`(`hl-order-service-v3/src/main/java/com/hulalv/order/adjustment/controller/admin/vo/`)
#### 使用场景
前端内存收集改动后一次性提交,后端单事务原子应用。房、车需求通过updates.hotelRequirement和updates.vehicleRequirement传入,走同一权限守卫。
管理端子订单详情「订单调整」一次性提交各子域改动;本次只涉及 `updates.hotelRequirement`(用房需求完整新版本)与 `updates.vehicleRequirement`(用车需求完整新版本),两者与接口 1 / 接口 3 走同一团期闸门与团单房型间数校验。其它子域(人数 / 日期 / 出行人等)本次不变、省略。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| id | Path | Long | 是 | 正整数 | 订单ID |
| updates.hotelRequirement | Body | Object | 否 | 完整新版本 | 房需求(days/specialTags/remark) |
| updates.vehicleRequirement | Body | Object | 否 | 完整新版本 | 车需求(fleet/specialTags/remark) |
| `id` | Path | String(Long) | 是 | 订单 ID | — |
| `updates` | Body | Object | 是 | 至少一个子域非空 | 各子域修改内容 |
| `updates.hotelRequirement` | Body | `HotelRequirementBodyVO` | 否 | 结构同接口 1 的 `days / specialTags / remark` | 用房需求完整新版本,团单房型间数必填规则同接口 1 |
| `updates.vehicleRequirement` | Body | `VehicleRequirementBodyVO` | 否 | 结构同接口 3 的 `fleet / specialTags / pickupRequired / dropoffRequired / remark` | 用车需求完整新版本 |
#### 出参
#### 出参 `Result<AdjustmentSubmitRespVO>`
| 字段 | 类型 | 说明 |
|---|---|---|
| data.success | Boolean | true表示提交成功 |
| `data.success` | Boolean | 提交成功恒 `true` |
#### 请求示例
包含updates.hotelRequirement和updates.vehicleRequirement两个可选字段,其结构同对应的两个直接提交接口。
```json
{
"updates": {
"hotelRequirement": {
"days": [
{ "dayNumber": 1, "segments": [ { "candidates": [ { "hotelName": "网关实测酒店", "rooms": [ { "roomCategory": "KING", "roomCount": 1 } ] } ] } ] },
{ "dayNumber": 2, "segments": [ { "candidates": [ { "hotelName": "网关实测酒店", "rooms": [ { "roomCategory": "TWIN", "roomCount": 1 } ] } ] } ] }
],
"remark": "#7149 网关实测 调整入口"
}
}
}
```
#### 响应示例
成功返回code=200,data.success=true。
```json
{ "code": 200, "message": "成功", "success": true, "data": { "success": true } }
```
#### 空数据 / 降级响应
`updates` 各子域全空时沿用既有「无有效变更」拒绝;`hotelRequirement` 提交成功但版本号不变(`PENDING_EDIT` 覆盖同版本)属正常,前端以订单详情重新拉取为准。
#### 错误响应
冻结返回code=589536;房型缺失返回code=582099。
```json
{ "code": 589536, "message": "团期已进入物资准备,需求已冻结,请联系团期管理员", "success": false, "data": null }
```
```json
{ "code": 582099, "message": "团期订单第2晚第1段需填写房型大类与房间数", "success": false, "data": null }
```
#### 业务边界
- 房、车改动走同一闸门:RECRUITING/RESOURCE_PREPARING允许,MATERIAL_PREPARING及之后冻结。
- 房型间数必填校验同PUT /hotel-requirement。
- 单事务原子应用。
- 调整入口绕过「订单须为定制中」的普通提交闸,但**不绕**团期闸门与团单房型间数校验,行为与接口 1 / 3 一致。
- 团期冻结期内该入口同样 589536,被打回户例外同接口 1。
- 拒绝发生在事务内任何写操作之前,其它子域改动一并回滚。
### 3. 提交/修改用车需求 `PUT /v3/admin/order/{id}/vehicle-requirement`
### 3. 提交 / 修改用车需求 `PUT /v3/admin/order/{id}/vehicle-requirement`
**VO**: VehicleRequirementReqVO转VehicleRequirementRespVO
**VO**: `VehicleRequirementReqVO → VehicleRequirementRespVO`(`hl-order-service-v3/src/main/java/com/hulalv/order/requirement/controller/admin/vo/`)
#### 使用场景
定制师提交/修改/调整用车需求。后端按是否存在active行与当前状态自动三分支。
定制师在子订单详情「用车安排」提交或修改用车需求。本次只改团期闸门(放宽 + 冻结 + 打回例外),**不加任何必填**;车型 / 座位联动校验、容量校验、派单展开等既有逻辑不变。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| id | Path | Long | 是 | 正整数 | 订单ID |
| fleet | Body | Array | 是 | 至少1项 | 车辆类型与需求行 |
| fleet[].vehicleType | Body | String | 是 | SUV/MPV/大巴/轿车 | 车型大类 |
| fleet[].seats | Body | Integer | 是 | 大于0 | 每辆座位数 |
| fleet[].count | Body | Integer | 是 | 大于0 | 该类车数量 |
| specialTags | Body | Array | 否 | 字典vehicle_special_demand | 特殊诉求标签 |
| remark | Body | String | 否 | 最长500字 | 备注 |
| `id` | Path | String(Long) | 是 | 订单 ID | — |
| `fleet` | Body | Array | 是(≥1) | 缺 582021 | 用车明细 |
| `fleet[].vehicleType` | Body | String | 是 | 车务车型大类 key(582022) | 车型大类 |
| `fleet[].seats` | Body | Integer | 是 | 该车型可选座位数(582024) | 座位数 |
| `fleet[].count` | Body | Integer | 是 | >0(582023) | 车辆数 |
| `specialTags` | Body | String[] | 否 | 字典 `vehicle_special_demand`(582025) | 特殊诉求 |
| `pickupRequired / dropoffRequired` | Body | Boolean | 否 | — | 接 / 送机 |
| `remark` | Body | String | 否 | — | 备注 |
#### 出参
#### 出参 `Result<VehicleRequirementRespVO>`
| 字段 | 类型 | 说明 |
|---|---|---|
| data.requirementId | String | 需求ID |
| data.version | Integer | 版本号 |
| data.status | String | 需求状态 |
| data.submittedAt | String | 提交时间 |
| `data.requirementId` | String | 需求行 ID |
| `data.version` | Integer | 版本号 |
| `data.isActive` | Boolean | 是否当前生效版本 |
| `data.status` | String | 团单恒 `PENDING_REVIEW` |
| `data.submittedAt` | String | 首提时间 |
| `data.branchTaken` | String | `INIT_SUBMIT / PENDING_EDIT / DONE_ADJUST` |
#### 请求示例
提交MPV车2辆,座位数7,包含wifi特殊诉求。
```json
{ "fleet": [ { "vehicleType": "SUV", "seats": 7, "count": 1 } ] }
```
#### 响应示例
成功返回code=200,data包含requirementId、version、status=PENDING_REVIEW。
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "requirementId": "2096504700000000001", "version": 1, "isActive": true, "status": "PENDING_REVIEW", "submittedAt": "2026-09-06 15:46:02", "branchTaken": "INIT_SUBMIT" }
}
```
#### 空数据 / 降级响应
`fleet` 为空返回 582021「用车需求数组不能为空」;车队车型库不可用返回 582091「车队车型库不可用,无法校验座位数选项」(既有降级,本次不变)。
#### 错误响应
冻结返回code=589536;团期不存在返回code=589501。
```json
{ "code": 589536, "message": "团期已进入物资准备,需求已冻结,请联系团期管理员", "success": false, "data": null }
```
其它沿用:589501(团期 `CANCELLED` / 查不到)、582022 / 582024 / 582023 / 582025(车型 / 座位 / 数量 / 诉求校验)。
#### 业务边界
- 权限守卫同房需求:允许RECRUITING/RESOURCE_PREPARING,拒绝MATERIAL_PREPARING(589536)/CANCELLED(589501)。
- 房、车需求用同一闸门判。
- 团期闸门与接口 1 完全一致(同一方法),放行 / 冻结 / 打回例外按**用车需求**自身的最新一版判。
- 车侧不加房型类必填;`fleet` 结构与既有校验不变。
- 成功后 `order_main.vehicle_control_status=PENDING_REVIEW`(既有行为)。
## 四、契约约束与正确调用方式
## 四、契约约束与正确调用方式(接口类必写)
### 正确 payload 对照
### ✅ 正确 / ❌ 错误 payload 对照
团期子订单第1晚填房型(TWIN 2间),第2晚自订:应答返回200和PENDING_REVIEW状态。
| 场景 | payload 要点 | 结果 |
|---|---|---|
| ✅ 团单每晚房型行齐全 | `candidates[0].rooms[]` 每行 `roomCategory` + `roomCount ≥ 1` | 200,`status=PENDING_REVIEW` |
| ✅ 团单某晚客人自订 | `{ "dayNumber": 2, "customerSelfBooked": true }`,无 `segments` | 200,该晚跳过校验 |
| ✅ 旧结构团单 | 无 `rooms[]`,段级 `roomCategory` + `roomCount` 齐全 | 200 |
| ❌ 团单房型行缺 `roomCategory` | `rooms: [ { "roomCount": 1 } ]` | 582099 |
| ❌ 团单旧结构缺段级 `roomCategory` | 段级只有 `roomCount`,候选带 `roomTypeId` | 582099 |
| ❌ 团单「加晚次空白占位」段 | 候选与段全空 | 582099(核心订单该形态仍放行) |
| ❌ 团期物料准备中提交 / 修改 | 任何合法 payload | 589536(该户最新需求被打回除外) |
| ❌ 子订单未支付 | 任何 payload | 582017「订单状态不允许提交需求」 |
### 错误 payload 对照
### 切换状态时的必要动作
第2晚缺房型大类:返回582099;物料准备中提交:返回589536;空白占位段(仅团单):返回582099。
- 团期从招募中推进到物料准备中后,前端应把提需求入口置灰并以 589536 文案提示;管理员打回某户后该户入口恢复一次。
- 团单弹窗保存前在前端做房型大类必填校验,减少 582099 往返;后端仍以 582099 兜底。
## 五、数据库行为
## 五、数据库行为(涉及写操作时必写)
- 无表结构变更。
- 提交成功时order_hotel_requirement新版本status=PENDING_REVIEW、version++。
- 冻结拒绝时零副作用。
- 无表结构变更、无 Flyway。
- 提交成功:`order_hotel_requirement` / `order_vehicle_requirement` 写入新版本(`PENDING_EDIT` 为同版本换行,其它版本 +1),`status=PENDING_REVIEW`;`order_main.room_control_status` / `vehicle_control_status` 回写 `PENDING_REVIEW`(既有行为)。
- 被拒(589536 / 582099 / 589501 / 结构校验):无任何写入。
## 六、边界行为
- **自订晚处理**:customerSelfBooked=true允许segments为空,不受房型必填约束;不计入配房分母。
- **打回例外与重提限制**:被打回后可重提一次,重提后再改仍返589536;被打回配房行释放库存。
- **旧客户端兼容**:rooms数组为空时回退段级roomCategory+roomCount;房型必填校验对新旧结构同样适用。
- **团期状态枚举**:RECRUITING/RESOURCE_PREPARING允许,MATERIAL_PREPARING及之后冻结,CANCELLED拒绝。
- **团单不再接受「加晚次空白占位」段**:核心订单允许先提交全空的候选 / 段占位,团期子订单的非自订晚一律按缺房型拒绝(582099「第 N 晚第 M 段」);团单每晚要么填齐 `rooms[]`,要么标 `customerSelfBooked=true`。
- **文案「物资准备」= 团期状态芯片「物料准备中」(`MATERIAL_PREPARING`)**:589536 文案沿用代码里的「物资准备」叫法,与状态枚举文案同义,前端直接展示 `message` 即可。
- **冻结期打回只给一次重提机会**:重提后最新版变 `PENDING_REVIEW`,再改回到 589536;这是有意设计(改需求须经管理员再次打回)。
- **自订晚**:`customerSelfBooked=true` 的晚不做房型校验;前端标记自订后应清空该晚 `segments`,避免残留段进入全团汇总。
- **未知团期状态**按冻结处理(fail closed),不会误放行。
## 六.5、枚举 / 数据字典
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
| 字段 | 值 | 中文 |
### roomCategory(字典 `room_category`)
| 值 | 含义 |
|---|---|
| STANDARD | 标准间 |
| SINGLE | 单人间 |
| TWIN | 双床房 |
| QUEEN | 大床房 |
| KING | 特大床房 |
| SUITE | 套房 |
| FAMILY | 家庭房 |
| YURT | 帐篷 / 毡房 |
| SPECIAL | 特色房 |
### 团期状态(`com.hulalv.order.groupbatch.enums.GroupBatchStatus`)
| 值 | 芯片文案 | 提 / 改需求 |
|---|---|---|
| roomCategory | STANDARD/SINGLE/TWIN/QUEEN/KING/SUITE/FAMILY/YURT/SPECIAL | 房型大类 |
| GroupBatchStatus | RECRUITING | 招募中 |
| GroupBatchStatus | RESOURCE_PREPARING | 资源准备中 |
| GroupBatchStatus | MATERIAL_PREPARING | 物料准备中 |
| GroupBatchStatus | PENDING_DEPARTURE等 | 已出发或更后 |
| GroupBatchStatus | CANCELLED | 已取消 |
| RECRUITING | 招募中 | 放行 |
| RESOURCE_PREPARING | 资源准备中 | 放行 |
| MATERIAL_PREPARING | 物料准备中 | 589536(打回户例外) |
| PENDING_DEPARTURE | 待出发 | 589536(打回户例外) |
| TRAVELLING | 出行中 | 589536(打回户例外) |
| REVIEWING | 核单中 | 589536(打回户例外) |
| SETTLED | 已结算 | 589536(打回户例外) |
| CANCELLED | 已取消 | 589501 |
## 六.6、修改前后对比
### 需求状态(`com.hulalv.order.requirement.enums.RequirementStatus`,`data.status` / `room_control_status`)
| 值 | 含义 |
|---|---|
| PENDING_REVIEW | 待团期管理员审核(团单提交后) |
| REJECTED_TO_CONSULTANT | 已打回定制师(冻结期可重提一次) |
| PENDING / PROCESSING / DONE | 核心订单 / 管理员提交房务后的房务侧状态,本次不变 |
## 六.6、修改前后对比(修改/删除类接口必写,新增跳过)
### 字段级对比
无字段增删;roomCategory和roomCount既有字段,仅约束条件变。
无字段增删;`rooms[].roomCategory`、`rooms[].roomCount`(及旧结构段级同名字段)由「选填」改为「团期子订单必填」,核心订单不变。
### 行为级对比
| 行为 | 修改前 | 修改后 |
|---|---|---|
| 团期子订单提需求权限 | 成团后 | 招募中或资源准备中 |
| 物料准备中操作 | 允许 | 冻结(589536) |
| 房型与间数 | 选填 | 必填(582099) |
| 团期子订单提 / 改需求时机 | 团期须为 `RESOURCE_PREPARING`(成团后),否则 589501 | 支付后即可:`RECRUITING / RESOURCE_PREPARING` 放行 |
| 团期物料准备及之后 | 589501 | 589536(新码,文案明确「已冻结」),最新需求被打回的户可重提一次 |
| 团单缺房型大类 / 房数 | 放行,全团汇总出现「未知」房型 | 582099 拒绝,零副作用 |
| 团单空白占位段 | 放行 | 582099 拒绝 |
| 用车需求 | 同 589501 闸 | 同新闸门,不加必填 |
## 六.7、影响评估
## 六.7、影响评估(修改/删除类必写)
- **破坏向后兼容**:是;权限、冻结时机、校验规则均变。
- **前端必须同步上线**:是。
- **前端workaround清理点**:删除「等待成团」逻辑;删除「物料准备中允许」分支;房型改必填;直接展示589536和582099。
- 破坏向后兼容:**部分**——请求 / 响应结构不变,但团单缺房型或空白占位段的旧调用会从 200 变 582099,招募中的调用从 589501 变 200,物料准备中从 589501 变 589536。
- 前端是否必须同步上线:**建议同步**——不同步时功能可用但用户会收到 582099 / 589536 提示且入口显示时机不准。
- 前端需清理的分支:「未成团不显示提需求入口」「物料准备中仍允许提交」「团单空白占位先提交」。
## 七、不影响范围
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
- 核心订单房型仍选填。
- 其他接口逻辑不变。
- DB表结构无变更。
- 核心订单(`productBatchId` 为空)的提需求、房型选填、抢单池、房务配房全部不变。
- 团期管理员确认 / 打回 / 提交房务(`…/hotel-requirement/reject`、`…/dispatch`、`group-batch/{groupBatchId}/requirement/*`)本次不改;打回后重提的版本号规则不变。
- `GET /v3/admin/order/group-batch/{groupBatchId}/requirement-summary` 结构不变,只是按本次提交的团单不再出现 `roomCategory="未知"` 项(历史数据仍可能出现)。
- 网关路由、权限码、表结构均无改动。
## 八、测试环境已验证
**环境**:TEST,api.test.1814.love:9443,2026-09-06 15:44,admin/1001
**环境**:TEST 网关 `https://api.test.1814.love:9443`,order-v3 dev-v3 `a52365278` 两实例 2026-09-06 15:43 滚动部署,登录 `admin`(adminId 1001,SUPER_ADMIN,且为两张测试单的定制师),实测 15:44–15:47。
**网关实测**:招募中提房型→200,PENDING_REVIEW;缺房型大类→582099;资源准备中可提→200;物料准备中冻结→589536;被打回户可重提→200,version=2;重提后再改→589536;用车同样冻结→589536;自订晚不受约束→200。
| 场景 | 请求 | 结果 |
|---|---|---|
| 招募中(`RECRUITING`)子订单 2096412454488612866 提完整房型 | `PUT …/hotel-requirement` | 200,`status=PENDING_REVIEW` |
| 招募中,第 2 晚房型行缺 `roomCategory` | `PUT …/hotel-requirement` | 582099「团期订单第2晚第1段需填写房型大类与房间数」,DB 无新版本 |
| 招募中,旧结构段级缺 `roomCategory`(候选带 `roomTypeId`) | `PUT …/hotel-requirement` | 582099,同上 |
| 资源准备中完整提交 | `PUT …/hotel-requirement` | 200,`version=1 INIT_SUBMIT PENDING_REVIEW`;`order_main.room_control_status=PENDING_REVIEW` |
| 资源准备中经调整入口改需求 | `POST …/adjustment/submit` | 200,`data.success=true`(同版本 `PENDING_EDIT` 换行) |
| 第 2 晚 `customerSelfBooked=true` 无段 | `PUT …/hotel-requirement` | 200 |
| 团期 2089713777065832450 子订单 2096029450184347649 资源准备中提交 | `PUT …/hotel-requirement` | 200 |
| 团期管理员打回该户 | `POST …/hotel-requirement/reject` | 200,需求行 `REJECTED_TO_CONSULTANT`、`is_active=0` |
| 团期改为 `MATERIAL_PREPARING` 后被打回户重提 | `PUT …/hotel-requirement` | 200,`version=2 PENDING_REVIEW`(冻结期例外) |
| 重提后再改 | `PUT …/hotel-requirement` | 589536「团期已进入物资准备,需求已冻结,请联系团期管理员」 |
| 物料准备中经调整入口改需求 | `POST …/adjustment/submit` | 589536 |
| 物料准备中提用车需求 | `PUT …/vehicle-requirement` | 589536 |
**单测**:BUILD SUCCESS 396全绿。
**部署**:a52365278已合dev-v3并部署。
## 验证证据
上述测试环境已验证的8个场景与单测全绿。
**单测**:`RequirementServiceTest` 244 / `OrderTodoServiceTest` 15 / `RequirementGroupBatchErrorCodeRangeTest` 3 + 5 个 ArchTest(RedLine / MapperBoundary / HouseModuleBoundary / DashboardLayer / LocalCacheVetting)全绿,`BUILD SUCCESS` 396 用例。
## 十、相关文档
- [Issue #7149](https://git.1814.love:8443/wx/HL/issues/7149)
- [PR #7177](https://git.1814.love:8443/wx/HL/pulls/7177)
- 团期房务方案:docs/group/团期房务实现方案-v1.0.html
- 团期模块接口文档 `docs/group/团期模块接口文档-v2.0.html` §0C.11.1(提需求时机)/ §0C.11.2(房型间数必填)/ GB-ADM-011:<https://web.test.1814.love:9443/hl-docs/group/>
- 团期房务实现方案 `docs/group/团期房务实现方案-v1.0.html` §3.12.1 / §3.12.2
- 后续工单(本次不做):管理员确认 / 打回改造(M1 / M2)、房务团期看板与按日订房、定制师 ↔ 团期管理员站内会话
## 关联 / 联系人
### 链接
- **Issue**: [#7149](https://git.1814.love:8443/wx/HL/issues/7149)
- **PR**: [#7177](https://git.1814.love:8443/wx/HL/pulls/7177)
- **Merge commit**: [a52365278](https://git.1814.love:8443/wx/HL/commit/a52365278)
- Issue: <https://git.1814.love:8443/wx/HL/issues/7149>
- PR: <https://git.1814.love:8443/wx/HL/pulls/7177>
- 合并提交: `a52365278`(dev-v3)
### 联系人
- **后端**: @wx
- **前端**: @mmg
- 后端:wx
- 前端(hl-ui):mmg