docs(changelog): 打印行程单出参增强 changelog(Issue #4682,PR #4683)

notices 改自描述数组(破坏性)/ breakfast·lunch·dinner 改 Boolean(破坏性)/ 新增 days[].week / nodes[].description / nodes[].contactPerson·contactPhone(仅SCENIC)
这个提交包含在:
yaosutu 2026-07-01 09:34:44 +08:00
父节点 36178799e6
当前提交 7d68720e9b

查看文件

@ -0,0 +1,575 @@
# 打印行程单出参增强notices 自描述 / 景点联系人 / 周几 / 餐布尔)
**接口路径**GET /v3/admin/order/{id}/print-itinerary
**服务**hl-order-service-v3
**PR**[#4683](https://git.1814.love:8443/wx/HL/pulls/4683) | **Issue**[#4682](https://git.1814.love:8443/wx/HL/issues/4682) | **首上线 PR**[#4650](https://git.1814.love:8443/wx/HL/pulls/4650) | **合并至**dev-v3
**变更类型**:破坏性变更(修改接口)
---
## 1. 接口背景
本次是对 PR #4650 首上线的打印行程单接口出团行程单·Driver Copy的出参增强,对照前端原型 printItinerary.jsx 补齐 5 项字段变更。**只改出参,入参不变,零 DDL。**
变更核心:
- notices07 注意事项区块)由固定对象结构改为自描述数组,前端不再需要硬编码三档配色和图标。
- 餐食字段breakfast / lunch / dinner由枚举字符串改为布尔值,前端直接判断是否含餐。
- days[] 每日行程增加 week 字段(周几)。
- days[].nodes[] 每个点位增加 description点位介绍和 contactPerson / contactPhone景点联系人,仅 SCENIC 类型有值)。
---
## 2. 变更清单
| 类型 | 字段路径 | 变更说明 |
|------|----------|----------|
| 破坏性变更 | notices | 类型从对象 {forbidden,warning,standard} 改为 NoticeGroupVO[] 自描述数组 |
| 破坏性变更 | days[].breakfast | 类型从 String 枚举改为 Booleantrue=含餐,false=不含) |
| 破坏性变更 | days[].lunch | 同上 |
| 破坏性变更 | days[].dinner | 同上 |
| 新增字段 | days[].week | 周几(由 dayDate 派生;dayDate 为 null 时 week=null |
| 新增字段 | days[].nodes[].description | 点位介绍(景区等节点有值,可能 null |
| 新增字段 | days[].nodes[].contactPerson | 景点联系人(仅 SCENIC;其余类型或未维护时为空串 |
| 新增字段 | days[].nodes[].contactPhone | 景点联系电话(同上规则) |
**入参无变化**,**无 DDL**,**无新依赖**。
---
## 3. 接口详情
| 项 | 说明 |
|----|------|
| **方法 + 路径** | GET /v3/admin/order/{id}/print-itinerary |
| **接口名** | 打印行程单出团行程单·Driver Copy |
| **功能描述** | 聚合订单 11 个打印区块,供前端渲染 A4 行程单并打印 / 导出 PDF |
| **认证** | 需携带管理后台 JWTAuthorization: Bearer <token> |
| **权限** | 已登录的管理后台用户,无额外角色限制 |
| **幂等性** | 只读查询,天然幂等 |
| **限流** | 无特殊限流(走网关通用限流) |
| **响应格式** | application/json; charset=utf-8 |
| **包装类型** | Result<PrintItineraryRespVO> |
---
## 4. 接口入参
### 4.1 路径参数
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | LongJSON 序列化为 String,防 JS 精度丢失) | 是 | 订单 ID雪花 ID,前端应作为字符串透传,不要转 number |
### 4.2 请求体
本接口无请求体GET 请求)。入参与 PR #4650 首上线版本完全相同,本次未作任何修改。
---
## 5. 出参字段
响应结构Result<PrintItineraryRespVO>,code=200 时 data 为完整行程单对象。以下列出**本次有变动的字段和结构**包含字段完整表格,未变动字段agencyName / transports / customerOverview / feeDetail / hotels / emergencyContacts / refundNotes / handoverChecklist / collectReceipt 等)与首上线 Changelogchangelogs-v2/2026-06/30_4644_打印行程单接口-新增接口-管理后台.md保持一致,此处不重复列出。
### 5.1 notices07 注意事项区块)—— 结构破坏性变更
**旧结构**PR #4650,已废弃):
```json
{
"notices": {
"forbidden": ["..."],
"warning": ["...", "..."],
"standard": ["...", "...", "..."]
}
}
```
**新结构**(本次 PR #4683
```json
{
"notices": [
{
"level": "FORBIDDEN",
"title": "严禁事项 · 违者扣全部车费、永不录用",
"color": "#DC2626",
"icon": "🚫",
"items": ["..."]
}
]
}
```
NoticeGroupVO 每个元素字段:
| 字段名 | 类型 | 说明 |
|--------|------|------|
| level | String | 档位标识FORBIDDEN / WARNING / STANDARD |
| title | String | 档位标题(含完整描述,可直接渲染为区块标题) |
| color | String | 档位主题色,格式 #RRGGBB |
| icon | String | 档位图标emoji |
| items | String[] | 本档说明条目列表 |
**三档固定配置**
| level | title | color | icon | items 数量 |
|-------|-------|-------|------|-----------|
| FORBIDDEN | 严禁事项 · 违者扣全部车费、永不录用 | #DC2626 | 🚫 | 1 条 |
| WARNING | 警示事项 · 风险由领队、师傅承担 | #D97706 | ⚠️ | 3 条 |
| STANDARD | 流程标准 · 出团必做 | #16A34A | ✅ | 5 条 |
三档内容为后端常量,前端直接渲染 title / color / icon / items 即可,不需要再硬编码配色和图标。内容如需修改需后端发版。
---
### 5.2 days[]06 每日行程)—— 新增 week,餐食改布尔
days[] 数组完整字段表:
| 字段名 | 类型 | 变更 | 说明 |
|--------|------|------|------|
| dayNumber | Integer | 无变化 | 天序号1-based |
| dayDate | StringLocalDate | 无变化 | 实际日期,格式 YYYY-MM-DD |
| dayTitle | String | 无变化 | 当日标题 |
| week | String 或 null | **本次新增** | 周几(周一~周日;dayDate 为 null 时为 null |
| description | String 或 null | 无变化 | 当日描述 |
| breakfast | **Boolean** | **类型变更** | true=含早餐(原 HOTEL/SPECIAL/CAMP,false=不含(原 NONE/SELF/null |
| lunch | **Boolean** | **类型变更** | 同上 |
| dinner | **Boolean** | **类型变更** | 同上 |
| diningRemark | String 或 null | 无变化 | 餐饮备注 |
| gatherPlace | String 或 null | 无变化 | 集合地点POI JSON 字符串) |
| dismissalPlace | String 或 null | 无变化 | 解散地点POI JSON 字符串) |
| dailyMileage | StringBigDecimal或 null | 无变化 | 当日里程km |
| nodes | NodeVO[] | 见 5.3 | 当日点位列表,按 sort_order 升序 |
---
### 5.3 days[].nodes[]06 子项:行程点位)—— 新增 description / contactPerson / contactPhone
nodes[] 完整字段表:
| 字段名 | 类型 | 变更 | 说明 |
|--------|------|------|------|
| nodeName | String | 无变化 | 点位名称(优先取 node 自定义名,回退资源名) |
| nodeType | String | 无变化 | 节点类型枚举值(见 §6 |
| description | String 或 null | **本次新增** | 点位介绍(固化行程 node.description;景点节点通常有值,餐厅等可能 null |
| startTime | String 或 null | 无变化 | 开始时间,格式 HH:mm |
| timePeriod | String 或 null | 无变化 | 时段(上午 / 下午 / 全天) |
| contactPerson | String | **本次新增** | 景点联系人(仅 nodeType=SCENIC 时通过 resourceId 调 resource-service 取;其余节点类型及未维护景点固定为空串 "",不为 null |
| contactPhone | String | **本次新增** | 景点联系电话(同 contactPerson 规则) |
| supplierPhone | String | 无变化 | 供应商电话node.supplierPhone;无则空串—— 与 contactPhone 是两个不同字段 |
| remark | String 或 null | 无变化 | 操作备注(作司机 / 导游话术) |
contactPhone vs supplierPhone 区别:
- contactPerson / contactPhone景点资源的联系人/电话,来自 resource-service getSpotContacts 接口,仅 SCENIC 节点。
- supplierPhone节点层面手动录入的供应商电话,所有节点类型均有此字段无值时为空串。两者独立,前端分开展示。
---
## 6. 枚举 / 数据字典
### 6.1 notices.level注意事项档位—— 本次新增
| 枚举值 | 中文含义 | 配色 | 图标 |
|--------|----------|------|------|
| FORBIDDEN | 严禁事项 | #DC2626(红) | 🚫 |
| WARNING | 警示事项 | #D97706(橙) | ⚠️ |
| STANDARD | 流程标准 | #16A34A(绿) | ✅ |
### 6.2 nodeType行程点位类型
| 枚举值 | 说明 | contactPerson/contactPhone 是否有值 |
|--------|------|-------------------------------------|
| SCENIC | 景区 | 有(若 resource-service 已维护该景点联系人) |
| RESTAURANT | 餐厅 | 固定空串 |
| ACTIVITY | 活动 | 固定空串 |
| SERVICE | 服务项 | 固定空串 |
| CUSTOM | 自定义 | 固定空串 |
### 6.3 breakfast / lunch / dinner本次改为 Boolean
| 旧枚举值(已废弃) | 旧含义 | 新 Boolean 值 |
|--------------------|--------|--------------|
| HOTEL | 酒店早餐 | true |
| SPECIAL | 特色餐 | true |
| CAMP | 营地餐 | true |
| NONE | 不含餐 | false |
| SELF | 自理 | false |
| null / 空 | 未录入 | false |
### 6.4 其余枚举(与首上线相同)
- directionARRIVAL到达/ DEPARTURE出发
- transportTypeFLIGHT / TRAIN / SELF_DRIVE
- settleScopePER_PERSON / PER_TEAM / PER_VEHICLE
- createSource以字典 order_create_source 为准OTA / CUSTOMER / B2B / REFERRAL / MINI_PROGRAM 等)
---
## 7. 错误码
与首上线版本相同,本次无新增错误码。
| 错误码 | HTTP 状态 | message | 触发场景 |
|--------|-----------|---------|----------|
| 581007 | 200Result 业务码) | 订单不存在 | 路径参数 id 对应订单不存在或已软删除 |
| 401 | 401 | Unauthorized | JWT 未携带 / 已过期 |
| 403 | 403 | Forbidden | 无访问权限 |
resource-service getSpotContacts Feign 失败时不报错,contactPerson / contactPhone 降级为空串 ,不影响接口整体返回。
---
## 8. 示例
### 8.1 典型成功
**请求**
```http
GET /v3/admin/order/1914050000000001/print-itinerary
Authorization: Bearer eyJhbGci...
```
**响应**(含本次全部新增字段):
```json
{
"code": 200,
"message": "成功",
"data": {
"agencyName": "内蒙古呼籁国际旅行社有限公司",
"printTime": "2026-07-01T10:00:00",
"teamNo": "26-0554",
"productName": "游牧的森林-短途版",
"dateRange": "2026-07-01→2026-07-04",
"paxSummary": "2大2小",
"driverName": "扎西",
"driverPhone": "13700137001",
"guideName": "巴特尔",
"guidePhone": "13700137002",
"leaderName": "其其格",
"leaderPhone": "13700137003",
"consultantName": "王骁",
"transports": [
{
"direction": "ARRIVAL",
"transportType": "FLIGHT",
"transportNo": "CA1234",
"carrier": "中国国航",
"departStation": "北京首都T2",
"arriveStation": "海拉尔东山机场",
"departTime": "2026-07-01T08:00:00",
"arriveTime": "2026-07-01T10:30:00",
"selfDrivePeriod": null,
"selfDriveEta": null,
"pickupRequired": true,
"pickupRemark": "T2出口举牌",
"remark": null
}
],
"customerOverview": {
"customerName": "吕思远",
"customerPhone": "138****0020",
"adultCount": 2,
"childCount": 1,
"youngChildCount": 1,
"babyCount": 1,
"tripDays": 4,
"customerType": null,
"createSource": "CONSULTANT",
"customerRemark": "忌海鲜"
},
"feeDetail": {
"items": [
{"name": "成人包价", "unitPrice": "3380.00", "qty": 2, "subtotal": "6760.00"},
{"name": "儿童包价", "unitPrice": "1980.00", "qty": 1, "subtotal": "1980.00"}
],
"singleRoomSurcharge": null,
"grandTotal": "9540.00",
"onsiteBalance": "3540.00"
},
"hotels": [
{
"dayNumber": 1,
"stayDate": "2026-07-01",
"hotelName": "海拉尔海棠酒店",
"city": "呼伦贝尔市",
"roomCategory": "标间",
"roomCount": 4,
"contactPerson": "海棠-李经理",
"contactPhone": "0470-7777777",
"settleType": "公司付款",
"paymentMode": "月结",
"checkInTime": "14:00",
"checkOutTime": "12:00"
}
],
"emergencyContacts": [
{"contactName": "admin", "contactPhone": "13000000001", "role": "房务"}
],
"days": [
{
"dayNumber": 1,
"dayDate": "2026-07-01",
"dayTitle": "海拉尔接机",
"week": "周三",
"description": "接机入住,傍晚自由活动",
"breakfast": true,
"lunch": false,
"dinner": false,
"diningRemark": null,
"gatherPlace": null,
"dismissalPlace": null,
"dailyMileage": "200.00",
"nodes": [
{
"nodeName": "中俄边境公路(卡线)",
"nodeType": "SCENIC",
"description": "呼伦贝尔最美自驾线路,约200公里草原与边境风光",
"startTime": null,
"timePeriod": null,
"contactPerson": "卡线-王警官",
"contactPhone": "0470-8801234",
"supplierPhone": "",
"remark": "提前报备边防,携带身份证"
},
{
"nodeName": "午餐(特色餐)",
"nodeType": "RESTAURANT",
"description": null,
"startTime": "12:00",
"timePeriod": "上午",
"contactPerson": "",
"contactPhone": "",
"supplierPhone": "0470-88880099",
"remark": null
}
]
}
],
"notices": [
{
"level": "FORBIDDEN",
"title": "严禁事项 · 违者扣全部车费、永不录用",
"color": "#DC2626",
"icon": "🚫",
"items": ["严禁向客人推荐自费项目、带客进购物店;违者扣除本行程全部车费,且永不录用"]
},
{
"level": "WARNING",
"title": "警示事项 · 风险由领队、师傅承担",
"color": "#D97706",
"icon": "⚠️",
"items": ["送站时间不得早于出发时间前 2 小时", "景区半价票差价须由领队自行承担,不得转嫁客人", "行程如有任何变更须经书面确认后方可执行"]
},
{
"level": "STANDARD",
"title": "流程标准 · 出团必做",
"color": "#16A34A",
"icon": "✅",
"items": ["出发前须核实全程住宿安排", "在出行群内发出行提示及完整大交通信息", "妥善保管签单当日归还公司", "每次入住检查设施拍照留存", "司机出发主动自我介绍"]
}
],
"refundNotes": [
{
"sourceName": "黑山头",
"intro": "退费说明",
"items": [
{
"title": "单项未骑马",
"amount": "100",
"unitLabel": "/人",
"settleScope": "PER_PERSON",
"settleScopeLabel": "按人",
"remark": null,
"effectiveFrom": null,
"effectiveTo": null
}
]
}
],
"handoverChecklist": ["出行群已建立并拉入全部出行人", "签单核对完毕", "车辆及座位已确认", "特殊需求已告知", "代收款已清点"],
"collectReceipt": {"collectAmount": "3540.00"},
"remark": null
},
"success": true
}
```
---
### 8.2 边界情况SCENIC 联系人降级为空串 + dayDate 为 null 时 week 为 null + 三餐均 false
模拟 resource-service Feign 失败导致 contactPerson / contactPhone 降级为空串,以及 dayDate=null 时 week=null
```json
{
"code": 200,
"data": {
"days": [
{
"dayNumber": 1,
"dayDate": null,
"dayTitle": "待确认日期",
"week": null,
"description": null,
"breakfast": false,
"lunch": false,
"dinner": false,
"diningRemark": null,
"gatherPlace": null,
"dismissalPlace": null,
"dailyMileage": null,
"nodes": [
{
"nodeName": "呼伦湖景区",
"nodeType": "SCENIC",
"description": "呼伦湖,中国第五大湖",
"startTime": null,
"timePeriod": null,
"contactPerson": "",
"contactPhone": "",
"supplierPhone": "",
"remark": null
}
]
}
],
"notices": [
{"level": "FORBIDDEN", "title": "严禁事项 · 违者扣全部车费、永不录用", "color": "#DC2626", "icon": "🚫", "items": ["严禁向客人推荐自费项目、带客进购物店;违者扣除本行程全部车费,且永不录用"]},
{"level": "WARNING", "title": "警示事项 · 风险由领队、师傅承担", "color": "#D97706", "icon": "⚠️", "items": ["送站时间不得早于出发时间前 2 小时", "景区半价票差价须由领队自行承担,不得转嫁客人", "行程如有任何变更须经书面确认后方可执行"]},
{"level": "STANDARD", "title": "流程标准 · 出团必做", "color": "#16A34A", "icon": "✅", "items": ["出发前须核实全程住宿安排", "在出行群内发出行提示及完整大交通信息", "妥善保管签单当日归还公司", "每次入住检查设施拍照留存", "司机出发主动自我介绍"]}
]
}
}
```
SCENIC 节点的 contactPerson / contactPhone 在 resource-service Feign 失败时降级为空串 "" 而不是 null,前端统一判断 === "" 决定是否显示联系人行。
---
### 8.3 业务失败(订单不存在)
**请求**
```http
GET /v3/admin/order/9999999999999/print-itinerary
Authorization: Bearer eyJhbGci...
```
**响应**
```json
{
"code": 581007,
"message": "订单不存在",
"data": null
}
```
---
## 9. 业务边界
### 适用场景
- 订单任意状态均可查询(只读,不限状态)
- 出团前定制师打印行程单,交司机 / 导游 / 领队
- 本次增强后可直接用返回的 title / color / icon 渲染注意事项区块,无需前端硬编码配色
### 不适用场景
- 本接口不替代签单 PDFGET /v3/admin/order/{id}/sign-voucher
- 本接口不是小程序端行程查看接口,只给管理后台打印用
### 特殊边界
| 情况 | 行为 |
|------|------|
| dayDate 为 null | week 也为 null,前端按空值处理 |
| nodeType 非 SCENIC | contactPerson / contactPhone 固定为空串 ""(不是 null |
| SCENIC 节点 resource-service Feign 失败 | contactPerson / contactPhone 降级为空串 "",接口仍正常 200 返回 |
| 原 breakfast 为 null / SELF / NONE | 新字段值为 false |
| 原 breakfast 为 HOTEL / SPECIAL / CAMP | 新字段值为 true |
| nodes[].description 为 null | 该节点无介绍文字,前端按空值处理(隐藏介绍行) |
| feeDetail Feign 失败 | feeDetail 整体为 null,前端展示暂无与首上线一致,本次无变化 |
---
## 10. 修改前后对比
### 字段级对比
| 字段路径 | 修改前PR #4650 | 修改后(本次 PR #4683 |
|----------|-------------------|------------------------|
| notices | { "forbidden": [...], "warning": [...], "standard": [...] } | [{ "level": "FORBIDDEN", "title": "...", "color": "#DC2626", "icon": "🚫", "items": [...] }, ...] |
| days[].breakfast | "HOTEL" / "SPECIAL" / "NONE" / "SELF" / null | true 或 false |
| days[].lunch | 同上 | 同上 |
| days[].dinner | 同上 | 同上 |
| days[].week | 字段不存在 | "周三" 或 null |
| days[].nodes[].description | 字段不存在 | "点位介绍文字" 或 null |
| days[].nodes[].contactPerson | 字段不存在 | "景点联系人" 或 "" |
| days[].nodes[].contactPhone | 字段不存在 | "0470-12345678" 或 "" |
### 行为级对比
| 维度 | 修改前 | 修改后 |
|------|--------|--------|
| notices 渲染 | 前端硬编码三档颜色(红/橙/绿)和图标 | 直接使用返回的 color / icon / title,前端零硬编码 |
| 餐食展示 | 判断字符串枚举(=== "HOTEL" 等) | 直接判断布尔(=== true |
| 景点联系人 | 无此数据,前端无法展示 | SCENIC 节点自动回填,可展示联系人和电话 |
| 每日行程周几 | 前端需自行从 dayDate 计算 | 后端直接返回 week,前端零计算 |
| 节点介绍 | 无此字段 | 有 description 可展示点位简介 |
---
## 11. 影响评估 / 回滚
### 破坏兼容性
| 字段 | 影响 | 前端必改 |
|------|------|---------|
| notices | 类型从对象改为数组,旧代码 notices.forbidden 将为 undefined | 是:改为遍历 notices 数组,使用各元素的 level / title / color / icon / items |
| breakfast / lunch / dinner | 类型从 String 改为 Boolean,旧代码字符串判断全部失效 | 是:改为布尔判断(=== true |
### 前端需要同步上线
是,且为破坏性变更——不改前端会出现注意事项区块渲染报错以及餐食图标逻辑失效。
### 回滚方案
- 无 DDL,无数据库变更
- 后端回滚Revert PR #4683,重新部署 hl-order-service-v3,接口恢复旧结构
- 前端兼容过渡期:可同时判断 Array.isArray(notices)新结构vs 非数组(旧结构),两套渲染保留直到后端确认全量上线
---
## 12. 注意事项
1. **notices 破坏性变更是本次最大风险**:旧代码直接访问 notices.forbidden / notices.warning / notices.standard 会报 undefined。前端上线后须全面验证注意事项区块渲染正常。
2. **餐食字段变为布尔,不再区分细类**:如需展示「酒店早餐」/ 「特色餐」等细类,本次接口不支持(只返回是/否含餐)。如有此需求请反馈,另行评估。
3. **contactPerson / contactPhone 返回空串而非 null**:景点联系人未维护时为 "" 而非 null,前端判断用 === "" 不要用 === null。
4. **contactPhone 与 supplierPhone 是两个独立字段**,来源不同,前端按需分别展示如「景点电话」vs「供应商电话」
5. **week 字段 null 安全**dayDate 有值时 week 一定有值;dayDate 为 null 时 week 一定为 null。
6. **BigDecimal 字段仍为 String**grandTotal / unitPrice / subtotal / dailyMileage / collectAmount / amount 均序列化为 JSON String,前端直接展示,不要 parseFloat() 转换。
---
## 13. 关联 / 联系人
| 项 | 链接 / 信息 |
|----|------------|
| **Issue** | [#4682 打印行程单出参增强](https://git.1814.love:8443/wx/HL/issues/4682) |
| **PR本次** | [#4683 feat(order-v3): 打印行程单出参增强](https://git.1814.love:8443/wx/HL/pulls/4683) |
| **首上线 PR** | [#4650 feat(order-v3): 打印行程单 11 区块聚合接口](https://git.1814.love:8443/wx/HL/pulls/4650) |
| **首上线 Changelog** | changelogs-v2/2026-06/30_4644_打印行程单接口-新增接口-管理后台.md |
| **Merge Commit** | [602f0e68d](https://git.1814.love:8443/wx/HL/commit/602f0e68d) |
| **Feature Commit** | [2fcc42882](https://git.1814.love:8443/wx/HL/commit/2fcc42882) |
| **后端负责人** | yaosutu腰苏图 |
| **影响服务** | hl-order-service-v3端口 8086 |