文件
hl-api-changelog/changelogs-v2/2026-09/13_7327_团期配房行合流核单结算-修改接口-管理后台.md
T
2026-09-13 14:39:53 +08:00

37 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 7327 团期配房行合流进住宿核单 Step1:sourceType 新增取值 GROUP_BATCH_PLAN、新增行级确认闸 584129 admin wx(GIT) 修改接口 deployed verified verified mmg c966fd5b7b0f33de6fb0667e2038f060f0f95f54 2026-09-13 ⚠️ 2026-09-13 订正:PR-2(PR #7603,终态提交 d916986de,同日 09:19 已合 dev-v3)把本篇四处结论推翻了——「权威消失即软删」改为「不删、就地转 GROUP_BATCH_PLAN_REVOKED、原样返回、权威回归时三段认回」,且 REVOKED 取值本期就会出现。详见正文顶部「🔴 2026-09-13 订正」段。前端原 2026-09-13 not_required 闭环是在「REVOKED 本期产生不出来」这个前提下做的,前提已失效,故退回 pending 等 mmg 重新判一次(要重判的具体问题见订正段末尾)。 【以下为 PR-1 原文】Issue #7327 PR-1 已合并 dev-v3(PR #7600,终态提交 49ce99cc8),服务 hl-order-service-v3。住宿核单 Step1 的团期订单首次出现团期配房派生行;sourceType/sourceTypeName 是既有字段,本次只扩取值域,新增业务错误码 584129。接口路径、HTTP 方法、VO 字段集合均未变化,网关无新增路由。 前端 2026-09-13 闭环 not_required:逐块 grep 实证。①核单 Step1 住宿无独立来源列,来源合并「备注/来源」note 列 returnDetailAdapter.js firstValue(remark,note,sourceTypeName) 纯透传后端中文名,前端无 sourceType 映射表,新值自带中文「团期配房」天然兼容;行为分支只按 MANUAL 判手工行,新取值落入派生行语义不可删,保存 normalizeSource 原样回传不丢值。②584129 不带 silentError 走 request.js 通用业务码兜底 message.error 透后端原文,__handled 防双弹,无专属分支。③无「团期无住宿」workaround 可撤。零业务代码改动。【前端 2026-09-13 订正重判交付 verified】侦查三路实证核单 Step1 住宿现状后用户拍板两项:(1) REVOKED 行给删除入口——rowAllowsDelete 放行 GROUP_BATCH_PLAN_REVOKED(仍受已确认闸约束),删除确认文案点明「删除后权威回归无法认回」,删除走本地剔除+下次 PUT 全量覆写生效;(2) 合计/「共 N 条」剔除 REVOKED 行——categoryTotal 按 source.sourceType 剔除,calculateReports 兜底自算与 detail.vue 行数同步回落,页面仍展示该行。回传 sourceType/sourceId/hotelAssignmentId 锚点保持透传,权威回归认回不被前端截断。新增 model.spec.js 与 CategoryTable.spec.js REVOKED 4 例,settlement 全域 115/115,checkpoint medium 7 项全绿。 2026-09-13 dev-v3

订单模块: 团期配房行合流进住宿核单 Step1(sourceType 新增取值 + 行级确认闸 584129)

存放目录:

  • 一期(v2,无 order-v3 标签的工单)→ changelogs/{YYYY-MM}/
  • 二期(v3,order-v3 标签的工单)→ changelogs-v2/{YYYY-MM}/

服务: hl-order-service-v3 (端口 8086) PR: #7600(PR-1)、#7603(PR-2,2026-09-13 订正来源) Issue: #7327 日期: 2026-09-12 影响范围: 管理后台核单 Step1 住宿明细(团期订单);订单详情 / 行程单 / 小程序行程的住宿段行数


🔴 2026-09-13 订正(PR-2 把本篇四处结论推翻了,请以本段为准)

本篇写于 PR-1(#7600,49ce99cc8)当天。同日 09:19 合入的 PR-2(#7603,d916986de) 把其中四处行为改成了相反的。下面四行已在正文就地改正,这里集中列出供对账:

本篇原来说 实际行为(PR-2 后)
GROUP_BATCH_PLAN_REVOKED 本期产生不出来,下一期才会出现 本期就会出现。 团期订房计划行一被删/改,对应核单行当场转成该取值
权威消失即软删,返回结果里不再出现 不删。 就地改 sourceType=GROUP_BATCH_PLAN_REVOKED、remark 打上前缀 [团期来源已失效] ,该行照样随 GET step1 返回
(无) 新增三段认回:权威回归时按 source_id 精确认回原行,sourceType 改回 GROUP_BATCH_PLAN、剔掉一层前缀,实付与凭证不丢、不新建重复行
用例 listHotel_groupAuthorityGone_**softDeletes**DerivedRowAndKeepsRevokedAndManual 真实用例名是 listHotel_groupAuthorityGone_**revokes**DerivedRowAndKeepsRevokedAndManual(SettlementServiceGroupBatchReconcileTest.java:219)

前端实际要处理什么

  1. sourceType 会真的出现 GROUP_BATCH_PLAN_REVOKED,sourceTypeName = 团期配房(已失效)。后端直接给中文名,不需前端做映射表。
  2. 该行的 remark 会被后端加上前缀 [团期来源已失效] (末尾含一个空格,常量原文见 SettlementService.java:178)。 前缀只加一层、只剔一层、只剔开头(核单员可能在前缀后面写了自己的备注,整段清空会丢掉他的字)。 前端不要拿这个前缀做任何判断,它只供人读;要判失效一律看 sourceType。
  3. 行数不会因失效而减少。之前按本篇做了「失效后行会消失」预期的地方(空态、合计、分页总数)要重新看一眼。

⏸ 这次订正引出一个需要拍板的新问题

本篇原来的 frontend_status: not_required 闭环结论,是在「REVOKED 本期产生不出来」这个前提下做的, 而那个前提现在没了。其中一条原话是「行为分支只按 MANUAL 判手工行,新取值落入派生行语义不可删」——

一条权威已经消失的 REVOKED 行,核单员到底能不能删? 按现有前端逻辑它是「不可删的派生行」,但它的上游已经不存在了——这意味着它会永久留在核单页上。 后端这么设计是故意的(要保住已录的实付与凭证、以及权威回归时的认回锚点),但前端侧怎么展现、要不要给删除入口,本篇没定过。

故 frontend_status 从 not_required 退回 pending,请 mmg 在新前提下重新判一次。 (这不是说原来那份分析做错了——它在它的前提下是对的;失效的是前提,不是推理。)


⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)

一行红字说清:

  • sourceType / sourceTypeName 不是新增字段,是既有字段。这两个字段在本次改动之前就存在于 HotelItemVO 上,前端按「新增字段」去找会找不到。本次变的是取值域:sourceType 多出 GROUP_BATCH_PLAN(sourceTypeName = 团期配房)与 GROUP_BATCH_PLAN_REVOKED(sourceTypeName = 团期配房(已失效))两个取值。
  • 前端/调用方以前以为的是:核单 Step1 住宿明细只有两类行——配房派生行(HOUSE_ASSIGNMENT)与手工行(MANUAL);团期订单的住宿明细是空的。
  • 实际现在是:团期订单的住宿明细第一次出现团期配房派生行,sourceType=GROUP_BATCH_PLAN。这类行按「住宿事实」聚合成一行(同订单 + 同入住日 + 同酒店 + 同房型的多条分房记录合并为一行,间数求和、计划成本按合计间数重算)。
  • ✅ (2026-09-13 订正)GROUP_BATCH_PLAN_REVOKED 本期就会出现(PR-2 d916986de 起)。团期订房计划行被删或改成别的酒店/房型时,对应核单行不删、就地转成该取值并照样返回,sourceTypeName = 团期配房(已失效)。前端本期就要能展示它。
  • 新增业务错误码 584129:把一条「团期配房未分平」的住宿行置为已确认时被拒绝。

一、背景(选填)

团期业务的房是「账面上按团订房、核算时还原到户」。住宿成本必须逐户进核单,否则团期订单在核单页看不到任何住宿行、应付算不出来。本次由房务侧的只读契约把团期分房行合流进既有的住宿读取链路,核单 Step1 因此第一次看得到团期住宿。

维度 改前(团期订单) 改后(团期订单)
Step1 住宿行来源 仅 HOUSE_ASSIGNMENT 派生行 + MANUAL 手工行 增加 GROUP_BATCH_PLAN 派生行
团期订单住宿行数 0 行(团期分房不进核单) 按住宿事实分组,一组一行
行级确认前置条件 无团期相关前置 团期行须「该户该日已按房型分平」才允许置已确认

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 Step 1 查询住宿核单明细 GET /v3/admin/order/{orderId}/settlement/step1 响应取值域扩展 团期订单新增 sourceType=GROUP_BATCH_PLAN 的聚合行
2 Step 1 录住宿核单明细 PUT /v3/admin/order/{orderId}/settlement/step1 入参白名单扩展 + 新错误码 items 内 sourceType 放行两个新取值;未分平的团期行置已确认返 584129

三、接口详情

1. Step 1 查询住宿核单明细 GET /v3/admin/order/{orderId}/settlement/step1

VO: 无 ReqVO(仅路径参数 orderId) → List<HotelItemVO>

使用场景

管理后台「核单结算 - Step1 住宿」页面进入或刷新时调用,拿到该订单当前应展示的全部住宿明细行(派生行 + 手工行)。返回的每一行都带 id,前端后续 PUT 回写时必须原样带回该 id,否则会被当成新增行。

本接口是读写混合的:服务端在返回前会把库里的核单草稿与房务权威对平(缺的补、变的改、团期权威消失的转 GROUP_BATCH_PLAN_REVOKED 而非软删 ——2026-09-13 订正,详见正文顶部订正段),所以刷新页面本身会改变库里的行集合与确认状态。前端不需要额外调「同步」动作。

团期订单从本次起会在结果里出现 sourceType=GROUP_BATCH_PLAN 的行。出现条件:该户存在有效的团期分房记录、其所属的团期订房计划已确认、且该户不在团期历史旧户冻结名单内;三者任一不满足则该户仍只有原来的行。

入参

字段 位置 类型 必填 约束 说明
orderId Path Long ✅ 路径变量,非数字将被框架判为参数错误 订单 ID(团期场景下是子订单/户的订单 ID,不是团 ID)

出参 Result<List<HotelItemVO>>

字段 类型 说明
data[].id String 核单住宿行 ID(雪花 ID,强制序列化为字符串)。行身份只认它,回写必须原样带回
data[].hotelAssignmentId String 派生行的权威锚点 ID;手工行为 null。团期行取该组内最小的分房记录 ID,会随房务拆/合行漂移,前端不得用它做行身份
data[].hotelId String 酒店 ID
data[].roomTypeId String 房型 ID
data[].stayDate String 入住日期,yyyy-MM-dd
data[].hotelName String 酒店名快照
data[].roomType String 房型字典 code(团期行取团期订房计划行快照上的房型大类)
data[].roomTypeName String 房型名称快照
data[].roomCount Integer 间数。团期行等于该组内各分房记录间数之和
data[].unitPrice Number 核算单价(元/间夜)。团期行取团期订房计划行的结算价快照
data[].plannedCost Number 计划成本(元)。团期行等于 unitPrice 乘 roomCount(按合计间数重算)
data[].actualCost Number 实际成本(元)。新建行初始等于 plannedCost,之后由核单员维护
data[].paymentMethod String 付款方式:SIGNED / COMPANY_PAID / CASH_PAID;结算类型未知时为 null
data[].paymentMethodName String 付款方式中文名:签单 / 公司付款 / 现付
data[].settleType String 本接口恒为 null(读取路径不回填该字段,它只用于写入时代替 paymentMethod)
data[].sourceType String 来源类型。既有字段,本次新增取值 GROUP_BATCH_PLAN / GROUP_BATCH_PLAN_REVOKED(详见六.5)
data[].sourceTypeName String 来源类型中文名,与 sourceType 一一对应
data[].sourceId String 来源业务 ID;团期行等于 hotelAssignmentId(组内最小分房记录 ID),同样会漂移
data[].settlementConfirmStatus String 核单确认状态:UNCONFIRMED / CONFIRMED
data[].settlementConfirmStatusName String 确认状态中文名:未确认 / 已确认
data[].remark String 备注。团期行间数变化时,系统会在原备注前加上提示前缀(见业务边界)
data[].voucherUrls String[] 凭证图片 URL 数组;无凭证时为空数组

请求示例

GET /v3/admin/order/71001/settlement/step1
Authorization: Bearer <token>

响应示例

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "id": "93001",
      "hotelAssignmentId": "99001",
      "hotelId": "201",
      "roomTypeId": "202",
      "stayDate": "2026-08-01",
      "hotelName": "布达拉宫酒店",
      "roomType": "TWIN",
      "roomTypeName": "标准双床房",
      "roomCount": 2,
      "unitPrice": 400.00,
      "plannedCost": 800.00,
      "actualCost": 800.00,
      "paymentMethod": "COMPANY_PAID",
      "paymentMethodName": "公司付款",
      "settleType": null,
      "sourceType": "GROUP_BATCH_PLAN",
      "sourceTypeName": "团期配房",
      "sourceId": "99001",
      "settlementConfirmStatus": "UNCONFIRMED",
      "settlementConfirmStatusName": "未确认",
      "remark": null,
      "voucherUrls": []
    }
  ],
  "success": true
}

