--- schema: "hl-changelog/v2" ticket: "8385" title: "团期订房计划建守卫:超管未认领团上禁建(808612),释放文案改为可操作指引" consumer: "admin" author: "wx(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "not_required" frontend_owner: "" frontend_ref: "" target_release: "" verified_at: "" status_note: "超管在未被房务整团认领的团期上新建订房计划时返回 808612(复用已有码);释放端点的 808660 文案改为可操作的指引(删除计划、联系超管接管)。后端改动已合入 dev-v3 并部署测试服,808612 新守卫与 808660 新文案均已网关实测。前端实证(mmg 2026-09-27):room-plans 写口错误码拦截器透 message,808612/808660 常量已存在新文案自动上屏;超管无未认领团直接排房入口(看板仅 isMine 走团级接管),零改动 not_required。" updated_at: "2026-09-26" base: "dev-v3" --- # order-v3:团期订房计划守卫与释放指引改进(管理后台) **服务**: hl-order-service-v3 **PR**: #8395 **Issue**: #8385 --- ## ⚠️ 关键变化 1. **新增入口守卫**:超管(SUPER_ADMIN)在**未被房务认领的团期**上调用 `POST /v3/admin/house/group-batches/{groupBatchId}/room-plans` 时,返回 **808612**「该团期尚未被房务整团认领」,不落库。 - 背景:超管本可在无人认领的团上直接建计划,导致"团没人管、但计划和库存都在"的状态。释放时房务因为 808660 无法释放,只剩逐条删计划这一条路。 - 约束:超管要在团上排房,必须先 `POST /v3/admin/order/grab-pool/group-batches/{groupBatchId}/takeover` 指派给某个房务,再建计划。 2. **改进释放文案**:端点 `POST /v3/admin/order/grab-pool/group-batches/{groupBatchId}/release` 的错误码 808660 文案改为: 「该团仍有 {0} 条未取消的订房计划,无法释放:请先逐条删除订房计划后再释放;如需更换认领房务请联系超管接管」 - 改前文案提到的「走接管」对房务不可用(接管只有超管能做)。改后提供两条可行出口:删除计划(房务自助)/ 联系超管接管(超管权限)。 --- ## 一、背景 团期整团认领的唯一真实指针是 `order_group_batch.house_claimer_id`。超管出于"人离职、团转手"的清理需要被允许越过认领校验,但在一个**没人认领、也不需要清理**的团上直接建计划,不属于这类处置。 释放时的 808660 守卫堵住了"有计划+无认领人"的释放口,文案则指向一个房务无法执行的操作(接管),导致房务被无谓地卡住。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 提交订房计划 | POST | `/v3/admin/house/group-batches/{groupBatchId}/room-plans` | 新增入口守卫 | 超管未认领团上返回 808612 | | 2 | 释放认领 | POST | `/v3/admin/order/grab-pool/group-batches/{groupBatchId}/release` | 错误码文案改进 | 808660 文案改为可操作指引 | 网关无改动(既有 `/v3/admin/house/` 与 `/v3/admin/order/` 前缀均可达);返回码、触发条件、已认领团的行为全部不变。 --- ## 三、接口详情 ### 1. 提交订房计划 `POST /v3/admin/house/group-batches/{groupBatchId}/room-plans` **VO**: `GroupBatchRoomPlanSaveReqVO → Result>` #### 使用场景 房务或超管在团期上提交多条订房计划(房型、房数、价格、结算方式、库存扣减等)。超管在未认领的团上调用时新增拒绝,改后只能通过先 takeover 指派给房务的方式排房。 #### 入参字段表 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | 是 | — | 团期 ID | | items | Body | List | 是 | ≤200 条 | 订房计划行 | | items[].stayDate | Body | LocalDate | 是 | 格式 `yyyy-MM-dd` | 入住日期 | | items[].hotelId | Body | Long | 是 | — | 酒店 ID | | items[].roomTypeId | Body | Long | 是 | — | 房型 ID | | items[].roomCount | Body | Integer | 是 | ≥1 | 房间数 | | items[].roomCategory | Body | String | 否 | ≤32 | 房型大类 code;服务端以 resource 权威值覆盖,仅用于前端回显 | | items[].protoPrice | Body | BigDecimal | 否 | ≥0 | 协议价快照;不传按「日历价 → 酒店协议价」兜底 | | items[].settlementPrice | Body | BigDecimal | 否 | ≥0 | 结算价快照;不传按「日历价 → 协议价」兜底 | | items[].settleType | Body | String | 否 | `cash` \| `sign` \| `company` | 结算方式;不传取酒店资源配置 | | items[].deductInventory | Body | Boolean | 否 | 默认 true | 是否扣库存 | | items[].remark | Body | String | 否 | ≤512 | 备注 | | items[].version | Body | Integer | 否 | — | 乐观锁版本;本端点(批量新建)不校验,只有单行修改端点必填并校验 | | items[].replaceReason | Body | String | 否 | ≤256 | 替换原因;本端点不使用,仅单行修改端点在语义为"删旧建新"时使用 | #### 出参字段表 | 字段 | 类型 | 说明 | |------|------|------| | planId | String | 计划行 ID(雪花 ID,序列化为字符串) | | groupBatchId | String | 团期 ID(雪花 ID,序列化为字符串) | | stayDate | LocalDate | 入住日期 | | hotelId / hotelName | String / String | 酒店 ID(序列化为字符串)及名称 | | roomTypeId / roomTypeName | String / String | 房型 ID(序列化为字符串)及名称 | | roomCount | Integer | 房间数 | | protoPrice | String | 协议价快照,可为 null(BigDecimal,序列化为字符串) | | settlementPrice | String | 结算价快照,可为 null(BigDecimal,序列化为字符串) | | settleType | String | 结算方式快照,可为 null | | planStatus | String | 状态码(PENDING / CONFIRMED) | | version | Integer | 乐观锁版本号 | #### 请求示例 ```http POST /v3/admin/house/group-batches/2099918391610314754/room-plans HTTP/1.1 Host: api.test.1814.love:9443 Authorization: Bearer Content-Type: application/json { "items": [ { "stayDate": "2026-10-01", "hotelId": 1001, "roomTypeId": 5001, "roomCount": 2, "settlementPrice": 450.00, "deductInventory": true, "remark": "标准间" } ] } ``` #### 响应示例 **成功(已认领团)**: ```json { "code": 200, "message": "成功", "success": true, "data": [ { "planId": "2103794613192208385", "groupBatchId": "2099918391610314754", "stayDate": "2026-10-01", "hotelId": "1001", "hotelName": "丽思卡尔顿", "roomTypeId": "5001", "roomTypeName": "豪华标间", "roomCount": 2, "settlementPrice": "450.00", "planStatus": "PENDING", "version": 1, "createTime": "2026-09-26 14:30:00" } ] } ``` **失败(未认领团,超管)**: ```json { "code": 808612, "message": "该团期尚未被房务整团认领", "success": false, "data": null } ``` #### 空数据 / 降级响应 - 参数校验失败(日期格式错、房间数 ≤0):HTTP 200,`code=400`,message 含具体字段文案;不落库。 #### 错误响应 ```json { "code": 808612, "message": "该团期尚未被房务整团认领", "success": false, "data": null } ``` **其他错误码**: - **808090**:未登录或非房务角色,无权操作。 - **808091**:房务组长为只读监督角色,无权执行该操作(HOUSE_KEEPER_LEAD)。 - **808613**:该团期由其他房务认领,无权操作。 - **808600**:团期当前阶段不允许修改订房计划(订房计划仅在配置阶段可改)。 - **808614**:该入住日在酒店的该房型上已有订房计划,请改用修改单行。 - **808611**:团期出发日或结束日缺失,无法录入订房计划。 - **808602**:入住日不在团期出行区间内。 - **808603**:订房间数必须大于 0。 - **808691**:无法确定房型大类(取权威房型数据失败),请稍后重试。 - **808112**:房型不属于该酒店。 - **100502**:3 秒幂等窗口内重复提交,「订房计划提交处理中,请勿重复提交」。 #### 业务边界 - **整团认领**:认领人本人或超管可提交;非认领房务提交返回 808613;团未认领(`house_claimer_id IS NULL`)时所有人(含超管)返回 808612。 - **超管排房出口**:超管要在未认领的团上排房,必须先 `POST /v3/admin/order/grab-pool/group-batches/{groupBatchId}/takeover` 指派给房务,再来提交计划。 - **库存扣减**:`deductInventory=true` 时,CONFIRMED 状态的计划会扣掉 `resource_hotel_room_inventory` 对应房型该日的可用房数;PENDING 阶段不扣。 - **日期范围**:入住日期必须在团期的出发日期与结束日期之间。 --- ### 2. 释放认领 `POST /v3/admin/order/grab-pool/group-batches/{groupBatchId}/release` **VO**: `HouseGroupReleaseReqVO → Result` #### 使用场景 房务释放所有团期认领,团回到抢单池状态(`house_claimer_id → NULL`)。房务有订房计划待处理时返回 808660,文案告知删除计划或联系超管接管两条出口。 #### 入参字段表 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | groupBatchId | Path | Long | 是 | — | 团期 ID | | reason | Body | String | 否 | ≤200 字 | 释放原因;超管必填且不少于 10 字 | #### 出参字段表 | 字段 | 类型 | 说明 | |------|------|------| | — | null | 成功返回 null | #### 请求示例 ```http POST /v3/admin/order/grab-pool/group-batches/2099918391610314754/release HTTP/1.1 Host: api.test.1814.love:9443 Authorization: Bearer Content-Type: application/json { "reason": "已完成配房" } ``` #### 响应示例 **成功**: ```json { "code": 200, "message": "成功", "success": true, "data": null } ``` **失败(有计划待删)**: ```json { "code": 808660, "message": "该团仍有 4 条未取消的订房计划,无法释放:请先逐条删除订房计划后再释放;如需更换认领房务请联系超管接管", "success": false, "data": null } ``` #### 空数据 / 降级响应 不适用。 #### 错误响应 ```json { "code": 808660, "message": "该团仍有 4 条未取消的订房计划,无法释放:请先逐条删除订房计划后再释放;如需更换认领房务请联系超管接管", "success": false, "data": null } ``` **其他错误码**: - **808655**:该团期不属于当前房务,无法释放;非认领人释放时触发(超管不受此限),CAS 并发失败(读到时归本人、提交时已被超管释放/接管)也报此码。 - **808656**:该团期尚未被认领,无需释放。 - **808657**:超管操作原因长度不足 10 字(房务释放时 reason 可为空)。 - **808090**:未登录或非房务角色,无权操作。 - **808091**:房务组长为只读监督角色,无权执行该操作(HOUSE_KEEPER_LEAD)。 #### 业务边界 - **释放前置**:须先删光全部未取消的订房计划(PENDING + CONFIRMED)才能释放;所有角色(含超管)受 808660 约束,release 本身不碰计划行。 - **幂等**:幂等键按团(`groupBatchId`),窗口 5 秒;窗口内重复提交返回 100502「整团释放处理中,请勿重复提交」。 --- ## 四、契约约束与正确调用方式 | 场景 | 调用方法 | 说明 | |------|---------|------| | ✅ 超管先排房再指派 | 先 `POST /v3/admin/order/grab-pool/group-batches/{id}/takeover` 指派给房务 → 再 `POST .../room-plans` 提交 | takeover 让团被指定房务认领,之后超管仍可在上面操作 | | ✅ 房务提交计划 | `POST /v3/admin/house/group-batches/{id}/room-plans` | 认领的房务可随时提交,RESOURCE_PREPARING 阶段有效 | | ✅ 房务释放有计划 | 先 `DELETE /v3/admin/house/group-batches/{id}/room-plans/{planId}` 逐条删 → 再 `POST .../release` | 删除由 `HouseGroupBatchClaimGuard` 控制,认领人可删(含他人建的),释放成功返回 200 | | ❌ 超管在未认领团直接建计划 | — | 返回 808612,需先 takeover 指派 | | ❌ 有计划待删时释放 | — | 返回 808660,不释放;先删后释 | --- ## 五、数据库行为 无表结构变更、无 Flyway 迁移。 --- ## 六、边界行为 | 场景 | 行为 | |---|---| | 超管 POST room-plans on 未认领团 | 返回 808612,`group_batch_room_plan` 零新增 | | 超管 takeover 后 POST room-plans | 返回 200,计划行正常落库 | | 房务释放、仍有 PENDING 计划 | 返回 808660,计数含 PENDING | | 房务删掉全部计划、再释放 | 返回 200,团回到池里 | | 超管释放、仍有计划 | 同样返回 808660(不分角色) | ## 六.6、修改前后对比 | 维度 | 改前 | 改后 | |---|---|---| | 超管在未认领团上 POST room-plans | 返回 200,计划落库,团仍无主 | 返回 808612,不落库 | | 释放时有计划、808660 文案 | 「无法释放(请先处理订房计划或走接管)」 | 「无法释放:请先逐条删除订房计划后再释放;如需更换认领房务请联系超管接管」 | ## 六.7、影响评估 - **兼容性**:hl-ui 在 `origin/v2.1` 上房务页面无调用 `takeover`(该接口仍在,超管直接用也可以);超管排房流程可能需要调整为"先指派再排"。 - **前端要动的**:房务页面若有"直接排房"的超管入口,需提示"未认领团先指派再排";释放时若遇到 808660,按改后文案提示房务删除计划。 - **数据影响**:无;本单仅加守卫,不改历史数据。 - **其它服务**:product-v2、fleet、user-service 无改动。 --- ## 七、不影响范围 - 已认领团的超管操作(确认、分房、delete 等)全部不动。 - 更新/删除/确认计划的守卫不动(仍走 `assertWritableByCurrentUser`,超管仍可清理)。 - 其他团期相关接口(认领、接管、整团确认等)无改动。 --- ## 八、测试环境已验证 部署:测试环境 order-v3 `1f65d7894`(含 e2313790b)。网关 `api.test.1814.love:9443`。 | # | 场景 | 实测结果 | |---|---|---| | 1 | 超管 POST 未认领团(RESOURCE_PREPARING) | code 808612,计划行零新增 | | 2 | 该团由超管 takeover 给某房务后,超管再 POST | code 200 | | 3 | 超管 DELETE 上一步新增的计划行 | code 200,`releasedLogId=null` | | 4 | 该房务 release | code 200,团回到无主 | | 5 | 另一认领房务逐条 DELETE 自己团的 4 条 PENDING 计划 | 全部 200(`releasedLogId=null`) | | 6 | 上一步之后 release | code 200 | | 7 | 超管对他人认领团的存量计划行 DELETE(62 行) | 全部 200 | | 8 | 认领房务在团上挂 1 条 PENDING 计划时 release | code 808660,message「该团仍有 1 条未取消的订房计划,无法释放:请先逐条删除订房计划后再释放;如需更换认领房务请联系超管接管」,与源码逐字一致 | | 9 | 删掉该计划后再 release | code 200,团回到无主 | --- ## 十、相关文档 - Issue:https://git.1814.love/wx/HL/issues/8385 - PR:https://git.1814.love/wx/HL/pulls/8395 - takeover API:`POST /v3/admin/order/grab-pool/group-batches/{groupBatchId}/takeover` - 同批单号:#8386、#8387、#8388、#8389、#8390(房务接口审计批次) --- ## 关联 / 联系人 ### 联系人 - **后端负责人**: @wx