--- schema: "hl-changelog/v2" ticket: "8401" title: "房务配房台账联动:确认缺团号拒绝、清空/改价审计应付行" consumer: "admin" author: "wx(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "not_required" frontend_owner: "" frontend_ref: "" target_release: "" verified_at: "" status_note: "PR #8402 已合入 dev-v3(合并提交 57199b539);2026-09-27 部署测试环境并经网关实测 AC-1 至 AC-4;pre-flight 核对部署 COMMIT=57199b539 STATE=ok【2026-09-27 mmg】前端判 not_required(零代码):808188/599602 grep 前端零引用无错误码字典;五个配房写口全走标准 http 封装无 silentError,消费方三 composable catch 均由 request.js 拦截器统一弹 message(该拦截器对 4xxxxx/5xxxxx 全业务码透 body.message);改价/删除改前本就抛 599600/599601 前端零按码处理,599xxx 同路;§6.7 自述「前端 100% 透明」与实证一致,建议文案「请先完成收款/联系财务解冻」是 UX 建议非契约,后端 message 已可达因。" updated_at: "2026-09-27" base: "dev-v3" --- # 房务配房台账联动:确认前置关卡、改价/清空应付行审计 > **服务**: hl-order-service-v3 > **PR**: #8402 | **Issue**: #8401 | **合并提交**: `57199b539` > **影响范围**: 管理后台「房务配房工作台 → 配房确认、改价、删除、清空」、应付款台账行联动 --- ## ⚠️ 关键变化 **确认配房前新增团号检查;改价、删除、清空配房时对应付款台账行作同事务作废与冻结校验。** 三类场景导致配房行 CONFIRMED 但缺应付台账行或行被占用: | 场景 | 原行为 | 新行为 | |---|---|---| | 确认配房时团号未生成(收款前) | 200 成功但不推台账 | 拒绝,新错误码 **808188** | | 改价/删除该行时其无台账 | 改前 599601、删前 599601 | 改后 200(只改价不补推);删后 200(正常软删) | | 改价/删除/清空时台账被占用 | 各自独立校验 | 统一 599602「应付款台账行已锁定」 | **契约边界**:改价只是数值标度不同(`265` vs `265.00`)不再视作改价,应付行不作废。 --- ## 一、背景 房务配房成立应付款「该付供应商的钱」的凭据,必须在确认时同事务落应付款台账行。多轮测试发现三处漏洞: 1. **无团号可确认**:订单未收款时 team_no 为空,确认应该被拒,原实现放行确认但不推台账,留下「CONFIRMED 但无台账」。 2. **改价/删除无审计**:已确认配房改结算价、删除或清空时,假设被操作行原本就有应付台账,实际可能无行(存量或异常路径产生),改前的"有台账才改"假设不再成立。 3. **应付行被占用时整体锁定**:付款草稿(PENDING)、已付款(applied > 0)的应付行应该阻止对应配房的清空,整笔清空拒绝、一条都不删。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 单日确认配房 | POST | `/v3/admin/order/hotel-requirements/{requirementId}/assignments/days/{dayNumber}/confirm` | **行为变更** | 订单 team_no 为空时拒绝,新错误码 808188;配房行保持 INQUIRING,不生成应付台账 | | 2 | 修改单条配房 | PUT | `/v3/admin/order/assignments/{id}` | **副作用变更** | 改结算价时,原无应付台账行的只改价不补推;原有台账行的作废旧行按新价重推;只是标度不同不视作改价 | | 3 | 删除配房 | DELETE | `/v3/admin/order/assignments/{id}` | **副作用变更** | 已确认但无应付台账行的配房删除不再报 599601,正常 200 软删 | | 4 | 清空整需求配房 | DELETE | `/v3/admin/order/hotel-requirements/{requirementId}/assignments` | **校验变更** | 清空时同步作废已确认配房的应付台账行;台账行被占用时整体拒绝 599602 | | 5 | 清空某天配房 | DELETE | `/v3/admin/order/hotel-requirements/{requirementId}/assignments/days/{dayNumber}` | **校验变更** | 同接口 4,按日分流 | --- ## 三、接口详情 ### 1. 单日确认配房 `POST /v3/admin/order/hotel-requirements/{requirementId}/assignments/days/{dayNumber}/confirm` **VO**: `AssignmentDayConfirmReqVO` #### 使用场景 房务配房工作台选择该天候选配房后点「确认」,调用该接口保留选中行、软删落选行、推送应付款台账。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `requirementId` | Path | Long | ✅ | — | 房型需求 id | | `dayNumber` | Path | Integer | ✅ | ≥1 | 第几晚(从 1 开始)| | `keepAssignmentIds` | Body | List<Long> | ❌ | 非空时每个 id 须属于该天候选 | 要保留确认的配房行 id;缺省为空(保留全部)| #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | `data` | Null | 无返回体 | #### 请求示例 ```json { "keepAssignmentIds": [2104045881147965442] } ``` #### 响应示例 确认成功: ```json { "code": 200, "message": "成功", "success": true, "data": null } ``` #### 空数据 / 降级响应 入参 `keepAssignmentIds` 为空时视为保留该天全部候选,该天无候选时返回 808118: ```json { "code": 808118, "message": "该天无可确认的询房中候选, 请先配房再确认", "success": false, "data": null } ``` #### 错误响应 **新增错误码 808188**:订单团号未生成(收款前)时拒绝确认: ```json { "code": 808188, "message": "订单团号未生成(收款后生成),暂不能确认配房", "success": false, "data": null } ``` 保留的配房行不属于该天候选时 fail-fast 拒绝: ```json { "code": 808123, "message": "保留的配房行不属于该天询房中候选(可能配房已更新), 请刷新后重试", "success": false, "data": null } ``` #### 业务边界 - **团号检查**:CORE 订单检查 `order_main.team_no` 非空;GROUP 订单检查班期 `batch_no`(列定义 NOT NULL,实际打不到) - **台账推送时点**:确认成功后即事务内推送应付款 NORMAL 行,金额 = `settlementPrice × roomCount`,团号为键 - **并发场景**:后到的确认请求仍可能收到 599601(两个请求并发取消同一行时) - **幂等性**:同 (requirementId, dayNumber) 3 秒内重复确认拒绝 - **存量一致性**:确认不改变 SQL 求交逻辑,该天无 INQUIRING 候选照旧报 808118 ### 2. 修改单条配房 `PUT /v3/admin/order/assignments/{id}` **VO**: `AssignmentUpdateReqVO` #### 使用场景 房务工作台配房行改价/备注/支付方式时调用;路径参数、出参结构、签名、权限均未变。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `id` | Path | Long | ✅ | — | 配房行 id(`house_hotel_assignment.id`)| | `protoPrice` | Body | BigDecimal | ❌ | ≥0 | 原始供应商报价 | | `settlementPrice` | Body | BigDecimal | ❌ | ≥0 | 结算价(现在改后作废旧台账行)| | `settlementMethod` | Body | String | ❌ | 枚举值 | 支付方式 | | `remark` | Body | String | ❌ | — | 备注 | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | `data` | Null | 无返回体 | #### 请求示例 改结算价: ```json { "settlementPrice": "380.00", "remark": "协议优惠" } ``` #### 响应示例 改价成功(原无应付台账行,只改价不补推): ```json { "code": 200, "message": "成功", "success": true, "data": null } ``` #### 空数据 / 降级响应 无特殊空数据;行不存在或权限不足时返回对应业务码。 #### 错误响应 应付款台账行已被占用(付款申请占用或已付款): ```json { "code": 599602, "message": "应付款台账行已锁定", "success": false, "data": null } ``` #### 业务边界 - **改价判定**:值的数值部分不同才视作改价(`265.00` 与 `265.000` 视同不改),例如 `380.00` → `390.00` 才作废旧台账 - **无台账行的改价**:仅改配房行,不补推台账行(存量中部分 CONFIRMED 配房原本就无台账) - **有台账行的改价**:作废旧 NORMAL 行 + 按新价重推新行(仅限该配房对应的台账) - **新价校验**:若新价导致 `settlementPrice × roomCount ≤ 0`,重推 fail-fast 报 599600(与改前逻辑一致) - **并发场景**:改价与删除/清空并发时,清空可能因台账被占用而拒,改价则进行中 ### 3. 删除配房 `DELETE /v3/admin/order/assignments/{id}` **VO**: `StaffAssignmentVO` #### 使用场景 房务工作台删除单条配房行;路径参数、出参结构、签名、权限均未变。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `id` | Path | Long | ✅ | — | 配房行 id | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | `data` | Null | 无返回体 | #### 请求示例 ```http DELETE /v3/admin/order/assignments/2100873375659266050 ``` #### 响应示例 删除成功: ```json { "code": 200, "message": "成功", "success": true, "data": null } ``` #### 空数据 / 降级响应 行不存在或权限不足时返回 808120 或 808110。 #### 错误响应 应付款台账行已被占用: ```json { "code": 599602, "message": "应付款台账行已锁定", "success": false, "data": null } ``` #### 业务边界 - **已确认无台账的删除**:改前报 599601,改后正常 200 软删(存量或异常路径产生的 CONFIRMED 无台账行) - **已确认有台账的删除**:行被占用时报 599602,阻止删除;未被占用时正常软删并同步作废应付行 - **幂等性**:同一行删除后重删报 808120(行已软删、再读不到) - **与清空互斥**:单行删除与整需求/整天清空互斥,串行化执行 ### 4. 清空整需求配房 `DELETE /v3/admin/order/hotel-requirements/{requirementId}/assignments` **VO**: `AssignmentClearRespVO` #### 使用场景 房务工作台清空整个住宿需求下全部配房行,调用该接口一次性清空、同步作废应付台账。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `requirementId` | Path | Long | ✅ | — | 房型需求 id | #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | `clearedCount` | Integer | 清空行数 | #### 请求示例 ```http DELETE /v3/admin/order/hotel-requirements/2104045736624820226/assignments ``` #### 响应示例 清空成功: ```json { "code": 200, "message": "成功", "success": true, "data": { "clearedCount": 2 } } ``` #### 空数据 / 降级响应 需求无配房行时 `clearedCount=0`,接口仍 200: ```json { "code": 200, "message": "成功", "success": true, "data": { "clearedCount": 0 } } ``` #### 错误响应 清空范围内任一配房的应付台账行被付款申请占用或已付款,整体拒绝(一条都不删): ```json { "code": 599602, "message": "应付款台账行已锁定", "success": false, "data": null } ``` #### 业务边界 - **整体性**:检查待清空全部 CONFIRMED 行的应付台账,任一被占用则整笔拒绝(失败时零行被删) - **应付行同步作废**:清空成功后同事务作废该需求对应的全部 NORMAL 台账行(无论是否被占用) - **不动抢单**:清空仅删配房,不释放抢单人、不回抢单池 - **幂等性**:同需求 3 秒内重复清空拒绝 - **部分被占用的场景**:假设需求 2 个 CONFIRMED 行,其中 1 个台账被占用,整批拒、2 行都保留 ### 5. 清空某天配房 `DELETE /v3/admin/order/hotel-requirements/{requirementId}/assignments/days/{dayNumber}` **VO**: `AssignmentClearRespVO` #### 使用场景 房务工作台行程日行内「清空」按钮,调用该接口清空该天全部配房,无关其他天。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `requirementId` | Path | Long | ✅ | — | 房型需求 id | | `dayNumber` | Path | Integer | ✅ | ≥1 | 第几晚(从 1 开始)| #### 出参 `Result` | 字段 | 类型 | 说明 | |------|------|------| | `clearedCount` | Integer | 清空行数 | #### 请求示例 ```http DELETE /v3/admin/order/hotel-requirements/2104045736624820226/assignments/days/1 ``` #### 响应示例 清空成功: ```json { "code": 200, "message": "成功", "success": true, "data": { "clearedCount": 1 } } ``` #### 空数据 / 降级响应 该天无配房行时 `clearedCount=0`,接口仍 200: ```json { "code": 200, "message": "成功", "success": true, "data": { "clearedCount": 0 } } ``` #### 错误响应 该天清空范围内任一配房的应付台账行被占用,整体拒绝: ```json { "code": 599602, "message": "应付款台账行已锁定", "success": false, "data": null } ``` #### 业务边界 - **按日分流**:仅清空该天配房,不影响其他晚次 - **应付行同步**:清空成功后同事务作废该天对应的应付台账行 - **台账冻结**:该天任一台账被占用,整天清空拒绝 - **幂等性**:同 (requirementId, dayNumber) 3 秒内重复清空拒绝 - **与单行删除互斥**:整天清空与单行删除由同一分布式锁保护 --- ## 四、契约约束与正确调用方式 - **团号时点**:收款后(manual-receipt 或 transition 路径产生团号),确认前 team_no 必须非空 - **应付台账键**:落库应付行时以 team_no 为业务键,确认失败时台账行零行存在 - **改价与标度**:小数点后补零(`265` → `265.00`)不视作改价,应付行不变;需财务的驳回或重新定价时前端提示「联系财务调整」 - **台账被占用判定**:`fin_payable_line.applied > 0`(付款申请已占用)或支付单已删除但 `applied` 未清零时视作被占用 - **清空原子性**:清空失败为原子操作,检查与删除在同一事务内,失败时零行被删 - **权限一致性**:全部接口遵循现有权限校验规则,本单无权限变更 --- ## 五、数据库行为 无 DDL、无迁移、无新增列。行为变化: | 表 | 列 | 变化 | |---|---|---| | `house_hotel_assignment` | `confirm_status` | 确认拒绝 808188 时保持 INQUIRING(不翻态) | | `fin_payable_line` | — | 改价/删除/清空时按新规则作废旧行(deleted_at = 当前时刻) | | `fin_payable_line` | `applied` | 作废时 applied 不清零,保留历史记录 | | 其他表 | — | **无变化** | --- ## 六、边界行为 - 确认前团号为空 → 808188,拒绝,配房行保持 INQUIRING,台账零行 - 改价无台账行 → 200,只改配房行 - 改价有台账行 → 200,作废旧行按新价重推 - 改价新价非法 → 599600,与改前一致 - 删除无台账行 → 200,正常软删 - 删除有台账被占用 → 599602,拒绝 - 清空全部被占用 → 599602,整批拒 - 清空部分被占用 → 599602,整批拒(一条都不删) ## 六.5、枚举 / 数据字典 本次新增错误码 **808188** `HOUSE_TEAM_NO_MISSING`:「订单团号未生成(收款后生成),暂不能确认配房」。 统一使用错误码 **599602** `PAYABLE_LINE_LOCKED` 表示应付台账行被占用(原为单行改价/删除场景,改后扩展到清空场景)。 ## 六.6、修改前后对比 | 项 | 变更前 | 变更后 | |---|---|---| | 无团号确认 | 200 成功但不推台账 | **808188 拒绝** | | 无台账改价 | 599601 回滚 | **200 成功(只改价)** | | 无台账删除 | 599601 回滚 | **200 成功(正常删除)** | | 有台账改价 | 599601 / 200 异常组合 | **200 成功(作废旧+新推)** | | 清空整需求 | 无台账冻结校验 | **任一被占用则整体拒** | | 清空按天 | 无台账冻结校验 | **该天任一被占用则整天拒** | | 台账占用拒绝码 | 599602(单行)/ 零(清空) | **统一 599602** | | 路径、入参、出参结构 | — | **全部不变** | ## 六.7、影响评估 | 维度 | 评估 | |---|---| | 兼容性 | **对前端 100% 透明**。路径、入参、出参结构、HTTP 方法均未变;行为变更仅增加拒绝场景(无团号确认、台账被占用),不破坏现有成功调用 | | 前端 | **无需改动**。错误码 808188 交付时前端应已配置提示「请先完成收款」,599602 提示沿用「联系财务解冻」| | 数据 | 无 DDL、无迁移、无回填 | | 性能 | 改价、删除、清空时新增应付行查询与冻结校验,单次 O(n)(n=配房行数,通常 <10)| | 回滚 | `git revert`,单一 PR,无数据侧残留 | | 风险 | 低。前置检查只能降低風险、不会升高;应付行作废改为同事务内原子操作,消除中间态 | ## 七、不影响范围 - **零影响**:所有接口的路径、HTTP 方法、入参结构 - **零影响**:出参结构(均为 Result 或 Result) - **零影响**:权限校验、鉴权闸口 - **零影响**:库存扣减逻辑(确认失败不改库存,改价/删除不再动库存) - **零影响**:房型快照、房型冻结逻辑 - **零影响**:与定制师的交互(改期、驳回等) - **零影响**:订单状态机推进(resource_ready / confirm 等) --- ## 八、测试环境已验证 ✅ 2026-09-27 于测试环境网关实测,真实鉴权(房务端)。 - 网关 `https://api.test.1814.love`,分支 `dev-v3`,部署提交 `57199b539` - pre-flight:`ssh -p 2220 root@192.168.100.236 bash /opt/hulalv/scripts/deploy-status.sh` 确认 COMMIT=57199b539 STATE=ok | # | 用例 | 期望 | 实测 | |---|---|---|---| | AC-1 | 无台账行改价 / 删除 | 200,无台账新增 | ✅ 改价 0→0;删除 0→0(夹具:2 条存量行) | | AC-2 | 确认后删除、清空、改价 | 200,应付行同步作废 | ✅ confirmed→deleted_at(SQL 验证) | | AC-3 | 清空时付款草稿占用 | 599602,0 行被删 | ✅ 整天拒,deleted_at 仍 NULL | | AC-4 | team_no 为空确认 | 808188 拒绝 | ✅ INQUIRING 未翻态,fin_payable_line COUNT=0 | **观察**:部署后服务无重启,双实例持续 running,日志最近 200 行 `ERROR` / `Exception` 命中 **0** 条。 **存量数据**:测试环境所造数据一律 `AC-*` 前缀,验证后已物理删除,残留合计 0 条。 --- ## 十、相关文档 - 房务配房 API 规范:`docs/apis/house/assignments/`(dev-v3 分支) - 应付款台账设计:`docs/finance/payable-lines.md` - 团号生成规则:`docs/order/team-no-generation.md` --- ## 关联 / 联系人 - **Issue**: #8401 - **PR**: #8402(合并提交 `57199b539`)