文件
hl-api-changelog/changelogs-v2/2026-09/26_8390_房务读侧权限门补齐16个端点与ADMIN菜单撤授-修改接口-管理后台.md
T
Mimingguang bcfe323b60
changelog-filename-gate / validate (push) Failing after 1s
docs(changelog): 回写 #8385~#8390 前端 not_required(6 条 grep 实证零改动)
88caa74 房务接口审计批次:#8385 订房计划守卫/#8386 住宿驳回加门+删车务口/#8387 旧房间分配三口下线/#8388 回执认领校验+读门/#8389 家庭维度写口下线/#8390 16 读端点补门+菜单撤授。前端按钮后端字段驱动+错误码拦截器透 message+被删端点零调用/入口仅房务角色页,6 条均 not_required;#8387/#8389 后端已标,余 4 条翻 not_required,owner/ref 留空,status_note 引号内追充实证。
2026-09-27 09:55:25 +08:00

1361 行
50 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
schema: "hl-changelog/v2"
ticket: "8390"
title: "房务数据权限:16 个读端点补房务角色门(808090),ADMIN 角色菜单撤授权"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "not_required"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "房务日历/详情/对账/酒店视图/转单候选/待办/旧抢单池等 16 个读端点补房务角色门:非房务角色返回 808090;开关 group-batch.acl.enforce.house-read-role 默认 true 可灰度。ADMIN 角色房务菜单由 Flyway 撤授权(PR-2 user-service)。测试服实测验证。前端实证(mmg 2026-09-27):16 端点调用者全在 housekeeper 视图(菜单门);定制师仅调契约排除的 requirement-history;hotels 系/订单房间零调用;菜单后端下发,808090 拦截器透 message,零改动 not_required。"
updated_at: "2026-09-26"
base: "dev-v3"
---
# order-v3 + hl-user-service:房务读侧权限门补齐(管理后台)
**服务**: hl-order-service-v3(PR-1,读门)/ hl-user-service(PR-2,菜单撤授权)
**PR**: PR-1 #8393(order-v3)/ PR-2 #8392(hl-user-service)
**Issue**: #8390
---
## ⚠️ 关键变化
### 1. 16 个房务旧只读端点补房务角色门(order-v3 PR-1)
日历、房务订单详情、酒店视图(列表/详情/核房历史/操作日志)、转单候选、待办、月度对账(汇总/明细/导出)、旧抢单池、订单房间共 16 个端点,新增房务读权限校验(Service 层调 `HouseReadGuard.assertHouseReadPermissionOnLegacyEndpoint()`,17 个调用点对应这 16 个端点;不存在名为 `@HouseReadGuarded` 的注解):
- **放行角色**:ROOM_MANAGER / house_keeper_lead / SUPER_ADMIN / 零角色 token(#7609 G-2 保留的 fail-open 行为,本单不收紧)。
- **拦截角色**:定制师(CUSTOMIZER)、车控(VEHICLE_MANAGER)、物料(MATERIAL_ADMIN)、运营(OPERATOR)、客服(CUSTOMER_SERVICE)、团期管理员(GROUP_BATCH_MANAGER)、**ADMIN**、FINANCE 等一切名单外角色 → 返回 **808090**「未登录或非房务角色,无权操作」。
- **开关**:`group-batch.acl.enforce.house-read-role`(默认 true,`@RefreshScope` 热刷新)。开关关闭时守卫照样被调用,只是不再抛出、改打 WARN 日志 `HOUSE_READ_ROLE_MISSING`(`enforced=false`)旁路观察,对前端可见的返回结果与改前一致;开关开(默认)时拒绝先打同一条 WARN(`enforced=true`)再抛 808090。
### 2. ADMIN 角色房务菜单撤授权(user-service PR-2)
用户侧 `sys_role_menu` 在 ADMIN(管理员)身上撤掉房务管家菜单 5 行(目录 1 + 页面 4),不改 `sys_menu`;超管(SUPER_ADMIN)菜单不动。
---
## 一、背景
房务读侧权限门此前并不完整:新端点(#8375 起)已挂读门,但日历 / 订单详情 / 酒店视图 / 转单候选 / 待办 / 月度对账 / 旧抢单池 / 订单房间等 16 个早期端点缺权限守卫,定制师、车控等非房务角色可读房务订单金额、客人信息、酒店协议价等敏感数据;测试服 ADMIN 账号还被授了房务菜单。
测试环境已实证 7 个非房务角色对这 16 个端点全部返回 200(改前)。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 房务日历 | GET | `/admin/house/calendar` | 修改接口 | 新增读门(808090)|
| 2 | 日历单天下钻 | GET | `/admin/house/calendar/day` | 修改接口 | 新增读门(808090)|
| 3 | 房务订单详情 | GET | `/admin/house/orders/{orderId}` | 修改接口 | 新增读门(808090)|
| 4 | 房务酒店列表 | GET | `/v3/admin/house/hotels` | 修改接口 | 新增读门(808090)|
| 5 | 房务酒店详情 | GET | `/v3/admin/house/hotels/{hotelId}` | 修改接口 | 新增读门(808090)|
| 6 | 酒店核房历史 | GET | `/v3/admin/house/hotels/{hotelId}/check-log` | 修改接口 | 新增读门(808090)|
| 7 | 酒店维度操作日志 | GET | `/v3/admin/house/hotels/{hotelId}/operation-log` | 修改接口 | 新增读门(808090)|
| 8 | 订单维度操作日志 | GET | `/v3/admin/house/orders/{orderId}/operation-log` | 修改接口 | 新增读门(808090)|
| 9 | 转单候选员工 | GET | `/v3/admin/house/staff` | 修改接口 | 新增读门(808090)|
| 10 | 房务待办列表 | GET | `/v3/admin/order/todos` | 修改接口 | 新增读门(808090),scope=all/others 非组长/超管另返 582204 |
| 11 | 月度对账汇总 | GET | `/v3/admin/house/reconciliation/monthly` | 修改接口 | 新增读门(808090)|
| 12 | 月度对账订单明细 | GET | `/v3/admin/house/reconciliation/monthly/hotel-orders` | 修改接口 | 新增读门(808090)|
| 13 | 月度对账导出 | GET | `/v3/admin/house/reconciliation/monthly/export` | 修改接口 | 新增读门(808090),format 非法先返 808171 |
| 14 | 抢单池-房型需求列表(@Deprecated) | GET | `/v3/admin/order/grab-pool/hotel-requirements` | 修改接口 | 新增读门(808090)|
| 15 | 抢单池-我的接单(@Deprecated) | GET | `/v3/admin/order/grab-pool/my-claims/hotel` | 修改接口 | 新增读门(808090)|
| 16 | 订单房间分配查询(按家庭分组) | GET | `/v3/admin/order/orders/{orderId}/rooms` | 修改接口 | 新增读门(808090)|
不在本单之列:旧抢单池监督视图 `GET /v3/admin/order/grab-pool/all-claims/hotel`(只有既有的 808092 门,不受本单读门覆盖,见七、不影响范围)。
---
## 三、接口详情
本单只加读门,16 个端点各自的入参/出参字段**没有变化**(本节按 origin/dev-v3 源码逐一列出改前已有的字段;下方“错误响应”一律新增了 808090 一条)。除标注外,读门校验顺序统一是**先过角色门(808090),角色门通过后再走该端点原有的业务校验**。
### 1. 房务日历 `GET /admin/house/calendar`
**VO**: `CalendarQueryReqVO → Result<CalendarRespVO>`
#### 使用场景
房务侧日历首页,按月展示每天的团数与状态点分布(进行中/待配房/询房中/异常/已完成)。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| month | Query | String | 是 | 正则 `^\d{4}-(0[1-9]\|1[0-2])$` | 月份,如 `2026-04` |
| scope | Query | String | 否 | — | `mine`(默认,我的)/ `all`(团队全部) |
| status | Query | String | 否 | 逗号分隔多选 | `inProgress`/`inquiry`/`pending`/`exception`,不填=全部 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| month | String | 月份 |
| summary | Object | 月度摘要:totalNights/inProgress/pending/inquiry/exception/confirmed |
| days | Array | 完整周排版的天列表(28~42 项),每项含 date/isCurrentMonth/isToday/tourCount/statusDots[] |
#### 请求示例
```http
GET /admin/house/calendar?month=2026-04&scope=mine HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"month": "2026-04",
"summary": { "totalNights": 8, "inProgress": 1, "pending": 0, "inquiry": 3, "exception": 1, "confirmed": 2 },
"days": [ { "date": "2026-04-01", "isCurrentMonth": true, "isToday": false, "tourCount": 1, "statusDots": [ { "status": "exception", "label": "异常", "count": 1, "color": "red" } ] } ]
}
}
```
#### 空数据 / 降级响应
当月无团时 `days[].tourCount` 为 0、`statusDots` 为空数组;`summary` 各字段为 0,不降级、不报错。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色(含 ADMIN)一律返 808090;ROOM_MANAGER / house_keeper_lead / SUPER_ADMIN / 零角色 token 放行。
- `month` 格式错时由 `@Valid` 校验拦截,HTTP 200 + code 400,在角色门**之后**触发(先过 808090)。
---
### 2. 日历单天下钻 `GET /admin/house/calendar/day`
**VO**: `CalendarDayQueryReqVO → Result<List<DayTourItemVO>>`
#### 使用场景
点击日历某天单元格,下钻查看该天按「团」折叠的明细列表(团期单按 productBatchId 折叠,散客单一单一项)。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| date | Query | LocalDate(`yyyy-MM-dd`) | 是 | ISO DATE | 下钻日期 |
| scope | Query | String | 否 | — | `mine`(默认)/ `all` |
| status | Query | String | 否 | 逗号分隔多选 | 同日历首页状态桶 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| teamKey | String | 团分组键(`B:团期ID` 或 `O:订单ID`) |
| isGroup | Boolean | 是否团期单 |
| orderId / orderNo | String | 代表订单 ID / 订单号 |
| productName / productType | String | 产品名 / 类型 |
| customerName | String | 主联系人 |
| adultCount / roomCount | Integer | 当日成人数 / 当晚房间数 |
| hotelName / roomCategory / roomCategoryLabel | String | 酒店名 / 房型 code / 房型中文(未配房为空) |
| stayDate | LocalDate | 入住日 |
| status / statusLabel | String | 状态桶 / 中文标签 |
| claimerId / claimerName | String | 房务 ID / 姓名(`scope=all` 才返) |
#### 请求示例
```http
GET /admin/house/calendar/day?date=2026-04-22&scope=mine HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": [
{ "teamKey": "B:1928374", "isGroup": true, "orderId": "5566778", "orderNo": "HL202604220001",
"productName": "华东双飞5日游", "productType": "GROUP", "customerName": "张三",
"adultCount": 32, "roomCount": 16, "hotelName": "杭州西湖国宾馆", "roomCategory": "STANDARD",
"roomCategoryLabel": "标准间", "stayDate": "2026-04-22", "status": "inProgress", "statusLabel": "配房中" }
]
}
```
#### 空数据 / 降级响应
当天无团时返回空数组 `[]`。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090;`date` 缺失由 `@NotNull` 校验拦截(在角色门之后)。
- `claimerId`/`claimerName` 仅 `scope=all` 时返回,`scope=mine` 恒为空。
---
### 3. 房务订单详情 `GET /admin/house/orders/{orderId}`
**VO**: `HouseOrderDetailRespVO`(无独立入参 VO,Path + 单个 Query 参数)
#### 使用场景
房务侧订单详情聚合页:订单基本信息、跟单信息、4 步状态进度条、定制师需求区、配房行程、操作日志计数等一次性拉齐。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| orderId | Path | Long | 是 | — | 订单 ID |
| requirementId | Query | Long | 否 | — | 指定查看某个历史需求版本,不传取当前生效版本 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| viewedRequirementId | String | 本次展示的房型需求 ID |
| historicalRequirement / voided | Boolean | 是否历史版本 / 是否已作废 |
| voidReason / voidedAt | String / LocalDateTime | 作废原因/时间(仅 voided=true) |
| order | Object | 订单基本信息(详见 API-SPEC-HOUSE §2.0) |
| claim | Object | 房务跟单信息 |
| progress | Object | 4 步状态进度条 |
| requirement | Object | 定制师房型需求区 |
| tabCounts | Object | 4 个 Tab 徽标计数 |
| itinerary | Array | 配房行程 Tab(住宿视角,每天一项) |
| tripItinerary / travelers / transport | Array/Array/Object | 订单详情 Tab 三块数据;取数异常时各自降级为空/null |
| orderDetailReady | Boolean | 订单详情 Tab 三块是否全部取数成功 |
| pendingRescheduleAssignments | Array | 改期后待人工删除的旧日期配房,非空时禁止最终确认 |
| actions / permissions | Object | 底部按钮可用性 / 房务侧权限标志 |
#### 请求示例
```http
GET /admin/house/orders/5566778?requirementId=70123 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"viewedRequirementId": "70123",
"historicalRequirement": false,
"voided": false,
"orderDetailReady": true
}
}
```
#### 空数据 / 降级响应
`tripItinerary`/`travelers` 取数异常时降级为空列表、`transport` 降级为 null,同时 `orderDetailReady=false`,前端据此提示「订单信息暂不可用,请稍后刷新」,接口本身不因此失败。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090。
- `/admin/house/orders/{orderId}/requirement-history`(历史需求版本列表)是定制师订单详情的有意跨角色端点,不在本单 16 个之列,勿混淆。
---
### 4. 房务酒店列表 `GET /v3/admin/house/hotels`
**VO**: `HouseHotelListReqVO → Result<PageResult<HouseHotelListItemVO>>`
#### 使用场景
房务侧酒店列表:resource 端酒店主数据代理 + HOUSE 核房叠加 + 近 30 天统计,支持城市/等级/合作状态/结算方式/住宿形态/关键词等过滤。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| city | Query | String | 否 | 逗号分隔多选 | 城市代码,不传=全部 |
| level | Query | String | 否 | 逗号分隔多选 | 酒店等级 |
| status | Query | String | 否 | 字典 hotel_status | 合作状态 active/pause/end |
| settleType | Query | String | 否 | 字典 resource_settle_type | 结算方式 cash/sign/company |
| form | Query | String | 否 | 字典 hotel_form | 住宿形态 hotel/bnb/yurt/logcabin |
| keyword | Query | String | 否 | ≤32 字 | 酒店名/联系人/微信号模糊搜 |
| lastCheckedBefore | Query | LocalDate | 否 | `yyyy-MM-dd` | 仅看最近核房早于此日期的 |
| sortBy | Query | String | 否 | — | 默认 `city,asc,level,asc`,可切 `lastCheckTime,asc` |
| page | Query | Long | 否 | ≥1,默认 1 | 页码 |
| pageSize | Query | Long | 否 | ≥1 且 ≤200,默认 20 | 每页条数 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| hotelId | String | 酒店 ID |
| name / city / cityName / level / form | String | 酒店名 / 城市代码 / 城市中文 / 等级 / 住宿形态 |
| status / settleType | String | 合作状态 / 结算类型 |
| protoPrice | String | 协议价(元/间·晚) |
| contactPerson / wechat | String | 联系人 / 微信 |
| roomTypes | Integer | 房型数 |
| todayAvailable | Integer | 今日可用房数(无核房快照为 0) |
| lastCheckTime | LocalDateTime | 最近核房时间,没核过为 null |
| availFreshness | String | 快照新鲜度 fresh/stale/never_checked |
| stat30d | Object | 近 30 天统计:assignmentCount(配房单数)/ todoCount(待办数) |
#### 请求示例
```http
GET /v3/admin/house/hotels?city=hailar&status=active&page=1&pageSize=20 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"list": [ { "hotelId": "1001", "name": "伯爵大酒店", "city": "hailar", "cityName": "海拉尔",
"level": "高档型", "status": "active", "settleType": "sign", "protoPrice": "480.00",
"todayAvailable": 8, "availFreshness": "fresh", "stat30d": { "assignmentCount": 45, "todoCount": 1 } } ],
"total": 1
}
}
```
#### 空数据 / 降级响应
无匹配酒店返回 `list: []`、`total: 0`;resource 端 Feign 不可用时按 §四契约约束降级。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090。
---
### 5. 房务酒店详情 `GET /v3/admin/house/hotels/{hotelId}`
**VO**: `HouseHotelDetailVO`(无独立入参 VO,只有 Path 参数)
#### 使用场景
酒店详情弹窗 4 个 Tab 一次性聚合:基础信息、房型列表、30 天价格日历预览、最近 10 条核房记录。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| hotelId | Path | Long | 是 | — | 酒店 ID |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| basic | Object | Tab1 基础信息(resource 全字段,详见 `HouseHotelBasicFeignVO`) |
| roomTypes | Array | Tab2 房型列表(含设施,详见 `HouseRoomTypeFeignVO`) |
| priceCalendar30d | Array | Tab3 未来 30 天价格日历预览(详见 `HousePriceCalendarItemFeignVO`) |
| recentCheckLog | Array | Tab4 最近 10 条核房记录(字段同 §6 核房历史行) |
#### 请求示例
```http
GET /v3/admin/house/hotels/1001 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "basic": { }, "roomTypes": [ ], "priceCalendar30d": [ ], "recentCheckLog": [ ] }
}
```
#### 空数据 / 降级响应
basic 强依赖 resource,酒店不存在抛 808500;roomTypes/priceCalendar30d/recentCheckLog 为软依赖,取数失败各自降级为空数组,不影响整体返回。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090(先于 808500 判定)。
---
### 6. 酒店核房历史 `GET /v3/admin/house/hotels/{hotelId}/check-log`
**VO**: `HouseInventoryCheckHistoryReqVO → Result<PageResult<HouseInventoryCheckLogItemVO>>`
#### 使用场景
酒店详情「完整核房历史」Tab:分页查看某酒店全部核房记录,支持方式/操作人/价格变动/时间区间过滤。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| hotelId | Path | Long | 是 | — | 酒店 ID |
| startDate / endDate | Query | LocalDate | 否 | `yyyy-MM-dd` | 核房日期区间 |
| method | Query | String | 否 | — | 核房方式 phone/wechat/visit |
| operatorId | Query | Long | 否 | — | 核房操作人 ID |
| priceChangeOnly | Query | Boolean | 否 | — | 仅看价格变动了的核房 |
| page / pageSize | Query | Long | 否 | ≥1;pageSize ≤200,默认 20 | 分页 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| checkLogId / hotelId | String | 核房记录 ID / 酒店 ID |
| checkDate | LocalDate | 核房日期 |
| method / contactName | String | 核房方式 / 对接联系人 |
| totalAvailable | Integer | 总可用房数 |
| byRoomType | Object | 按房型分布 `{roomTypeId: {label, count}}` |
| note | String | 备注 |
| priceChange / priceChangeNote | Boolean / String | 价格是否变动 / 说明 |
| operatorId | String | 核房人 ID |
| createTime | LocalDateTime | 核房时间 |
#### 请求示例
```http
GET /v3/admin/house/hotels/1001/check-log?page=1&pageSize=20 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "list": [ { "checkLogId": "70300", "hotelId": "1001", "checkDate": "2026-05-15",
"method": "phone", "contactName": "张经理", "totalAvailable": 8, "priceChange": false } ], "total": 1 }
}
```
#### 空数据 / 降级响应
无核房记录返回 `list: []`、`total: 0`。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090。
---
### 7. 酒店维度操作日志 `GET /v3/admin/house/hotels/{hotelId}/operation-log`
**VO**: `HouseOperationLogReqVO → Result<PageResult<HouseOperationLogItemVO>>`
#### 使用场景
按酒店维度查操作日志(P2 候用端点,原型 v1.1 暂无 UI 触发点,接口设计已完整保留)。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| hotelId | Path | Long | 是 | — | 酒店 ID |
| opType | Query | String | 否 | 逗号分隔多选 | 操作类型(见 HouseOpType 常量类) |
| source | Query | String | 否 | — | 数据来源 HOUSE/RESOURCE_ADMIN |
| operatorId | Query | Long | 否 | — | 操作人房务 ID |
| keyword | Query | String | 否 | ≤32 字 | summary/operatorName 模糊 |
| startDate / endDate | Query | LocalDateTime | 否 | ISO 8601 | 时间区间 |
| sortBy | Query | String | 否 | — | 默认 `time,desc` |
| page / pageSize | Query | Long | 否 | ≥1;pageSize ≤200,默认 20 | 分页 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | String | 日志 ID |
| time | LocalDateTime | 操作时间 |
| opType / opTypeLabel | String | 操作类型(英文/中文) |
| summary | String | 操作摘要(后端拼好) |
| operator | Object | 操作人 { userId, name } |
| source | String | 数据来源 |
| detail | Object | 关联实体 ID 集(orderId/requirementId/assignmentId/hotelId/inquiryId/reason/toUserName,按 opType 不同结构) |
#### 请求示例
```http
GET /v3/admin/house/hotels/1001/operation-log?page=1&pageSize=20 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "list": [ { "id": "9001", "time": "2026-05-15T09:30:00", "opType": "CLAIM",
"opTypeLabel": "抢单", "summary": "李房务 抢单", "operator": { "userId": "1001", "name": "李房务" } } ], "total": 1 }
}
```
#### 空数据 / 降级响应
无日志返回 `list: []`、`total: 0`。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090。
---
### 8. 订单维度操作日志 `GET /v3/admin/house/orders/{orderId}/operation-log`
**VO**: `HouseOperationLogReqVO → Result<HouseOrderOperationLogRespVO>`
#### 使用场景
订单详情弹窗「操作日志」Tab:按订单查全部操作日志,包含跨服务动作(resource 改价、主 API 最终确认通过 MQ 写入本表),默认每页 50 条。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| orderId | Path | Long | 是 | — | 订单 ID |
| opType/source/operatorId/keyword/startDate/endDate/sortBy/page/pageSize | Query | — | 否 | 同上(§7) | pageSize 默认值为 50(订单维度) |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| records | Array | 日志列表(字段同 §7 出参行,按时间倒序) |
| total / page / pageSize | Integer | 总条数 / 当前页 / 每页条数 |
| summary | Object | 操作汇总:totalCount(总操作数)/ byOpType(按类型聚合计数) |
#### 请求示例
```http
GET /v3/admin/house/orders/5566778/operation-log HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "records": [ ], "total": 2, "page": 1, "pageSize": 50,
"summary": { "totalCount": 2, "byOpType": { "CLAIM": 1, "ASSIGNMENT_CREATE": 1 } } }
}
```
#### 空数据 / 降级响应
无日志返回 `records: []`、`total: 0`,`summary.byOpType` 为空对象。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090。
---
### 9. 转单候选员工 `GET /v3/admin/house/staff`
**VO**: `HouseTransferCandidateReqVO → Result<HouseStaffListRespVO>`
#### 使用场景
转单弹窗展示可选候选房务员工列表,按当前在跟订单数升序排列,最多 50 条。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| online | Query | Boolean | 否 | 默认 false | true=只看在线 |
| excludeMe | Query | Boolean | 否 | 默认 true | 是否过滤掉自己 |
| maxActive | Query | Integer | 否 | ≥0 | 过滤在跟订单数 ≤ N 的,不填不过滤 |
| keyword | Query | String | 否 | ≤32 字 | 按姓名/工号模糊 |
| fromUserId | Query | Long | 否 | 仅超管/集成可显式指定 | 默认取当前登录人 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| list | Array | 候选员工列表:userId/name/avatar/avatarColor/activeCount/online/isUpperLimitReached/selectable |
| total | Long | 总条数(无分页,等于 list 长度) |
#### 请求示例
```http
GET /v3/admin/house/staff?online=false&excludeMe=true HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "list": [ { "userId": "1002", "name": "王房务", "avatar": "王", "avatarColor": "#5B8FF9",
"activeCount": 3, "online": true, "isUpperLimitReached": false, "selectable": true } ], "total": 1 }
}
```
#### 空数据 / 降级响应
无候选员工返回 `list: []`、`total: 0`。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090。
- `activeCount ≥ 30` 时 `isUpperLimitReached=true`,普通房务对应行 `selectable=false`;超管恒 `selectable=true`。
---
### 10. 房务待办列表 `GET /v3/admin/order/todos`
**VO**: `HouseTodoPageReqVO → Result<HouseTodoListRespVO>`
#### 使用场景
房务待办中心:13 个筛选项 + 排序 + 9 类 stats 统计,`scope` 决定看「我的」「同事的」还是「全部」。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| scope | Query | String | 否 | — | `mine`(我的+未归属,默认)/ `others`(同事在跟)/ `all`(全部,仅房务组长/超管) |
| todoType | Query | String | 否 | 逗号分隔多选 | 待办类型,空=全部 |
| status | Query | String | 否 | — | `OPEN`/`RESOLVED`,空=全部 |
| urgency | Query | String | 否 | — | `danger`/`warn`/`normal`(运行时推导值) |
| keyword | Query | String | 否 | ≤32 字 | 匹配 title/reason/团号 |
| orderId / ownerUserId / hotelId | Query | Long | 否 | — | 订单 / 归属房务 / 酒店过滤 |
| createTimeFrom / createTimeTo | Query | LocalDateTime | 否 | ISO 8601 | 创建时间区间 |
| overdueMinutes | Query | Integer | 否 | — | 仅看超时 N 分钟以上 |
| sortBy | Query | String | 否 | — | 默认 `urgency,desc,createTime,desc` |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| list | Array | 待办列表(详见 `HouseTodoItemVO`),已按默认排序 |
| total | Long | 总条数(过滤后,分页前) |
| stats | Object | 按 todoType 分类统计(9 类,含查询时派生类型) |
#### 请求示例
```http
GET /v3/admin/order/todos?scope=mine&status=OPEN HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "list": [ ], "total": 0, "stats": { } }
}
```
#### 空数据 / 降级响应
无待办返回 `list: []`、`total: 0`,`stats` 各分类计数为 0。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
普通房务传 `scope=all` 或 `scope=others`(本单读门通过后):
```json
{
"code": 582204,
"message": "无权查看全部/他人房务待办(仅房务组长或超管可查看)",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律先返 808090(读门在 `assertScopeAllowed` 之前调用)。
- 通过读门后,普通房务传 `scope=all`/`others` 返 582204;只有房务组长/超管可用这两档。
---
### 11. 月度对账汇总 `GET /v3/admin/house/reconciliation/monthly`
**VO**: `MonthlyReconciliationReqVO → Result<MonthlyReconciliationRespVO>`
#### 使用场景
月度对账首页:配房完毕订单按酒店汇总的概览 + 明细列表,含核单实际花销只读展示(无录入写口)。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| month | Query | String | 是 | 正则 `^\d{4}-(0[1-9]\|1[0-2])$` | 对账月份,如 `2026-05` |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| month | String | 对账月份 |
| overview | Object | 概览:hotelCount/orderCount/roomNights/totalAmount/paidAmount(=totalAmount)/unpaidAmount(恒0)/totalActualExpense(全部未录入为 null)/monthOverMonth |
| hotels | Array | 单酒店账单行(字段见 §12 出参) |
#### 请求示例
```http
GET /v3/admin/house/reconciliation/monthly?month=2026-05 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "month": "2026-05", "overview": { "hotelCount": 5, "orderCount": 30, "roomNights": 96,
"totalAmount": "86400.00", "paidAmount": "86400.00", "unpaidAmount": "0.00", "totalActualExpense": "81200.00" },
"hotels": [ ] }
}
```
#### 空数据 / 降级响应
空月返回 `hotels: []` + 概览全 0(`totalActualExpense` 为 null,不是 0)。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090。
- `month` 缺失或格式错时由 `@Valid` 拦截,HTTP 200 + code 400,先于读门以外的其它业务校验,但仍在角色门**之后**。
---
### 12. 月度对账订单明细 `GET /v3/admin/house/reconciliation/monthly/hotel-orders`
**VO**: `HotelReconciliationOrderReqVO → Result<List<HotelReconciliationOrderVO>>`
#### 使用场景
点击某酒店账单行「查看订单」,展开该酒店当月逐订单明细。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| month | Query | String | 是 | 正则同上 | 对账月份 |
| hotelId | Query | Long | 否 | 有值优先按 ID 查 | 酒店 ID |
| hotelName | Query | String | hotelId 为空时必传 | — | 酒店名快照,也用于合并同名无 ID 历史配房行 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| orderId / orderNo | String | 订单 ID / 订单号 |
| teamNo | String | 规范团号,无值为 null |
| productName / productType | String | 产品名 / 类型 |
| departDate | LocalDate | 订单出发日期 |
| hotelId / hotelName | String | 酒店 ID(无 hotelId 时为 null)/ 酒店名快照 |
| roomNights | Integer | 该订单在该酒店本月间夜数 |
| totalAmount | String | 兼容旧字段,等于 actualExpenseAmount |
| actualExpenseAmount | String | 实际花销金额,未录入时为 null |
| expenseEntered | Boolean | 是否已录入实际花销 |
#### 请求示例
```http
GET /v3/admin/house/reconciliation/monthly/hotel-orders?month=2026-07&hotelId=1234567890123 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": [ { "orderId": "2074303545149980674", "orderNo": "HL20260707092437709", "teamNo": "TEAM-2026-001",
"productName": "测试核心产品-单档-固定订金", "productType": "GROUP", "departDate": "2026-07-23",
"hotelId": "1234567890123", "hotelName": "呼伦贝尔香格里拉大酒店", "roomNights": 2,
"totalAmount": "580.00", "actualExpenseAmount": "580.00", "expenseEntered": true } ]
}
```
#### 空数据 / 降级响应
无匹配订单返回空数组 `[]`。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090。
- `hotelId` 与 `hotelName` 均为空时由 `@Valid`/业务校验拦截(角色门之后)。
---
### 13. 月度对账导出 `GET /v3/admin/house/reconciliation/monthly/export`
**VO**: `MonthlyReconciliationReqVO`(+ Query 参数 `format`)→ 文件流(`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` 或 `application/pdf`)
#### 使用场景
导出月度对账 Excel(默认)或 PDF,含核单实际花销只读列。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| month | Query | String | 是 | 正则同 §11 | 对账月份 |
| format | Query | String | 否 | 默认 `xlsx` | `xlsx`(默认,不传按 xlsx 处理,向后兼容)/ `pdf`;其余取值报 808171 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| — | 二进制流 | `Content-Disposition: attachment`,文件名 `house-reconciliation-{month}.xlsx/pdf` |
#### 请求示例
```http
GET /v3/admin/house/reconciliation/monthly/export?month=2026-05&format=pdf HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
文件流接口,body 为二进制文件(非 JSON 包装),响应头示意:
```json
{
"httpStatus": 200,
"headers": {
"Content-Type": "application/pdf",
"Content-Disposition": "attachment; filename=house-reconciliation-2026-05.pdf"
},
"body": "<binary file stream>"
}
```
#### 空数据 / 降级响应
不适用(文件流接口,无数据也导出表头/空表格)。
#### 错误响应
`format` 非法(不属于空/`xlsx`/`pdf`)时,**在角色门之前**由 Controller 直接抛出:
```json
{
"code": 808171,
"message": "导出格式非法(仅支持 Excel/PDF)",
"success": false,
"data": null
}
```
非房务角色(`format` 合法时):
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- **校验顺序与其它 15 个端点相反**:`format` 合法性校验在 Controller 方法体内直接判断,早于 Service 层的角色门;`format` 非法时无论角色都先报 808171,角色门不会被触发。`format` 合法后才轮到 808090。
---
### 14. 抢单池-房型需求列表 `GET /v3/admin/order/grab-pool/hotel-requirements`
**VO**: `HouseGrabPageReqVO → Result<PageResult<HouseGrabPageItemRespVO>>`
⚠️ 源码已标 `@Deprecated`,`@ApiOperation` 注明「已废弃,改用 `/v3/admin/order/house-allocation/households`」;本单只补读门,不下线该端点。
#### 使用场景
旧版抢单池列表:展示可抢的房型需求,支持关键词/产品类型/定制师/出行日期区间等过滤。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| keyword | Query | String | 否 | ≤32 字 | 订单号/团号/客人姓名/电话/产品名模糊搜 |
| productType | Query | String | 否 | — | `CORE`/`ROUTE`/`CUSTOM`/`GROUP`,不填=全部 |
| productName | Query | String | 否 | — | 产品名模糊搜 |
| consultantId | Query | Long | 否 | — | 定制师 ID 精确过滤 |
| guestName | Query | String | 否 | — | 客人姓名/联系人模糊搜 |
| departDateFrom / departDateTo | Query | LocalDate | 否 | `yyyy-MM-dd` | 出行日期区间 |
| page | Query | Integer | 否 | ≥1,默认 1 | 页码 |
| pageSize | Query | Integer | 否 | ≥1 且 ≤100,默认 20 | 每页条数 |
| sortBy | Query | String | 否 | 默认 `createTime,desc` | 可切 `departDate,asc` |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| id / orderId | String | 房型需求 ID / 订单 ID |
| orderNo / teamNo | String | 订单号 / 团号(未生成为 null) |
| guestName / personsDesc | String | 客人姓名 / 人数描述 |
| productType / productName / productNo | String | 产品类型 / 名称 / 编号 |
| route | String | 档位·夜数(后端拼) |
| departDate / nights | LocalDate / Integer | 出行日期 / 夜数 |
| cities | Array | 行程城市列表(中文,按行程顺序去重) |
| totalAmount | String | 订单总额 |
| consultantName / consultantId | String | 定制师姓名 / adminId |
| consultantRemark / requirementNote / dispatchRemark | String | 定制师订单级备注 / 需求备注摘要 / 提交房务备注 |
| special | Array | 特殊诉求标签 |
| requirementVersion | Integer | 需求版本号(>1 时前端标红「已修订」) |
| urgencyLevel / urgencyLabel / daysToDepart | String / String / Integer | 紧急度码/中文标签/距出发天数 |
| manualUrgent | Boolean | 定制师手动加急 |
| createTime | LocalDateTime | 创建时间 |
| isRework / reworkPrevClaimerName | Boolean / String | 是否返工单 / 上个房务姓名 |
#### 请求示例
```http
GET /v3/admin/order/grab-pool/hotel-requirements?page=1&pageSize=20 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "list": [ { "id": "70123", "orderId": "30456", "orderNo": "26-0501", "guestName": "张先生一家",
"productType": "CORE", "productName": "额吉的故乡", "departDate": "2026-05-01", "nights": 5,
"totalAmount": "6840.00", "urgencyLevel": "URGENT", "urgencyLabel": "紧急" } ], "total": 1 }
}
```
#### 空数据 / 降级响应
无可抢需求返回 `list: []`、`total: 0`;`productType`/`productNo`/`cities` 取数据依赖 product-v2 Feign,失败时各自降级为 null/空。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090。
- 端点已 `@Deprecated`,前端新功能应改接 `/v3/admin/order/house-allocation/households`;本单不改变该端点的可用性,仅补读门。
---
### 15. 抢单池-我的接单 `GET /v3/admin/order/grab-pool/my-claims/hotel`
**VO**: `HouseMyOrderPageReqVO → Result<HouseMyOrderPageRespVO>`
⚠️ 源码已标 `@Deprecated`,同 §14 改用 `/v3/admin/order/house-allocation/households`;本单只补读门。
#### 使用场景
房务查看自己已认领、正在跟进的订单列表,含 4 类状态(进行中/待最终确认/已确认/异常)统计。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| keyword | Query | String | 否 | ≤32 字 | 订单号/团号/客人姓名/电话/产品名模糊搜 |
| status | Query | String | 否 | — | `unfinished`/`allUnfinished`/`todo`(全部未完成)/`inProgress`(兼容)/`claiming`/`pendingConfirm`/`confirmed`/`exception`/`voided`;旧值 `inInquiry` 兼容映射到 `claiming` |
| productType / productName / consultantId / guestName | Query | — | 否 | — | 同 §14 |
| departDateFrom / departDateTo | Query | LocalDate | 否 | — | 出行日期区间 |
| stayDate | Query | LocalDate | 否 | — | 入住晚下钻,只看该晚有效配房所属订单 |
| claimedAtFrom / claimedAtTo | Query | LocalDateTime | 否 | ISO 8601 | 抢单时间区间 |
| city / hasException / hasTodo / hasUnreadMessage | Query | — | 否 | — | 本期未实现,接口预留字段 |
| page / pageSize / sortBy | Query | — | 否 | 同 §14 | 默认 `claimedAt,desc` |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| list | Array | 我的接单行(详见 `HouseMyOrderItemRespVO`) |
| total | Long | 总条数 |
| stats | Object | 按房务跟单状态的分类统计(`HouseMyOrderStatsVO`,进行中/待最终确认/已确认/异常 4 类) |
#### 请求示例
```http
GET /v3/admin/order/grab-pool/my-claims/hotel?status=unfinished&page=1&pageSize=20 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "list": [ ], "total": 0, "stats": { } }
}
```
#### 空数据 / 降级响应
无接单记录返回 `list: []`、`total: 0`,`stats` 各分类计数为 0。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090。
- `city`/`hasException`/`hasTodo`/`hasUnreadMessage` 为接口预留字段,本期传了也不生效。
- 端点已 `@Deprecated`;旧版监督视图 `GET /v3/admin/order/grab-pool/all-claims/hotel`(`listAllClaims`)**不在本单 16 个之列**,只受既有 808092 门约束,不加本单读门(见七、不影响范围)。
---
### 16. 订单房间分配查询(按家庭分组) `GET /v3/admin/order/orders/{orderId}/rooms`
**VO**: `OrderRoomsRespVO`(无独立入参 VO,Path + 单个 Query 参数)
#### 使用场景
按家庭分组查看某订单的房间分配详情:每个家庭的出行人 + 逐日配房卡片。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| orderId | Path | Long | 是 | — | 订单 ID |
| dayNumber | Query | Integer | 否 | 默认全部 | 第几天过滤 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| orderId | String | 订单 ID |
| families | Array | 家庭列表(按 `roomGroupNo` 分组),每项含 roomGroupNo/travelers[]/assignments[] |
| families[].travelers | Array | 出行人简化项:name/idType/phone(脱敏) |
| families[].assignments | Array | 逐日配房:dayNumber/stayDate/hotelName/roomCount/roomAssignments[] |
| roomAssignments[] | Array | 房间细分:id/bedType/bedTypeLabel/travelerCount/remark |
#### 请求示例
```http
GET /v3/admin/order/orders/5566778/rooms HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <room_manager token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "orderId": "5566778", "families": [ { "roomGroupNo": "F1",
"travelers": [ { "name": "张先生", "idType": "id", "phone": "138****5612" } ],
"assignments": [ { "dayNumber": 1, "stayDate": "2026-05-01", "hotelName": "海拉尔假日酒店",
"roomCount": 2, "roomAssignments": [ { "id": "70300", "bedType": "double", "bedTypeLabel": "大床",
"travelerCount": 2, "remark": "加床" } ] } ] } ] }
}
```
#### 空数据 / 降级响应
订单未配房返回 `families: []`;本单删除的家庭维度写口(#8389)下线后新数据的 `roomAssignments` 恒为空数组,`house_room_assignment` 表本身保留、级联软删仍对存量数据有效。
#### 错误响应
```json
{
"code": 808090,
"message": "未登录或非房务角色,无权操作",
"success": false,
"data": null
}
```
#### 业务边界
- 非房务角色一律返 808090。
- 与 #8389 下线的家庭维度写口 `POST /v3/admin/order/assignments/{assignmentId}/rooms` 是同一 Controller 的读写一对;写口已删(404),本端点是唯一保留的读口。
---
## 四、契约约束与正确调用方式
| 场景 | 结果 | 说明 |
|------|------|------|
| ✅ 房务角色(ROOM_MANAGER / house_keeper_lead / SUPER_ADMIN)GET 本单 16 个端点 | 200 + 数据 | 行为与改前同 |
| ✅ 零角色 token(网关未透传 `X-Admin-Role`) | 200 + 数据 | `#7609 G-2` 保留项,本单不改 |
| ✅ 定制师在订单详情查看行程 | 仍 200(走 `requirement-history`/`hotel-candidates`,不在本单 16 个端点内) | 有意跨角色端点,源码注释明确标注勿挂本门 |
| ⚙️ 开关 `group-batch.acl.enforce.house-read-role` 关闭后非房务角色再查 | 按改前逻辑放行 | 源码:`observeWhenToggleOff()` 只记 WARN(`enforced=false`)不抛异常,17 处调用点逐字退回"不调守卫" |
| ❌ 非房务角色(ADMIN/CUSTOMIZER/FINANCE/VEHICLE_MANAGER/GROUP_BATCH_MANAGER 等)GET 本单 16 个端点(开关默认 true) | 808090 | 新增拦截 |
| ❌ 非组长/超管查 `GET /v3/admin/order/todos?scope=all`(或查他人 scope) | 582204 | 既有二级门,先过 808090 才轮到它 |
| ❌ 导出对账单 `format` 非法 | 808171 | 在 808090 门之前触发(见 §三-13,校验顺序与其余 15 个端点相反) |
---
## 五、数据库行为(PR-2)
user-service Flyway `V20260926_002` 撤掉 ADMIN 角色在房务管家子树(目录 + 4 个页面)的 `sys_role_menu` 授权(5 行),不改 `sys_menu`,幂等可重跑。user-service 启动时清一次菜单缓存,撤权后 ADMIN 重新登录/刷新菜单即生效;SUPER_ADMIN 的菜单授权不受影响。
---
## 六、边界行为
| 场景 | 行为 |
|---|---|
| 房务组长(house_keeper_lead)查日历/待办等 | 200,与 ROOM_MANAGER/SUPER_ADMIN 一致(只读监督角色,读门不额外收窄) |
| 非房务角色查本单 16 个端点 | 808090 |
| 开关关闭后非房务角色再查 | 按改前逻辑放行(不是"零角色口径放行",是逐字回到不调守卫) |
| ADMIN 登录 hl-ui | 左侧无房务管家菜单(Flyway 撤授权后,需重新登录/刷新菜单缓存) |
| SUPER_ADMIN 登录 | 房务菜单照常显示 |
## 六.6、修改前后对比
| 维度 | 改前 | 改后 |
|------|------|------|
| CUSTOMIZER 查月度对账 | 返回 200 + 数据 | 返回 808090 |
| VEHICLE_MANAGER 查日历 | 返回 200 + 数据 | 返回 808090 |
| ADMIN 查本单 16 个端点 | 返回 200 + 数据 | 返回 808090 |
| 房务组长查酒店视图/待办 | 返回 200 | 返回 200(不动) |
| ADMIN 角色菜单 | 有房务管家目录 + 4 个页面 | Flyway 撤授权,无该菜单 |
## 六.7、影响评估
- **兼容性**:非房务角色对本单 16 个读端点集体拦截(角色是 user-service 侧的字符串值,order-v3 常量类 `AdminRoleConstants` 里已知的非房务角色含 ADMIN/CUSTOMIZER/FINANCE/VEHICLE_MANAGER/GROUP_BATCH_MANAGER,实际角色集合以 user-service 角色表为准);ADMIN 打开 hl-ui 左侧菜单无房务管家项。
- **前端要动的**:
- 非房务角色页面若调这 16 个端点,新收 808090,需要补错误提示分支。
- `GET /v3/admin/order/todos` 传 `scope=all` 或查他人时,非组长/超管会先过 808090、再撞 582204(既有错误码,未变)。
- 导出对账单 `format` 非法时先收 808171,早于角色门(见 §三-13)。
- ADMIN 账号因菜单撤授权,房务管家入口本身从左侧菜单消失(不是点击后报错)。
- 定制师订单详情仍可看「行程」等 Tab(`requirement-history`/`hotel-candidates` 未挡),无改动。
- **数据影响**:零;仅加权限门与撤菜单授权,不改业务数据。
- **其它服务**:product-v2、fleet、hl-ui 后端无改动。
---
## 七、不影响范围
- 订单列表新端点(households/group-batches):已有读门,不动。
- 团期看板/allocations/room-plans/confirm-check:已有读门,不动。
- 认领人校验(#8386 / #8388):独立,不重叠。
- 旧抢单池的监督视图 `GET /v3/admin/order/grab-pool/all-claims/hotel`(`listAllClaims`):**不在本单 16 个端点之列**,只受既有 808092「无权查看全部房务订单」门约束,本单未给它补 808090。
- 零角色 token(#7609 G-2 保留):照常放行,本单不改。
---
## 八、测试环境已验证
部署:测试环境 order-v3 与 user-service 均为 dev-v3 `5cb43db94`(含 PR-1 `08747e304` 与 PR-2 `5f29f7ac6`),user-service Flyway `20260926.002 revoke admin house menus` 已执行(2026-09-26 21:55);order-v3 升到 `1f65d7894` 后的改后回归中,非房务角色调这 16 个端点仍为 808090。网关 `api.test.1814.love:9443`。
- 16 个端点:VEHICLE_MANAGER / CUSTOMIZER / ADMIN 等非房务角色 → HTTP 200 + code 808090「未登录或非房务角色,无权操作」;ROOM_MANAGER / house_keeper_lead / SUPER_ADMIN → 200 正常数据。
- 零角色 token → 200(保留行为,按设计放行)。
- ADMIN 菜单:user-service Flyway 执行后,`hl_user_service` 角色菜单表里 ADMIN 已无房务管家子树(只读 SQL 核对)。
- `GET /admin/menu/my`:SUPER_ADMIN 返回 `/housekeeper` 及其 calendar / ledger / orders / todos 四个子菜单;ADMIN 返回的菜单树里没有任何 `/housekeeper` 路径;ROOM_MANAGER、house_keeper_lead 仍是这 5 个菜单,与改前一致。
---
## 九、开关说明
**key**: `group-batch.acl.enforce.house-read-role`
**默认**: `true`
**类型**: 热刷新 `@RefreshScope`(`GroupBatchAclToggle`)
**范围**: 只管本单新挂的 17 处方法入口(16 个端点,对账导出内部拆 2 个调用点算 1 个端点),已有的读门/写门调用点不读这个开关。
**回滚**: Nacos 置 `false` 后热生效,17 处调用点逐字回到"不调守卫"(不是按零角色口径放行,比改前更窄的口径不会出现);关闭期间对本应被拒绝的访问仍打 `HOUSE_READ_ROLE_MISSING`(`enforced=false`)WARN 日志用于观察。
---
## 十、相关文档
- Issue:https://git.1814.love/wx/HL/issues/8390
- PR-1(order-v3,读门):https://git.1814.love/wx/HL/pulls/8393
- PR-2(hl-user-service,菜单撤授权):#8392
- 相关单号:#8386 / #8388(认领人校验同规则)、#8389(家庭维度写口下线,本单为其 GET 端点补读门)
- API-SPEC:各端点错误码已补 808090
---
## 关联 / 联系人
### 联系人
- **后端负责人**: @wx