docs(changelog): #7324 房务团期看板与整团按日订房计划 CRUD(H1-H6 + release-all)
changelog-filename-gate / validate (push) Successful in 2s
changelog-filename-gate / validate (push) Successful in 2s
七个新端点的前端交接件,重点写清四处「后端行为对、前端按直觉写会踩坑」的地方: 1. H1 列表 total 可能是 -1,语义是「未统计」——传 planStatus 且扫描页(500)被填满时 走内存过滤降级路径。全仓其它分页都保证 total >= 0,直接拿它算总页数会出负页码。 2. H5 只传 version 的空提交:PENDING 行返 200 且只把 version +1(updateById 默认 NOT_NULL 策略,null 不进 SET 子句,不清空任何业务字段),CONFIRMED 行抛 808692。 3. release-all 会返回「200 但什么都没释放」——整团都是过去的已确认行时, releasedPlanIds 为空、retainedPastPlanIds 非空、warnings=[PAST_STAY_RETAINED], 与「团里根本没订房」的 808619 幂等出口是两条不同分支。 4. H4 响应恒带两个空数组 warnings / removedManualAllocIds(与 H5 共用 RespVO)。 另记两条与工单口径不一致、以源码为准的事实: - 错误码实际新增 17 个(工单写 12 个),已按 HouseGroupBatchErrorCode 逐个列出。 - 部署顺序:hl-resource-service 必须先于 hl-order-service-v3,反序会让 order-v3 拿到 roomCategory=null,fail-closed 房型守卫把每次 H4/H5 提交拒成 808691。 Refs #7324
这个提交包含在:
@@ -0,0 +1,857 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7324"
|
||||
title: "房务团期看板与整团按日订房计划 CRUD:H1-H6 + 整团释放共 7 端点;H1 total=-1 新契约、H5/release-all 各有一处「200 但看似没生效」的静默分支"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "2026-09-10 已部署测试服并实测通过(squash 07feaffe0,PR #7465)。⚠️ 部署顺序:本单唯一跨服务契约变更是 HouseRoomTypeDTO.roomCategory(resource 侧新增字段,HouseRoomTypeDTO.java:51)与 HouseRoomTypeFeignVO.roomCategory(order-v3 侧消费,HouseRoomTypeFeignVO.java:47),hl-resource-service 必须先于 hl-order-service-v3 部署——反序会让 order-v3 拿到 roomCategory=null,H4/H5 的 fail-closed 房型守卫(HouseRoomTypeGuard.requireRoomTypes)把每次提交全部拒成 808691。2026-09-10 实测确实撞过一次短暂不同步窗口(GET /v3/admin/house/group-batches 一度 404),重滚 order-v3 后恢复,未影响最终验收。⚠️ 与工单 #7324 核对:工单口径「HouseGroupBatchErrorCode 新增 12 个错误码」与源码不符,squash 07feaffe0 实际新增 17 个常量(808600-808603 共 4 个 + 808608 共 1 个 + 808611-808619 共 9 个 + 808690-808692 共 3 个 = 17,见 HouseGroupBatchErrorCode.java 本次 diff +165/-2 行),本篇按源码 17 个如实列出,以代码为准。"
|
||||
updated_at: "2026-09-10"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 房务团期看板与整团按日订房计划 CRUD(H1-H6 + 整团释放)
|
||||
|
||||
> **服务**: `hl-order-service-v3`(H1-H6 + release-all 全部落在此服务);跨服务契约方 `hl-resource-service`(房型大类字段回填)
|
||||
> **PR**: #7465
|
||||
> **Issue**: #7324
|
||||
> **日期**: 2026-09-10
|
||||
> **影响范围**: 房务团期看板全新页面(列表 / 详情 / 逐日需求明细)+ 团期按日订房计划的新增 / 改 / 删 / 整团释放,管理后台全新功能,无存量前端调用方
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**这是一批全新端点,没有存量调用方,但有四处「后端行为对,前端按直觉写会踩坑」的地方,逐一说明:**
|
||||
|
||||
### 1.【新契约】H1 列表的 `total` 可能是 `-1`,语义是「未统计」不是负数
|
||||
|
||||
`GET /v3/admin/house/group-batches` 在**不传 `planStatus`** 时走普通数据库分页,`total` 与全仓其它分页接口一样恒 `>= 0`。
|
||||
|
||||
但**一旦传了 `planStatus`(PENDING / CONFIRMED)**,后端走内存过滤降级路径(`HouseGroupBatchBoardManager.java:99-114`):先按认领/关键字/状态等条件扫一页最多 500 条候选(`PLAN_STATUS_SCAN_LIMIT = 500`,`HouseGroupBatchBoardManager.java:72`),逐条在内存里判定 `planStatus` 是否匹配再切页。**若候选总数超过 500(扫描页被填满)**,真实总数已经无法算出,此时 `PageResult.total` 返回 **`-1`**(`TOTAL_UNKNOWN`,`HouseGroupBatchBoardManager.java:81`、`268-275`)。
|
||||
|
||||
全仓所有既有分页接口的 `total` 都保证非负,前端惯用它算「共 N 条」文案或总页数(`Math.ceil(total / pageSize)`)。**不识别这个新语义会渲染出「共 -1 条」或算出负数页码**。前端拿到 `total === -1` 时应显示「—」或类似占位文案,并提示用户收窄筛选条件(缩小 `stayDateFrom/To`、`departDateFrom/To` 或去掉 `planStatus`),不要按负数处理分页控件。
|
||||
|
||||
同时注意 `PageResult.total` 是 Java 基本类型 `int`(`PageResult.java:15`),不是可空类型,正常场景下的 0 与「无匹配」是同一个值,只有 `planStatus` 降级路径的扫描填满态才会出现 `-1`。
|
||||
|
||||
### 2.【易漏分支】H5 只传 `version` 字段:在 PENDING 行是「静默成功」,在 CONFIRMED 行是 808692
|
||||
|
||||
`PUT .../room-plans/{planId}` 的请求体语义是「字段为 `null` = 本次不改」(`GroupBatchRoomPlanItemReqVO.java:19-21`)。若前端「保存」按钮无差别提交整个表单对象、但用户实际没有改任何字段,等价于只传了必填的 `version`:
|
||||
|
||||
- 若该计划行当前是 **`PENDING`**:五要素(入住日/酒店/房型/间数/大类)均判定为未变化,服务端进入「原地更新」分支(`isIdentityChanged` 返回 `false`),构造一个只设了 `planId`/`version`(其余字段原样取自请求,未传的为 `null`)的 `patch` 对象调用 `updateWithVersion`(`doUpdateInPlace`,`GroupBatchRoomPlanManager.java:230-249`)。`updateWithVersion` 的实现是 `roomPlanMapper.updateById(plan)`(`GroupBatchRoomPlanService.java:174-178`),MyBatis-Plus(`3.5.5`,根 `pom.xml:46`)`updateById` 的默认 `FieldStrategy` 是 `NOT_NULL`——**`null` 字段不会进 UPDATE 的 SET 子句**;`GroupBatchRoomPlanDO` 的 `protoPrice`/`settlementPrice`/`settleType`/`deductInventory`/`remark` 五个字段均未加 `@TableField(updateStrategy=...)` 覆盖(`GroupBatchRoomPlanDO.java:46/48/50/52/61`),全仓在 order-v3 与 hl-common 范围内也没有任何 `update-strategy`/`FieldStrategy` 全局覆盖。因此只传 `version` 的请求**返回 200,SET 子句里只有乐观锁 `version`(+1)与审计时间列,不会清空或改动任何业务字段**——这与 VO 注释承诺的「字段为 `null` = 本次不改」是一致的,是一次「空更新 + `version` 自增」,不是字段清空。
|
||||
- 若该计划行当前是 **`CONFIRMED`**:同样判定五要素未变化,但已确认行禁止原地更新(`version` 是扣减幂等键的一部分,见「五、数据库行为」),服务端抛 **`808692`**(`GroupBatchRoomPlanManager.java:206-212`)。
|
||||
|
||||
前端仍应只把用户实际改过的字段放进请求体(未改的留 `null`),但理由**不是**「防止字段被清空」——PENDING 行的原地更新不会清空任何字段。真正需要注意的是:只要走进这条分支,无论字段是否真的变化,服务端都会把 `version` **+1**(本地缓存的旧 `version` 随之失效,下次操作若仍带旧值会撞 **808608**),并在团期时间线上留下一条 `UPDATE_IN_PLACE` 记录(`recordReviseTimeline`,`GroupBatchRoomPlanManager.java:245-246`)——用户没有实际改动时不应发起这次 PUT,前端应做「脏字段检测」,没有脏字段就不提交。
|
||||
|
||||
### 3.【易漏分支】`release-all` 会返回「200 成功,但什么都没释放」
|
||||
|
||||
`POST .../room-plans/release-all` 只在**该团完全没有 active 计划行也没有 active 分房行**时才返回 **`808619`**(`GB_ROOM_PLAN_RELEASE_NOTHING`,幂等出口,零写入)。
|
||||
|
||||
但如果该团已流团(`CANCELLED`)、计划行**全部是入住日早于操作当日的已确认(CONFIRMED)行**——即整团都是「已发生的间夜」——服务端会判定为「有东西但全部该保留」,返回 **200**,且:
|
||||
- `releasedPlanIds` / `releasedAllocationIds` / `markedLogIds` 均为**空数组**
|
||||
- `retainedPastPlanIds` **非空**(列出被保留的已确认行 ID)
|
||||
- `warnings` 含 **`"PAST_STAY_RETAINED"`**
|
||||
|
||||
(`GroupBatchRoomPlanManager.java:394-409`、`GroupBatchRoomReleaseResult.java:11-16` 显式把这两种情形拆成不同分支,理由见该 DTO 的类注释:「团里根本没有订房」要给 808619 的幂等出口,「订房全都是已发生的间夜、按规矩保留了」是正常业务态必须返 200。)
|
||||
|
||||
前端对 `release-all` 的成功响应,**不能只看 HTTP 200 / `success:true` 就提示「已全部释放」**,必须检查 `releasedPlanIds.length === 0 && retainedPastPlanIds.length > 0` 这一组合,命中时改用「已发生间夜已按规定保留,未做任何释放」这类文案,并建议展示 `retainedPastPlanIds` 供核对。
|
||||
|
||||
### 4. H4 响应体携带两个恒为空数组的字段
|
||||
|
||||
`POST .../room-plans`(H4)与 `PUT .../room-plans/{planId}`(H5)**共用同一个响应 VO** `GroupBatchRoomPlanRespVO`(既是数组元素类型也是 H5 单行响应类型)。这个 VO 上有 `warnings: string[]` 与 `removedManualAllocIds: string[]` 两个字段(`GroupBatchRoomPlanRespVO.java:105-109`)——它们对 H5 的「删旧建新」「原地更新(核单期)」分支有真实语义(见下方接口详情),但**对 H4 新建恒为空数组 `[]`**(`GroupBatchRoomPlanConverter.toRespVO` 无条件 `setWarnings(Collections.emptyList())` / `setRemovedManualAllocIds(Collections.emptyList())`,`GroupBatchRoomPlanConverter.java:51-52`,H4 的 `doSave` 未再覆盖)。
|
||||
|
||||
若前端按「H4 是纯新增,字段集合不会变」的假设去做响应类型/契约测试,会把这两个恒空字段判定为「意外新增字段」。它们是设计如此(VO 复用),不是漏改。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
`#7324` 是「团期房务」整体方案(`#7323` 定案)的第一张落地单,交付两块能力:
|
||||
|
||||
1. **只读看板**(H1-H3):房务在「已认领团」维度查看团期配房进度(列表 / 详情 / 逐日需求明细),供每日核对「需求 vs 已订」。
|
||||
2. **写口**(H4-H6 + release-all):按日订房计划的新增 / 改 / 删,以及流团后的整团批量释放。
|
||||
|
||||
包落位与错误码段位按 `#7323` 定案 #25/#17 预先占号(`HouseGroupBatchErrorCode.java` 类头注释 `:6-32`):`808600-808603/808608/808611-808619/808690-808692` 归 `#7324`,`808604/808605/808607/808609/808610` 留给 `#7325`(按日确认与自动分房,尚未开发),`808606` 留给 `#7326`(分房与只读),`808650-808662` 已由已上线的 `#7322`(团期整团抢单)占用。**本次交付前段位表本身有笔误已被修正**:`808611` 此前误记在 `#7325` 行,与本单 `GB_ROOM_PLAN_BATCH_DATE_MISSING` 撞号;`808612-808619`、`808650-808662`、`808690-808694` 整段此前没写进表——已在本次 diff 里一并修正(`HouseGroupBatchErrorCode.java:15-29`)。
|
||||
|
||||
新增字段的跨服务契约:团期订房要在提交时把「房型大类」(`roomCategory`,判「对平」的分组键)以 `hl-resource-service` 权威值覆盖客户端传入值,防止客户端伪造已对平;为此 `hl-resource-service` 的 `HouseRoomTypeDTO` 与 `hl-order-service-v3` 消费侧的 `HouseRoomTypeFeignVO` 都新增了 `roomCategory` 字段(详见「五、数据库行为」与本文件顶部部署顺序说明)。网关侧 `/v3/admin/**` 路由块此前已通配到相应服务(`#3264`),本单零网关改动。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 房务团期看板列表(H1) | GET | `/v3/admin/house/group-batches` | 新增接口 | `planStatus` 过滤降级为内存扫描,候选超 500 时 `total=-1` |
|
||||
| 2 | 团期配房详情(H2) | GET | `/v3/admin/house/group-batches/{groupBatchId}` | 新增接口 | 逐日按房型需求/已订/差额 + 该日计划行 + 各户特殊诉求 |
|
||||
| 3 | 团期逐日用房需求明细(H3) | GET | `/v3/admin/house/group-batches/{groupBatchId}/room-requirements` | 新增接口 | 含各户拆分,按最近一次放行房务的需求版本 |
|
||||
| 4 | 整团按日提交订房计划(H4) | POST | `/v3/admin/house/group-batches/{groupBatchId}/room-plans` | 新增接口 | 追加式,不做集合对账;同格子拒 808614 |
|
||||
| 5 | 修改单条订房计划(H5) | PUT | `/v3/admin/house/group-batches/{groupBatchId}/room-plans/{planId}` | 新增接口 | 字段 `null`=不改;五要素变化=删旧建新,否则原地更新 |
|
||||
| 6 | 删除单条订房计划(软删,H6) | DELETE | `/v3/admin/house/group-batches/{groupBatchId}/room-plans/{planId}` | 新增接口 | 已流团团期也放行;已发生间夜仍冻结 |
|
||||
| 7 | 整团批量释放订房计划 | POST | `/v3/admin/house/group-batches/{groupBatchId}/room-plans/release-all` | 新增接口 | 仅已流团(CANCELLED)团期可用;可能 200 但零释放 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 房务团期看板列表(H1) `GET /v3/admin/house/group-batches`
|
||||
|
||||
**VO**: `HouseGroupBatchBoardPageReqVO(GET 直接绑定)→ Result<PageResult<HouseGroupBatchBoardSimpleRespVO>>`
|
||||
|
||||
(Controller:`HouseGroupBatchBoardController.java:52-59`;编排:`HouseGroupBatchBoardManager.page`,`:99-114`)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务每日打开的「我的团」工作台首页,只显示**已被房务整团认领**的团(未认领的团在团期抢单池页,两页以认领动作为界互斥,`HouseGroupBatchBoardController.java:48-50`)。
|
||||
|
||||
#### 入参字段表(`HouseGroupBatchBoardPageReqVO.java`)
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `scope` | Query | String | 否 | ≤8 字;`MINE`(默认)/`ALL` | `ALL` 是组长/超管监督视图,普通房务传 `ALL` 得 **808092**(`:27-29`) |
|
||||
| `claimerAdminId` | Query | Long | 否 | 仅 `scope=ALL` 生效,`scope=MINE` 时忽略 | 按认领人筛(`:31-32`) |
|
||||
| `planStatus` | Query | String | 否 | ≤16 字;`PENDING`/`CONFIRMED`,不传=全部 | 触发内存过滤降级路径,见「关键变化 1」(`:42-44`) |
|
||||
| `batchStatus` | Query | String | 否 | ≤200 字,逗号分隔多选;不传取默认四态;传 `CANCELLED` 被忽略 | 默认四态=`RESOURCE_PREPARING,MATERIAL_PREPARING,PENDING_DEPARTURE,TRAVELLING`(`HouseGroupBatchBoardManager.java:60-64`);非法状态码同样被过滤掉,不报错(`:224-236`) |
|
||||
| `stayDateFrom` / `stayDateTo` | Query | LocalDate(ISO) | 否 | — | 住期区间(团期 `[出发日,结束日)` 与之有交集即命中) |
|
||||
| `departDateFrom` / `departDateTo` | Query | LocalDate(ISO) | 否 | — | 出发日区间 |
|
||||
| `keyword` | Query | String | 否 | ≤32 字 | 团期号或产品名包含匹配 |
|
||||
| `page` | Query | Long | 否 | `>= 1`,默认 1 | — |
|
||||
| `pageSize` | Query | Long | 否 | `1~50`,默认 20 | 上限比房务其它列表(200)小得多——本页每行都要解析各户需求 JSON(`:75-84`) |
|
||||
|
||||
#### 出参字段表 `PageResult<HouseGroupBatchBoardSimpleRespVO>`
|
||||
|
||||
`PageResult`(`PageResult.java`): `records: T[]`、`total: int`(⚠️ 见「关键变化 1」,可能为 `-1`)、`page: int`、`pageSize: int`。
|
||||
|
||||
`HouseGroupBatchBoardSimpleRespVO` 每项(`HouseGroupBatchBoardSimpleRespVO.java:23-127`):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `groupBatchId` | String(雪花 ID,`ToStringSerializer`) | 运营团期 ID |
|
||||
| `batchNo` | String | 团期号,如 `GB-26-000001` |
|
||||
| `productId` | String | 产品 ID |
|
||||
| `productName` / `batchName` / `batchLabel` | String | 产品名/班期名/第 N 期快照 |
|
||||
| `batchStatus` / `batchStatusText` | String | 状态码/中文,如 `RESOURCE_PREPARING`/「资源准备中」 |
|
||||
| `departDate` / `endDate` | LocalDate(可空) | 团期出发日/结束日(离店日) |
|
||||
| `nights` | Integer(可空) | 间夜数 = 结束日 − 出发日 |
|
||||
| `enrolledRooms` / `enrolledPeople` | Integer | 已报名房数(户数)/人数 |
|
||||
| `requirementConfirmed` | Boolean | 团级需求已确认标记;`false` 不代表从列表消失,只提示「待管理员重新确认」 |
|
||||
| `hotelReady` | Boolean | 配房完成标志;本单只在整团释放时置回 `false`,不置 `true` |
|
||||
| `claimerAdminId` / `claimerName` / `claimedAt` | String/String/LocalDateTime | 认领房务信息 |
|
||||
| `boardStatus` | String | `NOT_STARTED`/`PARTIAL`/`ALL_CONFIRMED`(`:86-93`) |
|
||||
| `demandDays` / `plannedDays` / `confirmedDays` / `mismatchDays` | Integer | 有需求/有计划行/全确认/存在差额的日历日数 |
|
||||
| `demandRoomNights` / `plannedRoomNights` / `confirmedRoomNights` | Integer | Σ需求/Σ已订/Σ已确认 间·晚 |
|
||||
| `outOfRangeHouseholdCount` | Integer | 越界/日期缺失户数(阻塞整团配房完成,但**不**从需求分母里剔除) |
|
||||
| `days[]` | `DaySummary[]` | 逐日摘要:`stayDate`、`demandRooms`、`plannedRooms`、`confirmedRooms`、`dayStatus`(`NONE`/`PENDING`/`CONFIRMED`/`MIXED`)、`mismatch`(Boolean) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/house/group-batches?scope=MINE&planStatus=PENDING&page=1&pageSize=20
|
||||
```
|
||||
|
||||
#### 响应示例(正常态)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": {
|
||||
"records": [{
|
||||
"groupBatchId": "20260610001", "batchNo": "GB-26-000001",
|
||||
"batchStatus": "RESOURCE_PREPARING", "batchStatusText": "资源准备中",
|
||||
"boardStatus": "PARTIAL", "demandDays": 5, "plannedDays": 3, "confirmedDays": 1,
|
||||
"demandRoomNights": 20, "plannedRoomNights": 12, "confirmedRoomNights": 4,
|
||||
"outOfRangeHouseholdCount": 0, "days": [ /* ... */ ]
|
||||
}],
|
||||
"total": 37, "page": 1, "pageSize": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例(`total` 未统计,⚠️ 见关键变化 1)
|
||||
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": { "records": [ /* 20 条 */ ], "total": -1, "page": 1, "pageSize": 20 } }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 无匹配团期时 `records` 返回空数组 `[]`,`total` 为 `0`(不走降级路径时恒准确)。
|
||||
- `planStatus` 降级扫描路径若扫描到 0 条匹配,同样 `total=0`;`total=-1` 仅在候选超过 500 条扫描上限时出现(见「关键变化 1」),不会与「无匹配」混淆。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808092, "message": "无权查看全部房务订单(仅房务组长或超管可查看)", "success": false, "data": null }
|
||||
```
|
||||
|
||||
(`808092` 属预先存在的 `HouseGrabErrorCode`,`module="house-grab"`,非本单新增;本单读端点复用它,见「六.5」。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 未认领团**一个都不在本页**,即便它满足所有筛选条件。
|
||||
- `planStatus` 过滤是唯一会触发降级扫描的参数,其余筛选条件走数据库分页,`total` 恒准确非负。
|
||||
|
||||
---
|
||||
|
||||
### 2. 团期配房详情(H2) `GET /v3/admin/house/group-batches/{groupBatchId}`
|
||||
|
||||
**VO**: `无请求体(仅 Path)→ Result<HouseGroupBatchBoardRespVO>`
|
||||
|
||||
(Controller:`HouseGroupBatchBoardController.java:64-71`;编排:`HouseGroupBatchBoardManager.detail`,`:122-158`)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
点开某个团查看逐日按房型的需求/已订/差额,核对每天哪个房型还差几间。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `groupBatchId` | Path | Long | 是 | 雪花 ID | 团期主订单 ID |
|
||||
|
||||
#### 出参字段表 `HouseGroupBatchBoardRespVO`(`HouseGroupBatchBoardRespVO.java:26-258`)
|
||||
|
||||
团级字段与 H1 列表项**完全一致**(同名同型,见上表),另加:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `maxRooms` | Integer | 班期最大房间数,`0`/空=不限 |
|
||||
| `inGroupHouseholds` | Integer | 在团户数(只排已取消,含已完成) |
|
||||
| `hotelHouseholds` | Integer | 需房户数(`needs_hotel=true`,已排除历史旧户冻结名单) |
|
||||
| `releasedHouseholds` / `pendingReviewHouseholds` / `rejectedHouseholds` / `notSubmittedHouseholds` | Integer | 有基准需求版本 / 另有待确认新版本 / 无基准且最新被打回 / 需房但从未提过需求,各自户数 |
|
||||
| `outOfRangeHouseholds[]` | `OutOfRangeHousehold[]` | 阻塞名单(不是剔除名单,需求已计入分母),字段:`orderId`(String)、`orderNo`、`customerName`、`departDate`(可空)、`reason`(`OUT_OF_RANGE`/`DATE_MISSING`) |
|
||||
| `specialTags[]` | `HouseholdSpecialTags[]` | 各户特殊诉求(取自基准需求版本),字段:`orderId`(String)、`customerName`、`tags: string[]` |
|
||||
| `days[]` | `DayDetail[]` | 逐日详情,**可能出现团期区间之外的日历日**(越界户按其实际入住日落格,此时 `dayNumber=null`) |
|
||||
|
||||
`DayDetail`(`:155-189`):`stayDate`、`dayNumber`(Integer 可空)、`dayStatus`(`NONE`/`PENDING`/`CONFIRMED`/`MIXED`)、`demand[]`/`planned[]`(`RoomCategoryCount[]`:`roomCategory`+`rooms`)、`mismatch[]`(`CategoryMismatch[]`,只列 `diff≠0` 的:`roomCategory`/`demanded`/`planned`/`diff`)、`plannedTotal`、`exceedsMaxRooms`(Boolean)、`plans[]`(该日 active 计划行,元素即 H4/H5 返回的 `GroupBatchRoomPlanRespVO`,字段见 H4 出参表)。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/house/group-batches/20260610001
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": {
|
||||
"groupBatchId": "20260610001", "batchNo": "GB-26-000001", "maxRooms": 30,
|
||||
"outOfRangeHouseholds": [], "specialTags": [{ "orderId": "900001", "customerName": "张三", "tags": ["轮椅需求"] }],
|
||||
"days": [{
|
||||
"stayDate": "2026-06-12", "dayNumber": 1, "dayStatus": "PENDING",
|
||||
"demand": [{ "roomCategory": "STANDARD", "rooms": 5 }],
|
||||
"planned": [{ "roomCategory": "STANDARD", "rooms": 3 }],
|
||||
"mismatch": [{ "roomCategory": "STANDARD", "demanded": 5, "planned": 3, "diff": -2 }],
|
||||
"plannedTotal": 3, "exceedsMaxRooms": false, "plans": []
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 无空态:团期不存在直接返 589500;只要团期存在就必有完整响应体。`days[]`、`outOfRangeHouseholds[]`、`specialTags[]` 均可能是空数组(团期日期缺失或尚无需求提交时),前端应按空数组正常渲染而不是当作异常。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808612, "message": "该团期尚未被房务认领,请先到团期抢单池认领", "success": false, "data": null }
|
||||
```
|
||||
```json
|
||||
{ "code": 808613, "message": "该团期由其他房务认领,无权操作", "success": false, "data": null }
|
||||
```
|
||||
(组长/超管不受此限,见「四、契约约束」。团期不存在为既有 `589500`。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 普通房务只能看本人认领的团(`HouseGroupBatchClaimGuard.assertReadableByCurrentUser`,`HouseGroupBatchClaimGuard.java:42-54`),组长/超管可看任意团(`HouseReadGuard.canViewOthersClaims()` 放行)。
|
||||
|
||||
---
|
||||
|
||||
### 3. 团期逐日用房需求明细(H3) `GET /v3/admin/house/group-batches/{groupBatchId}/room-requirements`
|
||||
|
||||
**VO**: `无请求体(仅 Path)→ Result<HouseGroupBatchRoomRequirementRespVO>`
|
||||
|
||||
(Controller:`HouseGroupBatchBoardController.java:76-82`;编排:`HouseGroupBatchBoardManager.requirements`,`:166-199`)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
核对「每户每晚要几间什么房」的逐户明细,供订房前对照。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `groupBatchId` | Path | Long | 是 | 雪花 ID | 团期主订单 ID(同 H2) |
|
||||
|
||||
#### 出参字段表 `HouseGroupBatchRoomRequirementRespVO`(`HouseGroupBatchRoomRequirementRespVO.java:20-183`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `groupBatchId` / `batchNo` / `departDate` / `endDate` | — | 团期基本信息 |
|
||||
| `basisRule` | String | 固定文案「按各户最近一次放行房务的需求版本」,后端写死供前端原样展示,口径改动只改这一处 |
|
||||
| `days[]` | `Day[]` | 逐日明细:`stayDate`、`dayNumber`(可空)、`totals[]`(`RoomCategoryCount[]`)、`households[]` |
|
||||
| `householdsWithoutBasis[]` | `HouseholdWithoutBasis[]` | 需房但无放行基准版的户:`orderId`/`orderNo`/`customerName`、`reason`(`NOT_SUBMITTED`/`PENDING_REVIEW`/`REJECTED`)、`referenceRooms[]`(仅展示不计入合计) |
|
||||
| `outOfRangeHouseholds[]` | 同 H2 | 阻塞名单 |
|
||||
|
||||
`Household`(`:69-123`):`orderId`(String)/`orderNo`/`customerName`/`consultantId`(String)/`peopleCount`/`householdDayNumber`/`dateShifted`(Boolean,该户出发日≠团期出发日)/`roomControlStatus`/`basisRequirementId`(String)/`basisVersion`/`basisStatus`(`PENDING`/`PROCESSING`/`DONE`)/`pendingNewVersion`(Boolean)/`rooms[]`(`RoomLine[]`:`roomCategory`+`roomCount`)/`specialTags: string[]`。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/house/group-batches/20260610001/room-requirements
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": {
|
||||
"groupBatchId": "20260610001", "batchNo": "GB-26-000001",
|
||||
"basisRule": "按各户最近一次放行房务的需求版本",
|
||||
"days": [{
|
||||
"stayDate": "2026-06-12", "dayNumber": 1,
|
||||
"totals": [{ "roomCategory": "STANDARD", "rooms": 5 }],
|
||||
"households": [{
|
||||
"orderId": "900001", "orderNo": "ORD202606120001", "customerName": "张三",
|
||||
"peopleCount": 2, "householdDayNumber": 1, "dateShifted": false,
|
||||
"roomControlStatus": "PENDING", "basisVersion": 2, "basisStatus": "DONE",
|
||||
"pendingNewVersion": false, "rooms": [{ "roomCategory": "STANDARD", "roomCount": 1 }],
|
||||
"specialTags": []
|
||||
}]
|
||||
}],
|
||||
"householdsWithoutBasis": [], "outOfRangeHouseholds": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 无空态:团期不存在直接返 589500。`days[]`/`householdsWithoutBasis[]`/`outOfRangeHouseholds[]` 均可能是空数组(团尚无任何户提交过需求时),前端应按空数组正常渲染。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808612, "message": "该团期尚未被房务认领,请先到团期抢单池认领", "success": false, "data": null }
|
||||
```
|
||||
```json
|
||||
{ "code": 808613, "message": "该团期由其他房务认领,无权操作", "success": false, "data": null }
|
||||
```
|
||||
(与 H2 同一组错误码,判定逻辑完全一致。团期不存在为既有 `589500`。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 归属判定与 H2 完全一致:普通房务只能看本人认领的团,组长/超管可看任意团(`HouseGroupBatchClaimGuard.assertReadableByCurrentUser`)。
|
||||
- `basisRule` 文案由后端写死并原样透传,前端应直接展示该字段值,不要本地硬编码同义文案(口径变更只改后端一处)。
|
||||
|
||||
---
|
||||
|
||||
### 4. 整团按日提交订房计划(H4) `POST /v3/admin/house/group-batches/{groupBatchId}/room-plans`
|
||||
|
||||
**VO**: `GroupBatchRoomPlanSaveReqVO → Result<List<GroupBatchRoomPlanRespVO>>`
|
||||
|
||||
(Controller:`HouseGroupBatchRoomPlanController.java:69-82`;编排:`GroupBatchRoomPlanManager.save`/`doSave`,`:120-173`)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务批量登记某团多天 × 多酒店 × 多房型的订房安排。**追加式,不做集合对账**:库里有而本次没提交的行不会被删(避免超出 3 秒幂等窗后重放请求把未重复提交的行整批删掉)。
|
||||
|
||||
#### 请求头
|
||||
|
||||
`@Idempotent`:`keyPrefix="house:gb:room-plan:save"`、`keyArg=#groupBatchId`、`timeout=3` 秒,窗口内重复提交返回「订房计划提交处理中,请勿重复提交」。角色门 `@HouseWriteGuarded` 在幂等/锁**之前**执行,非房务角色不会消耗幂等令牌。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
`GroupBatchRoomPlanSaveReqVO`(`GroupBatchRoomPlanSaveReqVO.java:35-40`):
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `items` | Body | `GroupBatchRoomPlanItemReqVO[]` | 是 | 1-200 行 | 上限 200 是团期级锁(30 秒租约)与按酒店聚合 Feign 调用次数的取舍 |
|
||||
|
||||
`items[]` 元素 `GroupBatchRoomPlanItemReqVO`(H4 走 `@Validated({Default.class, RoomPlanValidationGroups.Create.class})`,`RoomPlanValidationGroups.java`):
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `stayDate` | Body | LocalDate(ISO) | 是(Create 组) | 须落在团期 `[出发日,结束日)` 内,否则 808602 | 入住日 |
|
||||
| `hotelId` | Body | Long | 是(Create 组) | — | 酒店 ID |
|
||||
| `roomTypeId` | Body | Long | 是(Create 组) | — | 房型 ID |
|
||||
| `roomCategory` | Body | String | 否 | ≤32 字 | **仅用于前端回显,服务端一律以 resource 权威值覆盖**,传与不传都不影响落库(`GroupBatchRoomPlanItemReqVO.java:44-52`) |
|
||||
| `roomCount` | Body | Integer | 是(Create 组) | `>= 1`,否则 808603 | 订房间数 |
|
||||
| `protoPrice` / `settlementPrice` | Body | BigDecimal | 否 | `>= 0.00` | 不传按「日历价→协议价」链路兜底 |
|
||||
| `settleType` | Body | String | 否 | 正则 `^(cash\|sign\|company)$` | 不传取酒店资源配置 |
|
||||
| `deductInventory` | Body | Boolean | 否 | — | 是否扣减 resource 库存,语义默认 true(真正扣减在按日确认时,#7325) |
|
||||
| `remark` | Body | String | 否 | ≤512 字 | 备注 |
|
||||
| `version` | Body | Integer | 否(H4 忽略) | — | 仅 H5 用 |
|
||||
| `replaceReason` | Body | String | 否(H4 忽略) | ≤256 字 | 仅 H5 删旧建新用 |
|
||||
|
||||
请求内同 `(stayDate, hotelId, roomTypeId)` 的多行**合并间数**(其余字段取首行),与库中已有 active 行撞同一格子拒 **808614**。
|
||||
|
||||
#### 出参字段表 `Result<List<GroupBatchRoomPlanRespVO>>`
|
||||
|
||||
按 `(入住日, 酒店, 房型)` 升序排列。`GroupBatchRoomPlanRespVO`(`GroupBatchRoomPlanRespVO.java:20-109`):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `planId` / `groupBatchId` | String(雪花) | — |
|
||||
| `stayDate` | LocalDate | — |
|
||||
| `hotelId` | String | — |
|
||||
| `hotelName` | String(可空) | Feign 抖动时可为 null |
|
||||
| `roomTypeId` | String | — |
|
||||
| `roomTypeName` | String(可空) | — |
|
||||
| `roomCategory` | String | **resource 权威值**,非客户端传值 |
|
||||
| `roomCount` | Integer | — |
|
||||
| `protoPrice` / `settlementPrice` | String(可空,`ToStringSerializer`) | 价格快照 |
|
||||
| `settleType` | String(可空) | — |
|
||||
| `deductInventory` | Boolean | — |
|
||||
| `planStatus` | String | H4 新建行恒为 `PENDING` |
|
||||
| `confirmedAt` / `confirmedBy` | 可空 | H4 新建行恒 null |
|
||||
| `remark` / `replaceReason` | String | H4 新建行 `replaceReason` 恒 null |
|
||||
| `replacedFromPlanId` | String(可空) | H4 新建行恒 null |
|
||||
| `version` | Integer | H4 新建行恒 `0` |
|
||||
| `allocatedRooms` | Integer | 其下 active 分房行 Σ 间数,自动分房上线前恒 `0` |
|
||||
| `createTime` / `updateTime` | LocalDateTime | — |
|
||||
| `warnings` | `string[]` | ⚠️ **H4 恒为空数组 `[]`**,见「关键变化 4」 |
|
||||
| `removedManualAllocIds` | `string[]` | ⚠️ **H4 恒为空数组 `[]`**,见「关键变化 4」 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
POST /v3/admin/house/group-batches/20260610001/room-plans
|
||||
{
|
||||
"items": [
|
||||
{ "stayDate": "2026-06-12", "hotelId": 200001, "roomTypeId": 300001, "roomCount": 3, "settleType": "sign" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": [{
|
||||
"planId": "500001", "groupBatchId": "20260610001", "stayDate": "2026-06-12",
|
||||
"hotelId": "200001", "hotelName": "示例酒店", "roomTypeId": "300001", "roomTypeName": "标间",
|
||||
"roomCategory": "STANDARD", "roomCount": 3, "protoPrice": "320.00", "settlementPrice": "300.00",
|
||||
"settleType": "sign", "deductInventory": true, "planStatus": "PENDING",
|
||||
"confirmedAt": null, "confirmedBy": null, "remark": null, "replaceReason": null,
|
||||
"replacedFromPlanId": null, "version": 0, "allocatedRooms": 0,
|
||||
"createTime": "2026-09-10 10:00:00", "updateTime": "2026-09-10 10:00:00",
|
||||
"warnings": [], "removedManualAllocIds": []
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 无空态:`items` 非空是入参硬约束(`@NotEmpty`),不存在「空提交返回 200」的场景;`items` 为空数组会被 Bean Validation 拒在参数绑定阶段。
|
||||
|
||||
#### 错误响应(按触发顺序,见 `GroupBatchRoomPlanManager.java:123-126` 校验顺序注释)
|
||||
|
||||
```json
|
||||
{ "code": 589500, "message": "团期不存在", "success": false, "data": null }
|
||||
{ "code": 808600, "message": "团期当前阶段({0})不允许修改订房计划", "success": false, "data": null }
|
||||
{ "code": 808611, "message": "团期出发日或结束日缺失,无法录入订房计划", "success": false, "data": null }
|
||||
{ "code": 808612, "message": "该团期尚未被房务认领,请先到团期抢单池认领", "success": false, "data": null }
|
||||
{ "code": 808613, "message": "该团期由其他房务认领,无权操作", "success": false, "data": null }
|
||||
{ "code": 808602, "message": "入住日 {0} 不在团期出行区间内", "success": false, "data": null }
|
||||
{ "code": 808603, "message": "订房间数必须大于 0", "success": false, "data": null }
|
||||
{ "code": 808614, "message": "该入住日({0})在酒店 {1} 的房型 {2} 上已有订房计划,请改用修改单行", "success": false, "data": null }
|
||||
{ "code": 808112, "message": "房型不属于该酒店", "success": false, "data": null }
|
||||
{ "code": 808691, "message": "无法确定房型大类(酒店 {0} 房型 {1}),请稍后重试", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **阶段闸门(808600)覆盖面含 CANCELLED**:`HouseGroupBatchPlanGate.PLAN_WRITABLE_STATUSES` 只含 `RESOURCE_PREPARING/MATERIAL_PREPARING/PENDING_DEPARTURE/TRAVELLING/TRIP_FINISHED/REVIEWING` 六态(`HouseGroupBatchPlanGate.java:42-48`),`RECRUITING`/`SETTLED`/`CANCELLED` 均抛 808600。
|
||||
- **房型校验 fail-closed 两级**:`808691`=resource 压根没查到权威房型列表(Feign 失败/返回失败/空列表);`808112`=查到了非空列表但该 `roomTypeId` 不在其中(复用既有 `HouseAssignmentErrorCode`)。二者优先级:先判"拿不到列表整体"(808691),再判"列表里不含该行"(808112),逐酒店批量校验(见 `HouseRoomTypeGuard.requireRoomTypes`,`:93-157`)。
|
||||
- 团期级 `@Lock4j` 锁租约 30 秒,写口内不允许逐行 Feign,按酒店聚合调用。
|
||||
|
||||
---
|
||||
|
||||
### 5. 修改单条订房计划(H5) `PUT /v3/admin/house/group-batches/{groupBatchId}/room-plans/{planId}`
|
||||
|
||||
**VO**: `GroupBatchRoomPlanItemReqVO → Result<GroupBatchRoomPlanRespVO>`
|
||||
|
||||
(Controller:`HouseGroupBatchRoomPlanController.java:91-102`;编排:`GroupBatchRoomPlanManager.update`/`doUpdateInPlace`/`doReplace`,`:177-294`)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
改单条已录入的订房计划行。语义:**字段为 `null` = 本次不改**。五要素(`stayDate`/`hotelId`/`roomTypeId`/`roomCount`/`roomCategory`,已确认行还多算 `deductInventory`)任一变化 → **删旧建新**(旧行软删登记库存待释放,新行落 `PENDING`、`version` 归零,`replacedFromPlanId` 指回旧行);只改价格/结算方式/`deductInventory`(仅 PENDING 行)/备注 → **原地更新**(同 `planId`,`version+1`)。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
`GroupBatchRoomPlanItemReqVO`(Path:`groupBatchId`/`planId`;Body 走 `@Validated({Default.class, RoomPlanValidationGroups.Update.class})`):
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `groupBatchId` | Path | Long | 是 | 雪花 ID | 团期主订单 ID |
|
||||
| `planId` | Path | Long | 是 | 雪花 ID | 计划行 ID |
|
||||
| `stayDate`/`hotelId`/`roomTypeId`/`roomCount`/`roomCategory` | Body | 同 H4 | 否 | `null`=本次不改,非 `null` 时约束同 H4(808602/808603) | 五要素,任一非 null 且发生变化即触发删旧建新 |
|
||||
| `protoPrice`/`settlementPrice`/`settleType`/`deductInventory`/`remark` | Body | 同 H4 | 否 | 同 H4 | `null`=本次不改 |
|
||||
| `version` | Body | Integer | **是**(`Update` 组强制,`:83-86`) | 须等于当前行版本号 | 不匹配抛 **808608**,不自动重试 |
|
||||
| `replaceReason` | Body | String | 否 | ≤256 字;核单中团期(`REVIEWING`)强制填写且 `>=10` 字,否则 **808617** | 走「删旧建新」时落到新行,原地更新时忽略 |
|
||||
|
||||
`GroupBatchRoomPlanItemReqVO` 字段基础定义同 H4 请求体,H5 差异点:
|
||||
|
||||
| 字段 | H5 差异 |
|
||||
|---|---|
|
||||
| `stayDate`/`hotelId`/`roomTypeId`/`roomCount` | 均可为 `null`(不在 `Update` 组强制),非 `null` 时才参与「是否变化」判定 |
|
||||
| `version` | **必填**(`Update` 组强制,`:83-86`),不匹配抛 **808608**,不自动重试 |
|
||||
| `replaceReason` | 走「删旧建新」时落到新行;核单中团期(`REVIEWING`)强制填写且 `>=10` 字,否则 **808617** |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| 全部字段 | — | 同 H4 出参表 `GroupBatchRoomPlanRespVO`(见「4. 整团按日提交订房计划(H4)」),逐字段定义不再重复,下表只列 H5 场景下的语义差异 |
|
||||
|
||||
同 H4 的 `GroupBatchRoomPlanRespVO`,但语义不同:
|
||||
|
||||
| 场景 | `planId` | `version` | `warnings` | `removedManualAllocIds` |
|
||||
|---|---|---|---|---|
|
||||
| 原地更新 | 不变 | `+1` | `REVIEWING` 阶段含 `"SETTLEMENT_IN_PROGRESS"`,否则 `[]` | 恒 `[]`(原地更新不触发连锁重算) |
|
||||
| 删旧建新(新行) | 新 ID | `0` | 同上,若旧行是 `CONFIRMED` 且触发连锁重算清了人工分房行,额外含 `"MANUAL_ALLOCATION_REMOVED"` | 若触发连锁重算,含被软删的人工分房行 ID;自动分房上线前恒 `[]` |
|
||||
|
||||
#### 请求示例(原地改价)
|
||||
|
||||
```json
|
||||
PUT /v3/admin/house/group-batches/20260610001/room-plans/500001
|
||||
{ "version": 0, "protoPrice": "330.00", "remark": "临时涨价" }
|
||||
```
|
||||
|
||||
#### 请求示例(五要素变化,删旧建新)
|
||||
|
||||
```json
|
||||
PUT /v3/admin/house/group-batches/20260610001/room-plans/500001
|
||||
{ "version": 0, "roomCount": 4, "replaceReason": "酒店临时超售,加订一间" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": {
|
||||
"planId": "500002", "groupBatchId": "20260610001", "stayDate": "2026-06-12",
|
||||
"roomCount": 4, "planStatus": "PENDING", "version": 0,
|
||||
"replacedFromPlanId": "500001", "replaceReason": "酒店临时超售,加订一间",
|
||||
"warnings": [], "removedManualAllocIds": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 无空态:修改必须定位到一条存在的 `planId`,不存在直接返 808601,不会有「修改了但返回空对象」的场景。
|
||||
|
||||
#### 错误响应(按校验顺序,`GroupBatchRoomPlanManager.java:180-185`)
|
||||
|
||||
```json
|
||||
{ "code": 808601, "message": "订房计划行不存在或已删除", "success": false, "data": null }
|
||||
{ "code": 808615, "message": "订房计划行不属于该团期", "success": false, "data": null }
|
||||
{ "code": 808600, "message": "团期当前阶段({0})不允许修改订房计划", "success": false, "data": null }
|
||||
{ "code": 808617, "message": "该团期正在核单,修改订房计划必须填写不少于 10 字的原因", "success": false, "data": null }
|
||||
{ "code": 808612, "message": "该团期尚未被房务认领,请先到团期抢单池认领", "success": false, "data": null }
|
||||
{ "code": 808613, "message": "该团期由其他房务认领,无权操作", "success": false, "data": null }
|
||||
{ "code": 808690, "message": "入住日 {0} 已发生,订房记录不可修改或删除", "success": false, "data": null }
|
||||
{ "code": 808608, "message": "订房计划已被其他操作修改,请刷新后重试", "success": false, "data": null }
|
||||
{ "code": 808692, "message": "已确认的订房计划不支持直接改价或改备注,差异请在核单中登记", "success": false, "data": null }
|
||||
{ "code": 808611, "message": "团期出发日或结束日缺失,无法录入订房计划", "success": false, "data": null }
|
||||
{ "code": 808602, "message": "入住日 {0} 不在团期出行区间内", "success": false, "data": null }
|
||||
{ "code": 808603, "message": "订房间数必须大于 0", "success": false, "data": null }
|
||||
{ "code": 808614, "message": "该入住日({0})在酒店 {1} 的房型 {2} 上已有订房计划,请改用修改单行", "success": false, "data": null }
|
||||
{ "code": 808616, "message": "已确认订房计划的改删连锁能力尚未上线,请联系管理员", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **⚠️ 完整校验顺序(不可假设某一步先于全局都先做完,前端做乐观 UI 时需注意)**:计划行存在 808601 → 属本团 808615 → 阶段闸门 808600 → 核单期原因门 808617 → 认领归属 808612/808613 → **已发生间夜冻结 808690**(第 6 步,只看日期与行状态,与本次改什么字段无关)→ 版本一致 808608 → 五要素判定分叉 → **已确认行禁原地更新 808692**(第 9 步)→(走替换分支时)团期日期齐 808611 → 区间/间数/同格子 808602/808603/808614 → 已确认行连锁重算未上线 808616。**808690 始终先于 808692 判定**:即便五要素完全没变、本该走「原地更新」分支,只要这一行是已发生的过去 CONFIRMED 行,也会先被 808690 拦下,不会等到走进"原地更新 vs 808692"分支再判断。
|
||||
- 已确认行改删连锁重算(`#7325` 交付)当前**未接入**:五要素变化且旧行是 `CONFIRMED` 时,若触发到需要重算分房的场景会抛 **808616**、整事务回滚、`afterCommit` 不执行、零副作用。
|
||||
|
||||
---
|
||||
|
||||
### 6. 删除单条订房计划(软删,H6) `DELETE /v3/admin/house/group-batches/{groupBatchId}/room-plans/{planId}`
|
||||
|
||||
**VO**: `GroupBatchRoomPlanDeleteReqVO`(请求体可选) `→ Result<GroupBatchRoomPlanDeleteRespVO>`
|
||||
|
||||
(Controller:`HouseGroupBatchRoomPlanController.java:107-117`;编排:`GroupBatchRoomPlanManager.delete`/`doDelete`,`:296-365`)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
删掉一条订房安排。**已流团(CANCELLED)团期同样放行删除**(否则已扣的酒店库存会永久滞留),但已发生的间夜仍然删不掉。**不动分房行**——那一晚的房还要按需求重新分给各户,删除本身不做「全删重建」的决定(`#7325` 负责)。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
`GroupBatchRoomPlanDeleteReqVO`(`GroupBatchRoomPlanDeleteReqVO.java`,请求体可整体不传):
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `groupBatchId` | Path | Long | 是 | 雪花 ID | 团期主订单 ID |
|
||||
| `planId` | Path | Long | 是 | 雪花 ID | 计划行 ID |
|
||||
| `reason` | Body | String | 仅团期处于 `REVIEWING`(核单中)时必填 | ≤256 字,核单中团期需 ≥10 字,否则 **808617** | 删除原因 |
|
||||
|
||||
#### 出参字段表 `GroupBatchRoomPlanDeleteRespVO`(`GroupBatchRoomPlanDeleteRespVO.java:20-35`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `planId` | String | 被软删的计划行 ID |
|
||||
| `releasedLogId` | String(可空) | 本次由 `NONE` 标为 `PENDING` 的扣减日志 ID;从未扣过库存的行为 `null` |
|
||||
| `warnings` | `string[]` | `SETTLEMENT_IN_PROGRESS`(核单期)/`MANUAL_ALLOCATION_REMOVED`(连锁重算清了人工分房行),无告警为空数组 |
|
||||
| `removedManualAllocIds` | `string[]` | 被连锁重算软删的人工分房行 ID,自动分房上线前恒为空 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
DELETE /v3/admin/house/group-batches/20260610001/room-plans/500001
|
||||
```
|
||||
(核单中团期需带请求体:`{ "reason": "客人退团,该晚不再需要" }`)
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{ "code": 200, "success": true, "data": { "planId": "500001", "releasedLogId": "700001", "warnings": [], "removedManualAllocIds": [] } }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 无空态:删除必须定位到一条存在的 `planId`,不存在(含二次删除)直接返 808601,不会有「删除了但返回空对象」的场景。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808601, "message": "订房计划行不存在或已删除", "success": false, "data": null }
|
||||
{ "code": 808615, "message": "订房计划行不属于该团期", "success": false, "data": null }
|
||||
{ "code": 808600, "message": "团期当前阶段({0})不允许修改订房计划", "success": false, "data": null }
|
||||
{ "code": 808617, "message": "该团期正在核单,修改订房计划必须填写不少于 10 字的原因", "success": false, "data": null }
|
||||
{ "code": 808612, "message": "该团期尚未被房务认领,请先到团期抢单池认领", "success": false, "data": null }
|
||||
{ "code": 808613, "message": "该团期由其他房务认领,无权操作", "success": false, "data": null }
|
||||
{ "code": 808690, "message": "入住日 {0} 已发生,订房记录不可修改或删除", "success": false, "data": null }
|
||||
```
|
||||
|
||||
(H6 **不会**抛 808692——已确认行禁原地更新是 H5 专属场景,H6 是软删不涉及原地更新分支。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **阶段闸门比 H4/H5 宽一档**:`assertDeletableStage`(`GroupBatchRoomPlanManager.java:422-429`)允许「可写六态」∪「`releaseOnly`(仅 `CANCELLED`)」,即 **`CANCELLED` 团期的 H6 不抛 808600**(与 H4/H5 相反)。仍只在 `RECRUITING`/`SETTLED` 抛 808600。
|
||||
- 已发生间夜(CONFIRMED 行 ∧ 入住日早于操作当日 ∧ 团期处于 `TRAVELLING`/`TRIP_FINISHED`/`REVIEWING`/`CANCELLED`)一律 808690,不因软删豁免。
|
||||
- 二次删除同一 `planId` 同样返回 808601(天然幂等出口,不是新错误)。
|
||||
|
||||
---
|
||||
|
||||
### 7. 整团批量释放订房计划 `POST /v3/admin/house/group-batches/{groupBatchId}/room-plans/release-all`
|
||||
|
||||
**VO**: `GroupBatchRoomPlanReleaseAllReqVO → Result<GroupBatchRoomPlanReleaseAllRespVO>`
|
||||
|
||||
(Controller:`HouseGroupBatchRoomPlanController.java:122-135`;编排:`GroupBatchRoomPlanManager.releaseAll`,`:369-410`)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期流团(`CANCELLED`)后的收尾:一次性软删该团全部订房计划与分房、登记库存归还。是 `#7325` 流团自动钩子失败时的手动兜底入口,也是钩子上线前的过渡入口——两条路径调同一个实现方法。
|
||||
|
||||
#### 请求头
|
||||
|
||||
`@Idempotent`:`keyPrefix="house:gb:room-release"`、`keyArg=#groupBatchId`、`timeout=3` 秒。团期级锁租约 **60 秒**(`RELEASE_LOCK_EXPIRE_MILLIS`,比 H4/H5/H6 的 30 秒更长——整团批量处置行数更多)。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
`GroupBatchRoomPlanReleaseAllReqVO`(`GroupBatchRoomPlanReleaseAllReqVO.java`):
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `groupBatchId` | Path | Long | 是 | 雪花 ID | 团期主订单 ID |
|
||||
| `reason` | Body | String | **是,无条件必填**(与团期状态无关) | 10-256 字 | 释放原因。10 字下限用于挡掉「删」「测试」这类等于没写的原因,因为本端点一次会把整个团的订房与分房全部软删 |
|
||||
|
||||
#### 出参字段表 `GroupBatchRoomPlanReleaseAllRespVO`(`GroupBatchRoomPlanReleaseAllRespVO.java:16-46`)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `groupBatchId` | String | 团期 ID |
|
||||
| `releasedPlanIds` | `string[]` | 本次软删的计划行 ID;⚠️ 见「关键变化 3」,可能为空 |
|
||||
| `releasedAllocationIds` | `string[]` | 本次软删的分房行 ID |
|
||||
| `markedLogIds` | `string[]` | 本次由 `NONE` 标为 `PENDING` 的扣减日志 ID(真正归还在事务提交后异步进行,代表「已确保会还」不代表「已还完」) |
|
||||
| `hotelReady` | Boolean | 处置后的配房完成标志,**恒为 `false`** |
|
||||
| `retainedPastPlanIds` | `string[]` | 入住日早于操作当日、因已发生而未释放的已确认计划行 ID |
|
||||
| `warnings` | `string[]` | `retainedPastPlanIds` 非空时含 `"PAST_STAY_RETAINED"`,否则 `[]` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
POST /v3/admin/house/group-batches/20260610001/room-plans/release-all
|
||||
{ "reason": "团期已流团,释放剩余订房" }
|
||||
```
|
||||
|
||||
#### 响应示例(正常全释放)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": {
|
||||
"groupBatchId": "20260610001",
|
||||
"releasedPlanIds": ["500001", "500002"], "releasedAllocationIds": [],
|
||||
"markedLogIds": ["700001", "700002"], "hotelReady": false,
|
||||
"retainedPastPlanIds": [], "warnings": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例(⚠️ 见关键变化 3:200 但零释放)
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "success": true,
|
||||
"data": {
|
||||
"groupBatchId": "20260610001",
|
||||
"releasedPlanIds": [], "releasedAllocationIds": [], "markedLogIds": [],
|
||||
"hotelReady": false,
|
||||
"retainedPastPlanIds": ["500001", "500002"], "warnings": ["PAST_STAY_RETAINED"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- `releasedPlanIds`/`releasedAllocationIds`/`markedLogIds` 均可能返回空数组但整体仍是 200(该团计划行全部是已发生的已确认行、全部转入 `retainedPastPlanIds`),见「关键变化 3」——**不要把空数组当作请求失败**。
|
||||
- 该团完全没有可处置内容(无 active 计划行也无 active 分房行)时不返回空数据,改为业务错误码 **808619**(见下方错误响应),这是零写入的幂等出口。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808618, "message": "团期当前阶段({0})不支持批量释放订房,仅已流团团期可用", "success": false, "data": null }
|
||||
{ "code": 808613, "message": "该团期由其他房务认领,无权操作", "success": false, "data": null }
|
||||
{ "code": 808619, "message": "该团期没有可释放的订房计划", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **仅 `CANCELLED` 团期可用**,含 `SETTLED` 在内的其它阶段一律 **808618**(`HouseGroupBatchPlanGate.PLAN_RELEASE_ONLY_STATUSES` 只含 `CANCELLED`,`HouseGroupBatchPlanGate.java:58-60`);`SETTLED` 不放行的理由:已结算团每晚都住过,归还过去日期库存没有可售价值,还会让月度对账凭空少掉已发生房费。
|
||||
- **归属校验与 H4/H5/H6 不同**:`assertReleasableByCurrentUser`(`HouseGroupBatchClaimGuard.java:72-94`)对**无人认领**的团直接放行(任意房务都能操作,不抛 808612)——团已是终态,谁来收尾都行;**有认领人**时仍只放行本人或超管,否则 808613。
|
||||
- `808619` vs 200+`retainedPastPlanIds` 非空的区分逻辑见「关键变化 3」,`GroupBatchRoomReleaseResult.nothingToRelease` 是显式标志而非靠列表判空推断(`GroupBatchRoomReleaseResult.java:11-16`)。
|
||||
- 只释放**今日及以后**的入住日,早于操作当日的已确认行一律保留进 `retainedPastPlanIds`(不因流团豁免,与 H6 的 808690 同一判据)。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
- ✅ H5 只提交实际改动的字段(`version` + 变动字段),❌ 把整份读接口回显对象原样 PUT 回去——PENDING 行虽不会清空未提交字段(MyBatis-Plus 默认 `FieldStrategy.NOT_NULL`,`null` 不进 SET 子句),但仍会把 `version` 无意义地 `+1` 并留一条 `UPDATE_IN_PLACE` 时间线噪音记录;CONFIRMED 行会被直接拒 808692(见「关键变化 2」)。
|
||||
- ✅ H1 判断 `total === -1` 时改用占位文案,❌ 直接拿 `total` 算 `Math.ceil(total/pageSize)` 或拼「共 N 条」。
|
||||
- ✅ `release-all` 成功回调先判 `releasedPlanIds.length` 与 `retainedPastPlanIds.length` 再决定提示文案,❌ 只看 `success:true` 就提示「已全部释放」。
|
||||
- ✅ H4 请求体里保留 `roomCategory`(供本地即时回显),但提交后**以响应体里的 `roomCategory` 为准刷新本地态**,❌ 假设服务端会原样接受客户端传入的 `roomCategory`。
|
||||
- ✅ H4/H5 的 `settleType` 只能传 `cash`/`sign`/`company` 三值之一(大小写敏感),❌ 传其它字符串(会被 Bean Validation 拒在参数绑定阶段,报 400 级校验错误而非业务错误码)。
|
||||
|
||||
### 认领态与角色的显隐建议
|
||||
|
||||
- `scope=ALL` 仅组长/超管可用,前端应按角色隐藏该切换项,而不是等 808092 才提示。
|
||||
- H4/H5/H6 的操作入口只在「本人认领的团」(或超管)下展示;`release-all` 入口可对未认领的已流团团期展示(任意房务可操作)。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为(涉及写操作时必写)
|
||||
|
||||
- **新增表** `group_batch_room_plan`(团期按日订房计划,软删表;`GroupBatchRoomPlanDO`)与相关的团期分房支撑表(`GroupBatchRoomAllocationDO`),细节见后端 Flyway,前端不需要感知表结构。
|
||||
- **写口全部落在团期级分布式锁内**(`@Lock4j`,锁名 `HouseGroupBatchLockConstants.GROUP_BATCH_HOUSE_LOCK_NAME`,H4/H5/H6 租约 30 秒、`release-all` 租约 60 秒),同一团期的并发写请求会串行化,锁等待超时对前端表现为请求超时/失败,可直接重试。
|
||||
- **同日同酒店同房型只能有一条 active 计划行,这条不变量没有 DB 唯一键兜底**(软删表软删列可空,MySQL 唯一索引里 `NULL` 互不相等),只靠「请求内合并 + 团期级锁 + 应用层查重(808614)」三件事共同守住。
|
||||
- **跨服务契约新增字段**:`hl-resource-service` 的 `HouseRoomTypeDTO.roomCategory`(`HouseRoomTypeDTO.java:51`)与 `hl-order-service-v3` 消费侧 `HouseRoomTypeFeignVO.roomCategory`(`HouseRoomTypeFeignVO.java:47`),来源 `room_type.room_category`,历史数据可能为 `null`。H4/H5 写入时**一律以此权威值覆盖客户端传入的 `roomCategory`**,取不到时 fail-closed 拒绝写入(808691),不同于同包内既有的 `assertRoomTypeBelongsToHotel`(fail-open,用于越权写回价格场景)——两者口径刻意不同,理由是本字段是「对平」判定的分组键,放行等于允许伪造已对平。
|
||||
- 库存扣减/归还是**异步两阶段**:写口内只登记扣减日志状态(`NONE`→`PENDING`),真正归还发生在事务 `afterCommit`;若那一步失败,兜底任务凭 `PENDING` 状态捞回重做。响应体里的 `releasedLogId`/`markedLogIds` 代表「已确保会还」,不代表「已经还完」。
|
||||
- **H5 原地更新走 MyBatis-Plus 默认 `FieldStrategy.NOT_NULL`**:`updateWithVersion` 底层是 `roomPlanMapper.updateById(plan)`(`GroupBatchRoomPlanService.java:174-178`),`GroupBatchRoomPlanDO` 的可选字段均未声明 `@TableField(updateStrategy=...)`,全仓也无全局覆盖——`patch` 对象里为 `null` 的字段不会进 SET 子句。这是「H5 只传 `version` 在 PENDING 行返回 200 但不清空字段」的底层依据(见「关键变化 2」)。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- H1 `planStatus` 过滤降级为内存扫描,扫描上限 500 条(`PLAN_STATUS_SCAN_LIMIT`),超限时 `total=-1`(见关键变化 1);不超限时 `total` 精确等于内存过滤后的匹配数。
|
||||
- H1 `batchStatus` 若传入非法状态码或全部被过滤掉(如只传 `CANCELLED`),静默回退为默认四态,不报错。
|
||||
- H2 `days[]` 可能出现团期区间之外的日历日(越界户按其**实际入住日**落格,此时 `dayNumber=null`)。
|
||||
- H4 单次最多 200 行,超出报 400 级参数校验错误(`@Size(max=200)`),不占用团期级锁。
|
||||
- H5 删旧建新分支的「五要素比较」把已确认行的 `deductInventory` 也算进比较范围(未确认行不算),因为它决定这一行是否占用 resource 库存,原地翻转会让库存持有与行状态脱节。
|
||||
- H6 二次删除同一行幂等返回 808601,不是错误放大。
|
||||
- `release-all` 的团期级锁租约(60 秒)比其它写口更长,且允许无人认领的团被任意房务操作。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### 错误码新增清单(`HouseGroupBatchErrorCode.java`,段位 808600-808699 内本单实际占用 17 个常量)
|
||||
|
||||
⚠️ **与工单 #7324 核对发现的不符**:工单口径写「新增 12 个错误码」,但源码(squash `07feaffe0` diff `+165/-2`)实际新增 **17 个** `IErrorCode` 常量,以下按源码逐条列出:
|
||||
|
||||
| Code | 常量名 | Message 模板 | module | 触发条件 |
|
||||
|---|---|---|---|---|
|
||||
| 808600 | `GB_ROOM_PLAN_BATCH_STATUS_INVALID` | 团期当前阶段({0})不允许修改订房计划 | order | H4/H5 阶段闸门(六态之外,含 CANCELLED);H6/release-all 阶段闸门更宽(CANCELLED 不触发) |
|
||||
| 808601 | `GB_ROOM_PLAN_NOT_FOUND` | 订房计划行不存在或已删除 | order | H5/H6 找不到 `planId`;二次删除同码天然幂等 |
|
||||
| 808602 | `GB_ROOM_PLAN_DATE_OUT_OF_RANGE` | 入住日 {0} 不在团期出行区间内 | order | H4/H5 入住日不在 `[出发日,结束日)` |
|
||||
| 808603 | `GB_ROOM_PLAN_COUNT_INVALID` | 订房间数必须大于 0 | order | H4/H5 `roomCount < 1` |
|
||||
| 808608 | `GB_ROOM_PLAN_VERSION_CONFLICT` | 订房计划已被其他操作修改,请刷新后重试 | order | H5 乐观锁 `version` 不匹配,不自动重试 |
|
||||
| 808611 | `GB_ROOM_PLAN_BATCH_DATE_MISSING` | 团期出发日或结束日缺失,无法录入订房计划 | order | H4 新建 / H5 改日期时团期 `departDate`/`endDate` 为空 |
|
||||
| 808612 | `GB_ROOM_PLAN_BATCH_NOT_CLAIMED` | 该团期尚未被房务认领,请先到团期抢单池认领 | order | H2/H3 只读与 H4/H5/H6 写操作,当前团期未认领(`release-all` 不抛此码) |
|
||||
| 808613 | `GB_ROOM_PLAN_BATCH_NOT_OWNED` | 该团期由其他房务认领,无权操作 | order | 同上场景,团期由他人认领(组长/超管读、超管写免检) |
|
||||
| 808614 | `GB_ROOM_PLAN_DUPLICATE_CELL` | 该入住日({0})在酒店 {1} 的房型 {2} 上已有订房计划,请改用修改单行 | order | H4/H5 与库中 active 行撞同一 `(stayDate,hotelId,roomTypeId)` 格子 |
|
||||
| 808615 | `GB_ROOM_PLAN_BATCH_MISMATCH` | 订房计划行不属于该团期 | order | H5/H6 路径参数 `groupBatchId` 与行数据的 `groupBatchId` 不一致 |
|
||||
| 808616 | `GB_ROOM_PLAN_CONFIRMED_REVISION_UNAVAILABLE` | 已确认订房计划的改删连锁能力尚未上线,请联系管理员 | order | H5/H6 对已确认(CONFIRMED)行做五要素变化的改/删,连锁重算能力(`#7325`)未接入 |
|
||||
| 808617 | `GB_ROOM_PLAN_REVISE_REASON_REQUIRED` | 该团期正在核单,修改订房计划必须填写不少于 10 字的原因 | order | H5/H6 团期处于 `REVIEWING`,`reason`/`replaceReason` 缺失或 <10 字 |
|
||||
| 808618 | `GB_ROOM_PLAN_RELEASE_BATCH_NOT_CLOSED` | 团期当前阶段({0})不支持批量释放订房,仅已流团团期可用 | order | `release-all` 团期非 `CANCELLED`(含 `SETTLED`) |
|
||||
| 808619 | `GB_ROOM_PLAN_RELEASE_NOTHING` | 该团期没有可释放的订房计划 | order | `release-all` 该团既无 active 计划行也无 active 分房行,零写入幂等出口 |
|
||||
| 808690 | `GB_ROOM_PLAN_PAST_STAY_LOCKED` | 入住日 {0} 已发生,订房记录不可修改或删除 | order | H5/H6:CONFIRMED 行 ∧ 入住日早于操作当日 ∧ 团期处于 `TRAVELLING`/`TRIP_FINISHED`/`REVIEWING`/`CANCELLED`;**始终先于 808692 判定** |
|
||||
| 808691 | `GB_ROOM_PLAN_ROOM_TYPE_UNRESOLVED` | 无法确定房型大类(酒店 {0} 房型 {1}),请稍后重试 | order | H4/H5 无法从 resource 取得权威房型行(Feign 失败/返回失败/空列表/该行 `roomCategory` 为空),fail-closed |
|
||||
| 808692 | `GB_ROOM_PLAN_CONFIRMED_IN_PLACE_UPDATE_FORBIDDEN` | 已确认的订房计划不支持直接改价或改备注,差异请在核单中登记 | order | H5:五要素未变化(走原地更新分支)且该行是 CONFIRMED;**在 808690 判定之后触发** |
|
||||
|
||||
(复用的既有错误码,非本单新增:**808112**「房型不属于该酒店」属 `HouseAssignmentErrorCode`,H4/H5 权威房型批量校验查到非空列表但目标 ID 不在其中时复用;**808090/808091/808092** 属 `HouseGrabErrorCode`(`module="house-grab"`),是房务角色只读/只写/全量监督视图的通用角色门,本单只读端点复用 `HouseReadGuard`、写端点复用 `HouseWriteGuard` 触发;**589500** 团期不存在为 order 域既有码。)
|
||||
|
||||
### 枚举字典
|
||||
|
||||
- `planStatus`(H1 筛选 / H4-H6 响应):`PENDING`(未确认)/`CONFIRMED`(已确认)。H1 的 `PENDING` 档口径含「该团无 active 计划行」,不是单独一档。
|
||||
- `boardStatus`(H1/H2):`NOT_STARTED`/`PARTIAL`/`ALL_CONFIRMED`。
|
||||
- `dayStatus`(H1/H2 逐日):`NONE`/`PENDING`/`CONFIRMED`/`MIXED`。
|
||||
- `warnings` 取值:`SETTLEMENT_IN_PROGRESS`(H5 核单期改动)/`MANUAL_ALLOCATION_REMOVED`(H5/H6 连锁重算清人工分房)/`PAST_STAY_RETAINED`(release-all 保留已发生行)。
|
||||
- `settleType`:`cash`/`sign`/`company`。
|
||||
- `batchStatus`(9 态,`GroupBatchStatus`):`RECRUITING`招募中 / `RESOURCE_PREPARING`资源准备中 / `MATERIAL_PREPARING`物料准备中 / `PENDING_DEPARTURE`待出发 / `TRAVELLING`出行中 / `TRIP_FINISHED`出行完毕 / `REVIEWING`核单中 / `SETTLED`已结算 / `CANCELLED`已取消。H1 默认四态=前四个(不含 `RECRUITING`);写口可写六态=`RESOURCE_PREPARING`~`REVIEWING`;仅释放态=`CANCELLED`。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 团期需求(H09 系列)、团期抢单池/我的团/接管(`#7322`)、团期核单/结算(`#7411` 等)现有端点**零改动**,本单只新增。
|
||||
- 逐户(非团单)房务配房(`HouseAssignmentService` 等)零改动,`808112` 复用的是既有校验方法但走的是新的批量入口 `requireRoomTypes`,不影响原有的 `assertRoomTypeBelongsToHotel` 调用点(询房回填/配房 submit)。
|
||||
- 按日确认、自动分房、分房微调(`#7325`/`#7326`)**尚未交付**,本单已预留错误码段位(`808604/808605/808607/808609/808610`、`808606`、`808640`)与阶段闸门单源(`HouseGroupBatchPlanGate`),但对应端点/能力均不在本次 PR 范围内——H5/H6 对已确认行的连锁重算目前 fail-closed 返回 808616。
|
||||
- 网关路由零改动(`/v3/admin/**` 已由 `#3264` 通配)。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- 已部署测试服并实测通过(`squash 07feaffe0`,PR #7465)。
|
||||
- 部署过程中实测到一次跨服务不同步窗口:`hl-order-service-v3` 先于 `hl-resource-service` 完成部署的短暂时间段内,`GET /v3/admin/house/group-batches` 一度 404(网关侧尚未完成新路由注册的过渡态),重滚 `order-v3` 后恢复正常,最终按「`hl-resource-service` 先于 `hl-order-service-v3`」的正确顺序验收通过。
|
||||
- 兼容性结论:全部为新增端点,无存量调用方,无向后兼容负担;唯一的跨服务契约是 `roomCategory` 字段新增(非破坏性字段新增),但**部署顺序错误会导致新端点短暂不可用/写口被 fail-closed 拒绝**,务必按本文件顶部 `status_note` 的顺序部署。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- `#7323`《团期房务实现方案 v1.0》§3.11(错误码段位分配定案)
|
||||
- `#7322` 团期整团抢单与抢单池分流(前置依赖:本单的「已认领」判定复用其 `house_claimer_id` 字段)
|
||||
- `#7325`(按日确认与自动分房,未交付)/ `#7326`(分房与只读,未交付)——本单为它们预留的段位与闸门单源
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#7324](https://git.1814.love:8443/wx/HL/issues/7324)
|
||||
- **PR**: [#7465](https://git.1814.love:8443/wx/HL/pulls/7465)
|
||||
- **Merge commit**: [07feaffe0](https://git.1814.love:8443/wx/HL/commit/07feaffe09d7372f4d2eb828e5bf54ee776c7c90)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
在新工单中引用
屏蔽一个用户