22 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 | 7323 | 团期房务地基:新建 group_batch_room_plan / group_batch_room_allocation / order_group_batch_house_legacy 三表 + house_hotel_assignment.replace_reason 列 + house_dual_deduction_log.order_id 放宽可空/加 source_type;核心配房提交 items[].replaceReason 新增可选字段(重复提交不覆写);新增内部错误码 808620-808622 | admin | wx(GIT) | 修改接口 | deployed | verified | not_required | mmg | not_required 2026-09-10 mmg: 纯新增可选 items[].replaceReason(<=256,仅替换行落库、重复提交不覆写),不传时行为逐字一致向后兼容;changelog 自答非必须同步上线。grep 实证前端 postAssignments(api/housekeeper/assignment.js) 只透传 {items} 无 replaceReason,唯一真实变化=配房提交可选原因输入框,属后续 #7324-#7327 团期房务整族一并做的能力,非本条强制。arrange 的 inquiring 系既有缺陷非本单引入,前端兜底随 #7324+ 接线处理。本条地基单 0 外部可达改动。 | 2026-09-10 | dev-v3 |
团期房务:地基库表 + 核心配房替换原因字段 replaceReason
服务: hl-order-service-v3 PR: #7413 Issue: #7323 日期: 2026-09-10 影响范围: 核心/定制订单房务配房提交接口新增 1 个可选字段;团期房务地基(3 张新表 + 2 处 ALTER + 3 个内部错误码,暂无任何外部可达接口读写)
⚠️ 关键变化
- items[].replaceReason 是"仅记首次、不覆写"的语义,不是"每次提交都会更新":同一天对同一配房重复提交(未产生新的换店/改间数替换行,即 swap_from_id 不变的 UNCHANGED 情形)时,即使这次 payload 里的 replaceReason 文案与上次不同,后端也不会更新 house_hotel_assignment.replace_reason,数据库里仍是第一次落库的值。已在测试服网关实测复现(见"三、接口详情"与"八、测试环境已验证")。要修改已落库的原因文案,必须先撤销/删除该天已有配房,让新的替换动作产生新行,新行才会重新落 replaceReason。
- 提示(本次实测发现,非本单引入,既有缺陷):配房提交响应 items[].arrange 的 Swagger 文档(AssignmentSubmitRespVO.Item.arrange 的 @ApiModelProperty)只列了 pending / waiting / confirmed / problem 四个值,但本次网关实测(AC-9)复现响应里 arrange 实际返回过 inquiring——该值来自另一条既有派生逻辑(house_hotel_assignment.confirm_status = INQUIRING 时派生为 inquiring,HouseAssignmentService.arrangeOf),文档一直没跟着补。前端对 arrange 做分支/switch 时不要写成不带 default 的穷举,务必对未列举值做兜底展示。详见"六.5、枚举"。
- 本单是"地基单":不新增任何端点、不下线任何端点、网关零改动。3 张新表 + 2 处 ALTER + 段位 808600-808699 下新增的 3 个错误码(808620/808621/808622)目前只服务于尚未合入的后续单(#7324 订房 CRUD、#7325 按日确认与自动分房)的团期分支扣减逻辑,当前任何已上线业务流程都不会命中这三个码,列出仅为错误码表完整、供后续单核对。
一、背景
团期房务(房务按团整体订一次房,系统按各户已确认需求自动还原到户)此前在 order-v3 没有任何表承载,#7324-#7327 一行业务代码都写不了。本单铺两张地基表(按日订房计划 group_batch_room_plan、分房到户 group_batch_room_allocation)及其 Flyway/实体/Mapper,顺带做两条配套 ALTER:house_hotel_assignment 加 replace_reason(wx 2026-09-04 拍板"替换可选填原因,核心配房也要加")、house_dual_deduction_log.order_id 放宽可空 + 加 source_type(团期计划行扣库存复用既有 HouseDualDeductionService 链路,但该链路的审计表此前强绑 order_id,团期计划行没有 order_id,不松绑就插不进去)。同一 PR 里还做了旧户冻结名单(order_group_batch_house_legacy + order_group_batch.house_legacy_frozen_at),承接团期归属误判问题(另一复审修订,不影响本 changelog 面向的对外接口)。
对前端唯一直接可见的变化,是核心配房提交端点新增了 1 个可选字段 items[].replaceReason。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 提交配房方案 | POST | /v3/admin/order/hotel-requirements/{requirementId}/assignments |
请求体新增可选字段 | items[].replaceReason 选填 ≤256 字;仅当该项在本次提交中被判定为"替换"(新行携带 swap_from_id)时落库;重复提交同一配房不覆写 |
三、接口详情
1. 提交配房方案 POST /v3/admin/order/hotel-requirements/{requirementId}/assignments
VO: AssignmentSubmitReqVO → AssignmentSubmitRespVO
使用场景
房务配房工作台(hl-ui)为某条用房需求批量提交/调整逐日房型方案;房务角色抢单后,逐天选择酒店房型并提交,配房行进入"询房中"状态。同一天再次提交且换了酒店/改了间数会被判定为"替换"(原行软删、新行携带 swap_from_id),此时可选带上 replaceReason 记录换店原因。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| requirementId | Path | Long | 是 | 逻辑 FK 到 order_hotel_requirement.requirement_id | 用房需求 ID |
| items | Body | List of AssignmentItemReqVO | 是 | NotEmpty | 一项 = 一晚 乘 一组房间 |
| items[].dayNumber | Body | Integer | 是 | Min 1 | 第几天(1=Day1) |
| items[].hotelId | Body | Long | 是 | 非空 | resource 酒店 ID |
| items[].roomTypeId | Body | Long | 是 | 非空;不属于该酒店抛 808112 | resource 房型 ID |
| items[].roomCategory | Body | String | 是 | NotBlank | 房型字典 code(room_category) |
| items[].roomCount | Body | Integer | 是 | Min 1 | 间数 |
| items[].protoPrice | Body | BigDecimal | 否 | DecimalMin 0.00 | 协议价快照(元/间·晚);不传按房型当日协议价兜底 |
| items[].settlementPrice | Body | BigDecimal | 否 | DecimalMin 0.00 | 结算价快照;不传按结算价再兜底协议价 |
| items[].settleType | Body | String | 否 | cash / sign / company | 支付方式快照 |
| items[].deductInventory | Body | Boolean | 是(业务层) | 缺失抛 808124 | 是否扣减资源房型库存 |
| items[].syncProtocolPrice | Body | Boolean | 否 | 默认 false | 是否把 protoPrice 同步写回价格日历 |
| items[].syncSettlementPrice | Body | Boolean | 否 | 默认 false | 是否把 settlementPrice 同步写回价格日历 |
| items[].syncSettleType | Body | Boolean | 否 | 默认 false | 是否把 settleType 同步写回酒店资源 |
| items[].remark | Body | String | 否 | 无 | 备注 |
| items[].replaceReason | Body | String | 否 | 新增;Size max 256 | 替换原因。仅当本项在本次提交中被判定为替换(该天已有旧行、本项换店或改间数,新行写了 swap_from_id)时落库;NEW(无对应旧行)与未变项后端忽略该字段;同一天两个 item 各自换店时,原因按提交顺序与被替换的旧行按位置配对 |
出参字段表 Result<AssignmentSubmitRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| data.successCount | Integer | 成功条数 |
| data.failCount | Integer | 失败条数 |
| data.items | List of Item | 每条配房结果 |
| data.items[].dayNumber | Integer | 第几天 |
| data.items[].assignmentId | Long(JSON 字符串) | 配房行 ID(雪花) |
| data.items[].arrange | String | 配房状态;Swagger 文档列 pending/waiting/confirmed/problem,实测另有 inquiring(既有缺陷,见"关键变化"第 2 条与"六.5、枚举") |
| data.items[].deductInventory | Boolean | 本行是否扣减资源库存 |
请求示例
{
"items": [
{
"dayNumber": 1,
"hotelId": "2029926129876320258",
"roomTypeId": "2029944763138936833",
"roomCategory": "STANDARD",
"roomCount": 1,
"deductInventory": true,
"replaceReason": "酒店A满房"
}
]
}
(以上为测试服网关实测 AC-9 第二步真实请求体:Day1 已有酒店 A 的配房,本次改配酒店 B 并带 replaceReason。)
响应示例
{
"code": 200,
"message": "成功",
"data": {
"successCount": 1,
"failCount": 0,
"items": [
{
"dayNumber": 1,
"assignmentId": "2097856298618941442",
"arrange": "inquiring",
"deductInventory": true
}
]
},
"success": true
}
(测试服网关实测 AC-9 第二步真实响应;arrange 实测返回 inquiring,与 Swagger 文档列举的四值不同,见"关键变化"第 2 条。)
空数据 / 降级响应
本接口不存在"空数据"语义:items 为空数组在契约层已被 NotEmpty 拦截为 400/808111,不会返回成功空列表;下游资源服务(价格日历库存扣减)不可用时直接返回 808900 错误,不做静默降级或返回部分成功的伪结果。
错误响应
{
"code": 808111,
"message": "配房项不能为空, 请至少选择一家酒店/房型",
"success": false,
"data": null
}
业务边界
- replaceReason 仅在本项被 reconcileDay 判定为"替换"(新行携带非空 swap_from_id)时写入新行;NEW(该天此前无配房行)与"无对应旧行的纯新增房型"两种情形该字段恒被清空/忽略。
- UNCHANGED(原地更新旧行,未产生新的替换行)不合并该字段——重复提交同一配房、仅改 replaceReason 文案,不会覆盖已落库的值(已实测确认,见"关键变化"第 1 条)。
- 同一天提交多个换店 item 时,原因文本按提交顺序与被替换的旧行按位置(zip)配对,不按酒店名匹配;同日多项替换请按提交顺序对应理解归属。
- 权限:HouseWriteGuarded 注解——ROOM_MANAGER/SUPER_ADMIN 可写;其余角色(含 HOUSE_KEEPER_LEAD 组长)拒绝写入,走 808090/808091(Controller 入口前置校验,先于 Idempotent/Lock4j)。
- 幂等:Idempotent 同 requirementId 3 秒窗口内重复提交直接拒绝(100502 处理中);并发保护:Lock4j 需求级锁 house:req:write:{requirementId},与单日确认/释放/转单互斥。
- 需求不属于当前用户 → 808110;需求已作废 → 808113;未抢单 → 808116;同一次提交内同天同酒店同房型重复 → 808117;订单异常处置中 → 808119;deductInventory 缺失 → 808124。
- arrange 字段可能返回文档未列举的 inquiring(见"六.5、枚举"),前端渲染该字段务必留兜底分支。
四、契约约束与正确调用方式
本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
正确 / 错误 payload 对照
| 场景 | payload | 结果 |
|---|---|---|
| 正确:首次配房,不带原因 | items[0] 不含 replaceReason 字段 | 200;house_hotel_assignment.replace_reason 为 NULL |
| 正确:同天换店并带原因 | items[0].replaceReason = 酒店A满房 | 200;产生新行,swap_from_id 指向旧行,replace_reason 落库该文案,旧行软删(deleted_at 非空) |
| 需注意:对同一未变配房重放请求并改 replaceReason 文案 | 与上次请求内容一致(同酒店同房型,即 UNCHANGED),仅 replaceReason 文案不同 | 200,但不覆写:assignmentId 不变,replace_reason 仍是第一次落库的值 |
| 错误:replaceReason 超 256 字 | items[0].replaceReason 长度 257 | 400,Size 校验拒绝 |
切换状态时的必要动作
若需要修改一条配房行已经落库的 replaceReason 文案,不能寄望于"再传一次不同的值"——后端只在产生新的替换行时才写该字段。正确做法:先撤销/删除该天配房(走已有的 DELETE 端点或触发新的换店动作产生新行),让替换重新发生,新行才会带上新的 replaceReason。
五、数据库行为
- 本次 Flyway(V20260908_311)新建 3 张表:group_batch_room_plan(25 列,团期按日订房计划)、group_batch_room_allocation(18 列,分房到户)、order_group_batch_house_legacy(6 列,历史旧户冻结名单);order_group_batch 加列 house_legacy_frozen_at;house_hotel_assignment 加列 replace_reason;house_dual_deduction_log.order_id 由 NOT NULL 放宽为可空,并加列 source_type(VARCHAR(32) NOT NULL,默认值 HOUSE_ASSIGNMENT)。已在测试服 SHOW CREATE TABLE 逐列比对零差异,flyway_schema_history 记录 version=20260908.311、success=1,无 312/313 残留草稿。
- 以上表/列当前均无任何外部可达接口读写(地基单,专供尚未合入的 #7324/#7325/#7326/#7327 使用),本次不改变任何既有查询接口的返回结果。
- house_dual_deduction_log.source_type 加默认值后,存量行 100% 落默认值 HOUSE_ASSIGNMENT(已实测 COUNT(source_type 不等于 HOUSE_ASSIGNMENT) = 0),不影响既有幂等键与对账逻辑。
- house_hotel_assignment.replace_reason 写入规则(外部可观察):
| 提交场景 | swap_from_id | replace_reason |
|---|---|---|
| Day1 首次配酒店 A(不带 replaceReason) | 空 | 空 |
| 同一天改配酒店 B,带 replaceReason=酒店A满房 | 指向 A 行 id | 酒店A满房 |
| 再次提交同一酒店 B(UNCHANGED),带 replaceReason=换一个理由试试 | 不变(仍指向 A 行) | 仍是酒店A满房,未被覆写 |
六、边界行为
- 未登录/无权限 → 401(网关)或 808090/808091(房务写权限门禁,先于业务逻辑)。
- requirementId 对应需求不存在或不属于当前用户 → 808110。
- 需求已作废 → 808113;未抢单 → 808116。
- 同一次提交内出现同天同酒店同房型重复项 → 808117,禁止静默合并,需前端自己合并间数后再提交。
- 下游资源服务(库存/价格日历)异常 → 808900/808901/808902,不做静默降级、不返回部分成功。
- 团期计划行扣减来源相关的 808620/808621/808622 三个错误码当前不会被本端点触发(仅服务尚未合入的团期分支,见"关键变化"第 3 条)。
六.5、枚举 / 数据字典
arrange(AssignmentSubmitRespVO.Item.arrange)
所属字段: data.items[].arrange | 类型: String
| 值 | Swagger 文档是否列出 | 触发条件 |
|---|---|---|
| pending | 是 | 该天尚未配房/待处理 |
| waiting | 是 | 候选待定 |
| confirmed | 是 | 行 confirm_status 非 INQUIRING(HouseAssignmentService.arrangeOf 派生) |
| problem | 是 | 存在异常需处理 |
| inquiring | 否,文档遗漏(既有缺陷,非本单引入) | 行 confirm_status = INQUIRING(询房中候选,HouseAssignmentService.arrangeOf 方法派生)。本单网关实测(AC-9)两次真实复现该返回值 |
前端处理建议:对 arrange 做展示/分支逻辑时不要写不带 default 的穷举 switch;未列举值(至少已知有 inquiring)应有兜底展示,不能因为遇到未知值而崩溃或空白。
source_type(house_dual_deduction_log.source_type,新列,内部审计字段,不对外暴露)
所属字段: 数据库列,无对应 API 出参字段 | 类型: VARCHAR(32)
| 值 | 含义 |
|---|---|
| HOUSE_ASSIGNMENT | 逐单配房扣减(assignment_id = house_hotel_assignment.id);当前所有扣减记录均为此值 |
| GROUP_BATCH_PLAN | 团期计划行扣减(assignment_id = group_batch_room_plan.plan_id);供 #7325 落地后使用,当前无记录 |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
| items[].replaceReason | 不存在 | 新增,选填,Size max 256 |
| house_hotel_assignment.replace_reason 列 | 不存在 | 新增,VARCHAR(256) NULL |
| house_dual_deduction_log.order_id | BIGINT NOT NULL | BIGINT NULL |
| house_dual_deduction_log.source_type | 不存在 | 新增,VARCHAR(32) NOT NULL,默认值 HOUSE_ASSIGNMENT |
| 端点其余请求/响应字段 | 无 | 完全不变 |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 换店/改间数替换 | 只记 swap_from_id/swap_count,无原因文本 | 可选记录 replace_reason |
| 重复提交同一配房、只改 replaceReason 文案 | 字段不存在,无此场景 | 后端不覆写,仍是第一次落库的值(UNCHANGED 语义) |
| 团期计划行扣库存 | 无此来源(表不存在) | 复用既有扣减链路,按 source_type=GROUP_BATCH_PLAN 与 orderId=null 走团期分支(当前无调用方,供 #7325 使用) |
六.7、影响评估
- 是否破坏向后兼容:否。items[].replaceReason 是新增可选字段,老前端不传该字段完全不受影响;新表新列对现有查询零影响。
- 前端是否必须同步上线:否。不传 replaceReason 时端点行为与改前逐字一致。
- 前端 workaround 清理点:无强制清理项。建议(非强制):一是在配房提交 UI 中,仅当"该天已有配房"(即本次提交会产生替换)时显示 replaceReason 输入框;二是对 arrange 字段的展示/分支逻辑补一个 default 兜底,覆盖 inquiring 等文档未列举的值。
七、不影响范围
- 仅影响:核心/定制订单房务配房提交端点新增 1 个可选请求字段;团期房务地基库表与内部扣减错误码段位(当前无外部可达入口)。
- 零影响:
- 无新增端点、无端点下线、无路径变化、网关路由零改动。
- 该端点其余请求字段(dayNumber/hotelId/roomTypeId/roomCategory/roomCount/protoPrice/settlementPrice/settleType/deductInventory/syncProtocolPrice/syncSettlementPrice/syncSettleType/remark)与响应字段(successCount/failCount/items[].dayNumber/items[].assignmentId/items[].arrange/items[].deductInventory)一个都没删、没改类型。
- 内部端点 POST /v3/internal/house/deduct-room-stock、POST /v3/internal/house/restore-room-stock 请求/响应结构不变,source_type 走默认值 HOUSE_ASSIGNMENT,键格式与落库行为逐字一致。
- 契约 HotelAssignmentReadDTO 的既有 15 个字段(assignmentId/requirementId/hotelId/roomTypeId/dayNumber/stayDate/hotelName/roomCategory/roomTypeName/protoPrice/settlementPrice/settleType/deductInventory/roomCount/remark)不动;新增的 sourceType/replaceReason 两个字段未映射到任何对外 VO,11 个消费方(OrderDetailService/OrderDetailConverter/SettlementService 等)本单未改,对外 JSON 零变化。
- 已有酒店/房型/价格日历读取接口、订单创建/支付/详情、抢单/单日确认/删除配房等其余房务端点不受影响。
- 历史数据:存量 house_dual_deduction_log 行 100% 落 source_type=HOUSE_ASSIGNMENT 默认值,不迁移不回填。
八、测试环境已验证
环境:测试服网关,hl-order-service-v3 @ 1a3d3c43f(Deploy Panel 记录 dev-v3 @ 50c93199e,1a3d3c43f 为其祖先),双实例 8186/8086 均 UP,健康检查通过。
POST /v3/admin/order/hotel-requirements/{requirementId}/assignments
第一步 Day1 提交酒店A(不带 replaceReason) -> 200, successCount=1, assignmentId=2097856273935433730, arrange=inquiring [验证通过]
第二步 同日改提酒店B + replaceReason=酒店A满房 -> 200, successCount=1, 新 assignmentId=2097856298618941442, arrange=inquiring [验证通过]
DB 核对: 新行(2097856298618941442) swap_from_id=2097856273935433730(回指旧行), replace_reason=酒店A满房 [验证通过]
DB 核对: 旧行(2097856273935433730) deleted_at=2026-09-10 09:14:52(非空,已软删) [验证通过]
house_dual_deduction_log 核对: 两条记录 source_type 均为 HOUSE_ASSIGNMENT,
幂等键分别为 asgn-2097856215588499458-d1-rt2023727403196502017-h2023714929877450753(基础键)
与 asgn-2097856215588499458-d1-rt2029944763138936833-h2029926129876320258-s1(替换叠 -s1) [验证通过]
第三步 再次提交与第二步完全相同的配房(同酒店B)但 replaceReason=换一个理由试试 -> 200,
successCount=1, 返回同一个 assignmentId=2097856298618941442(未新建行) [验证通过]
DB 核对: 该行 replace_reason 仍是酒店A满房, 未被换一个理由试试覆写(UNCHANGED 语义) [验证通过]
DDL 验证:
SHOW CREATE TABLE group_batch_room_plan / group_batch_room_allocation -> 与 Flyway V20260908_311 定义列/索引/注释逐列比对零差异 [验证通过]
SHOW COLUMNS FROM house_hotel_assignment LIKE replace_reason -> 有该行 [验证通过]
SHOW COLUMNS FROM house_dual_deduction_log WHERE Field IN (order_id, source_type)
-> order_id Null=YES, source_type Default=HOUSE_ASSIGNMENT [验证通过]
SELECT COUNT(*) FROM house_dual_deduction_log WHERE source_type 不等于 HOUSE_ASSIGNMENT -> 0 [验证通过]
flyway_schema_history: version=20260908.311, success=1, execution_time=397ms, 无 312/313 残留 [验证通过]
验证订单:orderId=2097856215588499458,requirementId=2097856250409582593(AC-9 造数,测试用户 AC9测试客户,标签 ac9-7323)。
十、相关文档
- 关联 Issue: wx/HL#7323 https://git.1814.love:8443/wx/HL/issues/7323
- 关联 PR: wx/HL#7413 https://git.1814.love:8443/wx/HL/pulls/7413
- 后续依赖本单地基表/错误码/契约字段的单据:#7324(订房 CRUD 与团期看板)、#7325(按日确认与自动分房,落地团期分支扣减,808620-808622 起真正可达)、#7326(分房与只读)、#7327(契约合流,sourceType=GROUP_BATCH_PLAN 起真正出现在 HotelAssignmentReadDTO)。
关联 / 联系人
链接
联系人
- 后端负责人: @wx