diff --git a/changelogs-v2/2026-09/27_8401_房务配房台账联动——确认缺团号拒绝808188、清空锁定行拒绝599602-修改接口-管理后台.md b/changelogs-v2/2026-09/27_8401_房务配房台账联动——确认缺团号拒绝808188、清空锁定行拒绝599602-修改接口-管理后台.md new file mode 100644 index 00000000..7497a33a --- /dev/null +++ b/changelogs-v2/2026-09/27_8401_房务配房台账联动——确认缺团号拒绝808188、清空锁定行拒绝599602-修改接口-管理后台.md @@ -0,0 +1,557 @@ +--- +schema: "hl-changelog/v2" +ticket: "8401" +title: "房务配房台账联动:确认缺团号拒绝、清空/改价审计应付行" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-27" +status_note: "PR #8402 已合入 dev-v3(合并提交 57199b539);2026-09-27 部署测试环境并经网关实测 AC-1 至 AC-4;pre-flight 核对部署 COMMIT=57199b539 STATE=ok" +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`)