空数据 / 降级响应

  • 该订单没有任何住宿行(散客单未配房、团期订房计划未确认、该户在历史旧户冻结名单内)→ data 为空数组,不是 null,不报错。
  • 房务只读契约返回 null(下游读取失败)→ 降级返回库里已有的核单草稿行,不做对平,同时把该订单的住宿类目标记为「来源同步失败」;页面照常渲染,团期行不会凭空消失,也不会在这一次刷新里被删。
{ "code": 200, "message": "成功", "data": [], "success": true }

错误响应

{
  "code": 500,
  "message": "系统繁忙,请稍后重试",
  "data": null,
  "success": false
}

业务边界

  • 鉴权:走网关统一鉴权,未登录 401;本端点本身不带角色禁入判断(与同控制器其他只读端点一致)。
  • 团期行聚合:分组键是「订单 + 入住日 + 酒店 + 房型」,一组只返回一行。房务把一条分房记录拆成两条、或把两条合成一条,只要酒店/房型/日期/总间数不变,返回的行 id 不变,实际成本、凭证、备注、确认状态四项也不变,只有 hotelAssignmentId 与 sourceId 这两个锚点会变。
  • 事实变更即打回未确认:团期行的酒店、房型、入住日、酒店名、房型名、间数、单价、计划成本、付款方式任一与权威不一致时,该行 settlementConfirmStatus 被重置为 UNCONFIRMED,实际成本与凭证保留不清空。
  • 间数变化加备注前缀:间数变化时 remark 前面被系统加上 [住宿事实变更 间数 {旧}→{新},请复核实付] (末尾含一个空格)。连续变化只保留最新一层前缀,不叠加。该前缀仅供人读,前端不要用它做任何判断。
  • ✅ (2026-09-13 订正)权威消失不软删,转 GROUP_BATCH_PLAN_REVOKED:团期订房计划行被删除或改成了别的酒店/房型时,对应的核单行仍然返回,只是 sourceType 变成 GROUP_BATCH_PLAN_REVOKED、remark 被打上前缀 [团期来源已失效] (只加一层);实付、凭证、确认状态与 source_id 锚点全部保留。权威回归时按 source_id 三段认回原行(改回 GROUP_BATCH_PLAN、剔一层前缀),不新建重复行。手工行始终不受影响。
  • 失败零写入:对平过程在一个事务内,中途异常整体回滚,同时把住宿类目标记为来源同步失败。
  • 兼容:sourceType 的历史取值 CUSTOM_ASSIGNMENT / TEMPLATE 会被归一化为 MANUAL / SYSTEM 后再返回,前端不会读到这两个旧值。

