# 打印行程单出参增强(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。** 变更核心: - notices(07 注意事项区块)由固定对象结构改为自描述数组,前端不再需要硬编码三档配色和图标。 - 餐食字段(breakfast / lunch / dinner)由枚举字符串改为布尔值,前端直接判断是否含餐。 - days[] 每日行程增加 week 字段(周几)。 - days[].nodes[] 每个点位增加 description(点位介绍)和 contactPerson / contactPhone(景点联系人,仅 SCENIC 类型有值)。 --- ## 2. 变更清单 | 类型 | 字段路径 | 变更说明 | |------|----------|----------| | 破坏性变更 | notices | 类型从对象 {forbidden,warning,standard} 改为 NoticeGroupVO[] 自描述数组 | | 破坏性变更 | days[].breakfast | 类型从 String 枚举改为 Boolean(true=含餐,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 | | **认证** | 需携带管理后台 JWT(Authorization: Bearer ) | | **权限** | 已登录的管理后台用户,无额外角色限制 | | **幂等性** | 只读查询,天然幂等 | | **限流** | 无特殊限流(走网关通用限流) | | **响应格式** | application/json; charset=utf-8 | | **包装类型** | Result | --- ## 4. 接口入参 ### 4.1 路径参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | id | Long(JSON 序列化为 String,防 JS 精度丢失) | 是 | 订单 ID(雪花 ID,前端应作为字符串透传,不要转 number) | ### 4.2 请求体 本接口无请求体(GET 请求)。入参与 PR #4650 首上线版本完全相同,本次未作任何修改。 --- ## 5. 出参字段 响应结构:Result,code=200 时 data 为完整行程单对象。以下列出**本次有变动的字段和结构**(包含字段完整表格),未变动字段(agencyName / transports / customerOverview / feeDetail / hotels / emergencyContacts / refundNotes / handoverChecklist / collectReceipt 等)与首上线 Changelog(changelogs-v2/2026-06/30_4644_打印行程单接口-新增接口-管理后台.md)保持一致,此处不重复列出。 ### 5.1 notices(07 注意事项区块)—— 结构破坏性变更 **旧结构**(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 | String(LocalDate) | 无变化 | 实际日期,格式 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 | String(BigDecimal)或 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 其余枚举(与首上线相同) - direction:ARRIVAL(到达)/ DEPARTURE(出发) - transportType:FLIGHT / TRAIN / SELF_DRIVE - settleScope:PER_PERSON / PER_TEAM / PER_VEHICLE - createSource:以字典 order_create_source 为准(OTA / CUSTOMER / B2B / REFERRAL / MINI_PROGRAM 等) --- ## 7. 错误码 与首上线版本相同,本次无新增错误码。 | 错误码 | HTTP 状态 | message | 触发场景 | |--------|-----------|---------|----------| | 581007 | 200(Result 业务码) | 订单不存在 | 路径参数 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 渲染注意事项区块,无需前端硬编码配色 ### 不适用场景 - 本接口不替代签单 PDF(GET /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) |