文件
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

24 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 8659 房务价格日历与库存口径统一:候选页与控房表增加日历状态,扣减拒绝按原因分三码 admin wx(GIT) 修改接口 deployed not_required pending mmg 2026-10-02 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 候选酒店列表
candidates[].roomTypes[] List 该酒店当日真实房型列表
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)照常返回,只有库存字段归零

请求示例

GET /v3/admin/hotel-candidates?orderId=100001&dayNumber=1&city=hailar&roomCount=2

响应示例

{
  "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 处理(不单列「异常」状态)。

错误响应

{
  "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 按(酒店, 入住夜, 房型)升序
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 团组用房明细(散客配房行或团期计划行,本表只列扣库存行)
rows[].usages[].orderId / teamNo / batchNo - 订单 ID(团期计划行为 NULL)/ 团号 / 团期批次号(散客订单为 NULL)
rows[].usages[].roomCount / roomSource / roomSourceLabel / confirmStatusLabel - 用房间数 / 房源(STOCK)/ 房源标签 / 确认状态标签

请求示例

GET /v3/admin/order/house-console/room-control?cityCode=hailar&dateFrom=2026-10-01&dateTo=2026-10-31

响应示例

{
  "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(不是「状态异常」,无行与脏值是两种不同情况)。

错误响应

{
  "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 ✅ 非空 配房项列表(批量)
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 每条配房结果
items[].dayNumber Integer 第几天
items[].assignmentId Long(JSON 字符串) 配房 ID
items[].arrange String 配房状态,取值仅 inquiring(询价中)/ confirmed(已确认)
items[].deductInventory Boolean 本次配房是否扣减了库存

请求示例

{
  "items": [
    {
      "dayNumber": 1,
      "hotelId": 100001,
      "roomTypeId": 300001,
      "roomCategory": "STANDARD",
      "roomCount": 2,
      "deductInventory": true
    }
  ]
}

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "successCount": 1,
    "failCount": 0,
    "items": [
      {
        "dayNumber": 1,
        "assignmentId": "1970000000000000001",
        "arrange": "inquiring",
        "deductInventory": true
      }
    ]
  },
  "success": true
}

空数据 / 降级响应

无特殊降级。批量提交中任意一项触发库存扣减失败(808906/808907/808901)会导致整次提交被拒绝,不落库、不产生部分成功的配房记录;需要调整后整批重新提交。

错误响应

{
  "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 更新 ✓

十、相关文档

关联 / 联系人

关联工单: #8659
同批变更: #8662(旧接口删除与权限补漏)
后端负责人: @wx