2. Step 1 录住宿核单明细 PUT /v3/admin/order/{orderId}/settlement/step1

VO: SettlementHotelSaveReqVO(items 为 HotelItemVO 数组) → SettlementHotelSaveRespVO

使用场景

核单员在 Step1 住宿页编辑实际成本、付款方式、凭证、备注、确认状态后点保存时调用。全量替换语义:请求里没带的现库行视为删除,所以必须把页面上的全部行(含未改动的派生行)一起回传。

本次起,请求里可以出现 sourceType=GROUP_BATCH_PLAN 的行;把这类行置为已确认时会经过一道新的闸门(584129)。

入参

字段 位置 类型 必填 约束 说明
orderId Path Long ✅ - 订单 ID
items Body HotelItemVO[] ✅ 不能为 null;逐元素校验 住宿明细行全量数组。请求体也可以直接是数组,裸数组与对象包裹两种形态都接受
items[].id Body String/Long ❌ - 现库行 ID;不传或为 null 表示新增手工行
items[].stayDate Body String ✅ yyyy-MM-dd,不得早于出发日 入住日期
items[].hotelName Body String ✅ 非空白,长度 ≤200 酒店名
items[].roomType Body String ❌ 长度 ≤64 房型字典 code
items[].roomTypeName Body String ❌ 长度 ≤128 房型名称
items[].roomCount Body Integer ✅ - 间数
items[].unitPrice Body Number ❌ ≥ 0 核算单价
items[].plannedCost Body Number ✅ ≥ 0 计划成本
items[].actualCost Body Number ✅ ≥ 0 实际成本
items[].paymentMethod Body String ❌ 枚举 SIGNED / COMPANY_PAID / CASH_PAID 与 settleType 二选一;手工行必填其一
items[].settleType Body String ❌ 枚举 cash / sign / company 派生行可用它让后端映射 paymentMethod
items[].sourceType Body String ❌ 枚举 HOUSE_ASSIGNMENT / MANUAL / SYSTEM / TEMPLATE / GROUP_BATCH_PLAN / GROUP_BATCH_PLAN_REVOKED,长度 ≤32 本次新增放行后两个取值。派生行落库的来源以库里既有行为准,入参该字段不改变行的来源归属
items[].sourceId Body String/Long ❌ - 派生行落库时取库里既有值,入参值被忽略
items[].settlementConfirmStatus Body String ❌ 枚举 UNCONFIRMED / CONFIRMED,长度 ≤32 确认状态。新增行只能是未确认
items[].remark Body String ❌ 长度 ≤500 备注
items[].voucherUrls Body String[] ❌ - 凭证图片 URL 数组
items[].hotelId Body String/Long ❌ - 酒店 ID,回传即可
items[].roomTypeId Body String/Long ❌ - 房型 ID,回传即可
items[].hotelAssignmentId Body String/Long ❌ - 派生行锚点;落库时以库里既有值为准

