docs(changelog): #7322 团期整团抢单与抢单池分流(前端契约交接件)
changelog-filename-gate / validate (push) Successful in 2s
changelog-filename-gate / validate (push) Successful in 2s
五个新端点(抢单池列表/认领/释放/接管/我的团)+ 五个既有端点改造。 团单户不再进逐户抢单池,改以整团为单位认领;order_group_batch 新增 house_claimer_id / house_claimer_name / house_claimed_at 三列(Flyway V20260910_301 已执行)。 已部署测试服 HEAD 6d41d6148,32 条验收项中 28 条已取证, 含用 5 个真实登录账号过网关实测的归属与接管链路。 ⚠️ 正文已注明一项尚缺的交付:sys_menu 的两行抢单池菜单入口尚未随迁移落库, 在补上之前新端点只能直接调用、后台菜单里点不到。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,959 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7322"
|
||||
title: "团期整团抢单与抢单池分流:新增团期抢单池/我的团/接管四写口,普通抢单池排除团单逐户行"
|
||||
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: "本篇覆盖 PR #7436(合并提交 ab4118a01)。2026-09-10 已部署测试服(HEAD 6d41d6148,已验 ab4118a01 为其祖先),Flyway V20260910_301 执行成功,order_group_batch 三列 house_claimer_id/house_claimer_name/house_claimed_at 已落库。32 条验收项中 28 条已取证,含用 5 个真实登录账号过网关实测的归属与接管链路。⚠️ 尚缺一项交付:后台菜单(sys_menu)的两行抢单池入口尚未随 迁移落库,已另行补 Flyway;在那之前新端点只能直接调用、后台菜单里点不到。"
|
||||
updated_at: "2026-09-10"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期模块:整团抢单与抢单池分流(新增团期抢单池/我的团/团级接管,普通池排除团单逐户行)
|
||||
|
||||
> **服务**: `hl-order-service-v3`
|
||||
> **PR**: #7436
|
||||
> **Issue**: #7322
|
||||
> **日期**: 2026-09-10
|
||||
> **影响范围**: 房务管家「抢单池」体系——新增「抢单池·团期」与「我的团」两组共 5 个端点;改造既有「抢单池·普通」列表与逐户 claim/transfer 两个端点;团期详情追加 3 个只读字段
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
给房务加「整团认领」:团期管理员整体确认需求后,该团以**整团一条**进入独立的「团期抢单池」,由一个房务整团认领;此前团单会把每一户拆成一行落进**普通**抢单池,被抢 N 次、落到 N 个房务手上——本次同步把团单逐户行从普通池移出。
|
||||
|
||||
1. **新增 5 个端点**(详见「三、接口详情」):团期抢单池列表、整团认领、整团释放、**团级接管(超管专属,处理历史脏数据死结)**、我的团(含组长/超管监督视图)。
|
||||
2. **`GET /v3/admin/order/grab-pool/hotel-requirements`(普通抢单池)行为变化**:追加 `order_main.product_batch_id IS NULL` 过滤,团期子订单的逐户需求行**不再出现**在普通池,`total` 同口径减少;`productType=GROUP` 筛选仍接受但结果恒为空。
|
||||
3. **`POST /v3/admin/order/hotel-requirements/{requirementId}/claim`(逐户抢单)行为变化**:对团期子订单新增守卫,**无条件**拒绝,新增错误码 **808650**,零写入。
|
||||
4. **`POST /v3/admin/order/hotel-requirements/{requirementId}/transfer`(逐户转单)行为变化**:对团期子订单新增守卫——普通房务恒拒 808650;`SUPER_ADMIN` 仅在该团**尚未被整团认领**(`house_claimer_id IS NULL`)时放行,用于把历史脏数据集中到一个在职房务名下,团已被整团认领后超管也拒。
|
||||
5. **`POST /v3/admin/order/hotel-requirements/{requirementId}/release`(逐户释放)行为变化**:⚠️ 与工单正文「口径与定案 #6」早期结论(release 不改)不同——2026-09-08 第六轮复审后追加了一道更严守卫:**团期子订单**若仍存在任意 active 逐户配房行(不限 `confirm_status`,含询房中 `INQUIRING` 候选)→ 新增错误码 **808662**,零写入;非团单行为完全不变(仍是既有的「仅 `CONFIRMED` 阻塞释放」口径 808021)。
|
||||
6. **`GET /v3/admin/order/group-batch/{groupBatchId}`(团期详情)追加 3 个只读字段**:`houseClaimerId` / `houseClaimerName` / `houseClaimedAt`(均可空,未认领为 `null`),供团期管理员侧展示「负责房务」。
|
||||
7. **团级指针是团单房务归属的唯一真源,认领/接管不回写任何户级字段**(不动 `claimer_id`/`status`/`house_status`/`room_control_status`,不 fire 户级状态机,不绑会话,不广播 SSE)——认领后户级 `room_control_status` 仍会显示「待配房」直到后续订房工单(`#7325`)把它置 `DONE`,这是有意为之,不是缺陷。
|
||||
8. **无 SSE 广播**:团期池/我的团页面需前端手动或定时刷新,不会像普通抢单池那样实时推送变更。
|
||||
9. **不新增权限码**,5 个新端点走既有的「房务角色门」(`HouseWriteGuard`,写口)与新增的读口角色门(`ROOM_MANAGER`/`house_keeper_lead`/`SUPER_ADMIN`,否则 808090),hl-user-service **零 Flyway**(菜单种子是否需要待确认,见「口径与定案 #11」,如需要会是独立的 user-service Flyway,不影响本篇端点契约)。
|
||||
10. **数据库变更**:`order_group_batch` 新增 3 列 + 1 索引(`V20260910_301__order_group_batch_add_house_claimer.sql`),无新表。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
团期管理员点「整体确认」后,`RequirementService.dispatchGroupHotelRequirements` 把每一户的 active 房需求 CAS 成 `status=PENDING`、`claimer_id=NULL`,而普通抢单池此前无任何团单排除条件——一个 30 户的团在普通池里是 30 行,会被抢 30 次、落到 N 个房务手上。「某个房务认领了整个团」这个概念此前在代码里不存在:`GroupBatchDO.batchManagerId` 语义是「团期管理员」不是房务,且全仓零写入。
|
||||
|
||||
本单给 `order_group_batch` 加三列认领指针,新建团期专属的抢单池/我的团/接管三个写口 + 两个读口,并把团单逐户行从普通池移出、给逐户 claim/transfer 加团单守卫,杜绝「一个团被多个房务分持」。历史脏数据(团单户已被逐户抢走)由「团级接管」端点(超管专属)兜底处理,测试库不存在生产存量(`origin/main` 上团期相关三个核心类全部零命中,二期功能尚未上线现网)。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 团期抢单池列表 | GET | `/v3/admin/order/grab-pool/group-batches` | 新增 | 整团一条,分页 + 多维筛选 |
|
||||
| 2 | 整团认领 | POST | `/v3/admin/order/grab-pool/group-batches/{groupBatchId}/claim` | 新增 | CAS 先抢先得,户级零副作用 |
|
||||
| 3 | 整团释放回池 | POST | `/v3/admin/order/grab-pool/group-batches/{groupBatchId}/release` | 新增 | 认领人本人或超管 |
|
||||
| 4 | 团级接管 | POST | `/v3/admin/order/grab-pool/group-batches/{groupBatchId}/takeover` | 新增 | 超管专属,处理历史脏数据死结 |
|
||||
| 5 | 我的团 | GET | `/v3/admin/order/grab-pool/my-claims/group-batches` | 新增 | 含 `scope=all` 组长/超管监督视图 |
|
||||
| 6 | 普通抢单池列表 | GET | `/v3/admin/order/grab-pool/hotel-requirements` | 行为修改 | 追加排除团单逐户行 |
|
||||
| 7 | 逐户抢单 | POST | `/v3/admin/order/hotel-requirements/{requirementId}/claim` | 行为修改 | 新增 808650 团单守卫 |
|
||||
| 8 | 逐户转单 | POST | `/v3/admin/order/hotel-requirements/{requirementId}/transfer` | 行为修改 | 新增 808650 团单守卫(超管有条件例外) |
|
||||
| 9 | 逐户释放 | POST | `/v3/admin/order/hotel-requirements/{requirementId}/release` | 行为修改 | 团单户新增 808662 更严守卫(active 配房未清空不可释放),非团单不变 |
|
||||
| 10 | 团期详情 | GET | `/v3/admin/order/group-batch/{groupBatchId}` | 行为修改 | 响应追加 3 个只读字段 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 团期抢单池列表 `GET /v3/admin/order/grab-pool/group-batches`
|
||||
|
||||
**VO**: `HouseGroupGrabPoolPageReqVO → PageResult<HouseGroupGrabPoolItemRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务管家「抢单池·团期」页,展示未被认领、需求已整体确认、团期阶段在四态内的团(整团一条)。角色 `ROOM_MANAGER` / `house_keeper_lead` / `SUPER_ADMIN`,其它角色 808090。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| keyword | Query | String | 否 | ≤32 字 | `batchNo`/`productName` 模糊二选一 OR |
|
||||
| productId | Query | Long | 否 | — | 产品精确过滤 |
|
||||
| batchStatus | Query | String | 否 | 只接受 `RESOURCE_PREPARING`/`MATERIAL_PREPARING`/`PENDING_DEPARTURE`/`TRAVELLING`,非法值 400 | 团期阶段精确过滤 |
|
||||
| departDateFrom / departDateTo | Query | LocalDate | 否 | `yyyy-MM-dd` | 出发日区间 |
|
||||
| page | Query | Integer | 否 | ≥1,默认 1 | 页码 |
|
||||
| pageSize | Query | Integer | 否 | 1-100,默认 20 | 每页条数 |
|
||||
| sortBy | Query | String | 否 | `departDate,asc`(默认)/ `createTime,desc` | 排序 |
|
||||
|
||||
#### 出参字段表 `Result<PageResult<HouseGroupGrabPoolItemRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.list[].groupBatchId` | String(Long ToString) | 团期主订单 ID |
|
||||
| `data.list[].batchNo` | String | 运营团期号 |
|
||||
| `data.list[].productId` | String(Long ToString) | 产品 ID |
|
||||
| `data.list[].productName` | String | 产品名快照 |
|
||||
| `data.list[].batchName` | String | 班期名快照 |
|
||||
| `data.list[].batchLabel` | String | 第 N 期快照 |
|
||||
| `data.list[].batchStatus` | String | 团期状态 code |
|
||||
| `data.list[].batchStatusLabel` | String | 团期状态中文 |
|
||||
| `data.list[].departDate` / `endDate` / `enrollDeadline` | LocalDate | 可空 |
|
||||
| `data.list[].enrolledRooms` / `enrolledPeople` | Integer | 已报名房数 / 人数(持久计数器直取) |
|
||||
| `data.list[].activeOrderCount` | Integer | 活跃子订单数(排除 `CANCELLED`) |
|
||||
| `data.list[].hotelOrderCount` | Integer | 已放行到房务的需房户数(active 房需求 `status ∈ {PENDING,PROCESSING,DONE}` 的户数) |
|
||||
| `data.list[].hotelReady` | Boolean | 团期酒店资源是否已就绪 |
|
||||
| `data.list[].daysToDepart` | Integer | 今天到出发日天数,无出发日 `null` |
|
||||
| `data.list[].urgencyLevel` / `urgencyLabel` | String | 紧急度 code/中文(`NORMAL`/`URGENT`/`CRITICAL`) |
|
||||
| `data.list[].createTime` | LocalDateTime | 团期创建时间 |
|
||||
|
||||
(字段取自 `HouseGroupGrabPoolItemRespVO.java:17-86`。)
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
GET /v3/admin/order/grab-pool/group-batches?page=1&pageSize=20&sortBy=departDate,asc
|
||||
Authorization: Bearer <持房务三角色之一的管理端 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "message": "成功", "success": true,
|
||||
"data": {
|
||||
"total": 1, "pages": 1, "current": 1, "size": 20,
|
||||
"list": [
|
||||
{ "groupBatchId": "1930000000000000001", "batchNo": "GB26060101", "productId": "1001",
|
||||
"productName": "额吉的故乡", "batchName": "6月首发团", "batchLabel": "第3期",
|
||||
"batchStatus": "RESOURCE_PREPARING", "batchStatusLabel": "资源准备中",
|
||||
"departDate": "2026-06-15", "endDate": "2026-06-20", "enrollDeadline": "2026-06-10",
|
||||
"enrolledRooms": 12, "enrolledPeople": 26, "activeOrderCount": 12, "hotelOrderCount": 10,
|
||||
"hotelReady": false, "daysToDepart": 12, "urgencyLevel": "NORMAL", "urgencyLabel": "正常",
|
||||
"createTime": "2026-05-01 10:00:00" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
(示例字段取自 VO 定义与 `HouseGroupGrabService.toPoolItem` 装配逻辑,非真实网关调用。)
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无符合条件的团时 `list` 为空数组、`total=0`,返回 200。0 需房户的团(全团客户自订酒店)同样进池,`hotelOrderCount=0` 明示,判定条件不含需房户数。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808090, "message": "未登录或非房务角色,无权操作", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 入池判定 = `house_claimer_id IS NULL AND requirement_confirmed=1 AND batch_status IN (RESOURCE_PREPARING, MATERIAL_PREPARING, PENDING_DEPARTURE, TRAVELLING)`(+ 未软删)。
|
||||
- 读口角色门与写口不同:`house_keeper_lead`(房务组长)在这里**可以正常查看**(三角色平权只读),只有写操作(认领/释放/接管)才会把组长挡在 808091。
|
||||
- 无角色(系统态/内部调用/单测)放行,与 `HouseWriteGuard` 同口径。
|
||||
|
||||
---
|
||||
|
||||
### 2. 整团认领 `POST /v3/admin/order/grab-pool/group-batches/{groupBatchId}/claim`
|
||||
|
||||
**VO**: `无请求体 → Result<Void>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务在团期抢单池卡片点击「认领」,CAS 先抢先得。成功后只写团级三列 + 团期时间线 + 通知事件,**户级行零变化**。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `groupBatchId` | Path | Long | 是 | 团期主订单 ID(雪花) | 前端应按 String 传递避免精度丢失 |
|
||||
|
||||
无请求体。
|
||||
|
||||
#### 出参字段表 `Result<Void>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data` | null | 成功即 200,与逐户 claim 一致 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
POST /v3/admin/order/grab-pool/group-batches/1930000000000000001/claim
|
||||
Authorization: Bearer <持 ROOM_MANAGER 或 SUPER_ADMIN 的管理端 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "success": true, "data": null }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无(写操作)。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808654, "message": "该团有 2 户已被房务 王五 逐户抢单,请先释放后再整团认领", "success": false, "data": null }
|
||||
```
|
||||
|
||||
| 码 | 符号 | 触发 |
|
||||
|---|---|---|
|
||||
| 808090 | `NOT_LOGIN_OR_NOT_HOUSE_ROLE` | 未登录 / 非房务角色 |
|
||||
| 808091 | `READ_ONLY_LEAD_FORBIDDEN` | 房务组长(只读监督,不可写) |
|
||||
| 589500 | `GROUP_BATCH_NOT_FOUND` | 团期不存在 |
|
||||
| 808654 | `GB_GRAB_HOUSEHOLD_CLAIMED_BY_OTHERS` | 「该团有 {0} 户已被房务 {1} 逐户抢单,请先释放后再整团认领」——有户被**别人**逐户抢走 |
|
||||
| 808659 | `GB_GRAB_TAKEOVER_HAS_HOUSE_ASSIGNMENT` | 「该团有 {0} 户存在逐户配房(首个订单 {1}),请先删除配房后再操作」——分流后剩余户存在 active 逐户配房行 |
|
||||
| 808651 | `GB_GRAB_BATCH_NOT_CLAIMABLE` | 「该团期当前不可认领(需求未整体确认或团期阶段不允许)」——CAS 落空且未被他人认领 |
|
||||
| 808652 | `GB_GRAB_BATCH_ALREADY_CLAIMED` | 「该团期已被其他房务认领」 |
|
||||
| 808653 | `GB_GRAB_BATCH_CLAIMED_BY_SELF` | 「该团期已由您认领,请勿重复认领」 |
|
||||
|
||||
(错误码字面量与消息文案取自 `HouseGroupBatchErrorCode.java:72-121`。)
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 顺序不可颠倒:脏数据预检(808654)→ 冻结名单分流(历史已 finalize 户不受影响)→ 无损前置(808659)→ 团级 CAS → 时间线 → afterCommit 通知(`HouseGroupGrabService.claim`,`HouseGroupGrabService.java:245-284`)。
|
||||
- 全部持有人都是本人时 808654 不触发(本人把「逐户抢的团」升级成整团认领)。
|
||||
- 认领**不**回写户级 `claimer_id`/`status`/`house_status`/`room_control_status`,不 fire 户级状态机,不绑定会话,不广播 SSE——团级指针是唯一真源。
|
||||
- 三个写口(claim/release/takeover)共用同一把团级 `@Lock4j`(`name=HouseGroupBatchLockConstants.GROUP_BATCH_HOUSE_LOCK_NAME`),配合 CAS 保证并发安全,与幂等注解(`@Idempotent`,5 秒防重复提交)叠加。
|
||||
|
||||
---
|
||||
|
||||
### 3. 整团释放回团期池 `POST /v3/admin/order/grab-pool/group-batches/{groupBatchId}/release`
|
||||
|
||||
**VO**: `HouseGroupReleaseReqVO → Result<Void>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
认领人本人释放自己认领的团,或 `SUPER_ADMIN` 强制释放任意团(需附理由)。释放后该团若仍满足进池条件即重新出现在团期池。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `groupBatchId` | Path | Long | 是 | 团期主订单 ID | — |
|
||||
| `reason` | Body | String | 否(超管必填≥10字) | ≤200 字 | 超管释放必填理由(trim 后 <10 字 → 808657);普通房务可空,空白兜底文案「释放回池」 |
|
||||
|
||||
请求体可为 `null`(`@RequestBody(required=false)`)。
|
||||
|
||||
#### 出参字段表 `Result<Void>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data` | null | 成功 200 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
POST /v3/admin/order/grab-pool/group-batches/1930000000000000001/release
|
||||
Authorization: Bearer <认领人本人 token>
|
||||
|
||||
{ "reason": "临时调休,交回团期池" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "success": true, "data": null }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808655, "message": "该团期不属于当前房务,无法释放", "success": false, "data": null }
|
||||
```
|
||||
|
||||
| 码 | 符号 | 触发 |
|
||||
|---|---|---|
|
||||
| 808090 / 808091 | 角色门 | 同上 |
|
||||
| 589500 | `GROUP_BATCH_NOT_FOUND` | 团期不存在 |
|
||||
| 808656 | `GB_GRAB_BATCH_NOT_CLAIMED` | 「该团期尚未被认领,无需释放」 |
|
||||
| 808655 | `GB_GRAB_RELEASE_NOT_OWNER` | 「该团期不属于当前房务,无法释放」——非认领人且非超管;或并发落空(CAS 时已被他人释放/接管) |
|
||||
| 808657 | `GB_GRAB_ADMIN_RELEASE_REASON_TOO_SHORT` | 「超管操作原因长度不足 10 字」 |
|
||||
| **808660** | `GB_HOUSE_RELEASE_HAS_ROOM_PLAN` | 「该团仍有 {0} 条未取消的订房计划,无法释放(请先处理订房计划或走接管)」——该团存在未取消的订房计划行(`GroupBatchRoomPlanService.countActivePlans`) |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 判定顺序:登录/角色门 → 团期存在 → 归属判定(本人/超管)→ reason 长度(超管)→ **已订房守卫 808660** → CAS 释放 → 时间线 → 通知(`HouseGroupGrabService.release`,`HouseGroupGrabService.java:296-341`)。
|
||||
- 808660 存在的理由:若无此守卫,把团释放成无主但计划行与已扣库存都还在,之后没有任何角色能继续处理;超管要强行换人须走「团级接管」而非「释放」。
|
||||
- 释放后原认领人的历史轨迹只记团期时间线(`BATCH_HOUSE_RELEASE`),不落回任何户级表。
|
||||
|
||||
---
|
||||
|
||||
### 4. 团级接管 `POST /v3/admin/order/grab-pool/group-batches/{groupBatchId}/takeover`
|
||||
|
||||
**VO**: `HouseGroupTakeoverReqVO → Result<HouseGroupTakeoverRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
**超管专属**。用于处理历史脏数据死结——当同一历史团的户被两个不同房务分别持有、且至少一户已有确认配房时,整团 `claim`(撞 808654)、持有人逐户 `release`(撞既有的 808021 已确认配房守卫)、逐户 `transfer`(撞本单新增的 808650)三条路同时封死,接管是唯一的无损出口。接管 = 覆盖式指定团期房务归属 + 清理该团历史遗留的户级抢单归属。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `groupBatchId` | Path | Long | 是 | 团期主订单 ID | — |
|
||||
| `toUserId` | Body | Long | 是 | 须在 user-service 在职房务列表内,否则 808011 | 接管人 userId |
|
||||
| `reason` | Body | String | 是 | trim 后 10-200 字 | 接管原因,写入团级时间线 |
|
||||
|
||||
#### 出参字段表 `Result<HouseGroupTakeoverRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.groupBatchId` | String(Long ToString) | 团期主订单 ID |
|
||||
| `data.fromClaimerId` | String(Long ToString,可空) | 接管前的团级认领人(未认领时 `null`) |
|
||||
| `data.toClaimerId` | String(Long ToString) | 接管后的团级认领人 |
|
||||
| `data.toClaimerName` | String | 接管人真实姓名(user-service 解析失败时兜底 `user-{id}`) |
|
||||
| `data.clearedOrderIds` | Array\<String\> | 本次被清掉户级 `claimer_id` 的子订单 ID 列表 |
|
||||
| `data.legacyFinalizedOrderIds` | Array\<String\> | 冻结名单里的历史户(保留逐户配房,不迁移,见「业务边界」) |
|
||||
| `data.skippedOrderIds` | Array\<String\> | 户级 CAS 并发落空、未清掉的子订单,需重跑或人工处理 |
|
||||
|
||||
(字段取自 `HouseGroupTakeoverRespVO.java:16-46`。)
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
POST /v3/admin/order/grab-pool/group-batches/1930000000000000001/takeover
|
||||
Authorization: Bearer <持 SUPER_ADMIN 的管理端 token>
|
||||
|
||||
{ "toUserId": 30002, "reason": "原认领房务离职,指派新房务接管该团" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "message": "成功", "success": true,
|
||||
"data": {
|
||||
"groupBatchId": "1930000000000000001", "fromClaimerId": "30001", "toClaimerId": "30002",
|
||||
"toClaimerName": "李四", "clearedOrderIds": ["100", "101"],
|
||||
"legacyFinalizedOrderIds": ["102"], "skippedOrderIds": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
(示例结构取自 `HouseGroupTakeoverRespVO` 字段定义与 `HouseGroupGrabService.takeoverTx` 装配逻辑,非真实网关调用。)
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无被清户/冻结户/落空户时对应数组为空列表 `[]`,不是 `null`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808658, "message": "团期接管仅超级管理员可操作", "success": false, "data": null }
|
||||
```
|
||||
|
||||
| 码 | 符号 | 触发 |
|
||||
|---|---|---|
|
||||
| 808090 / 808091 | 角色门 | 未登录/非房务角色/组长只读 |
|
||||
| **808658** | `GB_GRAB_TAKEOVER_NOT_SUPER_ADMIN` | 「团期接管仅超级管理员可操作」——非超管调用 |
|
||||
| 808657 | `GB_GRAB_ADMIN_RELEASE_REASON_TOO_SHORT` | `reason` trim 后 <10 字 |
|
||||
| 808011 | `RECEIVER_NOT_FOUND` | 「接收人不存在或已离职」——`toUserId` 不在在职房务列表 |
|
||||
| 589500 | `GROUP_BATCH_NOT_FOUND` | 团期不存在 |
|
||||
| 808659 | `GB_GRAB_TAKEOVER_HAS_HOUSE_ASSIGNMENT` | 分流后剩余户存在 active 逐户配房行,须先删配房 |
|
||||
| 808651 | `GB_GRAB_BATCH_NOT_CLAIMABLE` | 团级覆盖 CAS 落空(团期已被软删等极端并发场景) |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **判定顺序**(不可颠倒):超管判定 → `reason` 长度 → 接管人在职校验(Feign,**事务外**执行)→ 团期存在 → 冻结名单分流 → 无损前置(808659)→ 清剩余户级归属(CAS 落空计入 `skippedOrderIds`,不抛错)→ 团级覆盖 CAS → 时间线(复用 `BATCH_HOUSE_CLAIM` 事件类型,靠 `extra.takeover=true` 区分)→ afterCommit 通知(`HouseGroupGrabService.takeover`/`takeoverTx`,`HouseGroupGrabService.java:368-465`)。
|
||||
- **为什么接口是非事务外壳 + 事务内层两段**:接管人姓名解析要调 `houseStaffFeign.listHouseStaff`(Feign),与写操作放在同一事务违反「事务内禁同步 Feign」(CODE_RULES §9);外壳(`takeover`)纯读完成超管判定/reason 校验/姓名解析后,经自注入代理调用真正的事务方法(`takeoverTx`)。**对外契约(路径/入参/响应/错误码/执行顺序)不受这个内部拆分影响**,前端无需关心。
|
||||
- **冻结名单(`legacyFinalizedOrderIds`)语义**:接管时若某户在**首次**启用团期归属那一刻 `house_status=CONFIRMED`(即已有实际住宿),该户会被冻结、保留其既有的逐户 `house_hotel_assignment` 配房,**不迁移、不清 `claimer_id`、不计入团期需求分母、不计入团期分房**;此后该户即使重跑接管/认领也保持冻结(只读表,不重算)。
|
||||
- **无损前置(808659)只对分流后的剩余户求值**:真正冻结的历史户不受该守卫影响;判定用「是否存在 active 逐户配房行」(不限 `confirm_status`,含询房中候选),因为候选房同样占用已扣库存。
|
||||
- `skippedOrderIds` 非空时是并发导致的部分成功(户级 CAS 落空但团级已覆盖成功),需要人工核对或重试清理这些户的残留 `claimer_id`。
|
||||
- Feign 调用仍在 Controller 的团级 `@Lock4j` 范围**之内**(有意如此,非漏网):把它挪到锁外只能挪进 Controller 做业务编排,违反分层铁律;持分布式锁期间卡住只让同团后续写入排队(锁有 30 秒过期兜底),比持 DB 事务卡住吊死连接池代价小得多。
|
||||
|
||||
---
|
||||
|
||||
### 5. 我的团 `GET /v3/admin/order/grab-pool/my-claims/group-batches`
|
||||
|
||||
**VO**: `HouseMyGroupPageReqVO → HouseMyGroupPageRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
当前房务查看自己已整团认领的团(`scope=mine`,默认);房务组长/超管可传 `scope=all` 查看全部已认领团(监督视图,行内带认领人)。不限团期阶段——已认领的团可能已流团/已结束,本单不自动释放指针,房务仍要看得见并手动释放。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| scope | Query | String | 否 | `mine`(默认)/ `all` | 普通房务传 `all` → 808092 |
|
||||
| keyword | Query | String | 否 | ≤32 字 | `batchNo`/`productName` 模糊 |
|
||||
| batchStatus | Query | String | 否 | 团期九态任一,非法值 400 | — |
|
||||
| needsReconfirm | Query | Boolean | 否 | `true`=只看 `requirement_confirmed=0` 的团 | 待管理员重新确认 |
|
||||
| departDateFrom / departDateTo | Query | LocalDate | 否 | — | 出发日区间 |
|
||||
| claimedAtFrom / claimedAtTo | Query | LocalDateTime | 否 | `yyyy-MM-dd'T'HH:mm:ss` | 认领时间区间 |
|
||||
| page / pageSize | Query | Integer | 否 | 默认 1/20,pageSize≤100 | — |
|
||||
| sortBy | Query | String | 否 | `claimedAt,desc`(默认)/ `departDate,asc` | — |
|
||||
|
||||
#### 出参字段表 `Result<HouseMyGroupPageRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.total` | Long | 当前筛选条件下的总数(DB count,任何情况下都准) |
|
||||
| `data.stats.total` | Long | 该 scope 下已认领团数(不叠加列表筛选,只按 scope/认领人过滤) |
|
||||
| `data.stats.needsReconfirm` | Long | 其中待管理员重新确认的团数;**`stats.total` 超过扫描上限 500 时为 `null`**,前端显示「—」 |
|
||||
| `data.stats.cancelled` | Long | 其中已流团的团数;同口径超上限为 `null` |
|
||||
| `data.list[]` | `HouseMyGroupItemRespVO` | 字段 = 团期池列表项全部字段(见「## 1.」出参)+ 下列 4 个 |
|
||||
| `data.list[].houseClaimerId` | String(Long ToString) | 整团认领房务 adminId |
|
||||
| `data.list[].houseClaimerName` | String | 整团认领房务姓名 |
|
||||
| `data.list[].houseClaimedAt` | LocalDateTime | 整团认领时间 |
|
||||
| `data.list[].requirementConfirmed` | Boolean | `false` 时前端应标「待管理员重新确认」 |
|
||||
|
||||
(字段取自 `HouseMyGroupPageRespVO.java`、`HouseMyGroupItemRespVO.java`、`HouseMyGroupStatsVO.java`。)
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
GET /v3/admin/order/grab-pool/my-claims/group-batches?scope=mine&page=1&pageSize=20
|
||||
Authorization: Bearer <房务 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "message": "成功", "success": true,
|
||||
"data": {
|
||||
"total": 1,
|
||||
"stats": { "total": 1, "needsReconfirm": 0, "cancelled": 0 },
|
||||
"list": [
|
||||
{ "groupBatchId": "1930000000000000001", "batchNo": "GB26060101", "productId": "1001",
|
||||
"productName": "额吉的故乡", "batchStatus": "RESOURCE_PREPARING", "batchStatusLabel": "资源准备中",
|
||||
"activeOrderCount": 12, "hotelOrderCount": 10, "hotelReady": false,
|
||||
"houseClaimerId": "30001", "houseClaimerName": "张三",
|
||||
"houseClaimedAt": "2026-06-01 10:00:00", "requirementConfirmed": true }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
(部分字段省略以节省篇幅,完整字段见出参字段表;示例非真实网关调用。)
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
未认领任何团时 `list=[]`、`total=0`、`stats.total=0`。`stats.total` 超过 500(扫描上限)时 `stats.needsReconfirm`/`stats.cancelled` 为 `null`(不是少算的数字),前端应显示「—」而不是 0——这是刻意设计,避免与 `total` 自相矛盾。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808090, "message": "未登录或非房务角色,无权操作", "success": false, "data": null }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "code": 808092, "message": "无权查看全部房务订单(仅房务组长或超管可查看)", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `scope=all` 仅 `house_keeper_lead`/`SUPER_ADMIN` 可用,普通房务传 `all` → 808092(复用既有码,不是 808091「组长只读」,文案更贴切)。
|
||||
- `stats` 统计口径**只按 `scope`/认领人过滤,不叠加 `keyword`/日期等列表筛选**——头部数字是「我一共有多少团」,不随筛选跳动。
|
||||
|
||||
---
|
||||
|
||||
### 6. 普通抢单池列表(改造) `GET /v3/admin/order/grab-pool/hotel-requirements`
|
||||
|
||||
**VO**: `HouseGrabPageReqVO → PageResult<HouseGrabPageItemRespVO>`(本单**不改**任何入参/出参字段,只改查询条件)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务管家「抢单池·普通」页,逐户一条。本次改造后团期子订单的逐户需求行不再出现,只保留非团单的逐户需求。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
无字段变化,沿用既有 `HouseGrabPageReqVO`:
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| keyword | Query | String | 否 | — | 订单号/团号/客人姓名/产品名 OR 模糊 |
|
||||
| productType | Query | String | 否 | `CORE`/`ROUTE`/`CUSTOM`/`GROUP` | 传 `GROUP` 改后恒返空页(见业务边界) |
|
||||
| productName | Query | String | 否 | — | 产品名模糊 |
|
||||
| consultantId | Query | Long | 否 | — | 定制师精确 |
|
||||
| guestName | Query | String | 否 | — | 客人姓名模糊 |
|
||||
| departDateFrom / departDateTo | Query | LocalDate | 否 | — | 出发日区间 |
|
||||
| page / pageSize | Query | Integer | 否 | 默认 1/20,pageSize≤100 | — |
|
||||
| sortBy | Query | String | 否 | `createTime,desc`(默认)/ `departDate,asc` | — |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
无字段变化,沿用既有 `HouseGrabPageItemRespVO`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.list[].id` / `orderId` / `orderNo` / `teamNo` / `guestName` / `personsDesc` | 各自既有类型 | 需求/订单基本信息,本次不改 |
|
||||
| `data.list[].productType` / `productName` / `productNo` / `route` / `departDate` / `nights` / `cities` / `totalAmount` | 各自既有类型 | 产品/行程信息,本次不改 |
|
||||
| `data.list[].consultantName` / `consultantId` / `consultantRemark` | 各自既有类型 | 定制师信息,本次不改 |
|
||||
| `data.list[].requirementNote` / `dispatchRemark` / `special` / `requirementVersion` | 各自既有类型 | 需求备注/版本,本次不改 |
|
||||
| `data.list[].urgencyLevel` / `urgencyLabel` / `daysToDepart` / `manualUrgent` / `createTime` | 各自既有类型 | 紧急度/时间信息,本次不改 |
|
||||
| `data.list[].isRework` / `reworkPrevClaimerName` | 各自既有类型 | 返工标记,本次不改 |
|
||||
|
||||
(完整 29 个字段定义见 `HouseGrabPageItemRespVO.java:28-119`,本单零改动,此处不逐一展开类型。)
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
GET /v3/admin/order/grab-pool/hotel-requirements?page=1&pageSize=20&sortBy=createTime,desc
|
||||
Authorization: Bearer <房务 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "message": "成功", "success": true,
|
||||
"data": {
|
||||
"total": 1, "pages": 1, "current": 1, "size": 20,
|
||||
"list": [
|
||||
{ "id": "7001", "orderId": "200", "orderNo": "HL2606000200", "teamNo": null,
|
||||
"guestName": "王五", "productType": "CORE", "productName": "川西深度",
|
||||
"departDate": "2026-06-10", "createTime": "2026-06-01 09:00:00" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
(部分字段省略以节省篇幅;完整结构见出参字段表,示例非真实网关调用。)
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无符合条件行时 `list=[]`、`total=0`,返回 200;本次改造后团期子订单逐户行不再计入 `total`,非团单场景的空数据行为与改前完全一致。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 400, "message": "参数校验失败", "success": false, "data": null }
|
||||
```
|
||||
|
||||
无新增业务错误码,仅由全局参数校验处理器处理非法入参(如 `pageSize > 100`)。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **改前**:返回全部 `status=PENDING AND is_active=1 AND claimer_id IS NULL` 的行,含团期子订单逐户行。
|
||||
- **改后**:追加 `order_main.product_batch_id IS NULL`,团期子订单逐户行不再出现,`total` 同口径减少。
|
||||
- `productType=GROUP` 的筛选仍会被接受,但结果恒为空(团单只在团期池出现)——**前端建议隐藏「产品类型」下拉里的 `GROUP` 选项**,避免用户选中后困惑于「为什么筛出来是空的」。
|
||||
- 团单谓词用 `product_batch_id IS NULL` 而不是 `product_type <> 'GROUP'`:团期产品但尚未归到任何团期(`product_batch_id` 为空)的订单没有团期管理员确认链,若按 `product_type` 排除会让这类单在两个池里都不出现。
|
||||
|
||||
---
|
||||
|
||||
### 7. 逐户抢单(改造) `POST /v3/admin/order/hotel-requirements/{requirementId}/claim`
|
||||
|
||||
**VO**: `无请求体 → Result<Void>`(本单不改请求/响应结构,只在业务逻辑最前面加一道团单守卫)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务对非团期子订单逐户抢单,行为不变;对**团期子订单**新增拦截,引导房务改用团期抢单池整团认领。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
无变化:
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `requirementId` | Path | Long | 是 | 用房需求 ID | 与改前完全一致 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
无变化:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data` | null | 成功 200,与改前完全一致 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
POST /v3/admin/order/hotel-requirements/7002/claim
|
||||
Authorization: Bearer <房务 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "success": true, "data": null }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无(写操作),与改前一致。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808650, "message": "团期订单不支持逐户抢单/转单,请到团期抢单池整团认领", "success": false, "data": null }
|
||||
```
|
||||
|
||||
| 码 | 符号 | 触发 |
|
||||
|---|---|---|
|
||||
| 808090 / 808091 | 角色门 | 现状不变 |
|
||||
| 808002 | `REQUIREMENT_NOT_FOUND` | 现状不变 |
|
||||
| 808004 | `ORDER_CANCELLED` | 现状不变 |
|
||||
| 808003 | `REQUIREMENT_CLAIMED_BY_SELF` | 现状不变 |
|
||||
| 808001 | `REQUIREMENT_ALREADY_CLAIMED` | 现状不变 |
|
||||
| **808650** | `GB_GRAB_ORDER_IS_GROUP` | **新增**:需求所属订单 `product_batch_id != null`,无条件拒,零写入 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **改前**:对团单户 CAS 成功即逐户抢走。
|
||||
- **改后**:CAS **之前**先判团单,团单户零写入直接抛 808650;非团单行为完全不变。
|
||||
- 守卫放在 CAS 之前而非之后:即使抛错回滚,CAS 之后再判也已经真写过一次并多一次锁竞争。
|
||||
|
||||
---
|
||||
|
||||
### 8. 逐户转单(改造) `POST /v3/admin/order/hotel-requirements/{requirementId}/transfer`
|
||||
|
||||
**VO**: `HouseTransferReqVO → Result<Void>`(本单不改请求/响应结构,只在既有流程中插入一道团单守卫)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
超管指派或房务转单,非团单行为不变;对**团期子订单**新增有条件拦截(三分支,见业务边界)。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
无变化:
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `requirementId` | Path | Long | 是 | 用房需求 ID | 与改前完全一致 |
|
||||
| `toUserId` | Body | Long(JSON String) | 是 | 接收人房务 ID | 与改前完全一致 |
|
||||
| `reason` | Body | String | 否(超管≥10字) | ≤200 字 | 与改前完全一致 |
|
||||
| `skipUpperLimit` | Body | Boolean | 否 | 历史字段,仅审计留痕 | 与改前完全一致 |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
无变化:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data` | null | 成功 200,与改前完全一致 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
POST /v3/admin/order/hotel-requirements/7002/transfer
|
||||
Authorization: Bearer <SUPER_ADMIN token>
|
||||
|
||||
{ "toUserId": "30002", "reason": "原房务请假,临时指派" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "success": true, "data": null }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无(写操作),与改前一致。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808650, "message": "团期订单不支持逐户抢单/转单,请到团期抢单池整团认领", "success": false, "data": null }
|
||||
```
|
||||
|
||||
现状 808010/808011/808013/808014/808016/808001/808002/808090/808091/808930(状态机)均不变;**新增 808650**(`GB_GRAB_ORDER_IS_GROUP`)。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **改前**:团单户可被逐户转单。
|
||||
- **改后(三分支)**:
|
||||
1. 普通房务对团单户调用 → 恒 **808650**;
|
||||
2. `SUPER_ADMIN` 对团单户调用,且该团 `order_group_batch.house_claimer_id IS NULL`(尚未被整团认领)→ **放行**(用于把存量脏数据集中到一个在职房务名下);
|
||||
3. `SUPER_ADMIN` 对团单户调用,但该团**已被整团认领** → 同样 **808650**。
|
||||
- 团期行查不到(`order_group_batch` 尚未 lazy 建行)时按「未被整团认领」处理放行——拒绝会把历史脏数据唯一的无损出口封死。
|
||||
- 判定顺序:查到 active 需求后立即判团单归属,早于持有人校验(第 3 步)与转单次数上限判定。
|
||||
|
||||
---
|
||||
|
||||
### 9. 逐户释放(改造) `POST /v3/admin/order/hotel-requirements/{requirementId}/release`
|
||||
|
||||
**VO**: `HouseReleaseReqVO → Result<Void>`(本单不改请求/响应结构,只在既有守卫链后追加一道团单专属守卫)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务释放自己抢到的需求回普通抢单池;对**团期子订单**追加更严格的前置检查,防止释放后候选房与已扣库存无人能继续处理。
|
||||
|
||||
⚠️ **与工单正文「口径与定案 #6」的早期结论不同**:正文最初写「release、close:不改」,但 2026-09-08 第六轮复审后给团单户追加了本节的 808662 守卫;`close` 端点确认未改动。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
无变化:
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `requirementId` | Path | Long | 是 | 用房需求 ID | 与改前完全一致 |
|
||||
| `reason` | Body | String | 否 | ≤200 字 | 与改前完全一致,`@RequestBody(required=false)` |
|
||||
|
||||
#### 出参字段表
|
||||
|
||||
无变化:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data` | null | 成功 200,与改前完全一致 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
POST /v3/admin/order/hotel-requirements/7002/release
|
||||
Authorization: Bearer <认领人本人 token>
|
||||
|
||||
{ "reason": "客户改期,暂时放弃" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "success": true, "data": null }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无(写操作),与改前一致。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 808662, "message": "该团单户仍有 2 条配房(含询房中候选),请先删除配房后再释放", "success": false, "data": null }
|
||||
```
|
||||
|
||||
| 码 | 符号 | 触发 |
|
||||
|---|---|---|
|
||||
| 808090 / 808091 | 角色门 | 现状不变 |
|
||||
| 808002 | `REQUIREMENT_NOT_FOUND` | 现状不变 |
|
||||
| 808020 | `RELEASE_NOT_OWNER` | 现状不变 |
|
||||
| 808021 | `RELEASE_HAS_CONFIRMED_ASSIGNMENT` | 现状不变(仅数 `CONFIRMED`,全部订单通用) |
|
||||
| **808662** | `GB_HOUSE_RELEASE_HAS_ACTIVE_ASSIGNMENT` | **新增,仅团期子订单**:存在 active 配房行(含 `INQUIRING` 候选,不限 `confirm_status`),零写入 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- **改前**:团单户释放判据与普通单完全一致,只看 `RELEASE_HAS_CONFIRMED_ASSIGNMENT`(808021,仅数 `CONFIRMED` 状态的配房)。
|
||||
- **改后**:普通单判据不变;**团期子订单**在既有 808021 判定之后,额外追加「active 配房行(不限 `confirm_status`,含询房中候选)不为 0 → 808662」的更严守卫。
|
||||
- **为什么团单不能沿用「仅 `CONFIRMED` 阻塞」这条既有规则**:换酒店场景会 reopen 需求、软删旧确认行、插入 `INQUIRING` 候选且库存已真实扣减,此时 `CONFIRMED` 计数为 0、808021 不拦;若放行释放,`claimer_id` 被清空后,候选房与已占库存将无法被任何角色继续处理——delete/clear/finalize 会被 `HouseClaimGuard` 以 808116 拒绝(该守卫无超管免检分支),逐户 claim 被 808650 拒绝,逐户 transfer 的 CAS 条件(`claimer` 非空且 `status='PROCESSING'`)在释放后两者都不满足,团级 `takeover` 不处理这类冻结旧户,团级 `release` 又会被同团新户的订房计划以 808660 拒绝——形成无人能处理的死结,故必须在释放前一步就拦住。
|
||||
- `close`(标记异常完成)端点本次**未改动**,仅 `release` 追加了本条守卫。
|
||||
|
||||
---
|
||||
|
||||
### 10. 团期详情(改造:响应追加三字段) `GET /v3/admin/order/group-batch/{groupBatchId}`
|
||||
|
||||
**VO**: `无请求体 → GroupBatchDetailRespVO`(现有字段全部不变,仅追加 3 个只读字段)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期管理员侧团期详情页,展示「负责房务」;也是 `#7328`(房务↔团期管理员会话)取会话对端的依据。
|
||||
|
||||
#### 入参字段表
|
||||
|
||||
无变化:
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `groupBatchId` | Path | Long | 是 | 团期主订单 ID | 与改前完全一致 |
|
||||
|
||||
#### 出参字段表(仅列追加部分,其余既有字段本次不改动)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.houseClaimerId` | String(Long ToString,可空) | 整团认领房务 adminId(未认领 `null`) |
|
||||
| `data.houseClaimerName` | String(可空) | 整团认领房务姓名(未认领 `null`) |
|
||||
| `data.houseClaimedAt` | LocalDateTime(可空) | 整团认领时间(未认领 `null`) |
|
||||
|
||||
(取自 `GroupBatchDetailRespVO.java:190-204`;与 `batchManagerId`「团期管理员」是两个不同的人,全站契约写死不启用 `batchManagerId` 表示房务,不可混用。)
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
GET /v3/admin/order/group-batch/1930000000000000001
|
||||
Authorization: Bearer <团期管理员 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200, "message": "成功", "success": true,
|
||||
"data": {
|
||||
"groupBatchId": "1930000000000000001",
|
||||
"houseClaimerId": "30001", "houseClaimerName": "张三", "houseClaimedAt": "2026-06-01 10:00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
(仅展示本次新增的三字段,既有字段(数十个)省略以节省篇幅,完整既有结构不受本次改动影响。)
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
该团未被整团认领时,追加的三字段均为 `null`,其余既有字段的空数据行为不受影响。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 589500, "message": "团期不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
无变化,沿用既有 589500。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 三字段权限现状不变(沿用团期详情既有权限),本次仅多返回三个字段,不改变任何鉴权逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### 团期抢单池写口(claim / release / takeover)
|
||||
|
||||
| 场景 | 结果 |
|
||||
|---|---|
|
||||
| ✅ `ROOM_MANAGER`/`SUPER_ADMIN` 认领未被占用且已整体确认的团 | 200 |
|
||||
| ❌ `house_keeper_lead` 调 claim/release/takeover | 808091(只读监督) |
|
||||
| ❌ 非房务角色调 claim/release/takeover | 808090 |
|
||||
| ❌ 普通房务调 takeover | 808658(仅超管) |
|
||||
| ❌ 该团有户被别人逐户抢走时整团 claim | 808654 |
|
||||
| ❌ 该团有户存在 active 逐户配房行时 claim/takeover | 808659 |
|
||||
| ❌ 释放时该团仍有未取消订房计划 | 808660 |
|
||||
|
||||
### 普通池与逐户 claim/transfer
|
||||
|
||||
| 场景 | 结果 |
|
||||
|---|---|
|
||||
| 团期子订单的逐户需求行 | 不再出现在普通抢单池列表 |
|
||||
| 对团期子订单逐户 claim | 808650,零写入 |
|
||||
| 普通房务对团期子订单逐户 transfer | 808650 |
|
||||
| 超管对**未被整团认领**的团期子订单逐户 transfer | 放行(存量脏数据集中用) |
|
||||
| 超管对**已被整团认领**的团期子订单逐户 transfer | 808650 |
|
||||
| 逐户 close 对团单户 | 不变 |
|
||||
| 逐户 release 对团单户,且无 active 配房行 | 放行(保留作为历史脏数据清理出口) |
|
||||
| 逐户 release 对团单户,但仍有 active 配房行(含 `INQUIRING` 候选) | **808662**(新增,非团单不受影响,仍是既有 808021 口径) |
|
||||
|
||||
- 「我的接单」列表中历史上被逐户抢走的团单户仍会出现(脏数据可见性即清理入口,`GET /v3/admin/order/grab-pool/my-claims/hotel` 本单不改)。
|
||||
- 转单候选「在跟单数」统计(`countInProgressHotelClaimsByClaimer`)**不计**团级认领,这是已知口径差,前端展示候选人工作量时应注意。
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
- 无状态机字段切换;团级三列(`house_claimer_id`/`house_claimer_name`/`house_claimed_at`)是简单 CAS 覆盖,不经状态机。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- `order_group_batch` 新增 3 列 + 1 索引(`V20260910_301__order_group_batch_add_house_claimer.sql`):
|
||||
- `house_claimer_id BIGINT NULL`(整团认领房务 adminId,`NULL`=未认领)
|
||||
- `house_claimer_name VARCHAR(64) NULL`(认领房务姓名快照)
|
||||
- `house_claimed_at DATETIME NULL`(认领时间)
|
||||
- `KEY idx_house_claimer_id (house_claimer_id)`
|
||||
- 版本号说明:正文原规划 `V20260908_301`,因 `#7323` 的 `V20260908_311` 已先合入并在测试服执行过,Flyway 按乱序拒绝更低版本号,故实际落地为 `V20260910_301`。
|
||||
- 无新表;`house_operation_log`、`order_hotel_requirement` 本次均不写入改动。
|
||||
- 认领 / 释放 / 接管均写团期时间线(`group_batch_status_log`,事件类型 `BATCH_HOUSE_CLAIM`/`BATCH_HOUSE_RELEASE`,DATA 类),时间线写入失败仅记 WARN 日志、不回滚主事务。
|
||||
- 认领 / 释放 / 接管**不写入**任何户级表(`order_hotel_requirement`、`house_hotel_assignment` 等)——团级接管例外:会 CAS 清理**非冻结名单**户的 `order_hotel_requirement.claimer_id`(详见「三、4」业务边界)。
|
||||
- 团级写口 afterCommit 发通知中心事件(`GROUP_BATCH_HOUSE_CLAIMED`/`GROUP_BATCH_HOUSE_RELEASED`),`notification_event_config` 未配置时 dispatcher 只记日志,不影响主链路,不阻塞接口响应。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 0 需房户的团(全团客户自订酒店)仍会进入团期抢单池,`hotelOrderCount=0` 明示,房务据此自行跳过。
|
||||
- 团期无出发日(`depart_date` 可空)时,按出发日排序的分页里 `NULL` 行的页内位置由 Service 内存处理挪到本页末尾,不做跨页重排。
|
||||
- 认领后管理员对该团任一户打回需求,会清掉团级 `requirement_confirmed` 标记(但**不清认领指针**):「我的团」里该团 `requirementConfirmed=false`,前端应标「待管理员重新确认」;团级计划行/分房行校验本单不涉及,留给后续工单补充。
|
||||
- 「我的团」头部统计 `needsReconfirm`/`cancelled` 在 `stats.total > 500` 时返回 `null`(不是少算的数字),前端应显示「—」。
|
||||
- `house_operation_log` 与逐户「我的接单」列表不受团级认领影响,历史被逐户抢走的团单户仍在原列表可见,作为清理入口。
|
||||
- 权限服务或房务员工列表 Feign(团级接管的接管人姓名解析)异常时按兜底名 `user-{id}` 处理并跳过在职校验;接收人列表非空但查无该人时才会明确拒 808011。
|
||||
- **极窄边界场景(不属于本篇 10 个端点,供排查参考)**:冻结名单内的历史户(首次启用团期归属时已有确认配房)被 `release` 清空房务归属后,若定制师对该户提交新版本用房需求(既有的 `upsertHotelRequirement` 端点,走「已抢/已配房后的需求调整」分支),后端会 fail-closed 拒绝并返 **808661**(`GB_HOUSE_LEGACY_RESUBMIT_NO_OWNER`),提示需先清空候选配房再重提,避免出现「旧房仍占库存却无人能处理」。该端点请求/响应契约本身未变,仅新增这一条极窄场景下的业务错误。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### 错误码新增清单(`HouseGroupBatchErrorCode.java`,段位 808600-808699 内本单占用 808650-808662)
|
||||
|
||||
| 码 | 符号 | 消息 |
|
||||
|---|---|---|
|
||||
| 808650 | `GB_GRAB_ORDER_IS_GROUP` | 团期订单不支持逐户抢单/转单,请到团期抢单池整团认领 |
|
||||
| 808651 | `GB_GRAB_BATCH_NOT_CLAIMABLE` | 该团期当前不可认领(需求未整体确认或团期阶段不允许) |
|
||||
| 808652 | `GB_GRAB_BATCH_ALREADY_CLAIMED` | 该团期已被其他房务认领 |
|
||||
| 808653 | `GB_GRAB_BATCH_CLAIMED_BY_SELF` | 该团期已由您认领,请勿重复认领 |
|
||||
| 808654 | `GB_GRAB_HOUSEHOLD_CLAIMED_BY_OTHERS` | 该团有 {0} 户已被房务 {1} 逐户抢单,请先释放后再整团认领 |
|
||||
| 808655 | `GB_GRAB_RELEASE_NOT_OWNER` | 该团期不属于当前房务,无法释放 |
|
||||
| 808656 | `GB_GRAB_BATCH_NOT_CLAIMED` | 该团期尚未被认领,无需释放 |
|
||||
| 808657 | `GB_GRAB_ADMIN_RELEASE_REASON_TOO_SHORT` | 超管操作原因长度不足 10 字(释放/接管共用) |
|
||||
| 808658 | `GB_GRAB_TAKEOVER_NOT_SUPER_ADMIN` | 团期接管仅超级管理员可操作 |
|
||||
| 808659 | `GB_GRAB_TAKEOVER_HAS_HOUSE_ASSIGNMENT` | 该团有 {0} 户存在逐户配房(首个订单 {1}),请先删除配房后再操作(claim 与 takeover 共用) |
|
||||
| 808660 | `GB_HOUSE_RELEASE_HAS_ROOM_PLAN` | 该团仍有 {0} 条未取消的订房计划,无法释放(请先处理订房计划或走接管) |
|
||||
| 808661 | `GB_HOUSE_LEGACY_RESUBMIT_NO_OWNER` | 该户历史配房仍在但已无房务归属,请先由房务或超管清空候选配房后再重提需求——触发于**既有**用房需求提交端点(`RequirementService.upsertHotelRequirement`,非本篇新增/改造的 10 个端点之一),场景是冻结名单内的历史户被 release 后房务归属清空、定制师又对其重提新版本需求,`RequirementService.java:895-904` |
|
||||
| 808662 | `GB_HOUSE_RELEASE_HAS_ACTIVE_ASSIGNMENT` | 该团单户仍有 {0} 条配房(含询房中候选),请先删除配房后再释放——触发于本篇「### 9. 逐户释放(改造)」 |
|
||||
|
||||
(取自 `HouseGroupBatchErrorCode.java:72-150`。⚠️ 与工单正文「口径与定案 #6」早期表述不同——808661/808662 均已在**本次合并的 PR #7436** 内实际接线并可触发,不是预留给后续工单的空常量:808662 挂在本篇改造的逐户释放端点上;808661 挂在既有的用房需求提交端点(未改请求/响应契约,只是新增了一条极窄场景下的业务错误,未纳入本篇「三、接口详情」的 10 个端点,因为该端点本身不属于房务抢单池体系、且触发条件极窄——仅冻结名单内旧户被 release 后又被重提新版本需求时命中)。)
|
||||
|
||||
### `batchStatus`(团期抢单池筛选,`HouseGroupGrabPoolPageReqVO.batchStatus`)
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| `RESOURCE_PREPARING` | 资源准备中 |
|
||||
| `MATERIAL_PREPARING` | 材料准备中 |
|
||||
| `PENDING_DEPARTURE` | 待出发 |
|
||||
| `TRAVELLING` | 行程中 |
|
||||
|
||||
(仅这四态可入池;「我的团」的 `batchStatus` 筛选接受团期完整九态,因为已认领的团可能已流团/已结束。)
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 「我的接单」(逐户)、待办、日历、详情、组长监督视图等既有房务工作台功能**不变**——团级认领不回写户级,这些视图读的都是户级数据。
|
||||
- 团期需求确认/打回(`group-batch:demand:confirm`)不受影响,仍是团进池的唯一触发条件之一。
|
||||
- 逐户 `close` 端点**不改**。逐户 `release` 端点对**非团单**行为完全不改;对团单户继续可用(作为历史脏数据清理出口),但新增了 808662 更严守卫,见「三、9.」——这一点与工单正文早期「release 不改」的表述不同,以本篇为准。
|
||||
- 「我的接单」`GET /v3/admin/order/grab-pool/my-claims/hotel` 不改。
|
||||
- 房务日历不展示抢单池订单,本单排除条件不影响日历。
|
||||
- 无 SSE 广播改动:团期池变更不推送实时事件,前端仍需沿用既有的普通池 SSE(不受影响)或手动/定时刷新团期池。
|
||||
- CODE_RULES 不改;网关路由零改动(`/v3/admin/order/**` 已通配)。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**当前状态:代码已合入 `dev-v3`(HEAD `353f4b2d6`),测试服部署与网关实测待管理者安排,部署后回写本节与 frontmatter。**
|
||||
|
||||
已完成的验证(单测/编译层面,不等价于网关实测):
|
||||
|
||||
- 单测覆盖(`HouseGroupGrabServiceTest`,节选,完整清单见工单 #7322「测试要求」):认领成功写时间线、CAS 落空按持有人分类返 808652/808653/808651、脏数据户返 808654 且零写入、未登录返 808090、组长返 808091、释放非本人/未认领/超管理由过短分别返 808655/808656/808657、`scope=all` 非组长返 808092。
|
||||
- `HouseGrabServiceImplTest` 追加:团单户逐户 claim 返 808650 且零 CAS、团单户逐户 transfer 返 808650、团单户逐户 release 在无 active 配房行时仍放行、有 active 配房行(含 `INQUIRING` 候选)时返 808662。
|
||||
- IT(Testcontainers/H2):`selectGrabPoolPageWithJoin` 排除团单户后 `total` 与真实剩余行数一致;`GroupBatchMapper` 新增 default 方法(`selectHouseGrabPoolPage`/`selectHouseClaimedPage`/`casHouseClaim`/`casHouseRelease`/`casHouseTakeover`)覆盖四态过滤、并发 CAS 落空场景。
|
||||
- ArchTest:`HouseModuleBoundaryArchTest`、`MapperBoundaryArchTest`、`RedLineArchTest`、`HouseErrorCodeRangeTest`(新增段位 808650-808662 全部登记)均绿。
|
||||
|
||||
**待补(部署后由管理者执行并回填)**:网关实测 5 个新端点 + 4 个改造端点的全部错误码路径、Flyway 执行结果(`SHOW COLUMNS`/`SHOW INDEX` 核对)、角色矩阵、菜单种子口径确认结果(见「十、相关文档」)。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 团期看板权限地基 #6902、团期需求确认/打回 #7210:本单角色门不复用这两处的平台权限码体系,走既有的 `HouseWriteGuard` 房务角色门。
|
||||
- 团期房务批次后续工单:`#7323`(订房地基/扣减来源分支)、`#7324`(房务团期看板/订房计划 CRUD,读本单 `GroupBatchDetailRespVO` 新三字段、提供 `countActivePlans` 供本单 808660 使用)、`#7325`/`#7326`(按日确认与自动分房、分房微调)、`#7327`(双源巡检契约)、`#7328`(房务↔团期管理员会话,参与人取本单 `house_claimer_id`)。
|
||||
- 旧拟议契约 `docs/group/团期模块接口文档-v2.0.html` GB-ADM-016(`POST /v3/admin/order/group-batch/{groupBatchId}/hotel-requirements/claim`,逐户批量薄编排)**已被本单替代**,不采用该旧契约。
|
||||
- **待确认(口径与定案 #11)**:房务管家菜单是否需要 `sys_menu` 种子行(hl-user-service Flyway),需连测试库核实后由管理者补充,不影响本篇端点契约本身。
|
||||
|
||||
---
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#7322](https://git.1814.love:8443/wx/HL/issues/7322)
|
||||
- **PR**: [#7436](https://git.1814.love:8443/wx/HL/pulls/7436)(合并提交 [ab4118a01](https://git.1814.love:8443/wx/HL/commit/ab4118a0171927f4409e8de23925b1c5443e21ae))
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: wx
|
||||
- **待确认对象(前端 hl-ui)**: mmg——房务管家需新增「抢单池·团期」「我的团」两个页面(含团级接管的超管专属入口);普通抢单池「产品类型」筛选下拉建议隐藏 `GROUP` 选项;团期详情页展示「负责房务」三字段;无 SSE,页面需手动或定时刷新;`sys_menu` 菜单种子的 `path`/`component` 命名请回复后由管理者补 Flyway
|
||||
在新工单中引用
屏蔽一个用户