文件
hl-api-changelog/changelogs-v2/2026-09/20_8023_需求汇总与确认预检不再只认needs_hotel标记位-修改接口-管理后台.md
T

431 行
20 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
schema: "hl-changelog/v2"
ticket: "8023"
title: "需求汇总与确认预检不再只认 needs_hotel 标记位 + 提交住宿需求时就地纠正该标记"
consumer: "admin"
author: "jw(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "verified"
frontend_owner: "mmg"
frontend_ref: "9a14ad866b3ef6ac9acb0ca9ab9e9f0fe98f6559"
target_release: ""
verified_at: "2026-09-20"
status_note: "后端已合并 dev-v3(df66ec357)并部署 TEST,网关实测 AC-1~AC-8 全通过。一处口径放宽 + 一个纯新增出参 + 一处写侧副作用。口径:全团需求汇总 GET /v3/admin/order/group-batch/{groupBatchId}/requirement-summary 的 dailyRoomBreakdown 与整体确认预检 GET .../requirement/confirm-check,此前都只认订单上的 needs_hotel 标记位,导致「标记说不需要住宿、定制师却已提交完整需求」的户被整户静默丢弃——间数不进汇总(页面用房表空白)、确认时也不放行给房务(需求填了没人配房);现在计入条件改为「标记为真 或 已提交有效需求」,打回态仍不计。新增出参 hotelFlagMismatchOrderCount 把这种不一致显式暴露。写侧:PUT /v3/admin/order/{id}/hotel-requirement 提交成功后,若该单 needs_hotel 不为真则就地置 1(单向 0→1,同事务,已为真时不写)——该标记原先只在创单时按产品有没有配酒店派生一次、之后全仓无写通道,存量错单改不回来。既有字段一个没删没改,入参与路径不变。 前端实证维持 not_required(mmg 2026-09-20):requirement-summary/dailyRoomBreakdown/hotelNeededOrderCount/hotelFlagMismatchOrderCount 全仓零命中(汇总端点未接入,同 #7925/#7937 结论);confirm-check 已消费(orderV2GroupBatch.js)但出参结构不变,ready/missing 直渲自动受益;hotel-requirement 标记纠正为服务端同事务副作用、响应不变,零感知。 2026-09-20 前端接入(mmg):「用房 · 汇总」建页随 hl-admin v2.1 交付——requirement-summary 经 getGroupRequirementSummary 接入「查看需求」页 RoomSummarySection:户数条(在团/需订房/已提交)+不一致只提示不扣减+Day N|酒店|房型|所需间数扁平表;stayDate null 按 Day N、hotelName null 显「未知酒店」不隐藏行、roomCategoryName null 回落编码、全天合计直读 rooms[] 禁跨酒店累加、hotelId null 组间数不丢;整团确认/按户打回后自动重拉。定向 Vitest 17/17,checkpoint 全绿。"
updated_at: "2026-09-20"
base: "dev-v3"
---
# order-v3: 需求汇总/预检不再只认 needs_hotel,提交需求时就地纠正该标记
> **服务**: hl-order-service-v3 (端口 8086)
> **PR**: [#8028](https://git.1814.love:8443/wx/HL/pulls/8028)
> **Issue**: [#8023](https://git.1814.love:8443/wx/HL/issues/8023)
> **日期**: 2026-09-20
> **影响范围**: 管理后台「团期订单 → 查看需求」页的用房汇总、整体确认预检;定制师提交住宿需求的副作用
---
## ⚠️ 关键变化
- **不再只认标记位**:汇总与预检的计入条件从「`needs_hotel=1`」放宽为「**标记为真 或 已提交有效需求**」。需求是事实,标记只是创单时的推测,两者冲突时以事实为准。
- **新增出参 `hotelFlagMismatchOrderCount`**:「标记为假却已提交有效需求」的户数,把这种不一致显式暴露出来,不再静默吞掉。
- **`hotelNeededOrderCount` 口径同步放宽**:改为「标记为真的户 ∪ 已提交有效需求的户」,保证 `hotelSubmittedOrderCount ≤ hotelNeededOrderCount` 恒成立。
- **提交住宿需求会就地纠正标记**:`needs_hotel` 不为真时置 1,单向、同事务、已为真时不写。
- **打回态仍不计**:#7925 立的那条判定原样保留,不受本次放宽影响。
---
## 一、背景
`needs_hotel` 只在**创建订单那一刻**由「产品有没有配酒店 / 有没有默认房数」派生一次,之后**全仓没有任何写通道**(`OrderCreateTransactionExecutor.deriveNeedsHotel`)。产品当时没配酒店、后来补配了,或定制师按客户实际情况提了住宿需求,这个标记都改不回来。
而汇总与预检都按它筛户,于是出现了自相矛盾的一屏:子订单列表显示该户「待审核」,需求汇总却说这个团「0 户需要住宿」,用房表全空。
TEST 实证(团期 `jw测试1期`):三户 `needs_hotel=0`,其中一户已提交 6 晚 12 间的 `PENDING_REVIEW` 需求,改前 `dailyRoomBreakdown` 为空数组。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 全团需求汇总 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement-summary` | 修改 | 计入户口径放宽为「标记为真或已提交有效需求」;新增 1 个出参 |
| 2 | 整体确认预检 | GET | `/v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check` | 修改 | 取户口径同步放宽,字段结构不变 |
| 3 | 提交住宿需求 | PUT | `/v3/admin/order/{id}/hotel-requirement` | 修改 | 新增副作用:提交成功后就地纠正 needs_hotel |
---
## 三、接口详情
### 1. 全团需求汇总 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement-summary`
**VO**: `Result<GroupRequirementSummaryRespVO>`
#### 使用场景
团期详情「查看需求」页顶部的「用房 · 汇总」表:全团逐日要订几间、什么房型,运营据此向酒店报数。
#### 入参
本次入参**不变**。
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 团期 ID | 团期不存在返回 589500 |
#### 出参
| 字段 | 类型 | 说明 |
|------|------|------|
| activeOrderCount | Integer | 在团户数(不变) |
| hotelRequirementCount | Integer | 有效房需求条数(不变;不看标记位也不看打回状态) |
| hotelNeededOrderCount | Integer | **口径放宽**:标记为真的户 **∪** 已提交有效需求的户 |
| hotelSubmittedOrderCount | Integer | **口径放宽**:不再要求标记为真,只看需求是否有效 |
| hotelFlagMismatchOrderCount | Integer | **新增**。标记不为真却已提交有效需求的户数,恒 ≥0 |
| vehicleRequirementCount | Integer | 有效用车需求条数(不变) |
| dailyRoomBreakdown | List | 逐日房间明细(**计入户集合本次放宽**) |
| dailyRoomBreakdown[].dayNumber | Integer | 第几天(不变) |
| dailyRoomBreakdown[].rooms[].roomCategory | String | 房型编码(不变) |
| dailyRoomBreakdown[].rooms[].roomCategoryName | String | 房型中文名(不变,#7925 引入) |
| dailyRoomBreakdown[].rooms[].totalRoomCount | Integer | 该天该房型合计(不变,数值因放宽可能变大) |
| vehicleSeatSummary | List | 车型座位合计(不变) |
| orderSpecialTags | List | 各户特殊需求标签(不变) |
#### 请求示例
```http
GET /v3/admin/order/group-batch/2101506167098511362/requirement-summary
Authorization: Bearer <token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"activeOrderCount": 3,
"hotelRequirementCount": 1,
"hotelNeededOrderCount": 1,
"hotelSubmittedOrderCount": 1,
"hotelFlagMismatchOrderCount": 1,
"vehicleRequirementCount": 0,
"dailyRoomBreakdown": [
{"dayNumber": 1, "rooms": [{"roomCategory": "KING", "roomCategoryName": "豪华大床", "totalRoomCount": 2}]}
],
"vehicleSeatSummary": [],
"orderSpecialTags": []
},
"traceId": null,
"success": true
}
```
#### 空数据 / 降级响应
全团无有效需求且无标记为真的户时,三个户数均为 `0`、`dailyRoomBreakdown` 为 `[]`(**不是 null**),`hotelFlagMismatchOrderCount` 为 `0`,且不调房型字典。房型字典不可用时只有 `roomCategoryName` 为 `null`,编码与房间数照常返回,接口不报错。
#### 错误响应
```json
{ "code": 589507, "message": "无操作权限(当前角色未授予团期权限,或该团期不在您名下)", "data": null, "success": false }
```
| code | 触发条件 |
|------|----------|
| 589507 | 当前角色没有 `group-batch:view`(本次不变) |
| 589500 | 团期不存在(本次不变) |
| 401 | 未登录(网关拦截) |
本次无新增错误码。
#### 业务边界
- **计入的户**:标记为真 **或** 已提交有效需求(有效 = `PENDING_REVIEW` / `PENDING` / `PROCESSING` / `DONE`)。
- **打回态仍不计**:退回定制师、退回管理员两种状态不是「有效需求」,不能靠它把户拉进计入集合。
- **客户自订晚**整晚跳过,不进采购分母(不变)。
- **户范围**仍是「在团」口径(含已完成的户),与预检刻意不同(#7316)。
- `hotelFlagMismatchOrderCount` 仅用于提示与订正,**不要**用它去扣减间数——那批户的间数已经正常计入。
### 2. 整体确认预检 `GET /v3/admin/order/group-batch/{groupBatchId}/requirement/confirm-check`
**VO**: `Result<GroupBatchRequirementCheckRespVO>`
#### 使用场景
团期管理员点「确认需求」之前的缺失预检;确认时用同一份判定决定放行哪些户的需求给房务。
#### 入参
本次入参**不变**。
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| groupBatchId | Path | Long | ✅ | 团期 ID | 团期不存在返回 589500 |
#### 出参
字段结构**不变**(`ready` / `missing[]` / `checkedResourceTypes` 等一个没动)。变的是**取户口径**。
| 字段 | 类型 | 说明 |
|------|------|------|
| ready | Boolean | 是否可确认(不变) |
| missing | List | 缺失清单(结构不变;**新增**「标记为假但已提交」的户参与判定后,其缺项会如实出现在这里) |
| checkedResourceTypes | List | 本次检查的资源类型(不变) |
#### 请求示例
```http
GET /v3/admin/order/group-batch/2101506167098511362/requirement/confirm-check
Authorization: Bearer <token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"groupBatchId": "2101506167098511362",
"batchStatus": "RECRUITING",
"batchStatusName": "招募中",
"ready": false,
"missing": [],
"checkedResourceTypes": ["HOTEL", "VEHICLE"]
},
"traceId": null,
"success": true
}
```
#### 空数据 / 降级响应
无需订房户时 `missing` 为 `[]`(不是 null),`ready` 仍按车侧结果给出,行为不变。团内一户都没有时同样返回空清单而非报错。
#### 错误响应
```json
{ "code": 589500, "message": "团期不存在", "data": null, "success": false }
```
| code | 触发条件 |
|------|----------|
| 589500 | 团期不存在(本次不变) |
| 589507 | 无团期权限(本次不变) |
本次无新增错误码。
#### 业务边界
- **为什么必须跟着汇总一起放宽**:只放宽汇总会变成「间数算进汇总了、确认时却不放行给房务」——需求填了、汇总也算了、房务收不到,比改前更难排查。
- 放宽后这些户也参与「房型行填齐」「晚数与行程一致」等校验,填不全会如实报缺,这是应有的行为。
- 打回态的户仍不参与,避免逼管理员对一份正在返工的需求反复确认。
### 3. 提交住宿需求 `PUT /v3/admin/order/{id}/hotel-requirement`
**VO**: `Result<HotelRequirementRespVO>`
#### 使用场景
定制师逐晚填报住宿需求并提交。
#### 入参
本次入参**不变**。
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| id | Path | Long | ✅ | 订单 ID | 订单不存在返回 582001 |
| days | Body | List | ✅ | 逐晚需求 | 结构不变 |
#### 出参
本次出参**不变**,列出以便自包含。
| 字段 | 类型 | 说明 |
|------|------|------|
| requirementId | Long(String) | 本次写入的需求 ID(不变) |
| version | Integer | 需求版本号(不变) |
| status | String | 需求状态,团单为 `PENDING_REVIEW`、核心单为 `PENDING`(不变) |
| isActive | Boolean | 是否当前生效版本(不变) |
| branchTaken | String | 版本分支 `INIT_SUBMIT` / `PENDING_EDIT` / `DONE_ADJUST`(不变) |
| previousVersion | Integer | 仅 `DONE_ADJUST` 分支返回上一版本号(不变) |
#### 请求示例
```http
PUT /v3/admin/order/2101506167043985410/hotel-requirement
Authorization: Bearer <token>
Content-Type: application/json
{"days": [{"dayNumber": 1, "segments": [{"roomCount": 2, "candidates": [{"hotelId": "2023714929877450753", "rooms": [{"roomTypeId": "3002000000000000013", "roomCategory": "KING", "roomCount": 2}]}]}]}]}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {"requirementId": "2101558489333784578", "version": 1, "status": "PENDING_REVIEW", "isActive": true, "branchTaken": "INIT_SUBMIT"},
"traceId": null,
"success": true
}
```
#### 空数据 / 降级响应
不适用:本端点要么写入成功返回完整 `HotelRequirementRespVO`,要么抛业务异常,不存在空响应。响应体本次**一个字段都没改**,标记纠正是服务端副作用,不体现在返回值里——调用方若要确认标记已纠正,读汇总接口的 `hotelFlagMismatchOrderCount`。
#### 错误响应
```json
{ "code": 582017, "message": "订单状态不允许提交需求", "data": null, "success": false }
```
| code | 触发条件 |
|------|----------|
| 582017 | 订单非定制中(本次不变) |
| 582099 | 团单房型行未填齐(本次不变) |
| 582016 | 房间数必须大于 0(本次不变) |
本次无新增错误码。
#### 业务边界
- **新增副作用**:提交成功后若该单 `needs_hotel` 不为真,则置 1,**与需求写入同一事务**——分开提交会留下「需求写进去了、标记没纠正」的中间态,而那正是本单要修的不一致。
- **单向 0→1**:不提供反向置 0。反向意味着「这户不要住宿了」,那是退需求的业务动作,有自己的链路与副作用,不由提交需求顺带做掉。
- **已为真时不写**:避免每次提交都产生一行无意义的行变更。
- **提交被守卫拒绝时零写入**:标记也不动。
- **为 null 的历史行**与为假同等对待,同样置位。
---
## 四、契约约束与正确调用方式
- 前端不需要改代码即可受益:既有字段一个没变,部署后用房汇总直接出数。
- 若要展示不一致提示,读 `hotelFlagMismatchOrderCount`:大于 0 说明存在创单时被判定不需要住宿、事后却提了需求的存量户,其间数**已照常计入**,该字段只用于提示与订正,不要用它去扣减间数。
---
## 五、数据库行为
只写外部可观察行为:
| 动作 | 外部可观察结果 |
|------|----------------|
| 提交住宿需求且该单此前「不需要住宿」 | 该单从此被视为需要住宿:其间数进入全团用房汇总、该户参与确认预检、确认时其需求被放行给房务 |
| 提交住宿需求且该单本就「需要住宿」 | 无额外变化(不产生多余的行变更) |
| 提交被守卫拒绝(如订单非定制中) | 零变化:需求没写入,「是否需要住宿」也不变 |
| 读接口(汇总 / 预检) | 只读,无任何写入 |
- 纠正与需求写入在**同一事务**:需求回滚则纠正一并回滚,不会出现「需求没提上去、标记却变了」。
- **单向**:只会从「不需要住宿」变成「需要住宿」,反向不会发生。
- 无表结构变更、无 Flyway 脚本。
---
## 六、边界行为
| 场景 | 行为 |
|------|------|
| 标记为真 + 已提交 | 计入,`hotelFlagMismatchOrderCount` 不 +1 |
| 标记为假 + 已提交(有效态) | **计入**,`hotelFlagMismatchOrderCount` +1 |
| 标记为假 + 需求被打回 | 不计入,不报不一致 |
| 标记为假 + 无任何需求 | 不计入,不报不一致(真的不需要住宿) |
| 标记为 null(历史行) | 与为假同等对待;提交需求时同样置位 |
---
## 六.6、修改前后对比
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| `hotelFlagMismatchOrderCount` | 无 | 新增,Integer |
| `hotelNeededOrderCount` | 仅 `needsHotel=true` 的户数 | 标记为真的户 ∪ 已提交有效需求的户 |
| `hotelSubmittedOrderCount` | 需标记为真且已提交 | 只看需求是否有效 |
| 其余既有字段 | — | 不变(无删除、无改名、无类型变化) |
| 入参 / 路径 / 错误码 | — | 不变 |
### 行为级对比
| 场景 | 改前 | 改后 |
|------|------|------|
| 标记为假、已提交完整需求的户 | 整户丢弃:间数不进汇总、确认时不放行房务 | 间数计入、参与预检、确认时放行 |
| 汇总与子订单列表是否自洽 | 列表显示「待审核」、汇总说「0 户需住宿」 | 两处一致 |
| 提交住宿需求后的 `needs_hotel` | 保持创单时的值,永远改不回来 | 不为真则就地置 1 |
| 打回态需求 | 不计入 | 不计入(不变) |
## 六.7、影响评估
- **是否破坏向后兼容**: 字段层面兼容(只增不删);**数值层面**每日房间合计可能变大——这正是本次要修的漏算。
- **前端是否必须同步上线**: 否。
- **回滚**: 回滚本 PR 即可。注意写侧已置位的 `needs_hotel` 不会随回滚还原,但那些户本就应为 1,保留更正确。
---
## 七、不影响范围
- **仅影响**: 上述三个端点的取户口径、一个新增出参、提交需求的标记纠正副作用。
- **零影响**: 用车需求与座位合计、各户特殊需求标签、房务侧订房与分房逻辑、数据库结构(无表变更、无 Flyway)。
---
## 八、测试环境已验证
被测版本:hl-order-service-v3 = dev-v3 `df66ec357`(2026-09-20 15:2x 部署)。构建身份探针:部署后接口开始返回新字段 `hotelFlagMismatchOrderCount`。
验收团期 `jw测试1期`(`2101506167098511362`),在团 3 户。
```
AC-1 标记为假但已提交 → 计入
张三 HL20260920105808925(6 晚 12 间 PENDING_REVIEW)needs_hotel 改回 0
needed=1 submitted=1 mismatch=1
D1 豪华大床×2 | D2 标间×1+豪华大床×1 | D3 标间×2 | D4 豪华大床×2 | D5 豪华大床×2 | D6 豪华房×1+标间×1
改前同一数据 dailyRoomBreakdown = [] ✓
AC-2 标记为假且无需求(王五/李玉)→ 不计入、不报不一致 ✓
AC-3 标记为真的既有场景:六天逐日间数与标记为 0 时逐字相同,mismatch=0 ✓
AC-4 mismatch:标记1+已提交→0;标记0+已提交→1;无不一致时为 0 不是 null ✓
AC-5 写侧自动置位(真调提交接口,非 mock)
PUT /v3/admin/order/2101507276118626306/hotel-requirement(6 晚)→ 200
提交前 needs_hotel=0 → 提交后 needs_hotel=1
汇总立即计入该户,逐日每晚 +1:needed=2 submitted=2 mismatch=1 ✓
AC-6 存量排查(只出数):TEST 上不一致户 21、涉及团期 10
按状态 PENDING_REVIEW 16 / PENDING 5;改前这 21 户间数一条都没进过汇总 ✓
生产侧按既有口径不查(无任意 SQL 通道),不作阻塞
AC-7 判权:ROOM_MANAGER / VEHICLE_MANAGER → 589507;
GROUP_BATCH_MANAGER / SUPER_ADMIN → 200(超管短路放行,故用低权限角色验) ✓
```
未做:写侧 WARN 留痕日志未在 TEST 取证(order-v3 日志窗口只有 200 行、SQL 刷屏会冲掉证据),该分支由 4 条单测覆盖;未跑 order-v3 全量单测,四域定向回归 4717 用例全绿。
---
## 十、相关文档
- 工单 [#8023](https://git.1814.love:8443/wx/HL/issues/8023)
- 前置口径 [#7925](https://git.1814.love:8443/wx/HL/issues/7925)(本单放宽的正是该单引入的第一道过滤)
---
## 关联 / 联系人
### 链接
- **Issue**: [#8023](https://git.1814.love:8443/wx/HL/issues/8023)
- **PR**: [#8028](https://git.1814.love:8443/wx/HL/pulls/8028)
### 联系人
- **后端负责人**: @jw
- **前端负责人**: @mmg