23 KiB
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 | 7211 | 团期子订单「调整订单」弹窗联系入口由联系房务/联系车务改为联系团期管理员(沿用既有 open-group 能力) | admin | wx(GIT) | 前端缺陷 | deployed | not_required | verified | mmg | 4d092b09bec1e9e4c9e1326940616b4a19865aa7 | v2.1 | 2026-09-21 | wx 2026-09-21 定:团期子订单的定制师不能直接联系车务/房务,只能联系团期管理员。这是前端 UI 口径变更 + 后端既有能力复用,后端零代码改动。判据字段:GET /v3/admin/order/{id}/itinerary 响应 ItineraryVO.groupBatchId 非空即团期子订单(javadoc 原文『前端「联系团期管理员」按钮显隐只看本字段』),不要用 OrderMainVO.groupBatchId(两者派生口径不同,历史兼容单上后者为 null 会误判为非团期单)。联系入口调 POST /admin/message/chat/open-group(网关路由 /admin/message/** → lb://hl-user-service),请求体只收 orderId,团期管理员是团队制不接受 peerAdminId;准入=当前定制师或持权限码 group-batch:demand:confirm,鉴权失败 281002 且零写入;错误码另有 281012(缺 orderId)/281015(非团期单)。【前端交付 2026-09-21 mmg:FunItemAdjustModal 标题栏按 itineraryGroupBatchId 切换——团期单只显「联系团期管理员」(角标 itineraryGroupUnreadCount,emit chat group 走既有 GROUP 会话链),普通单维持联系房务+联系车务;判据禁 OrderMainVO.groupBatchId 已 spec 钉住,32 例全过,hl-admin v2.1 4d092b09。】改动原因:团期子订单在房务侧被 808650 无条件拒绝逐户抢单,结构上永远没有房务认领人,定制师点「联系房务」建出的是 peer=0 占位会话。⚠️ 『团期房务链路不读这条会话』这一句是源码推断——com.hulalv.house.groupbatch 包对聊天相关接口 grep 零命中,阳性对照 HouseDetailAggregator.java 命中 25 处(复核后的实际数字,不是最初口头给出的 20 处,以本文复核为准)——不是在库里对一条真实占位会话做过 SELECT/已读水位验证,本文档已如实标注为推断而非实测结论,不作为确定性事实使用。范围不含车务侧:接送机沟通仍走订单维度 FLEET:{orderId},#7440 既有定案不动;open-fleet/open-house 两端点本次不加团期守卫,仍可被直接调用,老客户端行为照旧。 | 2026-09-21 | dev-v3 |
管理后台:团期子订单「调整订单」弹窗联系入口改为联系团期管理员
存放目录: 二期(v3) →
changelogs-v2/2026-09/服务: hl-order-service-v3(判据字段来源:GET /v3/admin/order/{id}/itinerary)+ hl-user-service(联系入口能力:/admin/message/**) PR: 无(零代码改动,未产生新 PR) Issue: #7211(沿用该单引入的 open-group 能力;本次是新增应用场景,不是新单) 日期: 2026-09-21 影响范围: 管理后台订单详情「调整订单」弹窗标题栏联系入口按钮(仅团期子订单);普通订单不受影响
⚠️ 关键变化
本次没有新增、修改或删除任何接口,后端零代码改动。 变化在前端:团期子订单的「调整订单」弹窗标题栏,原来并排的「联系房务」「联系车务」两个按钮应当隐藏,替换为「联系团期管理员」一个入口;判据字段与联系接口都是已经存在、已经部署的能力(ItineraryVO.groupBatchId + POST open-group),不需要等后端。普通(非团期)订单行为完全不变。
一、背景
wx 2026-09-21 定案:团期订单的定制师不能直接联系车务/房务,只能联系团期管理员。
为什么要改(不是单纯挪按钮)
团期子订单在房务侧是无条件拒绝逐户抢单的(HouseGroupBatchErrorCode.java:340,code=808650「团期订单不支持逐户抢单/转单,请到团期抢单池整团认领」),所以团期子订单结构上永远不会有房务认领人。于是定制师点「联系房务」建出来的是一条 peer=0 的占位会话——消息能落库,但没有真实对端。这是一个静默黑洞:前端界面上显示发送成功,业务上大概率无人接收。
「联系车务」不受影响(车务侧沟通不走本次改动,见「七、不影响范围」)。
与原 #7211 changelog 的关系
open-group 这条能力不是本次新增,它在 changelogs-v2/2026-09/07_7211_定制师团期管理员站内会话GROUP-新增接口-管理后台.md 里已经交付并于 2026-09-08 验证通过(frontend_status: verified,frontend_ref: 86340b24)。但那份文档覆盖的应用场景是订单详情「住宿安排卡」;本次是把同一个已验证的后端能力,接到**「调整订单」弹窗标题栏**这个不同的 UI 位置。接口本身、请求体、权限判定、返回结构,均与原文档完全一致,未改一行代码。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 查询订单行程安排 Tab(团期判据 + 未读角标) | GET | /v3/admin/order/{id}/itinerary |
复用不改 | groupBatchId 非空即团期子订单;groupUnreadMessageCount 是新按钮的未读角标字段 |
| 2 | 打开/找回团期订单会话(联系团期管理员) | POST | /admin/message/chat/open-group |
复用不改 | 会话键 GROUP:{orderId};请求体只收 orderId |
三、接口详情
1. 查询订单行程安排 Tab GET /v3/admin/order/{id}/itinerary
VO: 无独立请求VO(GET 路径参数 orderId)→ ItineraryVO
使用场景
前端渲染订单详情「调整订单」弹窗标题栏之前,需要判断当前订单是否为团期子订单,以决定显示「联系房务」+「联系车务」两个按钮,还是显示「联系团期管理员」一个按钮;判据字段与对应未读角标字段均已存在于本接口的既有返回里,不需要新增字段或新接口。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| id | Path | Long | 是 | - | 订单ID({id}) |
出参字段表(Result<ItineraryVO>,仅列与本次判据/角标相关字段)
| 字段 | 类型 | 说明 |
|---|---|---|
| data.groupBatchId | String(Long) | 运营团期ID,取统一归团解析结果(含历史兼容单按 product_batch_id 反查的情形)。前端「联系团期管理员」按钮显隐只看本字段(非空即显示)。与 OrderMainVO.groupBatchId(直映射 order_main 原始列)语义不同:那个字段对历史兼容单为 null(ItineraryVO.java:46-52) |
| data.groupUnreadMessageCount | Integer | 「联系团期管理员」按钮未读角标;当前登录定制师在本订单 GROUP 会话的未读数;非团期子订单/聊天未读 Feign 降级/未登录均返 0(ItineraryVO.java:42-44) |
| data.unreadMessageCount | Integer | 「联系房务」按钮未读角标(HOUSE 会话),团期单改动后本按钮不再显示,该字段仍会返回但前端不读它 |
| data.fleetUnreadMessageCount | Integer | 「联系车务」按钮未读角标(FLEET 会话),本次不受影响 |
| data.canContactFleet | Boolean | 是否允许联系车务(存在 active 用车需求时为 true) |
| data.contactFleetDisabledReason | String | canContactFleet=false 时的不可联系原因文案 |
请求示例
GET /v3/admin/order/2100856430239121409/itinerary
(GET 请求,无请求体)
响应示例
按
ItineraryVO字段契约构造(仅摘录与联系入口判据相关字段,hotelGroup/vehicleGroup/vehicleHistory等字段本文档不展开),非测试服抓包实测:
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "70001",
"groupUnreadMessageCount": 1,
"unreadMessageCount": 0,
"fleetUnreadMessageCount": 0,
"canContactFleet": true,
"contactFleetDisabledReason": null
},
"success": true
}
空数据 / 降级响应
非团期子订单时 groupBatchId 为 null(不是空字符串或 0),前端应据此隐藏「联系团期管理员」入口,保留原「联系房务」「联系车务」;聊天未读 Feign 降级时未读角标字段恒返回 0,不阻断行程 Tab 渲染、不报错:
{ "code": 200, "message": "成功", "data": { "groupBatchId": null, "groupUnreadMessageCount": 0 }, "success": true }
错误响应
{ "code": 581007, "message": "订单不存在", "data": null, "success": false }
房务角色调用本接口会被拦(OrderViewGuard.assertNotHouseRole()):
{ "code": 581045, "message": "房务角色无权查看订单详情,房务仅可配房", "data": null, "success": false }
业务边界
- 判据字段唯一权威来源是
ItineraryVO.groupBatchId,禁止使用OrderMainVO.groupBatchId——两者派生口径不同:后者直映射order_main原始列,历史兼容单(该列未回填、需按product_batch_id反查归团的单)在该字段上为null,会被误判为非团期单。 - 本接口不是本次新增,前端此前已在使用;本次变化只是在既有响应上新增"用
groupBatchId驱动按钮显隐"这一层前端逻辑,不涉及接口改造。
2. 打开/找回团期订单会话(联系团期管理员) POST /admin/message/chat/open-group
VO: ChatOpenGroupReqVO → ChatOpenFullRespVO
使用场景
定制师在「调整订单」弹窗标题栏点击「联系团期管理员」时调用本端点;一个团期子订单对应一条会话(键 GROUP:{orderId}),双向可发起,一次调用返回会话元信息 + 订单卡 + 首屏消息 + 合并未读总数,并标记本会话已读。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| orderId | Body | String(Long) | 是 | @NotNull |
团期子订单id。请求体只收这一个字段,不接受 peerAdminId——团期管理员是团队制(不指派到人,batch_manager_id 全站契约写死不启用),传了也不会被读取(ChatOpenGroupReqVO.java) |
出参字段表(Result<ChatOpenFullRespVO>)
| 字段 | 类型 | 说明 |
|---|---|---|
| data.conversationKey | String | 规范化会话键,固定形如 GROUP:{orderId} |
| data.peerAdminId | Long | 团队制会话恒 0(无单一对端) |
| data.peerName | String | 对方名称快照,如「团期管理员」 |
| data.peerRole | String | 恒 GROUP_ADMIN |
| data.peerRoleLabel | String | 中文角色标签 |
| data.peerOnline | Boolean | 团队侧是否有在线成员 |
| data.unreadCount | Integer | 我在该会话的未读数 |
| data.isNew | Boolean | true=新建会话,false=找回已有会话 |
| data.order | Object | 订单卡(ChatOrderCardVO):orderNo/teamNo/customerName/destination/tripDays/productName/adultCount/childCount/groupBatchId/groupBatchNo/groupBatchName/departDate 等,其中 groupBatchId/groupBatchNo 供前端深链团期详情 |
| data.thread | Object | 首屏消息(conversationKey/hasMore/nextCursor/list[],最新一页20条) |
| data.thread.list[] | Array | 单条消息字段(ChatMessageRespVO):messageId/senderAdminId/senderName/senderRole/msgType/priority/content/isMine/readByPeer/sentAt |
| data.unreadTotal | Integer | 标记本会话已读后,我的合并未读总数(NOTIFY+CHAT),供前端刷新顶部角标 |
请求示例
{ "orderId": "2100856430239121409" }
响应示例
按
ChatOpenFullRespVO字段契约构造;字段结构与changelogs-v2/2026-09/07_7211_定制师团期管理员站内会话GROUP-新增接口-管理后台.md中 2026-09-08 已实测验证过的样本一致(当时应用场景是「住宿安排卡」,本次是同一接口在「调整订单」弹窗标题栏的新用法,接口本身未变),本轮未重新抓包:
{
"code": 200,
"message": "成功",
"data": {
"conversationKey": "GROUP:2100856430239121409",
"peerAdminId": 0,
"peerName": "团期管理员",
"peerRole": "GROUP_ADMIN",
"peerRoleLabel": "团期管理员",
"peerOnline": true,
"unreadCount": 0,
"isNew": false,
"order": {
"orderNo": "HL2609010001",
"teamNo": "T20260901001",
"customerName": "李四",
"destination": "三亚",
"tripDays": 4,
"productName": "豪华蜜月游",
"adultCount": 2,
"childCount": 0,
"groupBatchNo": "G20260901001",
"groupBatchId": "70001",
"groupBatchName": null,
"departDate": null
},
"thread": { "conversationKey": "GROUP:2100856430239121409", "hasMore": false, "nextCursor": null, "list": [] },
"unreadTotal": 0
},
"success": true
}
空数据 / 降级响应
首次打开、尚无历史消息时 thread.list 为空数组 [](不是 null),hasMore=false、nextCursor=null:
{ "code": 200, "data": { "thread": { "conversationKey": "GROUP:2100856430239121409", "hasMore": false, "nextCursor": null, "list": [] } }, "success": true }
错误响应
{ "code": 281012, "message": "缺少订单上下文", "data": null, "success": false }
{ "code": 281015, "message": "该订单不是团期子订单,无法联系团期管理员", "data": null, "success": false }
{ "code": 281002, "message": "无权访问该会话", "data": null, "success": false }
业务边界
- 准入:该单当前定制师(
CUSTOMIZER),或 token 当前角色持权限码group-batch:demand:confirm(超管天然通过);其余一律 281002,且授权前不创建任何成员或水位——ChatManager.java:467-472原文注释「先授权、后检查模块前置」,鉴权失败零写入。 - 前置条件只看摘要
groupBatchId是否非空,不要求先提交需求——与车务open-fleet的 281013(必须先有 active 用车需求)不同(ChatErrorCode.java:61-66)。 orderId缺失被@NotNull拦下走参数校验(400 系),不进入业务判定分支。
四、契约约束与正确调用方式
本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
✅ 正确 / ❌ 错误 用法对照
| 场景 | 用法 |
|---|---|
| ✅ 判断是否团期子订单 | 读 GET /itinerary 响应 data.groupBatchId,非空即团期单 |
| ❌ 判断是否团期子订单 | 用 OrderMainVO.groupBatchId(另一接口的另一字段,历史兼容单上为 null,会误判为非团期单) |
| ✅ 联系团期管理员 | POST open-group,body 只传 { "orderId": "..." } |
| ❌ 联系团期管理员 | body 里传 peerAdminId 试图指定接收人——不接受,团期管理员是团队制,传了也不生效 |
切换按钮显隐的必要动作
前端渲染「调整订单」弹窗标题栏时,用 itinerary 接口已经返回的 groupBatchId 做一次判断:非空 → 只渲染「联系团期管理员」按钮(角标取 groupUnreadMessageCount);为空(含 null)→ 保持原有「联系房务」「联系车务」两个按钮不变。不需要额外调用一个专门的"是否团期单"接口。
五、数据库行为
本次零改动。以下是既有行为,供前端理解结果落库口径:团期会话消息与车务/房务会话共用同一张消息表,只按会话键 GROUP:{orderId}(与 FLEET:{orderId}/HOUSE:{orderId} 同构)区分,不存在专属的团期消息表,本次也不改任何会话键格式。
六、边界行为
- 未登录/网关未透传角色 → 401(网关拦截)
- 订单不存在 →
itinerary接口返581007 - 房务角色调用
itinerary→581045 open-group缺orderId→281012- 订单不是团期子订单(摘要
groupBatchId为空)→281015 - 非该单定制师且不持权限码 →
281002,且授权前零写入 - 老数据兼容:团期归团解析对历史兼容单同样走统一门面解析出
groupBatchId,判据行为与新单一致,不会因为是历史单而漏判
六.5、枚举 / 数据字典
peerRole(ChatOpenRespVO.peerRole,open-group 场景固定值)
所属字段: data.peerRole | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
GROUP_ADMIN |
团期管理员 | open-group 场景固定返回该值,团队制占位角色,不对应具体某个 adminId |
六.6、修改前后对比
本文档不涉及接口改造(两条端点均为「复用不改」),无字段级契约对比;以下是 UI 行为级对比:
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 团期子订单「调整订单」弹窗标题栏按钮 | 联系房务 + 联系车务 | 联系团期管理员(替换前两者) |
| 普通订单「调整订单」弹窗标题栏按钮 | 联系房务 + 联系车务 | 不变 |
| 团期子订单点击「联系房务」的后果(改前遗留问题) | 建出 peer=0 占位会话,消息落库但缺乏真实对端(是否被团期房务链路读到未经 DB 实测,仅源码推断为否) |
入口不再出现,不会再产生这类占位会话 |
六.7、影响评估
- 是否破坏向后兼容: 否——两条接口字段/类型/语义零变更,只是前端新增了一层判断分支
- 前端是否必须同步上线: 后端不强制(零改动,不阻塞任何前端节奏),但业务上 wx 希望尽快切换以消除「联系房务」占位会话黑洞
- 前端 workaround 清理点: 无——本次是新增判断分支,不是撤销旧 workaround
七、不影响范围
- 仅影响: 管理后台订单详情「调整订单」弹窗标题栏联系入口按钮的渲染逻辑(仅团期子订单)
- 零影响:
- 车务侧自己的页面与沟通入口:接送机沟通仍走订单维度
FLEET:{orderId},是 #7440 D-A6/AC-16 的既有定案,本次不动(ChatMessageController.java:188原文注释「接送机等订单级沟通仍走 open-fleet 的 FLEET:orderId,本端点不替代它」) - 后端
open-fleet/open-house端点:本次不加团期守卫,仍可被直接调用;老客户端(mmg 本次发版前)行为照旧 - 不新建任何业务表,不改任何会话键格式
itinerary/open-group两接口在非团期单场景、及本文未提及字段上的既有契约与行为
- 车务侧自己的页面与沟通入口:接送机沟通仍走订单维度
八、测试环境已验证
本文档描述的是既有能力的新应用场景(新按钮位置)。联系入口 open-group 本轮在测试服网关真实调用过(含反例,见本节末「2026-09-21 测试服实测」);判据字段 ItineraryVO.groupBatchId 本轮未做新的 HTTP 实测,按 origin/dev-v3 源码引用取证:
OrderController.java:305-311 GET /v3/admin/order/{id}/itinerary 端点存在,返回 ItineraryVO ✓
ItineraryVO.java:46-52 groupBatchId 字段 + javadoc「前端按钮显隐只看本字段」✓
ItineraryVO.java:42-44 groupUnreadMessageCount 字段存在 ✓
ChatMessageController.java:153-158 POST /admin/message/chat/open-group 端点存在 ✓
ChatOpenGroupReqVO.java 请求体仅 orderId 一个字段(@NotNull) ✓
ChatManager.java:380-382,467-472 openGroup 委托 openTeamInternal,先授权后检查前置、零写入 ✓
TeamChatAuthorizationService.java:45 权限码 group-batch:demand:confirm 判团队侧准入 ✓
ChatErrorCode.java:19-20,42-44,61-66 281002/281012/281015 错误码定义 ✓
application.yml:157-160 网关路由 /admin/message/** → lb://hl-user-service ✓
HouseGroupBatchErrorCode.java:340 808650「团期订单不支持逐户抢单/转单」定义 ✓
grep -rin chat com/hulalv/house/groupbatch/ → 0 命中(团期房务相关包对聊天接口零引用)✓
grep -in chat HouseDetailAggregator.java → 25 命中(阳性对照:普通房务侧代码大量引用聊天相关字段/服务)✓
【推断,非实测】 「团期房务链路不读团期占位会话」这一条,是基于上面两条 grep 结果的结构性推断(团期房务代码路径找不到读聊天表/调用聊天服务的代码,普通房务代码路径有 25 处引用),不是在测试库里对一条真实 peer=0 占位会话做过 SELECT 或已读水位验证。如果需要把这条坐实成确定性结论,需要另外找一条真实产生过的团期占位会话,去库里核对它是否被任何团期房务角色读过。
2026-09-21 测试服实测(本轮补做)
open-group 端点本轮在测试服网关上真实调用过,带反例对照(账号 cw_test_7442):
| 场景 | 请求 | 响应 |
|---|---|---|
| 团期子订单(阳性) | POST /admin/message/chat/open-group,body {"orderId":"2101935981273976833"} |
code=200,data.isNew=true(该订单此前无 GROUP 会话,本次新建) |
| 非团期订单(反例) | 同上端点,body {"orderId":"9199000000000000002"} |
code=281015「该订单不是团期子订单,无法联系团期管理员」 |
反例那一行是本文判据的分辨力证明:端点确实按「是不是团期子订单」分流,而不是对任何订单都放行——
所以前端用 groupBatchId 控制按钮显隐后,即便漏判也不会把非团期单的会话建出来,只会收到 281015。
同一接口在旧场景的实测依据:open-group 端点的响应结构与权限判定,已于 2026-09-08 随 07_7211_定制师团期管理员站内会话GROUP-新增接口-管理后台.md 在「住宿安排卡」场景下测试服实测通过(该文档 frontend_status: verified,frontend_ref: 86340b24)。本文档只是把同一个已验证的能力接到新的按钮位置,未重复实测。
十、相关文档
- 关联 Issue: wx/HL#7211
- 原始能力交接件:
changelogs-v2/2026-09/07_7211_定制师团期管理员站内会话GROUP-新增接口-管理后台.md(open-group端点首次交付,2026-09-08 已验证) - 相关定案: #7440 D-A6/AC-16(接送机沟通走
FLEET:{orderId}的既有定案,本次不动) - 相关工单: #7322(团期整团抢单守卫
808650的来源) docs/group/团期模块接口文档-v2.0.html§0C.11.3
关联 / 联系人
链接
- Issue: #7211
- PR: 无(零代码改动,未产生新 PR)
联系人
- 后端负责人: @wx