文件
hl-api-changelog/changelogs-v2/2026-09/10_7323_团期房务地基核心配房新增可选原因字段replaceReason-修改接口-管理后台.md
T
Mimingguang 38a6cf5434
changelog-filename-gate / validate (push) Failing after 2s
docs(changelog): #7323 frontmatter 回写 not_required
2026-09-10 10:05:26 +08:00

22 KiB
原始文件 Blame 文件历史

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 个内部错误码,暂无任何外部可达接口读写)


⚠️ 关键变化

  1. items[].replaceReason 是"仅记首次、不覆写"的语义,不是"每次提交都会更新":同一天对同一配房重复提交(未产生新的换店/改间数替换行,即 swap_from_id 不变的 UNCHANGED 情形)时,即使这次 payload 里的 replaceReason 文案与上次不同,后端也不会更新 house_hotel_assignment.replace_reason,数据库里仍是第一次落库的值。已在测试服网关实测复现(见"三、接口详情"与"八、测试环境已验证")。要修改已落库的原因文案,必须先撤销/删除该天已有配房,让新的替换动作产生新行,新行才会重新落 replaceReason。
  2. 提示(本次实测发现,非本单引入,既有缺陷):配房提交响应 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. 本单是"地基单":不新增任何端点、不下线任何端点、网关零改动。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)。


十、相关文档

关联 / 联系人

链接

联系人

  • 后端负责人: @wx