出参 Result<SettlementHotelSaveRespVO>

字段 类型 说明
data.addedIds String[] 本次落库后全部行的新 ID(全量替换实现:每次保存所有行都会重新写入并拿到新 ID)。雪花 ID 超出 JS 安全整数范围,按全局规则序列化为字符串
data.updatedIds String[] 恒为空数组(全量替换语义下不区分更新)
data.deletedIds String[] 恒为空数组(被删除的行不单独列出,未回传即删除)
data.totalActualCost String 本次提交的 actualCost 之和,强制序列化为字符串

请求示例

{
  "items": [
    {
      "id": "93001",
      "hotelAssignmentId": "99001",
      "hotelId": "201",
      "roomTypeId": "202",
      "stayDate": "2026-08-01",
      "hotelName": "布达拉宫酒店",
      "roomType": "TWIN",
      "roomTypeName": "标准双床房",
      "roomCount": 2,
      "unitPrice": 400.00,
      "plannedCost": 800.00,
      "actualCost": 650.00,
      "paymentMethod": "COMPANY_PAID",
      "sourceType": "GROUP_BATCH_PLAN",
      "settlementConfirmStatus": "CONFIRMED",
      "remark": "财务备注",
      "voucherUrls": ["https://oss.example.com/a.jpg"]
    }
  ]
}

