文件
hl-api-changelog/changelogs-v2/2026-09/10_7322_团期整团抢单与抢单池分流-新增接口-管理后台.md
T
API Changelog Bot和Claude Fable 5.1 c5b40bb83e
changelog-filename-gate / validate (push) Successful in 2s
docs: 答复前端 #7324 后端需求(后端零改动)+ 补 #7322 我的团不过滤 batch_status
前端据 H1 看板过滤 CANCELLED 的行为,推断「整团释放」按钮无可达路径。
该推断的关键环节不成立:我的团(GET /v3/admin/order/grab-pool/my-claims/
group-batches)刻意不按 batch_status 过滤,H2 详情也没有阶段闸门,
CANCELLED 团期经「我的团 → H2 详情」完全可达。

根因在我方:#7322 的 changelog 从未写过这条行为,前端看不到 javadoc。
本次一并补记,并在需求单上答复 status=answered-no-backend-change。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 10:39:30 +08:00

57 KiB
原始文件 Blame 文件历史

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 7322 团期整团抢单与抢单池分流:新增团期抢单池/我的团/接管四写口,普通抢单池排除团单逐户行 admin wx(GIT) 新增接口 deployed not_required verified mmg 745cd9b8 hl-ui@745cd9b8 2026-09-10 本篇覆盖 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)的两行抢单池入口已由 PR #7467(squash 7f791a250,hl-user-service 的 V20260910_001)补上。⚠️ 部署两条:① #7322 的部署清单原本不含 hl-user-service,本次起必须带上;② 菜单树按 roleId 缓在 Redis(cache:menu:tree:role:{roleId},TTL 3600s),Flyway 直连 DB 不触发失效,部署后需 DEL cache:menu:tree:role:* 否则最长要等一小时。前端已交付(commit 745cd9b8):新增团期抢单池页(抢单池·团期+我的团两 Tab,含超管团级接管)与普通抢单池独立页,orders 页摘除 pool scope,BatchHero 接负责房务三字段;checkpoint 全绿。 2026-09-11 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),菜单种子(sys_menu)确实需要,已由独立的 hl-user-service Flyway V20260910_001__add_house_grab_pool_menus.sql(PR #7467)落地,不影响本篇端点契约。
  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。)

请求示例

GET /v3/admin/order/grab-pool/group-batches?page=1&pageSize=20&sortBy=departDate,asc
Authorization: Bearer <持房务三角色之一的管理端 token>

响应示例

