文件
hl-api-changelog/changelogs-v2/2026-10/02_8659_房务价格日历与库存口径统一-修改接口-管理后台.md
API Changelog Bot和Claude Opus 5.5 cdcd07d8e5
changelog-filename-gate / validate (push) Failing after 1s
docs(changelog): #8659 房务价格日历与库存口径统一 / #8662 删除旧住宿需求提交口与询房预览补权限
- #8659:候选页 inventoryStatus 按日历状态取值;控房表新增 calendarStatus / calendarStatusName(前端加一列展示);扣减拒绝分 808906 / 808907 / 808901。
- #8662:删除 PUT /v3/admin/order/{id}/hotel-requirement;询房预览补房务读守卫,非房务角色返回 808090。

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 01:15:08 +08:00

521 行
24 KiB
Markdown

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
---
schema: "hl-changelog/v2"
ticket: "8659"
title: "房务价格日历与库存口径统一:候选页与控房表增加日历状态,扣减拒绝按原因分三码"
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: ""
updated_at: "2026-10-02"
base: "dev-v3"
---
# 房务配房接口调整:价格日历与库存口径统一、扣减失败分码
> **存放目录**: 二期 → `changelogs-v2/2026-10/`
>
> **服务**: hl-order-service-v3、hl-resource-service
> **Issue**: #8659
> **日期**: 2026-10-02
> **影响范围**: 管理后台房务配房流程,涉及候选酒店页、控房表、配房失败反馈
---
## ⚠️ 关键变化
- **候选酒店页 `inventoryStatus` 扩大语义**:日历已售罄(`SOLD_OUT`)映射为 `FULL`,日历已关闭(`CLOSED`)或无日历行映射为 `CLOSED`;非 `AVAILABLE` 时 `available=0`、`unlimited=false`(即使库存数本身大于 0)。**前端无需改代码**:经 hl-ui v2.1 核实,候选弹窗(`PickHotelModal.vue`)已按 `inventoryStatus` 三值禁用非 `AVAILABLE` 选项、显示「满房」「已关」,且取值用 `pickFirst` 读取不会被 `available=0` 误判为缺省继续读旧的 `stock`。
- **控房表新增 `calendarStatus` / `calendarStatusName`**:价格日历状态原值与中文名。纯新增字段,不读不影响现有解析;**前端需在控房表加一列展示 `calendarStatusName`**,房务才能在剩余数大于 0 时看出该格「已关闭」或「已售罄」、扣减必被拒(这是本次唯一的前端动作)。
- **扣减失败时按原因分三种错误码**(原来统一报 808901):
- `808906`「该日该房型未配置价格日历」(新增)
- `808907`「该日该房型已关闭售卖或已售罄(状态:{0})」(新增,`{0}` 为状态中文名)
- `808901`「房型库存不足」(文案收窄,现仅表示日历可售但库存不够;旧文案与此不同,见下)
- 以上均由前端通用拦截器按 `message` 自动弹出,调用方无需按 `code` 分支即可正确展示。
---
## 一、背景
资源侧价格日历状态(可售 `AVAILABLE` / 已售罄 `SOLD_OUT` / 已关闭 `CLOSED`)与库存数是两个独立维度。读侧(候选页、控房表)曾只看库存、不读状态,与写侧(库存扣减按 `status=AVAILABLE` 谓词)产生口径断裂:房务在候选页看到「可订」而提交配房被拒,甚至产生"该加房量"的错误判断,实际原因是日历已关房。本次统一口径:读侧添加日历状态、扣减时按真实原因分码。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 候选酒店页 | GET | `/v3/admin/hotel-candidates` | 修改 | roomTypes[].inventoryStatus 取值语义扩大 |
| 2 | 控房表 | GET | `/v3/admin/order/house-console/room-control` | 修改 | 新增 calendarStatus / calendarStatusName |
| 3 | 逐晚提交配房 | POST | `/v3/admin/order/hotel-requirements/{requirementId}/assignments` | 修改 | 扣减拒绝返回 808906 / 808907 / 808901(原统一 808901) |
---
## 三、接口详情
本节示例 JSON 的字段名、类型、错误码与文案逐一取自源码;ID、日期等取值为说明用的构造值。所有接口统一返回 `Result` 信封(`code` / `message` / `data` / `traceId` / `success`),示例省略 `traceId`;业务失败与入参校验失败均为 HTTP 200,靠 `code` 区分。
### 1. 候选酒店页 `GET /v3/admin/hotel-candidates`
**VO**: `HotelCandidateQueryReqVO → HotelCandidateRespVO`
#### 使用场景
房务/定制师为某个订单的某一晚选酒店时查看候选酒店列表及房型库存。本次改动只影响候选房型 `roomTypes[].inventoryStatus`(及联动的 `available`/`unlimited`)的取值口径,请求参数与响应结构均未变。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| orderId | Query | Long | ✅ | - | 订单 ID |
| dayNumber | Query | Integer | ❌ | ≥1 | 第几天(从 1 开始,`stayDate = departDate + dayNumber - 1`),与 stayDate 二选一,直接传 stayDate 优先 |
| stayDate | Query | LocalDate | ❌ | - | 入住日期;直接指定优先于 dayNumber 推算 |
| city | Query | String | ❌ | - | 城市代码 |
| keyword | Query | String | ❌ | - | 关键词非空时突破单城限定,跨城/省按酒店名/城市/省份/地址匹配;为空维持单城行为 |
| limit | Query | Integer | ❌ | 1~50,默认 30 | 返回候选条数上限,池内/定制师优先排序后取前 N |
| roomCategory | Query | String | ❌ | 字典 room_category | 非空白时参与房型过滤,matched 房型只在同大类内选取 |
| roomCount | Query | Integer | ❌ | ≥1 | 需要的房间数;不传退化为 available>0 的旧行为 |
| preferredHotelId | Query | Long | ❌ | - | 定制师指定的优先酒店 ID |
| requirementId | Query | Long | ❌ | - | 住宿需求 ID;传入后该需求 days JSON 里所有 hotelId 作为定制师指定 |
#### 出参 `Result<HotelCandidateRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| stayDate | LocalDate | 入住日期 |
| city | String | 城市代码 |
| productType | String | 产品类型(CORE/GROUP/CUSTOM,响应专有字段,无同名入参) |
| candidates[] | List<Candidate> | 候选酒店列表 |
| candidates[].roomTypes[] | List<RoomTypeOption> | 该酒店当日真实房型列表 |
| candidates[].roomTypes[].available | Integer | **语义变化**。`unlimited=true` 时为 NULL(表不限);否则为当日可用房数。`inventoryStatus` 非 `AVAILABLE` 时恒为 `0`(即使库里 stock 列大于 0) |
| candidates[].roomTypes[].unlimited | Boolean | **语义变化**。resource 端 stock 列为 NULL 时才为 `true`;`inventoryStatus` 非 `AVAILABLE` 时恒为 `false`(工单 #8659 前,日历已关闭/售罄但 stock=NULL 的房型也会显示「不限」,现改为显示不可订) |
| candidates[].roomTypes[].inventoryStatus | String | **修改**。`AVAILABLE`=日历可售且有余量或不限库存;`FULL`=日历可售但余量为 0 或日历已售罄;`CLOSED`=未配置价格日历、日历已关闭或日历状态字典外脏值(工单 #8659)。`FULL`/`CLOSED` 时价格字段(protocolPrice/settlementPrice/basePrice)照常返回,只有库存字段归零 |
#### 请求示例
```http
GET /v3/admin/hotel-candidates?orderId=100001&dayNumber=1&city=hailar&roomCount=2
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"stayDate": "2026-10-05",
"city": "hailar",
"productType": "CORE",
"candidates": [
{
"hotelId": "200001",
"hotelName": "海拉尔假日酒店",
"roomTypes": [
{
"roomTypeId": "300001",
"name": "豪华大床房",
"roomCategory": "KING",
"available": 7,
"unlimited": false,
"stock": 7,
"protocolPrice": "328.00",
"inventoryStatus": "AVAILABLE"
},
{
"roomTypeId": "300002",
"name": "标准双床房",
"roomCategory": "TWIN",
"available": 0,
"unlimited": false,
"stock": 5,
"protocolPrice": "280.00",
"inventoryStatus": "CLOSED"
}
]
}
]
},
"success": true
}
```
上例第二个房型 `stock=5`(库里仍有余量)但日历已关闭,`inventoryStatus=CLOSED`、`available` 归零——这正是本次改动要修的口径断裂:改前 `available` 会原样显示库里的 5。
#### 空数据 / 降级响应
- 该订单该日无酒店或筛选无匹配:`candidates=[]`。
- 日历状态字典外脏值按 `CLOSED` 处理(不单列「异常」状态)。
#### 错误响应
```json
{
"code": 400,
"message": "orderId 不能为空",
"data": null,
"success": false
}
```
#### 业务边界
- `inventoryStatus` 判定日历状态优先于库存数:日历 `CLOSED` 或 `SOLD_OUT` 时,即使 resource 库存列数字大于 0,`available`/`unlimited` 仍归零,不能据库存字段反推是否可订。
- `limit` 默认 30、上限 50,候选列表本身是截断后的结果,不代表该城市全部酒店。
### 2. 控房表 `GET /v3/admin/order/house-console/room-control`
**VO**: `HouseRoomControlListReqVO → HouseRoomControlRespVO`
#### 使用场景
房务在房务控制台查看某个城市(或某酒店)、某日期区间的库存占用详情及团组用房明细。本次新增 `calendarStatus` / `calendarStatusName` 两个字段;前端在控房表加一列展示 `calendarStatusName` 后,房务无需逐行点开即可看到日历状态。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| cityCode | Query | String | ❌ | ≤32 字 | 城市编码;与 hotelId 都不传则查全部酒店 |
| hotelId | Query | Long | ❌ | - | 酒店 ID;传了则只查这一家,优先于 cityCode |
| dateFrom | Query | LocalDate | ✅ | - | 入住夜起(含) |
| dateTo | Query | LocalDate | ✅ | 与 dateFrom 跨度 ≤62 天 | 入住夜止(含) |
| onlyWithRemain | Query | Boolean | ❌ | - | true 时只返回剩余为不限或 >0 的行 |
#### 出参 `Result<HouseRoomControlRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| stockTrackingEnabled | Boolean | resource 全局库存追踪开关;false 时行照常返回、已用取实际值,但调房量会被拒(808312) |
| rows[] | List<Row> | 按(酒店, 入住夜, 房型)升序 |
| rows[].hotelId / hotelName / cityName | - | 酒店 ID / 酒店名 / 城市中文名 |
| rows[].roomTypeId / roomTypeName | - | 房型 ID / 房型名 |
| rows[].stayDate | LocalDate | 入住夜 |
| rows[].totalRooms | Integer | 控房总数 = 剩余 + 已用;NULL 表不限量 |
| rows[].usedRooms | Integer | 已用(resource stock_used;库存开关关闭时仍取实际值) |
| rows[].remainRooms | Integer | 剩余(resource stock);NULL 表不限量 |
| rows[].assignedRooms | Integer | 已分配:扣库存的配房行 + 已确认扣库存的团期计划行间数合计 |
| rows[].protocolPrice / settlementPrice | BigDecimal(字符串) | 控房价(协议价)/ 结算价 |
| rows[].calendarStatus | String | **新增**。价格日历状态原值:`AVAILABLE / SOLD_OUT / CLOSED`,字典外历史值原样返回;该格无日历行时为 NULL。非 `AVAILABLE` 时扣减必被拒,与剩余数无关(工单 #8659) |
| rows[].calendarStatusName | String | **新增**。价格日历状态中文名:可售 / 已售罄 / 已关闭;字典外值显示「状态异常(原值)」;无日历行时为 NULL |
| rows[].usages[] | List<Usage> | 团组用房明细(散客配房行或团期计划行,本表只列扣库存行) |
| rows[].usages[].orderId / teamNo / batchNo | - | 订单 ID(团期计划行为 NULL)/ 团号 / 团期批次号(散客订单为 NULL) |
| rows[].usages[].roomCount / roomSource / roomSourceLabel / confirmStatusLabel | - | 用房间数 / 房源(STOCK)/ 房源标签 / 确认状态标签 |
#### 请求示例
```http
GET /v3/admin/order/house-console/room-control?cityCode=hailar&dateFrom=2026-10-01&dateTo=2026-10-31
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"stockTrackingEnabled": true,
"rows": [
{
"hotelId": "100001",
"hotelName": "海拉尔草原酒店",
"cityName": "海拉尔",
"roomTypeId": "300001",
"roomTypeName": "豪华双床房",
"stayDate": "2026-10-01",
"totalRooms": 12,
"usedRooms": 5,
"remainRooms": 7,
"assignedRooms": 5,
"protocolPrice": "320.00",
"settlementPrice": "300.00",
"calendarStatus": "AVAILABLE",
"calendarStatusName": "可售",
"usages": [
{
"orderId": "1930000000000000001",
"teamNo": "HL20261001A",
"batchNo": null,
"roomCount": 3,
"roomSource": "STOCK",
"roomSourceLabel": "控房",
"confirmStatusLabel": "已确认"
}
]
},
{
"hotelId": "100002",
"hotelName": "海拉尔雅园宾馆",
"cityName": "海拉尔",
"roomTypeId": "300005",
"roomTypeName": "标准大床房",
"stayDate": "2026-10-01",
"totalRooms": 8,
"usedRooms": 2,
"remainRooms": 6,
"assignedRooms": 3,
"protocolPrice": "280.00",
"settlementPrice": "260.00",
"calendarStatus": "CLOSED",
"calendarStatusName": "已关闭",
"usages": []
}
]
},
"success": true
}
```
#### 空数据 / 降级响应
- 区间内无库存数据:`rows=[]`,`stockTrackingEnabled` 仍照常返回开关实际值。
- 该格无日历行时 `calendarStatus`/`calendarStatusName` 为 NULL(不是「状态异常」,无行与脏值是两种不同情况)。
#### 错误响应
```json
{
"code": 400,
"message": "dateFrom 不能为空",
"data": null,
"success": false
}
```
日期跨度超 62 天或起止颠倒不走参数校验码,由 Manager 本地校验后返业务码 808313。
#### 业务边界
- `calendarStatus=CLOSED` 或 `SOLD_OUT` 时,`remainRooms` 数字再大也**无法扣减**(扣减 UPDATE 只认 `status=AVAILABLE`);房务需在此处调整日历或释放库存,不能指望后续配房流程放行。
- 该表不隐藏 `remainRooms`,即使日历已关闭仍显示实际 stock,控房表目的是查看与调整库存,不把实际数字藏起来。
- `stockTrackingEnabled=false` 时各行仍按实际值返回 `usedRooms`/`remainRooms`,但调房量会被拒(808312),不能据此误判为"关闭追踪=不限量"。
### 3. 逐晚提交配房 `POST /v3/admin/order/hotel-requirements/{requirementId}/assignments`
**VO**: `AssignmentSubmitReqVO → AssignmentSubmitRespVO`
#### 使用场景
房务为一个住宿需求批量提交配房方案(一次可提交多晚 × 多组房间)。本次修改:扣减失败时根据真实原因(日历无行、日历不可售、库存不足)返回不同错误码,不再统一报 808901。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| requirementId | Path | Long | ✅ | - | 住宿需求 ID |
| items | Body | List<AssignmentItemReqVO> | ✅ | 非空 | 配房项列表(批量) |
| items[].dayNumber | Body | Integer | ✅ | ≥1 | 第几天(Day1=1) |
| items[].hotelId | Body | Long | ✅ | - | 酒店 ID |
| items[].roomTypeId | Body | Long | ✅ | - | 房型 ID |
| items[].roomCategory | Body | String | ✅ | 字典 room_category | 房型字典 code(如 STANDARD) |
| items[].roomCount | Body | Integer | ✅ | ≥1 | 间数 |
| items[].deductInventory | Body | Boolean | ✅ | - | 是否扣减资源酒店房型库存;true=占用系统库存,false=仅保存配房快照不扣库存 |
| items[].protoPrice | Body | BigDecimal | ❌ | ≥0 | 协议价快照;不传按所选房型当日资源协议价兜底 |
| items[].settlementPrice | Body | BigDecimal | ❌ | ≥0 | 结算价快照;不传按资源结算价兜底,再兜底协议价 |
| items[].settleType | Body | String | ❌ | cash\|sign\|company | 支付方式快照;不传取酒店资源配置 |
| items[].breakfast | Body | String | ❌ | INCLUDED/EXCLUDED/PENDING | 早餐;不传按待确认存空 |
| items[].syncProtocolPrice / syncSettlementPrice / syncSettleType | Body | Boolean | ❌ | - | 是否把本项对应快照同步写回 resource;默认 false 不同步 |
| items[].remark | Body | String | ❌ | - | 备注 |
| items[].replaceReason | Body | String | ❌ | ≤256 字 | 替换原因;仅该天已有旧行被本项替换时落库 |
#### 出参 `Result<AssignmentSubmitRespVO>`
| 字段 | 类型 | 说明 |
|------|------|------|
| successCount | Integer | 成功条数 |
| failCount | Integer | 失败条数。当前实现恒为 `0`——扣减失败走整笔拒绝(见下),不会出现"部分成功部分失败"的响应 |
| items[] | List<Item> | 每条配房结果 |
| items[].dayNumber | Integer | 第几天 |
| items[].assignmentId | Long(JSON 字符串) | 配房 ID |
| items[].arrange | String | 配房状态,取值仅 `inquiring`(询价中)/ `confirmed`(已确认) |
| items[].deductInventory | Boolean | 本次配房是否扣减了库存 |
#### 请求示例
```json
{
"items": [
{
"dayNumber": 1,
"hotelId": 100001,
"roomTypeId": 300001,
"roomCategory": "STANDARD",
"roomCount": 2,
"deductInventory": true
}
]
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"data": {
"successCount": 1,
"failCount": 0,
"items": [
{
"dayNumber": 1,
"assignmentId": "1970000000000000001",
"arrange": "inquiring",
"deductInventory": true
}
]
},
"success": true
}
```
#### 空数据 / 降级响应
无特殊降级。批量提交中任意一项触发库存扣减失败(808906/808907/808901)会导致**整次提交被拒绝**,不落库、不产生部分成功的配房记录;需要调整后整批重新提交。
#### 错误响应
```json
{
"code": 808906,
"message": "该日该房型未配置价格日历",
"data": null,
"success": false
}
```
其他错误:
| code | message | 触发 |
|------|---------|------|
| 808907 | 该日该房型已关闭售卖或已售罄(状态:{0}) | 日历 status 为 CLOSED 或 SOLD_OUT,`{0}` 为状态中文名 |
| 808901 | 房型库存不足 | 日历 status=AVAILABLE 但剩余数不够 |
| 808090 | 未登录或非房务角色,无权操作 | 非房务角色 |
| 808116 | 订单未抢单, 请先抢单再配房 | 需求无人持有(源码原文逗号为半角) |
| 808110 | 需求不属于当前用户 | 当前用户不是持有人(超管同样拒绝) |
#### 业务边界
- `deductInventory=true` 的项触发库存扣减,三种拒绝原因分别报 808906 / 808907 / 808901;批量提交中任一项被拒,整次提交失败,不做部分落库。
- `deductInventory=false` 的项不做库存校验,正常返回 200。
- `roomCategory` 为必填字段,传空或不传直接触发参数校验失败(`code=400`,HTTP 状态仍为 200)。
- 防重 3 秒,同一需求 3 秒内重复提交被拦截。
---
## 四、契约约束与正确调用方式
**通俗说法**:配房前看候选页或控房表,若日历状态显示「已关闭」或「已售罄」,不能提交配房;提交时若被拒,先按错误码判断原因——库存不足就加库存,日历问题就调日历。
### 示例对比
| 场景 | 旧行为 | 新行为 |
|------|--------|--------|
| 日历已关闭、stock=20 | 候选页显示可订、余 20 间;提交配房返 808901「库存不足」(误导) | 候选页 inventoryStatus=CLOSED、available=0;提交配房返 808907「已关闭」(准确);控房表 calendarStatus=CLOSED 可直观查看 |
| 日历无行 | 候选页显示可订或不显示(看评分);提交被拒 808901 | 候选页 inventoryStatus=CLOSED、available=0;提交配房返 808906「未配置日历」(准确指示资源侧缺陷) |
| 日历可售、stock=0 | 候选页显示 FULL、available=0;提交被拒 808901 | 候选页显示 FULL、available=0;提交被拒 808901(同前) |
---
## 五、数据库行为
扣减仍走库存表 UPDATE 逻辑,本次仅改返回码与日历状态展示,无 DDL 或表结构变化。
---
## 六、边界行为
- 错误码 808906 / 808907 / 808901 的优先级:先判日历有无(808906)→再判日历状态(808907)→最后判库存(808901)。
- 控房表的 `remainRooms` 与日历状态 `CLOSED` 同时出现时,表示日历被关了但库存数还在,房务调整库存须先对日历解冻(不归房务接口管)。
---
## 六.5 枚举
### inventoryStatus(HotelCandidateRespVO.RoomTypeOption)
**类型**: `String`
| 值 | 条件 | 前端表现 |
|----|------|--------|
| `AVAILABLE` | 日历 status=AVAILABLE 且库存>0(或 NULL 不限) | 可订、显示余量 |
| `FULL` | 日历 status=AVAILABLE 但库存=0,或日历 status=SOLD_OUT | 满房、显示「已满」 |
| `CLOSED` | 日历 status=CLOSED 或无日历行 | 关闭、显示「不可订」 |
### calendarStatus(HouseRoomControlRowRespVO)
**类型**: `String`
| 值 | 说明 |
|----|------|
| `AVAILABLE` | 可售 |
| `SOLD_OUT` | 已售罄 |
| `CLOSED` | 已关闭 |
| NULL | 无日历行 |
| 其他 | 字典外的历史脏数据(原样返回) |
---
## 六.6、修改前后对比
| 项 | 改前 | 改后 |
|----|------|------|
| 候选页 inventoryStatus 逻辑 | 仅看库存数;SOLD_OUT→FULL、CLOSED→CLOSED(但都显示有库存或空库存) | 优先看日历状态;SOLD_OUT→FULL、CLOSED→CLOSED(且 available=0) |
| 控房表字段 | calendarStatus / calendarStatusName 无 | **新增**,展示日历原值与中文名 |
| 扣减失败错误码 | 统一 808901(三种原因混合) | 分码:808906(无日历)、808907(日历不可售)、808901(库存不足) |
---
## 六.7、影响评估
- **前端改动只有一处:控房表加「日历状态」列**(hl-ui v2.1 实查结论):
- 候选弹窗 `src/views/housekeeper/components/PickHotelModal.vue` 已按 `inventoryStatus` 三值分支渲染禁用态与文案(`disabled: !rt.unlimited && rt.inventoryStatus !== 'AVAILABLE'`,CLOSED 显示「已关」/FULL 显示「满房」),取库存数用 `pickFirst()` 辅助函数正确处理 `available=0` 而不误判为缺省继续回退读旧字段,三值语义收紧不影响该组件现有行为。
- 控房表新增的 `calendarStatus`/`calendarStatusName`/`usages[]` 均为纯新增字段,hl-ui v2.1 当前对该查询结果的消费代码未读取这些字段名,新增不影响现有解析;**需要新增一列展示 `calendarStatusName`**(为 NULL 时显示「—」),否则剩余数大于 0 而日历已关闭的格子在页面上看不出来。
- 配房提交的错误码拆分(808906/808907/808901)均由 `src/api/housekeeper/assignment.js` 所在模块走通用拦截器按 `message` 弹窗展示,调用方代码未对 808xxx 做按值分支(`useHousekeeperPlacementAdjustment.js:71` 注释确认),拆分前后前端展示路径一致。
- **触发频率变化**:`inventoryStatus=CLOSED` 的触发条件由「仅库存为 0」扩大为「日历关闭/售罄/无日历行 或 库存为 0」,该状态出现频率会提升,但因前端已走统一的三值禁用逻辑,不需要代码改动去适配。
---
## 七、不影响范围
- 小程序端接口不变。
- 订单确认、支付、发票等下游流程无改动。
---
## 八、测试环境已验证
测试服环境,2026-09-30~10-02。
```
候选页(GET /v3/admin/hotel-candidates)
关闭日期场景:keyword 查询返回 inventoryStatus=CLOSED, available=0 ✓
重新开放:状态切回 AVAILABLE, available 恢复实际库存数 ✓
控房表(GET /v3/admin/order/house-console/room-control)
关闭日期:calendarStatus=CLOSED, calendarStatusName=已关闭 ✓
重新开放:calendarStatus=AVAILABLE, calendarStatusName=可售 ✓
配房扣减(POST /v3/admin/order/hotel-requirements/{id}/assignments)
提交已关闭日期的房型:返回 808907「已关闭或已售罄」✓
重新开放后提交:返回 200, stock 更新 ✓
```
---
## 十、相关文档
- **Issue**: [#8659](https://git.1814.love:8443/wx/HL/issues/8659)
- **PR**: [#8704](https://git.1814.love:8443/wx/HL/pulls/8704)
## 关联 / 联系人
**关联工单**: #8659
**同批变更**: #8662(旧接口删除与权限补漏)
**后端负责人**: @wx