响应示例

{
  "code": 200,
  "message": "成功",
  "data": {
    "addedIds": ["93011"],
    "updatedIds": [],
    "deletedIds": [],
    "totalActualCost": "650.00"
  },
  "success": true
}

空数据 / 降级响应

  • 提交空的 items 数组是合法的,语义是「清空该订单全部住宿核单行」,返回空的 addedIds 与合计 0。派生行会在下一次 GET step1 对平时按权威重新生成,届时实际成本与凭证已丢失,前端不要用空数组做「取消编辑」。
  • 本端点不提供下游降级:确认团期行时若房务只读契约不可用,保存整体失败并回滚,不会写入半个结果。
{
  "code": 200,
  "message": "成功",
  "data": { "addedIds": [], "updatedIds": [], "deletedIds": [], "totalActualCost": "0" },
  "success": true
}

错误响应

{
  "code": 584129,
  "message": "团期配房未分平,该住宿行暂不能确认",
  "data": null,
  "success": false
}

sourceType 传了白名单以外的值时(HTTP 仍为 200):

{
  "code": 400,
  "message": "sourceType 必须是 HOUSE_ASSIGNMENT / MANUAL / SYSTEM / GROUP_BATCH_PLAN / GROUP_BATCH_PLAN_REVOKED 之一",
  "data": null,
  "success": false
}

业务边界

  • 鉴权:走网关统一鉴权;另有核单财务写权限校验,无权限时按既有权限错误码返回。
  • 订单状态门禁:订单的核单审核状态必须是 PENDING 或 IN_PROGRESS 才允许保存,否则返回既有的「订单状态不可结算」错误码(本次未改)。
  • 并发:按 orderId 加分布式锁(30 秒),同一订单的并发保存串行执行。
  • 584129 触发条件(三个条件同时成立):① 该行带 id(是现库行);② 本次提交把它置为 CONFIRMED;③ 库里该行的来源是 GROUP_BATCH_PLAN,且房务侧该户该入住日未按房型大类分平,或该行对应的团期分房记录已经不存在了。
  • 判来源只信库、不信入参:把 sourceType 改成 MANUAL 再提交绕不开这道闸门。
  • GROUP_BATCH_PLAN_REVOKED 行不受该闸门约束:它已经没有权威可比,成本由核单员自己维护,允许确认。
  • 未分平的团期行仍可保存:只要不把它置成 CONFIRMED,实际成本、凭证、备注照常可以录入并保存。
  • 新增行必须从未确认起步:id 为空的行提交 CONFIRMED 会被既有错误码拒绝(与本次改动无关,该校验在闸门之前执行)。
  • 派生行事实变更再打回:即便 584129 放行,若提交内容与库里该派生行的系统事实不一致,落库后的确认状态仍会被强制写成 UNCONFIRMED;前端保存后应以下一次 GET step1 的返回为准渲染状态。
  • 前端处置建议(584129):提示「该团期住宿尚未分房完成,请等房务分平后再确认」,并引导用户刷新 Step1 重新拉取,而不是原样重试。

