一些检查失败了
changelog-filename-gate / validate (push) Failing after 2s
修改原因:hl-ui changelog loop 需要把前端消费进度回写到契约源,避免领取、实现状态与实际交付脱节。 修改内容:将 frontend_status 与已有 legacy frontend 同步为 implemented,记录负责人 hl-ui-codex,并关联 mmg/hl-ui@98c6f66843395b3b2d4bd1cd23cdc286e20b0bf9;发布和验收字段保持不变。 实际验证:回写器已校验目标文件、状态单调性、提交范围和 Front Matter 内容,提交只包含当前 changelog。 Frontend-Status: tools/mcp-api-sync/.changelog-repo/changelogs-v2/2026-07/24_5203_出行人手机号订单级门禁-修改接口-管理后台.md
1029 行
35 KiB
Markdown
1029 行
35 KiB
Markdown
---
|
||
schema: "hl-changelog/v2"
|
||
ticket: "5203"
|
||
title: "出行人手机号改为订单级门禁"
|
||
consumer: "admin"
|
||
change_type: "修改接口"
|
||
backend_status: "deployed"
|
||
gateway_status: "verified"
|
||
frontend_status: "implemented"
|
||
frontend_owner: "hl-ui-codex"
|
||
frontend_ref: "mmg/hl-ui@98c6f66843395b3b2d4bd1cd23cdc286e20b0bf9"
|
||
target_release: ""
|
||
verified_at: ""
|
||
status_note: "后端新语义已发布;前端需按逐人五项资料和订单级手机号门禁消费"
|
||
updated_at: "2026-07-24T09:57:22.169Z"
|
||
base: "dev-v3"
|
||
---
|
||
|
||
# 🔧【修改接口·管理后台】出行人手机号改为订单级门禁(#5203)
|
||
|
||
> **PR**: [#5210](https://git.1814.love:8443/wx/HL/pulls/5210)
|
||
> **服务**: `hl-order-service-v3`
|
||
> **更新时间**: 2026-07-24
|
||
> **影响范围**: 订单详情、出行人维护/校验、确认订单前置检查与确认提交
|
||
|
||
## 1. 接口背景
|
||
|
||
出行人逐人资料状态与订单签约门禁原来使用了冲突口径:成人没有手机号时,逐人的
|
||
`profileStatus` 会是 `PENDING`;但确认订单只需要整单至少一名出行人提供手机号。
|
||
|
||
本次统一为两层规则:
|
||
|
||
1. **逐人资料完整度**只检查 `name`、`gender`、`birthday`、`idType`、`idNo` 五项。
|
||
2. **手机号是订单级门禁**:整单至少一名出行人填写手机号,才允许校验通过、自动推进和确认行程。
|
||
|
||
手机号字段本身没有删除,所有请求和响应结构保持不变;变化的是
|
||
`profileStatus`、完成数统计、`missingFields` 和确认门禁的字段语义。
|
||
|
||
## 变更接口
|
||
|
||
| # | 接口 | 方法 | 路径 | 变更类型 | 本次变化 |
|
||
|---|---|---|---|---|---|
|
||
| 1 | 订单详情 | GET | <code>/v3/admin/order/{id}</code> | 出参语义修改 | `overview.customerInfo.travelers[].profileStatus` 改用逐人五项资料判断 |
|
||
| 2 | 出行人列表 | GET | <code>/v3/admin/order/{id}/traveler/list</code> | 出参语义修改 | `profileStatus` 改用逐人五项资料判断 |
|
||
| 3 | 批量编辑出行人 | POST | <code>/v3/admin/order/{id}/traveler/batch-edit</code> | 出参语义修改 | 完成数与 `allCompleted` 不再逐人要求手机号;自动推进仍要求整单至少一部手机号 |
|
||
| 4 | 单个新增出行人 | POST | <code>/v3/admin/order/{id}/traveler/add</code> | 出参语义修改 | 无手机号但五项资料齐全时返回 `COMPLETED` |
|
||
| 5 | 补全出行信息 | PUT | <code>/v3/admin/order/{id}/traveler-info</code> | 出参语义修改 | 完成数与 `allCompleted` 改用逐人五项资料判断;自动推进仍保留订单级手机号门禁 |
|
||
| 6 | 智能批量解析 | POST | <code>/v3/admin/order/{id}/traveler/smart-parse</code> | 出参语义修改 | 预览和入库结果的 `profileStatus` 改用逐人五项资料判断 |
|
||
| 7 | 出行人信息校验 | GET | <code>/v3/admin/order/{id}/traveler/validate</code> | 出参枚举语义修改 | `missingFields` 删除 `phone`,增加 `gender`、`birthday`;无任何手机号改由 `blockReasons` 表达 |
|
||
| 8 | 确认订单前置检查 | GET | <code>/v3/admin/order/{id}/confirm-checklist</code> | 出参语义修改 | `TRAVELER_COMPLETE` 先检查逐人五项,再检查整单至少一部手机号 |
|
||
| 9 | 确认行程 | POST | <code>/v3/admin/order/{id}/confirm-itinerary</code> | 行为修改 | 每人无须都有手机号;整单完全无手机号仍返回 `581036` |
|
||
|
||
## 3. 接口详情
|
||
|
||
所有接口均使用管理后台 JWT。除批量编辑、单个新增、补全出行信息外,接口没有额外幂等窗口。
|
||
公共响应包装为:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `code` | Integer | `200` 表示成功;业务失败返回业务错误码 |
|
||
| `message` | String | 响应说明 |
|
||
| `data` | 见各接口 | 业务数据;失败时通常为 `null` |
|
||
| `success` | Boolean | 是否成功 |
|
||
|
||
### 3.1 订单详情
|
||
|
||
- **方法/路径**: GET <code>/v3/admin/order/{id}</code>
|
||
- **使用场景**: 打开订单详情,读取主单、标签和概览中的出行人。
|
||
- **幂等性**: 是,只读。
|
||
- **路径参数**:
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `id` | Long | 是 | 订单 ID |
|
||
|
||
- **响应**: `Result<OrderDetailRespVO>`
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `data.main` | OrderMainVO | 订单主单、进度和各 Tab 状态;本次字段结构未变 |
|
||
| `data.tags[]` | TagVO[] | 标签列表;每项含 `name`、`color`、`creator` |
|
||
| `data.overview.customerInfo` | CustomerInfoVO | 客户信息 |
|
||
| `data.overview.customerInfo.contactName` | String/null | 联系人 |
|
||
| `data.overview.customerInfo.contactPhone` | String/null | 联系电话 |
|
||
| `data.overview.customerInfo.agencyName` | String/null | 商户名 |
|
||
| `data.overview.customerInfo.consultantName` | String/null | 定制师姓名 |
|
||
| `data.overview.customerInfo.peopleSummary` | String/null | 人数摘要 |
|
||
| `data.overview.customerInfo.adultCount` | Integer | 成人数 |
|
||
| `data.overview.customerInfo.childCount` | Integer | 儿童数 |
|
||
| `data.overview.customerInfo.youngChildCount` | Integer | 小童数 |
|
||
| `data.overview.customerInfo.babyCount` | Integer | 幼童数 |
|
||
| `data.overview.customerInfo.createTime` | String/null | 创建时间,格式 `yyyy-MM-ddTHH:mm:ss` |
|
||
| `data.overview.customerInfo.emergencyContactName` | String/null | 订单紧急联系人姓名 |
|
||
| `data.overview.customerInfo.emergencyContactPhone` | String/null | 订单紧急联系人电话 |
|
||
| `data.overview.customerInfo.travelers[]` | TravelerPlainVO[] | 出行人列表;字段见下表 |
|
||
| `data.overview.remarkInfo.customerRemark` | String/null | 客户备注 |
|
||
| `data.overview.remarkInfo.consultantRemark` | String/null | 定制师备注 |
|
||
| `data.overview.remarkInfo.hotelRemark` | String/null | 用房备注 |
|
||
| `data.overview.remarkInfo.vehicleRemark` | String/null | 用车备注 |
|
||
| `data.unreadMessageCount` | Integer | 联系房务未读数;无会话或降级时为 `0` |
|
||
|
||
`TravelerPlainVO` 完整字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `id` | Long | 出行人 ID |
|
||
| `orderId` | Long | 订单 ID |
|
||
| `travelerType` | String | 出行人类型 |
|
||
| `travelerTypeName` | String/null | 出行人类型中文名 |
|
||
| `name` | String/null | 姓名 |
|
||
| `gender` | String/null | 性别 |
|
||
| `birthday` | String/null | 出生日期,`yyyy-MM-dd` |
|
||
| `idType` | String/null | 证件类型 |
|
||
| `idTypeName` | String/null | 证件类型中文名 |
|
||
| `idCard` | String/null | 证件号 |
|
||
| `nationality` | String/null | 国籍 |
|
||
| `race` | String/null | 民族 |
|
||
| `phone` | String/null | 出行人手机号 |
|
||
| `emergencyContact` | String/null | 紧急联系人姓名 |
|
||
| `emergencyPhone` | String/null | 紧急联系人电话 |
|
||
| `roomGroupNo` | Integer/null | 同住分组号 |
|
||
| `profileStatus` | String | `PENDING` / `COMPLETED`;本次语义见第 6 节 |
|
||
| `transportPlanIds[]` | Long[] | 关联大交通批次 ID |
|
||
|
||
- **错误码**: `581007` 订单不存在;`581045` 房务角色无权查看订单详情。
|
||
- **业务边界**: 出行人没有手机号但五项资料齐全时,`profileStatus=COMPLETED`。
|
||
- **典型示例**:
|
||
|
||
**请求**
|
||
|
||
```http
|
||
GET /v3/admin/order/2079576729147338754
|
||
Authorization: Bearer <admin-token>
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"main": {
|
||
"id": "2079576729147338754",
|
||
"orderNo": "HL202607240001",
|
||
"orderStatus": "CUSTOMIZING",
|
||
"flowStatus": "AWAITING_CONFIRM"
|
||
},
|
||
"tags": [],
|
||
"overview": {
|
||
"customerInfo": {
|
||
"contactName": "张三",
|
||
"adultCount": 2,
|
||
"childCount": 0,
|
||
"youngChildCount": 0,
|
||
"babyCount": 0,
|
||
"travelers": [
|
||
{
|
||
"id": "2079576729147338801",
|
||
"name": "张三",
|
||
"gender": "1",
|
||
"birthday": "1990-01-01",
|
||
"idType": "ID_CARD",
|
||
"idCard": "110101199001011234",
|
||
"phone": "13800138000",
|
||
"profileStatus": "COMPLETED",
|
||
"transportPlanIds": []
|
||
},
|
||
{
|
||
"id": "2079576729147338802",
|
||
"name": "李四",
|
||
"gender": "2",
|
||
"birthday": "1992-02-02",
|
||
"idType": "ID_CARD",
|
||
"idCard": "110101199202021235",
|
||
"phone": null,
|
||
"profileStatus": "COMPLETED",
|
||
"transportPlanIds": []
|
||
}
|
||
]
|
||
},
|
||
"remarkInfo": {
|
||
"customerRemark": null,
|
||
"consultantRemark": null,
|
||
"hotelRemark": null,
|
||
"vehicleRemark": null
|
||
}
|
||
},
|
||
"unreadMessageCount": 0
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
### 3.2 出行人列表
|
||
|
||
- **方法/路径**: GET <code>/v3/admin/order/{id}/traveler/list</code>
|
||
- **使用场景**: 加载出行人 Tab。
|
||
- **幂等性**: 是,只读。
|
||
- **路径参数**: `id`,Long,必填,订单 ID。
|
||
- **响应**: `Result<List<TravelerVO>>`
|
||
|
||
`TravelerVO` 完整字段:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `id`、`orderId` | Long | 出行人 ID、订单 ID |
|
||
| `travelerType`、`travelerTypeName` | String/null | 出行人类型及中文名 |
|
||
| `name` | String/null | 姓名 |
|
||
| `gender` | String/null | 性别 |
|
||
| `birthday` | String/null | 出生日期 |
|
||
| `idType`、`idTypeName` | String/null | 证件类型及中文名 |
|
||
| `idCardMasked` | String/null | 脱敏证件号 |
|
||
| `idProvinceCode`、`idProvinceName` | String/null | 大陆身份证省级代码及名称 |
|
||
| `nationality`、`race` | String/null | 国籍、民族 |
|
||
| `phoneMasked` | String/null | 脱敏手机号 |
|
||
| `emergencyContact` | String/null | 紧急联系人姓名 |
|
||
| `emergencyPhoneMasked` | String/null | 脱敏紧急联系电话 |
|
||
| `roomGroupNo` | Integer/null | 同住分组号 |
|
||
| `profileStatus` | String | `PENDING` / `COMPLETED` |
|
||
| `transportPlanIds[]` | Long[] | 关联大交通批次 ID |
|
||
|
||
- **错误码**: `581102` 订单不存在。
|
||
- **业务边界**: 空列表返回 `data=[]`;手机号为空不再单独令 `profileStatus` 变成 `PENDING`。
|
||
- **典型示例**:
|
||
|
||
**请求**
|
||
|
||
```http
|
||
GET /v3/admin/order/2079576729147338754/traveler/list
|
||
Authorization: Bearer <admin-token>
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": [
|
||
{
|
||
"id": "2079576729147338802",
|
||
"orderId": "2079576729147338754",
|
||
"travelerType": "ADULT",
|
||
"travelerTypeName": "成人",
|
||
"name": "李四",
|
||
"gender": "2",
|
||
"birthday": "1992-02-02",
|
||
"idType": "ID_CARD",
|
||
"idTypeName": "身份证",
|
||
"idCardMasked": "110***********1235",
|
||
"idProvinceCode": "11",
|
||
"idProvinceName": "北京市",
|
||
"nationality": "中国",
|
||
"race": "汉族",
|
||
"phoneMasked": null,
|
||
"emergencyContact": null,
|
||
"emergencyPhoneMasked": null,
|
||
"roomGroupNo": 1,
|
||
"profileStatus": "COMPLETED",
|
||
"transportPlanIds": []
|
||
}
|
||
],
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
### 3.3 批量编辑出行人
|
||
|
||
- **方法/路径**: POST <code>/v3/admin/order/{id}/traveler/batch-edit</code>
|
||
- **使用场景**: 一次新增或更新最多 30 名出行人。
|
||
- **幂等性**: 同一订单 3 秒内重复提交会被拒绝。
|
||
- **路径参数**: `id`,Long,必填,订单 ID。
|
||
- **请求体**: `TravelerBatchEditReqVO`
|
||
|
||
| 字段 | 类型 | 必填 | 约束 |
|
||
|---|---|---|---|
|
||
| `travelers` | TravelerEditItem[] | 是 | 1~30 项 |
|
||
| `travelers[].id` | Long/null | 否 | `null` 新增;非空更新且必须属于本订单 |
|
||
| `travelers[].name` | String/null | 条件必填 | 新增项 `name/idType/idNo` 至少一项非空 |
|
||
| `travelers[].gender` | String/null | 否 | `0` / `1` / `2` |
|
||
| `travelers[].birthday` | String | 是 | 不得晚于今天;用于派生人群类型 |
|
||
| `travelers[].idType` | String/null | 条件必填 | 取值见第 6 节 |
|
||
| `travelers[].idNo` | String/null | 条件必填 | 与证件类型匹配 |
|
||
| `travelers[].nationality` | String/null | 否 | 不可传空字符串 |
|
||
| `travelers[].race` | String/null | 否 | 不可传空字符串 |
|
||
| `travelers[].phone` | String/null | 否 | 有值时必须为 11 位数字 |
|
||
| `travelers[].emergencyContact` | String/null | 否 | 出行人级紧急联系人 |
|
||
| `travelers[].emergencyPhone` | String/null | 否 | 有值时必须为 11 位数字 |
|
||
| `travelers[].roomGroupNo` | Integer/null | 否 | 不得超过订单家庭数上限 |
|
||
|
||
- **响应**: `Result<TravelerBatchEditRespVO>`
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `data.createdCount` | Integer | 本次新增数 |
|
||
| `data.updatedCount` | Integer | 本次更新数 |
|
||
| `data.completedCount` | Integer | 操作后五项资料完整的出行人数 |
|
||
| `data.pendingCount` | Integer | 操作后五项资料不完整的出行人数 |
|
||
| `data.allCompleted` | Boolean | 所有出行人的五项资料是否完整;不表示整单手机号门禁已通过 |
|
||
|
||
- **错误码**: `581101`、`581102`、`581103`、`581104`、`581105`、`581110`、
|
||
`581111`、`581112`、`581113`、`581114`、`581118`、`581119`、`581146`、
|
||
`581147`、`581148`、`581149`。
|
||
- **业务边界**: 两人五项资料齐全且仅一人有手机号时,
|
||
`completedCount=2`、`pendingCount=0`、`allCompleted=true`,同时满足订单级手机号门禁。
|
||
- **典型示例**:
|
||
|
||
**请求**
|
||
|
||
```http
|
||
POST /v3/admin/order/2079576729147338754/traveler/batch-edit
|
||
Authorization: Bearer <admin-token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"travelers": [
|
||
{
|
||
"id": "2079576729147338801",
|
||
"name": "张三",
|
||
"gender": "1",
|
||
"birthday": "1990-01-01",
|
||
"idType": "ID_CARD",
|
||
"idNo": "110101199001011234",
|
||
"nationality": "中国",
|
||
"race": "汉族",
|
||
"phone": "13800138000",
|
||
"roomGroupNo": 1
|
||
},
|
||
{
|
||
"id": "2079576729147338802",
|
||
"name": "李四",
|
||
"gender": "2",
|
||
"birthday": "1992-02-02",
|
||
"idType": "ID_CARD",
|
||
"idNo": "110101199202021235",
|
||
"nationality": "中国",
|
||
"race": "汉族",
|
||
"phone": null,
|
||
"roomGroupNo": 1
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"createdCount": 0,
|
||
"updatedCount": 2,
|
||
"completedCount": 2,
|
||
"pendingCount": 0,
|
||
"allCompleted": true
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
### 3.4 单个新增出行人
|
||
|
||
- **方法/路径**: POST <code>/v3/admin/order/{id}/traveler/add</code>
|
||
- **使用场景**: 在订单声明人数尚未补齐时新增一名出行人。
|
||
- **幂等性**: 同一订单 3 秒内重复提交会被拒绝。
|
||
- **路径参数**: `id`,Long,必填,订单 ID。
|
||
- **请求体**:
|
||
|
||
| 字段 | 类型 | 必填 | 约束 |
|
||
|---|---|---|---|
|
||
| `name` | String/null | 否 | 姓名 |
|
||
| `gender` | String/null | 否 | `0` / `1` / `2` |
|
||
| `birthday` | String | 是 | 不得晚于今天 |
|
||
| `idType` | String/null | 否 | 证件类型 |
|
||
| `idNo` | String/null | 否 | 证件号 |
|
||
| `nationality`、`race` | String/null | 否 | 默认中国、汉族 |
|
||
| `phone` | String/null | 否 | 有值时 11 位数字 |
|
||
| `emergencyContact`、`emergencyPhone` | String/null | 否 | 出行人级紧急联系人 |
|
||
| `roomGroupNo` | Integer/null | 否 | 同住分组号 |
|
||
|
||
- **响应**: `Result<TravelerVO>`,完整字段同 3.2。
|
||
- **错误码**: `581102`、`581103`、`581104`、`581112`、`581113`、`581114`、
|
||
`581115`、`581116`、`581119`、`581146`、`581147`、`581148`、`581149`。
|
||
- **业务边界**: 五项资料齐全、`phone=null` 时,新行可返回 `profileStatus=COMPLETED`;
|
||
若订单其他出行人也都没有手机号,整单仍不能通过订单级门禁。
|
||
- **典型示例**:
|
||
|
||
**请求**
|
||
|
||
```http
|
||
POST /v3/admin/order/2079576729147338754/traveler/add
|
||
Authorization: Bearer <admin-token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"name": "李四",
|
||
"gender": "2",
|
||
"birthday": "1992-02-02",
|
||
"idType": "ID_CARD",
|
||
"idNo": "110101199202021235",
|
||
"nationality": "中国",
|
||
"race": "汉族",
|
||
"phone": null,
|
||
"roomGroupNo": 1
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"id": "2079576729147338802",
|
||
"orderId": "2079576729147338754",
|
||
"travelerType": "ADULT",
|
||
"name": "李四",
|
||
"gender": "2",
|
||
"birthday": "1992-02-02",
|
||
"idType": "ID_CARD",
|
||
"idCardMasked": "110***********1235",
|
||
"phoneMasked": null,
|
||
"profileStatus": "COMPLETED",
|
||
"transportPlanIds": []
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
### 3.5 补全出行信息
|
||
|
||
- **方法/路径**: PUT <code>/v3/admin/order/{id}/traveler-info</code>
|
||
- **使用场景**: 全量同步出行人,并保存订单级紧急联系人和客户备注。
|
||
- **幂等性**: 同一订单 3 秒内重复提交会被拒绝。
|
||
- **路径参数**: `id`,Long,必填,订单 ID。
|
||
- **请求体**:
|
||
|
||
| 字段 | 类型 | 必填 | 约束 |
|
||
|---|---|---|---|
|
||
| `travelers` | TravelerEditItem[] | 是 | 全量列表,1~30 项;单项字段同 3.3 |
|
||
| `emergencyContactName` | String | 是 | 订单级紧急联系人姓名 |
|
||
| `emergencyContactPhone` | String | 是 | 11 位数字 |
|
||
| `customerRemark` | String/null | 否 | 客户备注 |
|
||
|
||
- **响应**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `data.createdCount` | Integer | 新增数 |
|
||
| `data.updatedCount` | Integer | 更新数 |
|
||
| `data.deletedCount` | Integer | 原有但未出现在本次全量列表中的删除数 |
|
||
| `data.completedCount` | Integer | 五项资料完整人数 |
|
||
| `data.pendingCount` | Integer | 五项资料不完整人数 |
|
||
| `data.allCompleted` | Boolean | 所有人五项资料是否完整;不等同于手机号门禁通过 |
|
||
|
||
- **错误码**: `581102`、`581103`、`581104`、`581109`、`581110`、`581111`、
|
||
`581112`、`581113`、`581114`、`581118`、`581119`、`581146`、`581147`、
|
||
`581148`、`581149`。
|
||
- **业务边界**: `travelers` 为全量数据;五项资料全部完整但整单没有任何出行人手机号时,
|
||
`allCompleted=true`,但流程不会自动越过订单级手机号门禁。
|
||
- **典型示例**:
|
||
|
||
**请求**
|
||
|
||
```http
|
||
PUT /v3/admin/order/2079576729147338754/traveler-info
|
||
Authorization: Bearer <admin-token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"travelers": [
|
||
{
|
||
"id": "2079576729147338801",
|
||
"name": "张三",
|
||
"gender": "1",
|
||
"birthday": "1990-01-01",
|
||
"idType": "ID_CARD",
|
||
"idNo": "110101199001011234",
|
||
"phone": "13800138000"
|
||
},
|
||
{
|
||
"id": "2079576729147338802",
|
||
"name": "李四",
|
||
"gender": "2",
|
||
"birthday": "1992-02-02",
|
||
"idType": "ID_CARD",
|
||
"idNo": "110101199202021235",
|
||
"phone": null
|
||
}
|
||
],
|
||
"emergencyContactName": "王五",
|
||
"emergencyContactPhone": "13900139000",
|
||
"customerRemark": null
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"createdCount": 0,
|
||
"updatedCount": 2,
|
||
"deletedCount": 0,
|
||
"completedCount": 2,
|
||
"pendingCount": 0,
|
||
"allCompleted": true
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
### 3.6 智能批量解析
|
||
|
||
- **方法/路径**: POST <code>/v3/admin/order/{id}/traveler/smart-parse</code>
|
||
- **使用场景**: 将多行“姓名、身份证、手机号”文本解析为出行人,可预览或直接保存。
|
||
- **幂等性/限流**: 每名管理员每分钟最多 10 次;`dryRun=true` 只读。
|
||
- **请求体**:
|
||
|
||
| 字段 | 类型 | 必填 | 约束 |
|
||
|---|---|---|---|
|
||
| `rawText` | String | 是 | 非空,最多 20000 字符 |
|
||
| `dryRun` | Boolean | 否 | 默认 `false`;`true` 仅预览 |
|
||
|
||
- **响应**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `data.successCount` | Integer | 成功解析行数 |
|
||
| `data.failCount` | Integer | 失败行数 |
|
||
| `data.successList[]` | TravelerVO[] | 成功结果,字段同 3.2;预览时 `id` 可为 `null` |
|
||
| `data.failures[].lineIndex` | Integer | 原文有效行序号,从 1 起 |
|
||
| `data.failures[].maskedSnippet` | String | 脱敏原文片段 |
|
||
| `data.failures[].reason` | String | 中文失败原因 |
|
||
|
||
- **错误码**: `100501` 调用过于频繁;`581102` 订单不存在;`581131` 原文超长;
|
||
`581132` 原文为空或全行无法解析;`581134` 当前订单状态不允许批量导入。
|
||
- **业务边界**: 成功行五项资料齐全时,即使没有解析到手机号也返回
|
||
`profileStatus=COMPLETED`;整单手机号门禁仍在校验和确认阶段单独判断。
|
||
- **典型示例**:
|
||
|
||
**请求**
|
||
|
||
```http
|
||
POST /v3/admin/order/2079576729147338754/traveler/smart-parse
|
||
Authorization: Bearer <admin-token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"rawText": "李四 110101199202021235",
|
||
"dryRun": true
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"successCount": 1,
|
||
"failCount": 0,
|
||
"successList": [
|
||
{
|
||
"id": null,
|
||
"name": "李四",
|
||
"gender": "2",
|
||
"birthday": "1992-02-02",
|
||
"idType": "ID_CARD",
|
||
"phoneMasked": null,
|
||
"profileStatus": "COMPLETED"
|
||
}
|
||
],
|
||
"failures": []
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
### 3.7 出行人信息校验
|
||
|
||
- **方法/路径**: GET <code>/v3/admin/order/{id}/traveler/validate</code>
|
||
- **使用场景**: 保存后检查人数、逐人五项资料和订单级手机号门禁。
|
||
- **幂等性**: 是,只读。
|
||
- **路径参数**: `id`,Long,必填,订单 ID。
|
||
- **响应**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `data.passed` | Boolean | 人数一致、逐人五项完整且 `blockReasons` 为空时为 `true` |
|
||
| `data.declaredCount` | Integer | 订单声明总人数 |
|
||
| `data.actualCount` | Integer | 当前实际出行人数 |
|
||
| `data.countMismatch` | Boolean | 声明人数与实际人数是否不一致 |
|
||
| `data.incompleteList[]` | IncompleteTravelerVO[] | 五项资料不完整的出行人 |
|
||
| `data.incompleteList[].travelerId` | Long | 出行人 ID |
|
||
| `data.incompleteList[].name` | String/null | 脱敏姓名 |
|
||
| `data.incompleteList[].missingFields[]` | String[] | 只可能是 `name/gender/birthday/idType/idNo` |
|
||
| `data.blockReasons[]` | String[] | 订单级阻断原因;整单无手机号时包含固定文案 |
|
||
|
||
- **错误码**: `581102` 订单不存在。
|
||
- **业务边界**:
|
||
- 两人五项资料齐全,仅一人有手机号:`passed=true`。
|
||
- 每人五项资料齐全,但所有人都无手机号:`incompleteList=[]`,
|
||
`blockReasons` 包含“至少需要一名出行人填写手机号用于合同签署”,`passed=false`。
|
||
- **典型示例**:
|
||
|
||
**请求**
|
||
|
||
```http
|
||
GET /v3/admin/order/2079576729147338754/traveler/validate
|
||
Authorization: Bearer <admin-token>
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"passed": true,
|
||
"declaredCount": 2,
|
||
"actualCount": 2,
|
||
"countMismatch": false,
|
||
"incompleteList": [],
|
||
"blockReasons": []
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
### 3.8 确认订单前置检查
|
||
|
||
- **方法/路径**: GET <code>/v3/admin/order/{id}/confirm-checklist</code>
|
||
- **使用场景**: 打开确认订单弹框前检查五项门禁。
|
||
- **幂等性**: 是,只读。
|
||
- **路径参数**: `id`,Long,必填,订单 ID。
|
||
- **响应**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `data.allPassed` | Boolean | 五项是否全部通过 |
|
||
| `data.items[]` | ChecklistItemVO[]/null | 未全部通过时返回;全部通过时为 `null` |
|
||
| `data.items[].code` | String | 检查项代码 |
|
||
| `data.items[].checkName` | String | 中文名称 |
|
||
| `data.items[].passed` | Boolean | 是否通过 |
|
||
| `data.items[].failReason` | String/null | 失败原因 |
|
||
| `data.preview` | PreviewVO/null | 全部通过时返回;未通过时为 `null` |
|
||
| `data.preview.departureDate` | String/null | 出发日期 |
|
||
| `data.preview.totalPeopleCount` | Integer | 总人数 |
|
||
| `data.preview.driverName` | String/null | 司机姓名 |
|
||
| `data.preview.driverPhoneMasked` | String/null | 脱敏司机手机号 |
|
||
| `data.preview.hotels[]` | HotelSummaryVO[] | 酒店摘要,含 `cityName`、`hotelName` |
|
||
| `data.preview.staffs[]` | StaffItemVO[] | 人员摘要 |
|
||
| `data.preview.staffs[].assignmentId` | String | 分配记录 ID |
|
||
| `data.preview.staffs[].staffId` | String | 员工 ID |
|
||
| `data.preview.staffs[].staffName` | String/null | 员工姓名 |
|
||
| `data.preview.staffs[].staffPhone` | String/null | 脱敏手机号 |
|
||
| `data.preview.staffs[].staffRole` | String | 人员角色 |
|
||
| `data.preview.staffs[].staffRoleName` | String/null | 角色中文名 |
|
||
| `data.preview.staffs[].isPrimaryReporter` | Boolean | 是否主报账人 |
|
||
| `data.preview.contractAutoAction` | Object/null | 合同动作,含 `planName`、`autoSign` |
|
||
| `data.preview.insuranceAutoAction` | Object/null | 保险动作,含 `planName`、`peopleCount`、`effectiveDescription` |
|
||
|
||
- **错误码**: `581007` 订单不存在;`581045` 房务角色无权查看。
|
||
- **业务边界**: `TRAVELER_COMPLETE` 通过必须同时满足:
|
||
所有出行人的五项资料均完整,且整单至少一人有手机号。
|
||
- **典型示例**:
|
||
|
||
**请求**
|
||
|
||
```http
|
||
GET /v3/admin/order/2079576729147338754/confirm-checklist
|
||
Authorization: Bearer <admin-token>
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"allPassed": false,
|
||
"items": [
|
||
{
|
||
"code": "PAYMENT_OK",
|
||
"checkName": "订单款项",
|
||
"passed": true,
|
||
"failReason": null
|
||
},
|
||
{
|
||
"code": "TRAVELER_COMPLETE",
|
||
"checkName": "出行人信息",
|
||
"passed": false,
|
||
"failReason": "至少需要一名出行人填写手机号用于合同签署"
|
||
}
|
||
],
|
||
"preview": null
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
### 3.9 确认行程
|
||
|
||
- **方法/路径**: POST <code>/v3/admin/order/{id}/confirm-itinerary</code>
|
||
- **使用场景**: 五项 checklist 全部通过后确认行程。
|
||
- **幂等性**: 状态机约束;成功后重复确认会因状态不匹配失败。
|
||
- **路径参数**: `id`,Long,必填,订单 ID。
|
||
- **请求体**: 可不传。
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `reporterAssignmentId` | Long/null | 否 | 新主报账人 assignmentId;不传沿用当前主报账人 |
|
||
|
||
- **响应**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `data.success` | Boolean | 是否成功 |
|
||
| `data.oldStatus` | String | 变更前粗状态 |
|
||
| `data.newStatus` | String | 变更后粗状态 |
|
||
| `data.oldFlowStatus` | String | 变更前细状态 |
|
||
| `data.newFlowStatus` | String | 变更后细状态 |
|
||
| `data.triggeredEvents[]` | String[] | 触发的后续事件代码 |
|
||
|
||
- **错误码**: `581007` 订单不存在;`581009` 状态不允许;`581036` 前置 checklist 未通过;
|
||
`581045` 房务角色无权查看;`581046` 主报账人 ID 格式非法。
|
||
- **业务边界**: 多名出行人不要求每人都有手机号;整单完全没有手机号时,
|
||
`TRAVELER_COMPLETE` 不通过,本接口返回 `581036`。
|
||
- **典型示例**:
|
||
|
||
**请求**
|
||
|
||
```http
|
||
POST /v3/admin/order/2079576729147338754/confirm-itinerary
|
||
Authorization: Bearer <admin-token>
|
||
Content-Type: application/json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"reporterAssignmentId": "2072930844657283074"
|
||
}
|
||
```
|
||
|
||
**响应**
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"success": true,
|
||
"oldStatus": "CUSTOMIZING",
|
||
"newStatus": "PENDING_DEPARTURE",
|
||
"oldFlowStatus": "AWAITING_CONFIRM",
|
||
"newFlowStatus": "PENDING_DEPARTURE",
|
||
"triggeredEvents": [
|
||
"ASYNC_CONTRACT_GENERATE",
|
||
"ASYNC_INSURANCE_ISSUE"
|
||
]
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
## 4. 接口入参汇总
|
||
|
||
本次没有新增、删除或改名请求字段。需要注意:
|
||
|
||
- `phone` 仍可在批量编辑、单个新增、补全出行信息和智能解析中提交。
|
||
- `phone` 允许单人为空,但整单至少一人必须填写。
|
||
- `gender`、`birthday` 原本已存在;本次只将二者纳入逐人资料完整度。
|
||
- 批量编辑和补全出行信息的 `allCompleted` 只代表逐人五项资料,不代表订单级手机号门禁。
|
||
|
||
## 5. 出参汇总
|
||
|
||
| 字段 | 改前 | 改后 |
|
||
|---|---|---|
|
||
| `profileStatus` | 成人通常还需手机号才为 `COMPLETED` | 五项资料齐全即为 `COMPLETED`,与手机号无关 |
|
||
| `completedCount` / `pendingCount` | 受逐人手机号影响 | 只按逐人五项资料统计 |
|
||
| `allCompleted` | 可能因某个成人没手机号为 `false` | 只表示所有人五项资料完整 |
|
||
| `incompleteList[].missingFields[]` | `name/idType/idNo/phone` | `name/gender/birthday/idType/idNo` |
|
||
| `blockReasons[]` | 手机号与逐人缺失字段存在语义重叠 | 整单无手机号时统一返回固定订单级阻断原因 |
|
||
| checklist `TRAVELER_COMPLETE` | 逐人状态可能要求成人各自有手机号 | 所有人五项完整,并且整单至少一人有手机号 |
|
||
|
||
## 6. 枚举 / 数据字典
|
||
|
||
### 6.1 `profileStatus`
|
||
|
||
| 值 | 中文 | 判定 |
|
||
|---|---|---|
|
||
| `PENDING` | 待完善 | `name/gender/birthday/idType/idNo` 任一为空 |
|
||
| `COMPLETED` | 已完善 | 上述五项全部非空;不检查该出行人的手机号 |
|
||
|
||
### 6.2 `missingFields`
|
||
|
||
| 值 | 中文 | 说明 |
|
||
|---|---|---|
|
||
| `name` | 姓名 | 姓名为空 |
|
||
| `gender` | 性别 | 性别为空;本次新增为缺失项 |
|
||
| `birthday` | 出生日期 | 出生日期为空;本次新增为缺失项 |
|
||
| `idType` | 证件类型 | 证件类型为空 |
|
||
| `idNo` | 证件号 | 证件号为空 |
|
||
|
||
`phone` 已从此列表删除,改由订单级 `blockReasons` 表达。
|
||
|
||
### 6.3 `gender`
|
||
|
||
| 值 | 中文 | 说明 |
|
||
|---|---|---|
|
||
| `0` | 未知 | 有值,满足逐人完整度 |
|
||
| `1` | 男 | 有值,满足逐人完整度 |
|
||
| `2` | 女 | 有值,满足逐人完整度 |
|
||
|
||
### 6.4 `travelerType`
|
||
|
||
| 值 | 中文 |
|
||
|---|---|
|
||
| `ADULT` | 成人 |
|
||
| `CHILD` | 儿童 |
|
||
| `YOUNG_CHILD` | 小童 |
|
||
| `BABY` | 幼童 |
|
||
|
||
### 6.5 `idType`
|
||
|
||
| 值 | 中文 |
|
||
|---|---|
|
||
| `ID_CARD` | 身份证 |
|
||
| `PASSPORT` | 护照 |
|
||
| `HK_MACAU_PASS` | 港澳通行证 |
|
||
| `TAIWAN_PASS` | 台湾通行证 |
|
||
| `HONGKONG_RESIDENT_PASS` | 回乡证 |
|
||
| `MILITARY_ID` | 军官证 |
|
||
| `OTHER` | 其他 |
|
||
|
||
### 6.6 checklist `code`
|
||
|
||
| 值 | 中文 |
|
||
|---|---|
|
||
| `PAYMENT_OK` | 订单款项 |
|
||
| `TRAVELER_COMPLETE` | 出行人信息 |
|
||
| `HOTEL_DONE` | 用房准备 |
|
||
| `VEHICLE_DONE` | 用车准备 |
|
||
| `CONTRACT_TEMPLATE_OK` | 合同方案 |
|
||
|
||
## 7. 错误码
|
||
|
||
本次没有新增错误码。与本次语义直接相关的错误:
|
||
|
||
| code | 含义 | 触发场景 |
|
||
|---|---|---|
|
||
| `581036` | 确认订单前置校验未通过,请先补全所有必填项 | 确认行程时整单无手机号,或其他 checklist 项未通过 |
|
||
| `581102` | 订单不存在,无法编辑出行人 | 出行人查询、编辑或校验的订单不存在 |
|
||
| `581103` | 性别编码不合法 | `gender` 不是 `0/1/2` |
|
||
| `581112` | 证件号格式不合法 | `idType` 与 `idNo` 不匹配 |
|
||
| `581113` | 手机号格式非法 | 非空手机号不是 11 位数字 |
|
||
| `581114` | 出生日期不能晚于今天 | `birthday` 为未来日期 |
|
||
| `581119` | 出行人证件号重复 | 同订单出现重复证件号 |
|
||
| `581149` | 出行人类型人数超出订单配置 | 按生日派生的人群类型超过订单声明配额 |
|
||
|
||
固定订单级阻断文案为:
|
||
|
||
```text
|
||
至少需要一名出行人填写手机号用于合同签署
|
||
```
|
||
|
||
## 8. 示例
|
||
|
||
### 8.1 典型成功:两名成人仅一人有手机号
|
||
|
||
前置数据:两人五项资料均完整,第一人有手机号,第二人没有手机号。
|
||
|
||
```json
|
||
{
|
||
"profileStatuses": [
|
||
"COMPLETED",
|
||
"COMPLETED"
|
||
],
|
||
"validate": {
|
||
"passed": true,
|
||
"incompleteList": [],
|
||
"blockReasons": []
|
||
},
|
||
"checklistTravelerComplete": {
|
||
"passed": true,
|
||
"failReason": null
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.2 边界:五项完整但整单无手机号
|
||
|
||
```json
|
||
{
|
||
"profileStatuses": [
|
||
"COMPLETED",
|
||
"COMPLETED"
|
||
],
|
||
"validate": {
|
||
"passed": false,
|
||
"incompleteList": [],
|
||
"blockReasons": [
|
||
"至少需要一名出行人填写手机号用于合同签署"
|
||
]
|
||
},
|
||
"checklistTravelerComplete": {
|
||
"passed": false,
|
||
"failReason": "至少需要一名出行人填写手机号用于合同签署"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.3 异常:缺性别并确认行程
|
||
|
||
先调用校验接口:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"passed": false,
|
||
"declaredCount": 1,
|
||
"actualCount": 1,
|
||
"countMismatch": false,
|
||
"incompleteList": [
|
||
{
|
||
"travelerId": "2079576729147338801",
|
||
"name": "张*",
|
||
"missingFields": [
|
||
"gender"
|
||
]
|
||
}
|
||
],
|
||
"blockReasons": []
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
继续提交确认行程会失败:
|
||
|
||
```json
|
||
{
|
||
"code": 581036,
|
||
"message": "确认订单前置校验未通过,请先补全所有必填项",
|
||
"data": null,
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
## 9. 业务边界
|
||
|
||
- 逐人五项资料是:姓名、性别、出生日期、证件类型、证件号。
|
||
- 手机号不属于逐人 `missingFields`,不影响单人的 `profileStatus`。
|
||
- `gender=0` 是有效枚举值,不等于字段缺失;`gender=null` 或空值才缺失。
|
||
- 订单至少需要一名出行人有手机号;可以不是每名成人都有手机号。
|
||
- `allCompleted=true` 只说明五项资料完整。需要判断订单能否确认时,应读取
|
||
`traveler/validate` 的 `passed/blockReasons` 或 `confirm-checklist` 的 `allPassed/items`。
|
||
- 确认行程仍会服务端复检,不能仅凭前端本地判断绕过。
|
||
|
||
## 10. 修改前后对比
|
||
|
||
### 10.1 字段级
|
||
|
||
| 字段 | 修改前 | 修改后 |
|
||
|---|---|---|
|
||
| `missingFields` 可选值 | `name/idType/idNo/phone` | `name/gender/birthday/idType/idNo` |
|
||
| `blockReasons` 手机号语义 | 与逐人 `phone` 缺失可能重叠 | 统一表达整单手机号门禁 |
|
||
|
||
### 10.2 行为级
|
||
|
||
| 场景 | 修改前 | 修改后 |
|
||
|---|---|---|
|
||
| 成人五项完整、本人无手机号、同行人有手机号 | 本人可能为 `PENDING` | 本人为 `COMPLETED`,整单可通过 |
|
||
| 五项完整、整单无手机号 | 逐人状态与订单门禁混在一起 | 每人可 `COMPLETED`,但校验与确认被订单级门禁阻断 |
|
||
| 只缺性别或出生日期 | 可能不进入 `missingFields` | 精确返回 `gender` 或 `birthday` |
|
||
|
||
## 11. 影响评估 / 回滚
|
||
|
||
- **是否破坏向后兼容**: 字段名、类型和层级不变,但枚举集合与字段语义变化;依赖旧
|
||
`missingFields=phone` 或自行按“每人必须有手机号”判断的前端逻辑需要调整。
|
||
- **前端是否必须同步上线**: 是。应删除逐人手机号必填对 `profileStatus` 的本地推断,
|
||
并使用 `blockReasons` / checklist 表达订单级阻断。
|
||
- **回滚影响**: 若接口语义回滚,旧逻辑会再次把部分无手机号成人标为 `PENDING`;
|
||
前端不应在本地固化任一后端历史口径。
|
||
|
||
## 12. 注意事项
|
||
|
||
- 不要用“手机号输入框是否为空”自行重算 `profileStatus`。
|
||
- 不要再判断 `missingFields` 是否包含 `phone`;该值已从允许集合删除。
|
||
- `allCompleted=true` 不等于可确认订单;确认按钮的权威结果是
|
||
`confirm-checklist.allPassed`。
|
||
- 手机号格式校验仍存在:非空时必须是 11 位数字。
|
||
- `GET order detail` 的出行人字段与 `GET traveler/list` 的敏感字段展示口径不同,
|
||
但两者的 `profileStatus` 语义完全一致。
|
||
|
||
## 验证证据
|
||
|
||
- 两名成人五项资料齐全、仅一人有手机号:两行均为 `COMPLETED`,校验通过。
|
||
- 成人五项资料齐全且本人无手机号:订单详情、列表、智能解析均返回 `COMPLETED`。
|
||
- 所有人均无手机号:逐人状态仍为 `COMPLETED`,校验返回订单级阻断原因,确认行程返回 `581036`。
|
||
- 只缺 `gender` 或只缺 `birthday`:逐人状态为 `PENDING`,`missingFields` 精确返回对应字段。
|
||
- PR 验证结果:Order + MP 聚焦测试 165 个通过;全量 Reactor 8420 个测试通过;本次增量行与分支覆盖率均为 100%。
|
||
|
||
## 13. 关联 / 联系人
|
||
|
||
- **Issue**: [#5203](https://git.1814.love:8443/wx/HL/issues/5203)
|
||
- **PR**: [#5210](https://git.1814.love:8443/wx/HL/pulls/5210)
|
||
- **Merge commit**: [88d0aec8](https://git.1814.love:8443/wx/HL/commit/88d0aec8375b56b5b8141984645a6998f8a42609)
|
||
- **后端负责人**: @yst
|