diff --git a/changelogs-v2/2026-07/13_4853_确认订单预览提交接口-修改接口-管理后台.md b/changelogs-v2/2026-07/13_4853_确认订单预览提交接口-修改接口-管理后台.md new file mode 100644 index 0000000..81cfb19 --- /dev/null +++ b/changelogs-v2/2026-07/13_4853_确认订单预览提交接口-修改接口-管理后台.md @@ -0,0 +1,438 @@ +# 【修改接口 · 管理后台】确认订单预览与提交接口契约补齐(#4853) + +> **PR**: #4839 / #4846 / #4860 / #4868 +> **服务**: hl-order-service-v3 +> **更新时间**: 2026-07-13 +> **适用端**: 管理后台订单详情页 + +## 1. 接口背景 + +管理后台订单详情页点击“确认订单/确认行程”时,前端需要先调用预览接口取得 5 项前置校验结果;只有 `allPassed=true` 时才展示确认弹窗,并在用户确认后调用专用提交接口。 + +本次补齐前端实际需要的两个接口契约: + +1. `GET /v3/admin/order/{orderId}/confirm-checklist` +2. `POST /v3/admin/order/{orderId}/confirm-itinerary` + +前端不要再接入或暴露通用状态机接口 `/v3/admin/order/{orderId}/transition` 来做确认订单动作。 + +## 2. 变更清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 确认订单前置校验与弹窗预览 | GET | `/v3/admin/order/{orderId}/confirm-checklist` | 修改接口 | 返回成功/失败互斥结构;失败返回 `items`,成功返回 `preview`;补齐 `items[].checkName`、`preview.staffs`,删除 `notifications` | +| 2 | 确认订单/确认行程提交 | POST | `/v3/admin/order/{orderId}/confirm-itinerary` | 新增专用接口 | 前端只传可选 `reporterAssignmentId`;服务端固定确认事件和审计原因;成功后推进到待出行并触发合同/投保异步事件 | + +## 3. 接口详情 + +### 3.1 确认订单前置校验与弹窗预览 + +- **路径**: `GET /v3/admin/order/{orderId}/confirm-checklist` +- **认证**: 管理后台登录态,Header 携带 `Authorization: Bearer ` +- **用途**: 点击确认按钮后先调用,用于决定弹窗展示内容或阻断原因。 +- **核心语义**: + - `allPassed=true`: `items=null`,`preview` 有值,前端展示确认弹窗。 + - `allPassed=false`: `items` 有值,`preview=null`,前端展示失败项并引导补齐。 + +### 3.2 确认订单/确认行程提交 + +- **路径**: `POST /v3/admin/order/{orderId}/confirm-itinerary` +- **认证**: 管理后台登录态,Header 携带 `Authorization: Bearer ` +- **用途**: 用户在确认弹窗里最终点击确认后调用。 +- **请求体**: 可传 `{}`;只有用户在弹窗里切换主报账人时才传 `reporterAssignmentId`。 +- **副作用**: 成功后订单从定制中/待确认推进为待出行,并可能触发合同生成、保险投保异步事件。 + +## 4. 入参 + +### 4.1 路径参数 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `orderId` | String | 是 | 订单 ID。雪花 ID 建议按字符串处理,避免前端数字精度丢失。 | + +### 4.2 `GET /confirm-checklist` 查询参数 + +无。 + +### 4.3 `POST /confirm-itinerary` 请求体 + +| 字段 | 类型 | 必填 | 说明 | 校验规则 | +|------|------|------|------|----------| +| `reporterAssignmentId` | String | 否 | 新的主报账人 staff assignmentId;不传则沿用当前主报账人。 | 传入时必须是当前订单下有效的 staff assignment;不能指向团期共享 staff;建议前端按字符串传。 | + +空请求体示例: + +```json +{} +``` + +切换主报账人示例: + +```json +{ + "reporterAssignmentId": "2072930844657283074" +} +``` + +## 5. 出参 + +### 5.1 通用响应包装 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `code` | Number | 业务状态码,`200` 表示成功。 | +| `message` | String | 业务提示,成功时通常为 `成功`。 | +| `data` | Object | 接口业务数据;失败时通常为 `null`。 | + +### 5.2 `GET /confirm-checklist` 的 `data` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `allPassed` | Boolean | 是否全部通过。唯一开关字段。 | +| `items` | Array / null | 5 项校验明细;仅 `allPassed=false` 时返回数组,全部通过时为 `null`。 | +| `preview` | Object / null | 确认弹窗预览数据;仅 `allPassed=true` 时返回对象,未通过时为 `null`。 | + +`items[]` 字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `code` | String | 检查项代码。 | +| `checkName` | String | 检查项中文名称,用于前端直接展示。 | +| `passed` | Boolean | 当前检查项是否通过。 | +| `failReason` | String / null | 未通过原因;通过时为 `null`。 | +| `actionPath` | String / null | 建议跳转路径;通过时为 `null`。 | + +`preview` 字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `departureDate` | String | 出发日期,格式 `yyyy-MM-dd`。 | +| `totalPeopleCount` | Number | 总出行人数。 | +| `driverName` | String / null | 司机姓名。 | +| `driverPhoneMasked` | String / null | 司机手机号脱敏值。 | +| `hotels` | Array | 酒店摘要列表,按城市/酒店去重。 | +| `staffs` | Array | 本单配置人员列表,主报账人优先展示。 | +| `contractAutoAction` | Object / null | 确认后合同自动处理预告。 | +| `insuranceAutoAction` | Object / null | 确认后保险自动处理预告。 | + +`preview.hotels[]` 字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `cityName` | String / null | 城市名称。 | +| `hotelName` | String / null | 酒店名称。 | + +`preview.staffs[]` 字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `assignmentId` | String | staff 分配记录 ID。 | +| `staffId` | String | 员工 ID。 | +| `staffName` | String | 员工姓名。 | +| `staffPhone` | String / null | 员工手机号脱敏值。 | +| `staffRole` | String | 员工角色代码。 | +| `staffRoleName` | String | 员工角色中文名称。 | +| `isPrimaryReporter` | Boolean | 是否主报账人。 | + +`preview.contractAutoAction` 字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `planName` | String / null | 确认后将使用的合同方案名称。 | +| `autoSign` | Boolean | 是否自动发送给客户线上签署。 | + +`preview.insuranceAutoAction` 字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `planName` | String / null | 确认后将使用的保险方案名称。 | +| `peopleCount` | Number | 预计投保人数,通常等于 `totalPeopleCount`。 | +| `effectiveDescription` | String / null | 生效时间描述。 | + +### 5.3 `POST /confirm-itinerary` 的 `data` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `success` | Boolean | 是否确认成功。 | +| `oldStatus` | String | 变更前订单主状态。 | +| `newStatus` | String | 变更后订单主状态。 | +| `oldFlowStatus` | String | 变更前订单流程状态。 | +| `newFlowStatus` | String | 变更后订单流程状态。 | +| `triggeredEvents` | Array | 确认成功后触发的异步事件标识。 | + +## 6. 枚举 / 数据字典 + +### 6.1 `items[].code` + +| 值 | 中文 | 说明 | +|----|------|------| +| `PAYMENT_OK` | 支付状态 | 订单需满足确认前支付要求。 | +| `TRAVELER_COMPLETE` | 出行人信息 | 出行人信息需完整。 | +| `HOTEL_DONE` | 配房状态 | 需要配房的订单必须已完成配房。 | +| `VEHICLE_DONE` | 配车状态 | 需要配车的订单必须已完成配车。 | +| `CONTRACT_TEMPLATE_OK` | 合同模板 | 产品需存在可用合同方案/模板。 | + +### 6.2 `preview.staffs[].staffRole` + +| 值 | 中文 | 说明 | +|----|------|------| +| `DRIVER` | 司机 | 车辆执行人员。 | +| `LEADER` | 领队 | 团队领队。 | +| `GUIDE` | 导游 | 导游人员。 | +| `PHOTOGRAPHER` | 摄影师 | 摄影人员。 | +| `OTHER` | 其他 | 其他配置人员。 | + +### 6.3 状态字段 + +| 字段 | 常见成功前 | 常见成功后 | 说明 | +|------|------------|------------|------| +| `oldStatus` / `newStatus` | `CUSTOMIZING` | `PENDING_DEPARTURE` | 订单主状态从定制中推进到待出行。 | +| `oldFlowStatus` / `newFlowStatus` | `PENDING_CONFIRM` | `PENDING_DEPARTURE` | 流程状态从待确认推进到待出行。 | + +### 6.4 `triggeredEvents` + +| 值 | 中文 | 说明 | +|----|------|------| +| `ASYNC_CONTRACT_GENERATE` | 异步生成合同 | 确认成功后可能触发合同生成/发送。 | +| `ASYNC_INSURANCE_ISSUE` | 异步投保 | 确认成功后可能触发保险投保。 | + +## 7. 错误码 + +| code | 含义 | 触发场景 | 前端建议 | +|------|------|----------|----------| +| `401` | 未登录或登录态无效 | 未携带有效 `Authorization`。 | 跳登录或刷新登录态。 | +| `581007` | 订单不存在 | `orderId` 不存在或已删除。 | 提示订单不存在并返回列表。 | +| `581016` | 当前订单状态不允许该操作 | 订单不处于可确认状态,或重复确认。 | 刷新详情和按钮状态。 | +| `581036` | 确认订单前置校验未通过 | 直接提交确认,但 checklist 未全部通过。 | 重新调用 `confirm-checklist` 展示失败项。 | +| `581046` | `reporterAssignmentId` 格式非法 | 主报账人 assignmentId 不是有效数字 ID。 | 清空选择或提示重新选择报账人。 | +| `582102` | 员工分配记录不存在 | `reporterAssignmentId` 不存在或不属于当前订单。 | 重新拉取 `preview.staffs` 后再选。 | +| `582109` | 团期共享 staff 不可在订单侧增删改 | `reporterAssignmentId` 指向团期共享 staff。 | 禁止选择该人员作为订单侧改动目标。 | + +## 8. 示例 + +### 8.1 典型成功:先预览,再确认 + +请求: + +```http +GET /v3/admin/order/2076236236812345346/confirm-checklist +Authorization: Bearer +``` + +响应: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "allPassed": true, + "items": null, + "preview": { + "departureDate": "2026-07-19", + "totalPeopleCount": 3, + "driverName": "测试司机", + "driverPhoneMasked": "1390000****", + "hotels": [ + { + "cityName": "拉萨", + "hotelName": "测试酒店" + } + ], + "staffs": [ + { + "assignmentId": "2076236240000000001", + "staffId": "2076236240000000002", + "staffName": "张三", + "staffPhone": "1380000****", + "staffRole": "DRIVER", + "staffRoleName": "司机", + "isPrimaryReporter": true + } + ], + "contractAutoAction": { + "planName": "标准国内电子签约方案", + "autoSign": true + }, + "insuranceAutoAction": { + "planName": "测试境内意外险方案", + "peopleCount": 3, + "effectiveDescription": "出发前 24h 内生效" + } + } + } +} +``` + +提交确认: + +```http +POST /v3/admin/order/2076236236812345346/confirm-itinerary +Authorization: Bearer +Content-Type: application/json +``` + +```json +{} +``` + +响应: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "success": true, + "oldStatus": "CUSTOMIZING", + "newStatus": "PENDING_DEPARTURE", + "oldFlowStatus": "PENDING_CONFIRM", + "newFlowStatus": "PENDING_DEPARTURE", + "triggeredEvents": [ + "ASYNC_CONTRACT_GENERATE", + "ASYNC_INSURANCE_ISSUE" + ] + } +} +``` + +### 8.2 边界成功:确认时切换主报账人 + +请求: + +```http +POST /v3/admin/order/2072930323661811714/confirm-itinerary +Authorization: Bearer +Content-Type: application/json +``` + +```json +{ + "reporterAssignmentId": "2072930358709415938" +} +``` + +响应: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "success": true, + "oldStatus": "CUSTOMIZING", + "newStatus": "PENDING_DEPARTURE", + "oldFlowStatus": "PENDING_CONFIRM", + "newFlowStatus": "PENDING_DEPARTURE", + "triggeredEvents": [ + "ASYNC_CONTRACT_GENERATE", + "ASYNC_INSURANCE_ISSUE" + ] + } +} +``` + +### 8.3 异常:预览未通过 + +请求: + +```http +GET /v3/admin/order/2072930000000000000/confirm-checklist +Authorization: Bearer +``` + +响应: + +```json +{ + "code": 200, + "message": "成功", + "data": { + "allPassed": false, + "items": [ + { + "code": "TRAVELER_COMPLETE", + "checkName": "出行人信息", + "passed": false, + "failReason": "出行人信息未完善", + "actionPath": "/admin/order/orders/2072930000000000000?tab=travelers" + }, + { + "code": "HOTEL_DONE", + "checkName": "配房状态", + "passed": false, + "failReason": "配房未完成", + "actionPath": "/admin/order/orders/2072930000000000000?tab=hotel" + } + ], + "preview": null + } +} +``` + +如果此时仍直接提交确认: + +```json +{ + "code": 581036, + "message": "确认订单前置校验未通过,请先补全所有必填项", + "data": null +} +``` + +## 9. 业务边界 + +1. 前端确认按钮流程固定为:先调 `GET /confirm-checklist`,`allPassed=true` 后再调 `POST /confirm-itinerary`。 +2. `allPassed=true` 和 `allPassed=false` 的结构互斥,前端不要同时依赖 `items` 和 `preview`。 +3. `preview.contractAutoAction` 和 `preview.insuranceAutoAction` 是“确认后将要执行的方案预告”,不是确认后的最终合同/保单详情。 +4. 确认成功后可能异步生成合同和保险,前端需要以订单详情、合同保险 tab 的回读为最终状态展示依据。 +5. 重复提交确认不是幂等成功,会返回状态不允许类错误;前端提交后应禁用按钮或防重复点击。 +6. 房务角色不可访问订单确认接口;前端应按菜单/角色控制入口展示。 + +## 10. 修改前后对比 + +| 项 | 修改前 | 修改后 | +|----|--------|--------| +| 确认提交接口 | 前端可能误接 `POST /v3/admin/order/{orderId}/transition` 并传 `eventCode=CONFIRM` | 使用专用 `POST /v3/admin/order/{orderId}/confirm-itinerary` | +| 预览成功结构 | `items` 可能仍被前端当作数组处理 | `allPassed=true` 时 `items=null`、`preview` 有值 | +| 预览失败结构 | 前端可能仍渲染空预览 | `allPassed=false` 时 `items` 有值、`preview=null` | +| 失败项展示 | 缺少稳定中文展示字段 | `items[].checkName` 可直接展示 | +| 酒店预览 | 可能为空或不可靠 | `preview.hotels[]` 返回真实配房酒店摘要 | +| 人员预览 | 缺少弹窗人员列表 | `preview.staffs[]` 返回本单配置人员,可用于主报账人选择 | +| 通知预览 | 曾短暂存在 `notifications` | `notifications` 已删除,前端不要读取 | +| 合同/保险预告 | 容易理解成已生成记录回读 | 明确是确认后将使用的 ACTIVE 方案预告 | + +## 11. 影响评估 / 回滚 + +| 维度 | 影响 | +|------|------| +| 前端调用 | 订单详情确认按钮需要接入 `confirm-checklist` + `confirm-itinerary` 两步流程。 | +| 旧接口兼容 | 不建议前端继续使用通用 `/transition` 完成确认动作。 | +| 展示兼容 | 前端要处理 `items=null` 和 `preview=null` 两种互斥结构。 | +| 回滚方式 | 如前端未接入,可先隐藏确认按钮或保留旧入口;但不要同时调用新旧确认提交接口。 | + +## 12. 注意事项 + +1. `orderId`、`assignmentId`、`staffId` 均建议按字符串处理。 +2. 确认提交成功会真实改变订单状态,并触发合同/保险异步动作,不要把 `POST /confirm-itinerary` 用作普通预检。 +3. 2026-07-13 测试服实测订单 `2076236236812345346`: + - `GET /confirm-checklist` 返回 `code=200`、`allPassed=true`、`preview` 有值。 + - `POST /confirm-itinerary` 返回 `code=200`、`success=true`。 + - 回读订单详情为 `PENDING_DEPARTURE / PENDING_DEPARTURE`,合同状态 `SIGNED`,保险状态 `INSURED`。 + - 状态日志包含 `CONFIRM` 事件。 + +## 13. 关联 / 联系人 + +- Issue #4838: https://git.1814.love:8443/wx/HL/issues/4838 +- Issue #4844: https://git.1814.love:8443/wx/HL/issues/4844 +- Issue #4853: https://git.1814.love:8443/wx/HL/issues/4853 +- Issue #4867: https://git.1814.love:8443/wx/HL/issues/4867 +- PR #4839: https://git.1814.love:8443/wx/HL/pulls/4839 +- PR #4846: https://git.1814.love:8443/wx/HL/pulls/4846 +- PR #4860: https://git.1814.love:8443/wx/HL/pulls/4860 +- PR #4868: https://git.1814.love:8443/wx/HL/pulls/4868 +- Commit: https://git.1814.love:8443/wx/HL/commit/b8abce7e2 +- Commit: https://git.1814.love:8443/wx/HL/commit/711850177 +- Commit: https://git.1814.love:8443/wx/HL/commit/89cbf9cdf +- Commit: https://git.1814.love:8443/wx/HL/commit/c772d83e7 +- 负责人: 腰苏图