四、契约约束与正确调用方式(接口类必写)

本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。

✅ 正确 / ❌ 错误 payload 对照

场景 payload
✅ 团期行只录实付、不确认 { "id": "93001", "sourceType": "GROUP_BATCH_PLAN", "actualCost": 650.00, "settlementConfirmStatus": "UNCONFIRMED" }
✅ 团期行已分平后确认 { "id": "93001", "sourceType": "GROUP_BATCH_PLAN", "settlementConfirmStatus": "CONFIRMED" } → 200
✅ 已失效团期行确认 { "id": "93002", "sourceType": "GROUP_BATCH_PLAN_REVOKED", "settlementConfirmStatus": "CONFIRMED" } → 200,且不查房务契约
✅ 裸数组请求体 [ { "id": "93001" }, { "id": "93002" } ],与对象包裹形态等价
❌ 未分平的团期行置确认 { "id": "93001", "sourceType": "GROUP_BATCH_PLAN", "settlementConfirmStatus": "CONFIRMED" } → 584129
❌ 改来源绕闸门 { "id": "93001", "sourceType": "MANUAL", "settlementConfirmStatus": "CONFIRMED" } → 仍 584129(来源以库里为准)
❌ 未知来源取值 { "sourceType": "GROUP_BATCH" } → code 400,sourceType 校验文案
❌ 只回传被改的那一行 { "items": [ 单行 ] } → 200,但其余行全部被删除(全量替换)

切换状态时的必要动作

  • 把一行从未确认切到已确认,请求里必须带上该行的 id;不带 id 会被当成新增行,直接被「新增行必须未确认」规则拒绝。
  • 团期行确认失败(584129)后,正确动作是重新 GET step1 拉取最新事实再确认,而不是把 sourceType 改掉或把 id 去掉重试——这两条路都会破坏该行已录的实付与凭证。
  • 付款方式二选一:派生行可只传 settleType(cash / sign / company)让后端映射,手工行请直接传 paymentMethod;两者都不传时该行付款方式为空。

五、数据库行为(涉及写操作时必写)

前端提交 落库后的来源 落库后的确认状态 落库后的实付与凭证
团期派生行,事实与库内一致,置 CONFIRMED,且已分平 GROUP_BATCH_PLAN(取库内值,忽略入参) CONFIRMED 按入参写入
团期派生行,事实与库内不一致,置 CONFIRMED GROUP_BATCH_PLAN 强制 UNCONFIRMED 按入参写入
团期派生行,未分平,置 CONFIRMED 不落库(整个请求回滚) 不落库 不落库
已失效团期行置 CONFIRMED GROUP_BATCH_PLAN_REVOKED CONFIRMED 按入参写入
新增手工行 MANUAL 强制 UNCONFIRMED 按入参写入
现库行未在 items 中回传 该行被软删 — —

派生行字段以库为准说明: 派生行的来源、来源 ID 与配房锚点三项一律取库内既有值,入参里携带的对应字段被忽略;只有手工行这三项才落 null 与 MANUAL。

读接口也会写库说明: GET step1 在对平阶段会新建、更新或软删住宿行(⚠️ 2026-09-13 订正:团期派生行的权威消失走「转 REVOKED」不走软删,此处的软删指其它来源的行)——间数或单价变化会写回计划成本并把确认状态打回未确认,锚点漂移会写回新的锚点 ID。对平只在「锚点变了」或「事实变了」时才发生写入,两者都没变时读接口一行库也不写。


六、边界行为

  • 未登录 → 401 (网关拦截)
  • 订单不存在 / 无权访问 → 按既有订单访问错误码返回,HTTP 仍为 200
  • 房务只读契约不可用 → GET step1 降级返回库内草稿行并标记来源同步失败;PUT step1 在需要判分平时整体失败回滚
  • 老数据兼容 → 历史 TEMPLATE / CUSTOM_ASSIGNMENT 来源归一化为 SYSTEM / MANUAL 后返回
  • 非团期订单 → 行为与改动前完全一致,不会出现 GROUP_BATCH_PLAN 行
  • 团期历史旧户(在团期模型启用前已按旧模式办完住宿的户)→ 不产生团期行,住宿段仍是原来的配房派生行

六.5、枚举 / 数据字典(接口出现枚举时必写)

每个枚举单独一个子节,不混表。字段+枚举类对应关系写在子节开头。

