# 合同/保险镜像状态"无记录"时从 `"NONE"` 改为 `null` - **变更类型**:修改接口 - **端类型**:管理后台 - **日期**:2026-06-18 - **Issue**:[#3953](https://git.1814.love:8443/wx/HL/issues/3953) - **PR**:[#3956](https://git.1814.love:8443/wx/HL/pulls/3956) - **Commit**:[dc21950a9](https://git.1814.love:8443/wx/HL/commit/dc21950a9)(feature)/ [41cfd5ad8](https://git.1814.love:8443/wx/HL/commit/41cfd5ad8)(merge) - **后端负责人**:腰苏图 --- ## ① 接口背景 order-v3 主表 `order_main` 中有两个镜像状态列:`contract_status`(合同状态)和 `insurance_status`(保险状态)。 以往在订单**尚无合同**或**尚无保险**时,这两列的值以字符串 `"NONE"` 写入并通过接口返回给前端。此次统一语义:「无记录」状态改为返回 `null`,不再返回字符串 `"NONE"`。 涉及枚举值(有进展时不变): - 合同:`GENERATING` / `GENERATED` / `SIGNED` / `VOIDED` / `RESIGNING` - 保险:`INSURING` / `INSURED` / `CANCELLED` / `FAILED` --- ## ② 变更清单 | # | 接口 | 字段路径 | 变更 | |---|------|----------|------| | 1 | `GET /v3/admin/order/{id}` | `main.contractStatus` | 无合同时:`"NONE"` → `null` | | 2 | `GET /v3/admin/order/{id}` | `main.insuranceStatus` | 无保险时:`"NONE"` → `null` | | 3 | `GET /v3/admin/order/{id}/contract-insurance` | `contract.contractStatus` | 无合同时:`"NONE"` → `null` | | 4 | `GET /v3/admin/order/{id}/contract-insurance` | `insurance.insuranceStatus` | 无保险时:`"NONE"` → `null` | | 5 | `GET /v3/admin/order/group-batch/{groupBatchId}/orders` | `[*].contractStatus` | 无合同时:`"NONE"` → `null` | | 6 | `GET /v3/admin/order/group-batch/{groupBatchId}/orders` | `[*].insuranceStatus` | 无保险时:`"NONE"` → `null` | > 订单列表 `GET /v3/admin/order/list` 返回的 `OrderListItemRespVO` **不含**这两个字段,不受影响。 --- ## ③ 接口详情 ### 接口一:订单详情(9 Tab 聚合) | 项 | 值 | |---|---| | 方法 + 路径 | `GET /v3/admin/order/{id}` | | 接口名 | 订单详情(9 Tab 聚合) | | 认证 | 需要 JWT(管理后台 token) | | 幂等性 | 只读,天然幂等 | | 限流 | 无特殊限流 | ### 接口二:合同保险 Tab | 项 | 值 | |---|---| | 方法 + 路径 | `GET /v3/admin/order/{id}/contract-insurance` | | 接口名 | 订单合同保险 Tab | | 认证 | 需要 JWT(管理后台 token) | | 幂等性 | 只读,天然幂等 | | 限流 | 无特殊限流 | ### 接口三:团期子订单列表 | 项 | 值 | |---|---| | 方法 + 路径 | `GET /v3/admin/order/group-batch/{groupBatchId}/orders` | | 接口名 | 团期子订单列表 | | 认证 | 需要 JWT(管理后台 token) | | 幂等性 | 只读,天然幂等 | | 限流 | 无特殊限流 | --- ## ④ 接口入参 三个接口均为路径参数,本次变更不影响入参。 | 参数 | 位置 | 类型 | 必填 | 说明 | |------|------|------|------|------| | `id` | Path | Long | 是 | 订单 ID(接口一、二) | | `groupBatchId` | Path | Long | 是 | 团期 ID(接口三) | --- ## ⑤ 出参字段(受影响字段) ### 接口一 `GET /v3/admin/order/{id}` — `main` 节点 | 字段路径 | 类型 | 说明 | |----------|------|------| | `main.contractStatus` | `String \| null` | 合同状态。**无合同时返回 `null`**(原来返回 `"NONE"`);有合同时为枚举值,见 § ⑥ | | `main.insuranceStatus` | `String \| null` | 保险状态。**无保险时返回 `null`**(原来返回 `"NONE"`);有保险时为枚举值,见 § ⑥ | ### 接口二 `GET /v3/admin/order/{id}/contract-insurance` | 字段路径 | 类型 | 说明 | |----------|------|------| | `contract.contractStatus` | `String \| null` | 合同状态。**无合同时返回 `null`** | | `insurance.insuranceStatus` | `String \| null` | 保险状态。**无保险时返回 `null`** | 其余字段(`contractSignedAt`、`contractFileUrl`、`events`、`insurancePolicyNo`、`insurancePremium` 等)在无合同/无保险时仍返回 `null`,无变化。 ### 接口三 `GET /v3/admin/order/group-batch/{groupBatchId}/orders` — 列表项 | 字段路径 | 类型 | 说明 | |----------|------|------| | `[*].contractStatus` | `String \| null` | 合同状态。**无合同时返回 `null`** | | `[*].insuranceStatus` | `String \| null` | 保险状态。**无保险时返回 `null`** | --- ## ⑥ 枚举 / 数据字典 ### contractStatus 合同状态 | 枚举值 | 含义 | 备注 | |--------|------|------| | `null` | 无合同记录 | **本次新值**(原来是 `"NONE"`) | | `GENERATING` | 合同生成中 | | | `GENERATED` | 合同已生成(待签署) | | | `SIGNED` | 已签署 | | | `VOIDED` | 已作废 | | | `RESIGNING` | 重新签署中 | | ### insuranceStatus 保险状态 | 枚举值 | 含义 | 备注 | |--------|------|------| | `null` | 无保险记录 | **本次新值**(原来是 `"NONE"`) | | `INSURING` | 出单中 | | | `INSURED` | 已投保 | | | `CANCELLED` | 已取消 | | | `FAILED` | 出单失败 | | --- ## ⑦ 错误码 本次变更不新增错误码。以下为接口既有错误码: | 错误码 | HTTP 状态 | 说明 | |--------|-----------|------| | `587000` | 400 | 订单不存在 | --- ## ⑧ 示例 ### 8.1 典型成功——订单已签约并已投保 **请求** ```http GET /v3/admin/order/2000000001 Authorization: Bearer ``` **响应(main 节点节选)** ```json { "code": 200, "data": { "main": { "id": "2000000001", "orderNo": "HL20260601001", "orderStatus": "ON_TRIP", "contractStatus": "SIGNED", "insuranceStatus": "INSURED", "refundStatus": null, "hasRefund": false } } } ``` **合同保险 Tab(`GET /v3/admin/order/2000000001/contract-insurance`)** ```json { "code": 200, "data": { "contract": { "contractStatus": "SIGNED", "contractSignedAt": "2026-06-01T10:30:00", "contractFileUrl": "https://oss.hulalv.com/contract/HL20260601001.pdf", "events": [ { "eventType": "GENERATE", "occurredAt": "2026-06-01T10:00:00" }, { "eventType": "SIGN", "occurredAt": "2026-06-01T10:30:00" } ] }, "insurance": { "insuranceStatus": "INSURED", "insurancePolicyNo": "PICC2026060100123", "insurancePremium": 88.00, "events": [ { "eventType": "ISSUE", "occurredAt": "2026-06-01T10:35:00" } ] } } } ``` --- ### 8.2 边界情况——订单刚创建,尚无合同和保险 **响应(main 节点节选)** ```json { "code": 200, "data": { "main": { "id": "2000000002", "orderNo": "HL20260602001", "orderStatus": "CUSTOMIZING", "contractStatus": null, "insuranceStatus": null, "hasRefund": false } } } ``` **合同保险 Tab** ```json { "code": 200, "data": { "contract": { "contractStatus": null, "contractSignedAt": null, "contractFileUrl": null, "events": [] }, "insurance": { "insuranceStatus": null, "insurancePolicyNo": null, "insurancePremium": null, "events": [] } } } ``` **团期子订单列表(`GET /v3/admin/order/group-batch/3000000001/orders`,无合同无保险的子订单项节选)** ```json { "code": 200, "data": [ { "orderId": "2000000002", "orderNo": "HL20260602001", "contractStatus": null, "insuranceStatus": null, "payStatus": "UNPAID" } ] } ``` ### 8.3 业务失败——订单 ID 不存在 **请求** ```http GET /v3/admin/order/9999999999 ``` **响应** ```json { "code": 587000, "msg": "订单不存在" } ``` --- ## ⑨ 业务边界 **适用**: - 创建后尚未触发签约的订单:`contractStatus` 返回 `null` - 创建后尚未触发投保的订单:`insuranceStatus` 返回 `null` - 合同被作废后重新签署流程未开始的中间态仍走枚举值(`VOIDED` → `RESIGNING`),不返回 `null` **不适用(不返回 null)**: - 合同/保险流程一旦触发(任意枚举值),即使中途失败(`FAILED`)也返回枚举值而非 `null` **特殊边界**: - 若历史订单数据库列值为 `"NONE"`(迁移前旧数据),后端读取时统一转换为 `null` 再返回,前端不会收到字符串 `"NONE"` --- ## ⑩ 修改前后对比 ### 字段级对比 | 字段 | 无合同/无保险时修改前 | 无合同/无保险时修改后 | |------|---------------------|---------------------| | `contractStatus` | `"NONE"` | `null` | | `insuranceStatus` | `"NONE"` | `null` | 三个接口均适用此对比。有合同/有保险时枚举值**不变**。 ### 行为级对比 | 场景 | 修改前 | 修改后 | |------|--------|--------| | 新建订单查详情 | `contractStatus: "NONE"` | `contractStatus: null` | | 查合同保险 Tab | `contract.contractStatus: "NONE"` | `contract.contractStatus: null` | | 团期子订单列表 | `contractStatus: "NONE"` | `contractStatus: null` | --- ## ⑪ 影响评估 / 回滚 ### 破坏兼容 **破坏性变更**。若前端代码以 `contractStatus === "NONE"` 或 `insuranceStatus === "NONE"` 判断「无状态」,**需改为 `contractStatus == null`**(或 `!contractStatus`),否则判断会失效。 ### 前端同步要求 前端需在本次后端上线前(或同时)完成如下改动: 1. 所有判断 `contractStatus === "NONE"` 的地方改为 `contractStatus == null`(或 `!contractStatus`) 2. 所有判断 `insuranceStatus === "NONE"` 的地方改为 `insuranceStatus == null`(或 `!insuranceStatus`) 3. 合同 Tab 徽标逻辑:判断是否显示「无合同」状态从 `=== "NONE"` 改为 `=== null` 4. 保险 Tab 徽标逻辑:同上 ### 回滚方案 后端可在镜像写入层回退 `null → "NONE"` 兜底逻辑,重新发布即生效,无需 DDL。 --- ## ⑫ 注意事项 1. **三个接口同时生效**:`GET /v3/admin/order/{id}`、`GET /v3/admin/order/{id}/contract-insurance`、`GET /v3/admin/order/group-batch/{groupBatchId}/orders` 同批上线,需一次性适配,不存在分步过渡。 2. **历史数据已处理**:后端上线时同步处理库中存量 `"NONE"` 值,前端永远不会收到字符串 `"NONE"`,无需做兼容两个值的过渡逻辑。 3. **列表接口不受影响**:`GET /v3/admin/order/list` 返回的列表项 VO 不含 `contractStatus` / `insuranceStatus`,无需处理。 4. **枚举值语义不变**:除 `"NONE"` 变为 `null` 外,其余枚举值含义和拼写完全不变。 --- ## ⑬ 关联 / 联系人 | 项 | 链接 | |---|------| | Issue | [#3953 合同/保险镜像状态统一 NONE→null](https://git.1814.love:8443/wx/HL/issues/3953) | | PR | [#3956 refactor(order-v3): 合同/保险镜像状态统一 NONE→null](https://git.1814.love:8443/wx/HL/pulls/3956) | | Feature Commit | [dc21950a9](https://git.1814.love:8443/wx/HL/commit/dc21950a9) | | Merge Commit | [41cfd5ad8](https://git.1814.love:8443/wx/HL/commit/41cfd5ad8) | | 后端负责人 | 腰苏图(yaosutu) |