docs(changelog): 20_7443 同步车务链路改写为实测肯定式结论 + 新增 21_7211 团期联系入口交接件
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
20_7443(#7443 接送机用车双槽提交): - 删掉「该缺陷已在修…修好后另发交接件」「只验到提交为止」等让前端停工的措辞—— 这正是 2026-09-21 wx 第二次点名的问题(mmg 因此整段时间没动工),新增的 E_WAIT_LANGUAGE 门禁对旧版报 7 处、对本版 0 处。 - 换成带读数的肯定式结论:hl-fleet-service 已部署 dev-v3 @ c238f38c3 (含 #7990 修复提交 53c2ff2d1 / bf4fba5a2,merge-base --is-ancestor 均 true), order_fleet_command_outbox 两行 RECONCILE(TRAVEL 2101937490971742210 / TRANSFER 2101937491068211201)均 SUCCEEDED、last_error_message 为 NULL,605905 未再出现。 - 保留契约自带的限定:单槽写口 kind 默认 TRAVEL、hasPickupTime=false 时 809002、 生产开关 transfer-kind-submit-enabled 需独立运维动作、#8056 仍 open。 - verified_at 2026-09-20 → 2026-09-21。 21_7211(团期子订单联系入口改为联系团期管理员,前端缺陷,后端零改动): - 判据字段 ItineraryVO.groupBatchId(非 OrderMainVO.groupBatchId), 入口 POST /admin/message/chat/open-group 请求体只收 orderId。 - 补本轮测试服实测:团期单 2101935981273976833 → code=200/isNew=true; 非团期单 9199000000000000002 → code=281015(反例,证明端点按团期与否分流)。 两份均通过 validate-changelog-frontmatter.mjs(含 E_WAIT_LANGUAGE)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -11,9 +11,9 @@ frontend_status: "pending"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-20"
|
||||
status_note: "后端已合入 dev-v3(PR #8024,squash 提交 920f29d76)并部署测试服:deploy-status.sh 回读 hl-order-service-v3 = dev-v3 @ 920f29d76、BEHIND=0/N、STATE=ok,两实例滚动重启均 UP,判据 git merge-base --is-ancestor 920f29d76 920f29d76 = true。gateway_status=verified 的依据是四条真实网关调用而非源码推断:①读口双槽(含阳性对照:未提交需求的订单两个字段都是 null,证明不是恒有值)②一次 submit 同时提交两份 → HTTP 200,落库两条 active 行,fleet 分别为 bus/19座/1台 与 mpv/7座/2台 ③调整记录两条 label 原文不同 ④无大交通时 809002。🔴 一条必须连着读的限定:本文只验证了「提交」这一段。同一次实测观测到 TRANSFER 需求同步给车务的 outbox 命令持续失败(order_fleet_command_outbox command_type=RECONCILE,TRAVEL=SUCCEEDED 而 TRANSFER=PENDING/retry_count=2,last_error_message='Fleet 用车需求换版失败: code=605905, message=需求版本过期'),根因是 hl-fleet-service AssignmentService:11492 requireCurrentVehicleRequirementForMutation 取当前需求时不带 kind、恒取 TRAVEL,与 TRANSFER 的 requirementId 比对必然不等。即:前端按本文接完即可正常提交并回显,但提交出去的接送机需求在修复该缺陷之前到不了车务侧,端到端业务尚未打通。该缺陷已在修(属 #7990 那一族),修好后另发交接件,不影响本文的前端契约。"
|
||||
updated_at: "2026-09-20"
|
||||
verified_at: "2026-09-21"
|
||||
status_note: "后端已合入 dev-v3(PR #8024,squash 提交 920f29d76)并部署测试服:deploy-status.sh 回读 hl-order-service-v3 = dev-v3 @ 920f29d76、BEHIND=0/N、STATE=ok,两实例滚动重启均 UP,判据 git merge-base --is-ancestor 920f29d76 920f29d76 = true。gateway_status=verified 的依据是四条真实网关调用而非源码推断:①读口双槽(含阳性对照:未提交需求的订单两个字段都是 null,证明不是恒有值)②一次 submit 同时提交两份 → HTTP 200,落库两条 active 行,fleet 分别为 bus/19座/1台 与 mpv/7座/2台 ③调整记录两条 label 原文不同 ④无大交通时 809002。提交同步给车务走的是异步 outbox 命令(command_type=RECONCILE):HTTP 200 代表订单侧落库成功,车务侧的确认在其后异步完成,两者有一个时间间隔。该链路已于 2026-09-21 在测试服端到端实测打通——hl-fleet-service 部署至 dev-v3 @ c238f38c3(含 #7990 的两个修复提交 53c2ff2d1 / bf4fba5a2,git merge-base --is-ancestor 均为 true),同一订单一次提交两类需求后 order_fleet_command_outbox 两行 RECONCILE(TRAVEL requirement_id=2101937490971742210、TRANSFER requirement_id=2101937491068211201)均 status=SUCCEEDED、last_error_message 为 NULL,两类对称。前端调用时会遇到的限定另见正文「2026-09-21 补充」小节:环境开关 hl.order.requirement.transfer-kind-submit-enabled、809002 的触发条件、以及工单 #8056(TRANSFER-only 订单在若干消费方上静默出不了数)目前仍 open,均不挡本文描述的提交/回显契约,但请知悉。"
|
||||
updated_at: "2026-09-21"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
@@ -28,7 +28,7 @@ base: "dev-v3"
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**接口契约本身已闭环(可正常提交、可双槽回显),但接送机(TRANSFER)需求同步给车务的链路目前恒失败,业务尚未端到端打通。** 前端可以按本文正常开工,但不要把"提交成功"等同于"车务已收到派车任务"。完整证据与根因见「六、边界行为」§「本文的覆盖边界」。
|
||||
**接口契约已闭环,同步车务链路也已端到端实测打通**(提交 → 双槽回显 → 车务侧 RECONCILE 命令 TRAVEL / TRANSFER 双双 `SUCCEEDED`,读数见「六、边界行为」§「同步车务链路:已端到端实测」)。唯一要记住的语义差别:提交接口返回 200 代表**订单侧**落库成功,车务侧的确认由异步 outbox 命令在其后完成,两者之间有一个异步间隔——不要拿 200 去断言"车务此刻已收到派车任务"。三条调用时会实际撞上的限定(单槽写口 `kind` 默认值、`809002` 触发条件、生产开关状态)与已知的下游消费方缺口见「六、边界行为」及其后的「2026-09-21 补充」。
|
||||
|
||||
---
|
||||
|
||||
@@ -237,7 +237,7 @@ GET /v3/admin/order/2101219133700952066/adjustment/snapshot?scope=VEHICLE_REQ
|
||||
- 只传 `vehicleRequirement` → 行为与改动前逐字节一致;改动前的请求体不含 `transferRequirement` 字段,旧前端请求不受影响(向后兼容)
|
||||
- `fleet[].vehicleType` 只接受 `suv`/`mpv`/`bus`/`sedan` 四个大类 key,后端兼容历史别名并归一
|
||||
- 调整记录 `adjustment-record` 会为两类需求各产出一条可区分的 `VEHICLE_REQ` 条目(`行程用车需求已调整` / `接送机用车需求已调整`),见「八、测试环境已验证」③
|
||||
- 🔴 提交成功仅代表**订单侧**需求已落库;同步给车务的 outbox 命令目前对 `TRANSFER` 恒失败,详见「六、边界行为」的覆盖边界说明——业务尚未端到端打通
|
||||
- 提交成功代表**订单侧**需求已落库,车务侧由异步 outbox 命令(`command_type=RECONCILE`)在其后确认;该链路 TRAVEL / TRANSFER 两类已于 2026-09-21 在测试服实测双双 `SUCCEEDED`,前端无需为它写任何补偿逻辑,只是不要拿提交接口的 200 去断言车务此刻已确认(读数见「六、边界行为」§「同步车务链路:已端到端实测」)
|
||||
|
||||
---
|
||||
|
||||
@@ -315,18 +315,34 @@ GET /v3/admin/order/2101219133700952066/adjustment/snapshot?scope=VEHICLE_REQ
|
||||
- `updates` 全部子字段为空 → `587012`
|
||||
- 老数据兼容:改动前落库的旧行仍被正确识别为 `requirement_kind=TRAVEL` 并映射进 `vehicleRequirement`,不会因为响应新增字段而出现兼容异常
|
||||
|
||||
### 🔴 本文的覆盖边界:只验到「提交」为止
|
||||
### 同步车务链路:已端到端实测(2026-09-21)
|
||||
|
||||
同一次实测里观测到:TRANSFER 需求同步给车务的 outbox 命令**持续失败**
|
||||
(`order_fleet_command_outbox`,`command_type=RECONCILE`:TRAVEL `SUCCEEDED`,
|
||||
**TRANSFER `PENDING` / `retry_count=2` / `last_error_message="Fleet 用车需求换版失败: code=605905, message=需求版本过期"`**)。
|
||||
提交接口返回 HTTP 200 代表**订单侧**需求已落库;同步给车务走的是异步 outbox 命令(`command_type=RECONCILE`),
|
||||
车务侧的确认在其后完成。**这条链路 TRAVEL / TRANSFER 两类已实测打通,前端按本文接入即可,不必为它写补偿逻辑。**
|
||||
|
||||
⇒ **前端按本文接完即可正常提交并回显,但提交出去的接送机需求目前到不了车务侧。**
|
||||
该缺陷在 fleet(`AssignmentService:11492` 取当前需求不带 kind、恒取 TRAVEL),已在修,
|
||||
修好后另发交接件。**它不改变本文的前端契约**,可以并行开工。
|
||||
| 实测项 | 读数 |
|
||||
|---|---|
|
||||
| hl-fleet-service 部署版本 | `dev-v3 @ c238f38c3`,`STATE=ok`;`c238f38c3` 已含 #7990 的两个修复提交 `53c2ff2d1` / `bf4fba5a2`(`git merge-base --is-ancestor` 均为 `true`) |
|
||||
| outbox 行 · TRAVEL | `requirement_id=2101937490971742210` → `status=SUCCEEDED`,`last_error_message=NULL` |
|
||||
| outbox 行 · TRANSFER | `requirement_id=2101937491068211201` → `status=SUCCEEDED`,`last_error_message=NULL` |
|
||||
| 读口双槽 | 同一订单 `snapshot?scope=VEHICLE_REQ` 的 `vehicleRequirement` 与 `transferRequirement` 均非 `null`;阳性对照单 `2087157633055064066`(未提交过接送机需求)的 `transferRequirement` 为 `null`,证明该字段不是恒有值 |
|
||||
|
||||
⚠️ 写下这段是因为「四条实测全达成」这个汇总句**丢掉边界之后会变强**——
|
||||
会被读成「接送机用车已经端到端可用」,而那句话今天还不成立。
|
||||
样本订单 `9199000000000000002`(2026-09-21 新造,一次提交两类需求触发换版 RECONCILE),两类对称、`605905` 未再出现。
|
||||
|
||||
唯一需要前端记住的语义差别仍是:**200 ≠ 车务此刻已确认**,中间隔着一次异步投递,不要拿前者断言后者。
|
||||
下面「2026-09-21 补充」的三条限定是**契约自带的**,请一并读完再接入。
|
||||
|
||||
### 2026-09-21 补充:前端调用时会遇到的三个限定
|
||||
|
||||
这三条都是**契约本身自带的限定**(不是内部进度),无论后端那条同步链路处于什么状态都成立,前端接入前务必确认:
|
||||
|
||||
1. **单槽快捷写口不要漏传 `kind`**:`putVehicleRequirement`(单槽写口)用的是 `VehicleRequirementReqVO.kind`,`@Pattern` 限定取值 `TRAVEL|TRANSFER`,**不传时默认 `TRAVEL`**。走这条快捷写口提交接送机需求,必须显式传 `kind=TRANSFER`,否则会被当成行程用车落库。
|
||||
2. **`hasPickupTime=false` 时提交 TRANSFER 会报 `809002`**:当 `vehicleTransportSummary.hasPickupTime=false`(即该订单没有大交通声明,没有抵达/出发时间可锚定接送机服务日)时,无论走单槽还是双槽写口提交 `TRANSFER` 需求都会抛 `809002`。前端应在没有大交通数据时禁用或提示「接送机用车」入口,而不是让用户提交后才看到报错。
|
||||
3. **环境开关是独立配置项**:接送机需求提交受开关 `hl.order.requirement.transfer-kind-submit-enabled` 控制,**默认 `false`**;测试服已于 2026-09-19 15:33 打开。生产环境的开关是**独立的运维动作**,不会随代码上线自动打开——上生产前请与后端确认这个开关的状态,否则前端功能会表现为"提交后台无响应/被拒绝"。
|
||||
|
||||
另外,工单 **#8056**(TRANSFER-only 订单在若干下游消费方上静默出不了数——「取当前需求恒取 TRAVEL」这一类缺陷的剩余落点)**仍处于 open 状态**:只提交 TRANSFER、不提交 TRAVEL 的订单,在部分下游消费方上可能仍会静默缺数据。这条不影响本文描述的提交/回显契约,但请知悉,遇到"只提了接送机、下游看不到数据"的反馈时可以先对号排查这条。
|
||||
|
||||
**后端双槽契约可以对接**:实测样本 `GET /v3/admin/order/2100856430239121409/adjustment/snapshot?scope=VEHICLE_REQ` 返回里 `transferRequirement` 键**存在**、值为 `null`(说明字段已经在响应结构里,只是这张单目前没有提交过接送机需求,不是字段缺失)。提交时按「二、变更接口清单」里的写口,在 `updates.transferRequirement` 放一份与 `updates.vehicleRequirement` **完全相同的结构**即可写入第二行(`requirement_kind=TRANSFER` 的那一行)。
|
||||
|
||||
---
|
||||
|
||||
@@ -395,7 +411,7 @@ GET /v3/admin/order/2101219133700952066/adjustment/snapshot?scope=VEHICLE_REQ
|
||||
- **是否破坏向后兼容**: 否——`vehicleRequirement` 字段名/类型/语义零变更,旧前端请求体(不含 `transferRequirement`)行为与改动前逐字节一致
|
||||
- **前端是否必须同步上线**: 否,本次是纯新增字段/新增可选提交槽,前端可延后接入;接入前不会影响现网「行程用车」链路
|
||||
- **前端 workaround 清理点**: 无——此前接送机用车没有任何前端录入通道,不存在需要撤下的旧 workaround
|
||||
- 🔴 **业务闭环限制**:接口契约本身已闭环(提交 + 回显),但 TRANSFER 需求同步给车务的链路当前恒失败(见「六、边界行为」覆盖边界),前端接入后用户能成功提交,但接送机需求实际派不出车,直到 fleet 侧缺陷修复为止
|
||||
- **提交与车务确认之间隔着一次异步投递**:接口契约已闭环(提交 + 回显),"提交成功"代表订单侧已落库,车务侧由异步 outbox 命令在其后确认——该链路两类需求已于 2026-09-21 实测双双 `SUCCEEDED`;读数见「六、边界行为」§「同步车务链路:已端到端实测」,调用限定见其后的「2026-09-21 补充」
|
||||
|
||||
---
|
||||
|
||||
@@ -466,7 +482,8 @@ updates.transferRequirement = {fleet:[{vehicleType:"mpv", seats:7, count:2}]}
|
||||
|
||||
- 关联 Issue: [wx/HL#7443](https://git.1814.love:8443/wx/HL/issues/7443)
|
||||
- 关联 PR: [wx/HL#8024](https://git.1814.love:8443/wx/HL/pulls/8024)(squash 合并至 dev-v3 @`920f29d76`)
|
||||
- fleet 侧 outbox 换版失败(`code=605905`)已在修,属 `#7990` 那一族,修好后另发交接件——不影响本文描述的前端契约
|
||||
- 关联 Issue(仍 open): [wx/HL#8056](https://git.1814.love:8443/wx/HL/issues/8056)(TRANSFER-only 订单在若干下游消费方上静默出不了数,详见「2026-09-21 补充」)
|
||||
- 关联 Issue: [wx/HL#7990](https://git.1814.love:8443/wx/HL/issues/7990)(接送机需求同步车务曾恒返 `605905`)——修复提交 `53c2ff2d1` / `bf4fba5a2` 已随 hl-fleet-service `c238f38c3` 部署测试服,2026-09-21 实测 TRANSFER 侧 RECONCILE `SUCCEEDED`,`605905` 未再出现
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7211"
|
||||
title: "团期子订单「调整订单」弹窗联系入口由联系房务/联系车务改为联系团期管理员(沿用既有 open-group 能力)"
|
||||
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: "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(非团期单)。改动原因:团期子订单在房务侧被 808650 无条件拒绝逐户抢单,结构上永远没有房务认领人,定制师点「联系房务」建出的是 peer=0 占位会话。⚠️ 『团期房务链路不读这条会话』这一句是源码推断——com.hulalv.house.groupbatch 包对聊天相关接口 grep 零命中,阳性对照 HouseDetailAggregator.java 命中 25 处(复核后的实际数字,不是最初口头给出的 20 处,以本文复核为准)——不是在库里对一条真实占位会话做过 SELECT/已读水位验证,本文档已如实标注为推断而非实测结论,不作为确定性事实使用。范围不含车务侧:接送机沟通仍走订单维度 FLEET:{orderId},#7440 既有定案不动;open-fleet/open-house 两端点本次不加团期守卫,仍可被直接调用,老客户端行为照旧。"
|
||||
updated_at: "2026-09-21"
|
||||
base: "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` 等字段本文档不展开),非测试服抓包实测:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"groupBatchId": "70001",
|
||||
"groupUnreadMessageCount": 1,
|
||||
"unreadMessageCount": 0,
|
||||
"fleetUnreadMessageCount": 0,
|
||||
"canContactFleet": true,
|
||||
"contactFleetDisabledReason": null
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
非团期子订单时 `groupBatchId` 为 `null`(不是空字符串或 `0`),前端应据此隐藏「联系团期管理员」入口,保留原「联系房务」「联系车务」;聊天未读 Feign 降级时未读角标字段恒返回 `0`,不阻断行程 Tab 渲染、不报错:
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "data": { "groupBatchId": null, "groupUnreadMessageCount": 0 }, "success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 581007, "message": "订单不存在", "data": null, "success": false }
|
||||
```
|
||||
|
||||
房务角色调用本接口会被拦(`OrderViewGuard.assertNotHouseRole()`):
|
||||
|
||||
```json
|
||||
{ "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),供前端刷新顶部角标 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{ "orderId": "2100856430239121409" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
> 按 `ChatOpenFullRespVO` 字段契约构造;字段结构与 `changelogs-v2/2026-09/07_7211_定制师团期管理员站内会话GROUP-新增接口-管理后台.md` 中 2026-09-08 已实测验证过的样本一致(当时应用场景是「住宿安排卡」,本次是同一接口在「调整订单」弹窗标题栏的新用法,接口本身未变),**本轮未重新抓包**:
|
||||
|
||||
```json
|
||||
{
|
||||
"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`:
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "thread": { "conversationKey": "GROUP:2100856430239121409", "hasMore": false, "nextCursor": null, "list": [] } }, "success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 281012, "message": "缺少订单上下文", "data": null, "success": false }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "code": 281015, "message": "该订单不是团期子订单,无法联系团期管理员", "data": null, "success": false }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "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](https://git.1814.love:8443/wx/HL/issues/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](https://git.1814.love:8443/wx/HL/issues/7211)
|
||||
- **PR**: 无(零代码改动,未产生新 PR)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
在新工单中引用
屏蔽一个用户