sourceType(com.hulalv.order.settlement.enums.SettlementDetailSourceType)

所属字段: HotelItemVO.sourceType(请求与响应同名同义) | 类型: String

值 中文 说明
HOUSE_ASSIGNMENT 配房结果 逐户配房派生行,与配房记录一一对应
MANUAL 手工 核单员手工新增行,无权威源
SYSTEM 系统 系统生成行(历史 TEMPLATE 归一化到此值)
GROUP_BATCH_PLAN 团期配房 本次新增取值。团期订房计划派生行,按住宿事实分组聚合,一组一行
GROUP_BATCH_PLAN_REVOKED 团期配房(已失效) 本次新增取值。团期来源已失效但成本仍保留的行。✅(2026-09-13 订正)本期就会出现:团期订房计划行被删/改时,对应核单行当场转成本取值并照样返回(remark 前会被加上 [团期来源已失效] );权威回归时按 source_id 三段认回、改回 GROUP_BATCH_PLAN

该枚举的其余取值(景区 / 游玩项目 / 餐饮安排 / 车务 / 人员安排 / 订单增费)用于其他核单步骤,住宿 Step1 不会返回,住宿入参白名单也不接受它们。

settlementConfirmStatus(核单确认状态)

所属字段: HotelItemVO.settlementConfirmStatus | 类型: String

值 中文 说明
UNCONFIRMED 未确认 新建行的初始值;事实变更会被打回该值
CONFIRMED 已确认 团期行置该值需过 584129 闸门

paymentMethod(付款方式)

所属字段: HotelItemVO.paymentMethod | 类型: String

值 中文 说明
SIGNED 签单 对应结算类型 sign
COMPANY_PAID 公司付款 对应结算类型 company
CASH_PAID 现付 对应结算类型 cash;该类行可上传凭证

六.6、修改前后对比(修改/删除类接口必写,新增跳过)

字段级对比

字段 改前 改后
sourceType(响应) 取值域 HOUSE_ASSIGNMENT / MANUAL / SYSTEM 增加 GROUP_BATCH_PLAN、GROUP_BATCH_PLAN_REVOKED(字段本身早已存在,不是新增字段)
sourceTypeName(响应) 配房结果 / 手工 / 系统 增加「团期配房」「团期配房(已失效)」
sourceType(入参白名单) HOUSE_ASSIGNMENT / MANUAL / SYSTEM / TEMPLATE 再加 GROUP_BATCH_PLAN / GROUP_BATCH_PLAN_REVOKED
hotelAssignmentId 与 sourceId(团期行) 团期订单无此类行 取该组内最小分房记录 ID,随房务拆/合行漂移,不可作为行身份
remark(团期行) 仅核单员自填内容 间数变化时被系统加上 [住宿事实变更 间数 {旧}→{新},请复核实付] 前缀
其余字段 — 无增删、无类型变化

行为级对比

行为 改前 改后
团期订单 GET step1 住宿行 0 行 按「订单+入住日+酒店+房型」分组各一行,间数求和、计划成本按合计重算
团期订单行确认 无限制(因为没有团期行) 未分平或权威已消失时返回 584129
团期行锚点漂移(房务拆/合分房) — 只换锚点,行 ID、实付、凭证、备注、确认状态五项不变
团期行事实变更 — 确认状态打回 UNCONFIRMED,实付与凭证保留
团期权威消失 — ✅(2026-09-13 订正)不软删:就地转 GROUP_BATCH_PLAN_REVOKED + remark 加前缀,行照样返回;权威回归时三段认回,实付/凭证不丢
逐户配房派生行与手工行 现有行为 完全不变

六.7、影响评估(修改/删除类必写)

  • 是否破坏向后兼容: 否。路径、方法、VO 字段集合、既有取值语义均未变化;老前端把 GROUP_BATCH_PLAN 当未知来源忽略时,仍能正常读写散客单。
  • 前端是否必须同步上线: 否(不同步上线不会报错),但团期订单的核单页在同步前会有两个问题:来源列渲染成空白或未知值;核单员点确认时收到未映射的 584129 错误码。建议同期处理取值域映射与 584129 文案。
  • 前端 workaround 清理点: 若管理后台此前对团期订单隐藏了 Step1 住宿页、或写死了「团期无住宿」的提示,本次可撤除。

