fix: 把 3 个 order-v3 changelog 从 changelogs/ 挪到 changelogs-v2/

错放原因:#2565 / #2569 / #2571 都是 order-v3 二期 PR,通知员漏判,写到了一期 changelogs/,
导致一期前端 mmg 在 sync-log 里看到了三条 v3 条目并反馈"全部跳过"。

按用户 2026-05-21 重申:order-v3 的任何内容严禁出现在 changelogs/,只能写 changelogs-v2/。

涉及文件:
- 18_2355_admin-order-v3-create-sharer-customizer.md (PR #2565)
- 19_feat_admin_order_list_consultant_name_filter.md (PR #2569)
- 19_fix_admin_order_detail_travelers_no_longer_empty.md (PR #2571)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot
2026-05-21 11:28:07 +08:00
共同撰写人 Claude Opus 4.7
父节点 376e28930f
当前提交 751ed717d3
共修改 3 个文件,包含 0 行新增和 0 行删除
@@ -0,0 +1,196 @@
# ⚠️✨ 管理端代下单接口 v3 新增分享追踪字段 + consultantSource 枚举新增 SHARED
> **服务**: hl-order-service-v3
> **接口**: `POST /admin/v3/orders`
> **PR**: #2565 refactor(order-v3): [Agency PR-4 / PR-1] 订单创建补 5 项基础逻辑
> **日期**: 2026-05-18 23:55
> **影响页面**: 管理后台「代下单」流程 + 小程序下单(通过 hl-mp-service 透传同接口)
---
## 变了什么(前端视角)
### 1. 请求体新增 2 个非必填字段
`POST /admin/v3/orders` 的请求 body 新增以下字段,**不传或传 null 行为与改造前完全一致**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `sharerOpenid` | String | 否 | 分享人微信 openid,用于 C 端裂变追踪 / 佣金归属。落表 `order_info.sharer_openid`,admin 代下单时通常不传 |
| `customizerId` | Long | 否 | C 端分享归因:从分享链接中带入的定制师 adminId。校验通过则锁定为该定制师(`consultantSource = SHARED`),校验失败兜底系统默认定制师 |
**注意**:`customizerId` 类型必须是 **number(Long)**,不能传 string。
### 2. 响应体枚举 consultantSource 新增值 SHARED
`OrderCreateRespVO` 中 `consultantSource` 字段的枚举值新增 `SHARED`:
| 枚举值 | 含义 | 何时出现 |
|--------|------|---------|
| `DEFAULT_ASSIGNED` | 系统默认定制师 | C 端下单,customizerId 为空或校验不通过,且有系统默认定制师 |
| `LINK_BOUND` | 链接绑定定制师 | 历史逻辑(v2 遗留) |
| `MANUAL` | admin 手动指定 | admin 代下单时由 JWT adminId 指定 |
| `SHARED`(**新增**) | C 端分享锁定定制师 | C 端下单,customizerId 校验通过 |
### 3. 新增可能抛出的错误码
| code | message | 触发场景 |
|------|---------|---------|
| `581035` | 订金金额不得超过订单总额 | DEPOSIT 模式下订金金额 > 订单总额时触发 |
### 4. 行为收紧(字段名不变,错误码变化)
| 场景 | 旧行为 | 新行为 |
|------|--------|--------|
| Agency 无默认 mchId | 抛 `MCH_RESOLVE_FAILED` | 抛 `AGENCY_NO_DEFAULT_MCHID` |
| customerName / customerRemark 含 HTML 标签 | 原样入库 | XSS 过滤后入库(前端无感,被清掉的标签不会报错) |
---
## 前端要改的地方
### 管理后台 B 端
1. **consultantSource 展示标签**:若订单详情 / 列表有按 `consultantSource` 显示来源文案的地方,需补 `SHARED` 的 case,建议展示文案为**"分享锁定"**:
```js
// 建议
const consultantSourceLabel = {
DEFAULT_ASSIGNED: '系统分配',
LINK_BOUND: '链接绑定',
MANUAL: '手动指定',
SHARED: '分享锁定', // 新增
}
```
2. **错误码监控 / 提示**:若代下单流程中有按错误码展示错误提示,建议给 `581035` 加 case,文案直接透传后端 message:`"订金金额不得超过订单总额"`。
若之前有对 `MCH_RESOLVE_FAILED` 的特殊提示逻辑,需同步改为 `AGENCY_NO_DEFAULT_MCHID`。
### 小程序 C 端(通过 hl-mp-service 透传)
3. **分享链接带 customizerId 下单**:从分享 URL 取出 `adminId`,下单时透传为 `customizerId`(参考 `07_feat_share_lock_customizer.md` 详细步骤):
```js
const customizerId = uni.getStorageSync('share_customizer_id') || null
// 放入 POST /mp/order/create 或 POST /admin/v3/orders 的请求体
```
4. **分享追踪 sharerOpenid**:若小程序有分享溯源需求(如显示"由 XXX 分享"),可在下单时透传 `sharerOpenid`;不传也不影响下单流程。
---
## 接口详细定义
### POST /admin/v3/orders — 管理端 / C 端创建订单
- **使用场景**:管理后台代客户下单,或 C 端小程序通过 hl-mp-service 创建订单
- **方法**: `POST`
- **路径**: `/admin/v3/orders`(通过 gateway 路由到 hl-order-service-v3)
#### 请求参数(Body JSON)
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `productId` | Long | 是 | 产品 ID |
| `productType` | String | 是 | 产品类型(CORE / ACTIVITY 等) |
| `startDate` | String | 是 | 出发日期,格式 `yyyy-MM-dd` |
| `adultCount` | Integer | 是 | 成人人数,≥ 1 |
| `childCount` | Integer | 否 | 儿童人数,默认 0 |
| `customerName` | String | 是 | 客户姓名(会走 XSS 过滤,HTML 标签会被清除) |
| `customerPhone` | String | 是 | 客户手机号 |
| `customerRemark` | String | 否 | 客户备注(会走 XSS 过滤) |
| `sharerOpenid` | String | 否 | **[新增]** 分享人微信 openid,C 端裂变追踪用 |
| `customizerId` | Long | 否 | **[新增]** 分享人定制师 adminId,校验通过则锁定为本单定制师 |
#### 请求示例(含新增字段)
```json
{
"productId": 10001,
"productType": "CORE",
"startDate": "2026-06-01",
"adultCount": 2,
"childCount": 0,
"customerName": "张三",
"customerPhone": "13800138000",
"customerRemark": "需要靠窗座位",
"sharerOpenid": "oXXXXXXXXXXX",
"customizerId": 50001
}
```
#### 响应结构
```json
{
"code": 200,
"msg": "success",
"data": {
"orderId": "202605181234567890",
"orderNo": "HL202605181234",
"totalAmount": 9800,
"depositAmount": 2000,
"consultantId": 50001,
"consultantName": "李定制师",
"consultantSource": "SHARED"
}
}
```
#### 响应字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| `orderId` | String | 雪花 ID,前端按字符串处理(Long 精度问题) |
| `orderNo` | String | 可读订单号 |
| `totalAmount` | Integer | 订单总额(分) |
| `depositAmount` | Integer | 订金金额(分),DEPOSIT 模式下有值 |
| `consultantId` | Long | 绑定的定制师 adminId |
| `consultantName` | String | 定制师姓名 |
| `consultantSource` | String | 定制师来源枚举,见下表 |
#### consultantSource 枚举值完整列表
| 值 | 中文展示建议 | 触发场景 |
|----|------------|---------|
| `DEFAULT_ASSIGNED` | 系统分配 | C 端下单,customizerId 无效或未传,有系统默认定制师 |
| `LINK_BOUND` | 链接绑定 | 历史逻辑(v2 遗留,v3 基本不再出现) |
| `MANUAL` | 手动指定 | admin 代下单,由登录态 JWT adminId 指定 |
| `SHARED` | 分享锁定 | C 端下单,`customizerId` 校验通过(定制师有效且角色正确) |
---
## 错误码完整列表(本接口可能返回)
| code | message | 处理建议 |
|------|---------|---------|
| `200` | success | 正常 |
| `400` | 参数校验失败 | 检查必填字段 |
| `581035` | 订金金额不得超过订单总额 | **[新增]** 直接 toast 后端 message |
| `AGENCY_NO_DEFAULT_MCHID` | Agency 未配置默认收款账号 | 联系运营配置 agency 默认 mchId |
---
## 兼容性
- 请求字段仅新增非必填字段,**向后兼容**(旧版前端不传新字段行为不变)
- 响应枚举仅新增值不删值,**向后兼容**
- 无 DDL 变更,无需数据迁移
- 只需重启 `hl-order-service-v3`,`hl-mp-service` 无需重启
---
## customizerId 校验兜底语义(前端无需实现)
后端 `CustomizerValidator` 校验失败时**全部静默兜底为系统默认定制师,不报错**:
| 失败原因 | 触发条件 | 前端表现 |
|---------|---------|---------|
| `ADMIN_NOT_FOUND` | adminId 不存在 / 已删除 | 兜底随机,静默 |
| `INACTIVE_STATUS` | admin status ≠ ACTIVE | 兜底随机,静默 |
| `WRONG_ROLE` | admin roleKey ≠ CUSTOMIZER | 兜底随机,静默 |
| `FEIGN_ERROR` | user-service Feign 调用失败 | 兜底随机,静默 |
| `INVALID_INPUT` | customizerId == null / ≤ 0 | 兜底随机,静默 |
前端不需要对这些失败场景做任何处理。
@@ -0,0 +1,121 @@
# API 变更通知
**更新时间**: 2026-05-19 03:00
**PR**: #2569 feat(order-v3): admin 订单列表加 consultantName 模糊筛选
## ✨ 订单列表接口新增「定制师姓名」筛选参数
### 变了什么(前端视角)
管理后台订单列表接口 `GET /v3/admin/order` 新增一个**可选查询参数** `consultantName`,支持按定制师姓名模糊筛选订单。
传入后,接口会对数据库 `consultant_name` 列做 `LIKE %xxx%` 匹配;不传(或传 `null`/空字符串)则不过滤,行为与之前完全一致。
**对照表**:
| 参数 | 原来 | 现在 |
|------|------|------|
| `consultantName` | 不存在,传了被忽略 | 支持,做 LIKE 模糊匹配 |
### 前端要改的地方
1. **订单列表顶部「定制师」筛选框**:将用户输入的定制师姓名作为 `consultantName` 参数拼到请求 query string 里,随其他已有筛选条件一起传给后端。
2. **清空筛选框时**:将 `consultantName` 置为 `undefined`(不传该字段)或传空字符串均可,后端两种情况都不过滤。
### 涉及的接口 / 模块
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 订单列表分页 | GET | `/v3/admin/order` | ✨ 新增请求参数 | 加 `consultantName` 可选筛选字段 |
### 接口详细定义
#### 订单列表分页
- **使用场景**:管理后台订单列表页,支持多条件组合筛选
- **请求参数(完整)**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| page | Integer | 是 | 页码,从 1 开始 |
| pageSize | Integer | 是 | 每页条数,建议 10 / 20 |
| keyword | String | 否 | 关键词模糊匹配(团号 / 客户姓名 / 产品名 / 订单号任一) |
| status | String | 否 | 订单状态枚举,见枚举表 |
| createSource | String | 否 | 创建来源枚举 |
| departureDateFrom | String | 否 | 出发日期起(yyyy-MM-dd) |
| departureDateTo | String | 否 | 出发日期止(yyyy-MM-dd) |
| cancelled | Boolean | 否 | 是否含已取消(默认 false) |
| tagNames | List\<String\> | 否 | 按标签名过滤(多选) |
| **consultantName** | **String** | **否** | **定制师姓名模糊匹配(LIKE %xxx%)。空/null 不过滤(本次新增)** |
- **请求示例(带定制师筛选)**:
```
GET /v3/admin/order?page=1&pageSize=20&consultantName=李定制
Authorization: Bearer {token}
```
- **请求示例(组合筛选)**:
```
GET /v3/admin/order?page=1&pageSize=20&status=CONFIRMED&consultantName=王
Authorization: Bearer {token}
```
- **响应示例(完整)**:
```json
{
"code": 200,
"msg": "success",
"data": {
"records": [
{
"id": 1234567890,
"orderNo": "HL20260519001",
"status": "CONFIRMED",
"productName": "云南大理 7 日游",
"consultantName": "李定制",
"consultantId": 100001,
"customerName": "张三",
"departDate": "2026-06-01",
"headCount": 4,
"totalAmount": 12800.00,
"createTime": "2026-05-19T10:00:00"
}
],
"total": 5,
"page": 1,
"pageSize": 20
}
}
```
- **响应字段说明**(本次无变化,仅供参考):
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Long | 订单 ID |
| orderNo | String | 订单号 |
| status | String | 订单状态,见枚举表 |
| productName | String | 产品名称 |
| consultantName | String | 定制师姓名 |
| consultantId | Long | 定制师 ID |
| customerName | String | 客户姓名 |
| departDate | String | 出发日期(yyyy-MM-dd) |
| headCount | Integer | 出行人数 |
| totalAmount | BigDecimal | 订单总金额 |
| createTime | String | 创建时间(ISO 8601) |
| total | Long | 满足条件的总记录数 |
| page | Integer | 当前页码 |
| pageSize | Integer | 每页条数 |
### 业务规则 / 校验规则
- `consultantName` 为空字符串或 null 时,SQL 不追加该条件,等价于不筛选
- 匹配方式:`LIKE %{consultantName}%`(前后都带通配符),输入"李"可匹配"李定制"、"王李明"等
- 筛选字段作用于订单表 `consultant_name` 列(存的是定制师的真实姓名,非企微名)
- 与其他筛选条件(keyword、status、departureDateFrom 等)是 AND 关系,可自由组合
### 向后兼容性说明
- 本次变更**完全向后兼容**:`consultantName` 为可选参数,不传则行为与改动前完全一致
- 响应结构 / 字段无任何变化
- 无需数据迁移,无需重启网关
@@ -0,0 +1,167 @@
# API 变更通知
**更新时间**: 2026-05-19 00:00
**PR**: #2571 fix(order-v3): 补通 overview.travelers 真实出行人数据(Issue #2460 遗留债)
## ✨ 订单详情接口 overview.travelers 字段从占位空数组变为真实出行人列表
### 变了什么(前端视角)
`GET /v3/admin/order/{id}` 的响应中,`data.overview.travelers` 字段此前**永远返回空数组 `[]`**(Issue #2460 PR-1 当时留了 TODO 占位)。
本次修复已将其**接通真实数据**,现在返回完整的出行人列表,字段口径与 `GET /v3/admin/order/{id}/traveler/list` 完全一致。
**对照表**:
| 字段 | 原来 | 现在 |
|------|------|------|
| `data.overview.travelers` | 永远 `[]` | 真实出行人列表(按 traveler_id 升序) |
### 前端要改的地方
这是**可选优化**,不是必须改:
1. **进入订单详情页时**:可直接从 `overview.travelers` 读取出行人数据,省去再调一次 `/v3/admin/order/{id}/traveler/list`。
2. **保持现有调用方式也完全没问题**:`/traveler/list` 接口字段口径不变,两个来源数据一致。
3. **如果原先有 workaround**(比如检测到 `travelers` 为空就补一次 list 请求):确认接口已返回真实数据后可以清理掉这段逻辑。
### 涉及的接口 / 模块
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 订单详情 | GET | `/v3/admin/order/{id}` | 🔧 字段行为修复 | `overview.travelers` 从空数组变为真实数据 |
### 接口详细定义
#### 订单详情
- **使用场景**:管理后台进入订单详情页时调用,返回订单概览 + 出行人列表等聚合信息
- **路径参数**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | Long | 是 | 订单 ID |
- **请求示例**:
```
GET /v3/admin/order/1234567890
Authorization: Bearer {token}
```
- **响应示例(完整 overview.travelers 部分)**:
```json
{
"code": 200,
"msg": "success",
"data": {
"overview": {
"travelers": [
{
"id": 987654321,
"orderId": 1234567890,
"travelerType": "ADULT",
"name": "张三",
"gender": "MALE",
"birthday": "1990-06-15",
"idType": "IDCARD",
"idNo": "110101199006151234",
"nationality": "中国",
"race": "汉族",
"phone": "13800138000",
"emergencyContact": "李四",
"emergencyPhone": "13900139000",
"roomGroupNo": 1,
"profileStatus": "COMPLETED",
"transportPlanIds": [1001, 1002]
},
{
"id": 987654322,
"orderId": 1234567890,
"travelerType": "CHILD",
"name": "张小五",
"gender": "MALE",
"birthday": "2018-03-20",
"idType": "IDCARD",
"idNo": "110101201803201234",
"nationality": "中国",
"race": "汉族",
"phone": null,
"emergencyContact": "张三",
"emergencyPhone": "13800138000",
"roomGroupNo": 1,
"profileStatus": "COMPLETED",
"transportPlanIds": []
}
]
}
}
}
```
- **overview.travelers 数组元素字段说明**:
| 字段 | 类型 | 说明 | 备注 |
|------|------|------|------|
| id | Long | 出行人 ID | 雪花 ID |
| orderId | Long | 所属订单 ID | |
| travelerType | String | 出行人类型 | 枚举,见下表 |
| name | String | 姓名 | |
| gender | String | 性别 | 枚举:MALE / FEMALE |
| birthday | String | 生日 | 格式 yyyy-MM-dd |
| idType | String | 证件类型 | 枚举,见下表 |
| idNo | String | 证件号 | **admin 端明文返回**(后台 DB 加密存储)|
| nationality | String | 国籍 | 默认"中国" |
| race | String | 民族 | 默认"汉族" |
| phone | String | 手机号 | **admin 端明文返回**;儿童可为 null |
| emergencyContact | String | 紧急联系人姓名 | 可为 null |
| emergencyPhone | String | 紧急联系人手机 | **admin 端明文返回**;可为 null |
| roomGroupNo | Integer | 同住分组号 | 相同数字表示同住一间 |
| profileStatus | String | 资料完善状态 | 枚举,见下表 |
| transportPlanIds | List\<Long\> | 关联的大交通批次 ID 列表 | 无关联时为空数组 `[]` |
### 枚举 / 字典值
#### travelerType(出行人类型)
| 值 | 中文 | 说明 |
|----|------|------|
| ADULT | 成人 | |
| YOUNG | 青年 / 学生 | |
| CHILD | 儿童 | |
| BABY | 婴儿 | |
#### idType(证件类型)
| 值 | 中文 | 说明 |
|----|------|------|
| IDCARD | 居民身份证 | 最常见 |
| PASSPORT | 护照 | |
| HKMO | 港澳居民来往内地通行证 | |
| TAIWAN | 台湾居民来往大陆通行证 | |
| OTHER | 其他 | |
#### gender(性别)
| 值 | 中文 |
|----|------|
| MALE | 男 |
| FEMALE | 女 |
#### profileStatus(资料状态)
| 值 | 中文 | 说明 |
|----|------|------|
| PENDING | 待完善 | 出行人资料未填完整 |
| COMPLETED | 已完善 | 出行人资料齐全 |
### 业务规则 / 校验规则
- 排序:按出行人 ID(`traveler_id`)升序
- 数据来源:与 `GET /v3/admin/order/{id}/traveler/list` **完全一致**,由同一个 `TravelerService.listTravelers` 方法产出,字段值不会有差异
- **敏感字段**:`idNo`、`phone`、`emergencyPhone` 在 admin 端明文返回(业务需要,例如紧急情况联系),在小程序端(mp)和内部接口(internal)走脱敏,后台 DB 使用 AES 加密存储
### 向后兼容性说明
- 本次变更**完全向后兼容**:字段名 / 路径 / 类型结构均无变化,只是原来恒为 `[]` 的字段现在有了真实内容
- 前端无需强制修改,保持调用 `/traveler/list` 的逻辑也能正常工作
- 若前端原有 workaround(检测 `travelers` 为空时额外请求 list),现在可按需清理