{
  "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 明示,判定条件不含需房户数。

错误响应

{ "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 一致

请求示例

POST /v3/admin/order/grab-pool/group-batches/1930000000000000001/claim
Authorization: Bearer <持 ROOM_MANAGER 或 SUPER_ADMIN 的管理端 token>

响应示例

{ "code": 200, "message": "成功", "success": true, "data": null }

空数据 / 降级响应

无(写操作)。

错误响应

{ "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

请求示例

POST /v3/admin/order/grab-pool/group-batches/1930000000000000001/release
Authorization: Bearer <认领人本人 token>

{ "reason": "临时调休,交回团期池" }

响应示例

{ "code": 200, "message": "成功", "success": true, "data": null }

空数据 / 降级响应

无。

错误响应

{ "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。)

请求示例

POST /v3/admin/order/grab-pool/group-batches/1930000000000000001/takeover
Authorization: Bearer <持 SUPER_ADMIN 的管理端 token>

{ "toUserId": 30002, "reason": "原认领房务离职,指派新房务接管该团" }

响应示例

{
  "code": 200, "message": "成功", "success": true,
  "data": {
    "groupBatchId": "1930000000000000001", "fromClaimerId": "30001", "toClaimerId": "30002",
    "toClaimerName": "李四", "clearedOrderIds": ["100", "101"],
    "legacyFinalizedOrderIds": ["102"], "skippedOrderIds": []
  }
}

(示例结构取自 HouseGroupTakeoverRespVO 字段定义与 HouseGroupGrabService.takeoverTx 装配逻辑,非真实网关调用。)

空数据 / 降级响应

无被清户/冻结户/落空户时对应数组为空列表 [],不是 null。

错误响应

{ "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 透传,不做任何过滤——含 CANCELLED。不传 = 全部已认领团(含已流团)。⚠️ 与 H1 看板语义相反,详见下方补记
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。)

🔴 补记(2026-09-11):本列表不按 batch_status 过滤,已流团(CANCELLED)团期就在里面

这条行为原本就是后端的刻意设计,但本篇首发时漏写了,在此补上。

  • HouseGroupGrabService.listMyGroups 的 javadoc 原话:「不限制 batch_status:已认领的团可能已流团 / 已结束,本单不自动释放指针,房务仍要看得见并手动释放」;
  • 服务层 GroupBatchService.pageHouseClaimed 把查询条件原样下推,零注入; 对照:同一个类里的 pageHouseGrabPool(团期抢单池)却强制写死 REQUIREMENT_CONFIRMABLE_STATUSES。 一个注入、一个不注入,是刻意区分,不是遗漏。

与 H1 房务团期看板(#7324)的语义正好相反,两个列表不能套用同一套前端逻辑:

本接口(我的团) H1 看板 GET /v3/admin/house/group-batches
不传 batchStatus 全部已认领团,含 CANCELLED 默认四态,不含 CANCELLED
传 batchStatus=CANCELLED 只看已流团的团(可当「待释放」筛选用) 被静默丢弃并回退默认四态

这条为什么要紧:「整团释放」写口 POST .../room-plans/release-all 仅对 CANCELLED 团期放行, 而 H2 详情 GET /v3/admin/house/group-batches/{groupBatchId} 没有阶段闸门(只判归属:未认领 808612 / 他人认领 808613)。 所以拿一个 CANCELLED 团的 groupBatchId 去打 H2,是能打开的——本接口就是拿到那个 id 的地方。

前端 2026-09-11 曾据 H1 的过滤行为推断「整团释放按钮无任何可达路径」并提了后端需求 (backend-requests/2026-09/11_7324_放开H1看板batchStatus过滤CANCELLED-整团释放可达.md)。 那个推断在当时掌握的信息下是合理的——缺的正是本篇漏写的这一条,不是前端疏忽。

请求示例

GET /v3/admin/order/grab-pool/my-claims/group-batches?scope=mine&page=1&pageSize=20
Authorization: Bearer <房务 token>

响应示例

{
  "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 自相矛盾。

错误响应

{ "code": 808090, "message": "未登录或非房务角色,无权操作", "success": false, "data": null }
{ "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,本单零改动,此处不逐一展开类型。)

请求示例

GET /v3/admin/order/grab-pool/hotel-requirements?page=1&pageSize=20&sortBy=createTime,desc
Authorization: Bearer <房务 token>

响应示例

{
  "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,非团单场景的空数据行为与改前完全一致。

错误响应

{ "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,与改前完全一致

请求示例

POST /v3/admin/order/hotel-requirements/7002/claim
Authorization: Bearer <房务 token>

响应示例

{ "code": 200, "message": "成功", "success": true, "data": null }

空数据 / 降级响应

无(写操作),与改前一致。

错误响应

{ "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,与改前完全一致

请求示例

POST /v3/admin/order/hotel-requirements/7002/transfer
Authorization: Bearer <SUPER_ADMIN token>

{ "toUserId": "30002", "reason": "原房务请假,临时指派" }

响应示例

{ "code": 200, "message": "成功", "success": true, "data": null }

空数据 / 降级响应

无(写操作),与改前一致。

错误响应

{ "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,与改前完全一致

请求示例

POST /v3/admin/order/hotel-requirements/7002/release
Authorization: Bearer <认领人本人 token>

{ "reason": "客户改期,暂时放弃" }

响应示例

{ "code": 200, "message": "成功", "success": true, "data": null }

空数据 / 降级响应

无(写操作),与改前一致。

错误响应

{ "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 表示房务,不可混用。)

请求示例

GET /v3/admin/order/group-batch/1930000000000000001
Authorization: Bearer <团期管理员 token>

响应示例

{
  "code": 200, "message": "成功", "success": true,
  "data": {
    "groupBatchId": "1930000000000000001",
    "houseClaimerId": "30001", "houseClaimerName": "张三", "houseClaimedAt": "2026-06-01 10:00:00"
  }
}

(仅展示本次新增的三字段,既有字段(数十个)省略以节省篇幅,完整既有结构不受本次改动影响。)

空数据 / 降级响应

该团未被整团认领时,追加的三字段均为 null,其余既有字段的空数据行为不受影响。

错误响应

{ "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 驱动(该目录 menu_id=2064956343462092801 下已有 4 个既有子菜单:订单列表 / 日历视图 / 待处理 / 月度对账),故需种子行;已由 PR #7467 落地:「抢单池·普通」/housekeeper/grab-pool、「抢单池·团期」/housekeeper/grab-pool-group,sort_order 5/6 接在既有四项之后,授 ROOM_MANAGER/house_keeper_lead/SUPER_ADMIN(不授 ADMIN:团期读口有角色门,ADMIN 调必得 808090)。

关联 / 联系人

链接

联系人

  • 后端负责人: wx
  • 待确认对象(前端 hl-ui): mmg——房务管家需新增「抢单池·团期」「我的团」两个页面(含团级接管的超管专属入口);普通抢单池「产品类型」筛选下拉建议隐藏 GROUP 选项;团期详情页展示「负责房务」三字段;无 SSE,页面需手动或定时刷新;⚠️ 菜单路由请确认——两个页面是「抢单池·普通」与「抢单池·团期」,「我的团」是团期页内的一个 Tab、不是独立页面(本行先前写成两个页面与工单两处口径不符,2026-09-10 订正)。迁移已先行落地(/housekeeper/grab-pool 与 /housekeeper/grab-pool-group,按同目录既有四行的风格)——理由:hl-ui 找不到组件会回落「开发中」页、不白屏不报错,有一个落到「开发中」的菜单项,好过没有菜单项。若实际路由不同,回一句我补一个迁移覆写(menu_id 不变、无副作用);sort_order 想调也一并说。