七、不影响范围(显式声明, 帮前端/QA 缩小排查面)

  • 仅影响: 管理后台核单结算 Step1 住宿明细的读写。
  • 连带变化(字段结构不变,行数变多): 订单详情、行程单、小程序行程的住宿段与核单读同一份房务配房契约,因此团期订单在这三处同样会多出团期配房行。字段名、类型、层级一个都没变,只是数组元素变多了;散客单这三处零变化。
  • 零影响:
    • 核单 Step2 门票、Step3 车辆及其余核单步骤
    • 团期整单核单闸(584130 住宿 / 584131 用车)的语义,本次未改
    • C 端算价、下单、支付链路
    • 逐户配房(非团期)派生行的对平逻辑
    • 历史数据:存量核单行不迁移,下一次打开 Step1 对平时才按新规则处理

八、测试环境已验证

接口行为以自动化用例断言实证,带 ✓ 标记(用例夹具: orderId=71001、departDate=2026-08-01、hotelId=201、roomTypeId=202、分房记录 99001 与 99002、单价 400.00):

GET  step1 团期权威存在        → 新建行 sourceType=GROUP_BATCH_PLAN、间数 2、计划与实际成本 800.00、
                                 UNCONFIRMED ✓ listHotel_groupAuthority_insertsDerivedRowWithGroupSourceAndAggregatedCost
GET  step1 同事实两条分房行    → 只返回一行、间数求和、锚点取最小 allocId 99001 ✓
                                 listHotel_splitAllocationsInSameFactGroup_aggregatesIntoSingleRow
GET  step1 仅锚点漂移          → 重绑锚点,实付/凭证/备注/确认状态四项零改动 ✓
                                 listHotel_allocIdDriftedOnly_persistsNewAnchorAndKeepsFinancialColumns
GET  step1 事实与锚点均未变    → 读路径一行库也不写 ✓
                                 listHotel_anchorAndFactsUnchanged_doesNotWriteOnReadPath
GET  step1 间数 2 变 1         → 计划成本 400.00、实付 650.00 与凭证保留、状态回 UNCONFIRMED、
                                 remark = "[住宿事实变更 间数 2→1,请复核实付] 财务备注" ✓
                                 listHotel_roomCountChanged_keepsActualCostResetsStatusAndPrefixesRemark
GET  step1 权威消失            → 派生行转 REVOKED(不删),已失效行与手工行保留 ✓  ← 2026-09-13 订正
                                 listHotel_groupAuthorityGone_revokesDerivedRowAndKeepsRevokedAndManual
PUT  step1 未分平置确认        → 584129 ✓ saveHotel_groupRowUnbalanced_confirmRejectedWith584129
PUT  step1 已分平置确认        → 放行 ✓ saveHotel_groupRowBalanced_confirmAllowed
PUT  step1 权威已消失置确认    → 584129 ✓ saveHotel_groupRowSourceIdMissingFromContract_rejectedWith584129
PUT  step1 未分平但保持未确认  → 放行 ✓ saveHotel_groupRowUnbalancedButStaysUnconfirmed_allowed
PUT  step1 已失效行置确认      → 放行且不查房务契约 ✓
                                 saveHotel_groupBatchPlanRevokedRow_confirmAllowedWithoutContractLookup
PUT  step1 逐户配房行置确认    → 不被团期闸门拦 ✓ saveHotel_houseAssignmentRow_confirmNotGatedByGroupBalance
PUT  step1 团期行事实变更      → 落库强制 UNCONFIRMED ✓ saveHotel_groupRowFactChanged_resetToUnconfirmed
sourceType 入参白名单          → 两个新取值通过、未知取值被拒 ✓ SettlementHotelSourceTypeValidationTest

网关:本次无新增路径,两个端点沿用既有路由 /v3/admin/** 到 hl-order-service-v3(hl-gateway/src/main/resources/application.yml 中的 order-service-v3 路由),无需网关改动。

上表为自动化用例断言原文;示例 JSON 中的 ID 与金额取自同一批用例夹具,不是测试服抓包报文。测试服真实网关的验收取证留在工单 #7327 的验收项里。


九、相关历史 PR(纠错 / 功能演进时必写)

PR Issue 说明 是否仍有效
本 PR #7600 #7327 PR-1:团期配房行合流进住宿核单 Step1 + 584129 行级确认闸 ✅ 最新

十、相关文档

  • 关联 Issue: wx/HL#7327
  • 关联 PR: wx/HL#7600
  • 团期整单住宿闸(584130): 见 changelogs-v2/2026-09/11_7446_住宿结算闸判团口径-修改接口-管理后台.md
  • 团期住宿户级 finalize 闸门: 见 changelogs-v2/2026-09/10_7347_finalize团期住宿户级闸门-修改接口-管理后台.md
  • 后续计划: GROUP_BATCH_PLAN_REVOKED 的产出与「失效行不计入金额」在下一期一起落地,届时另发条目

关联 / 联系人

链接

联系人

  • 后端负责人: @wx