diff --git a/changelogs-v2/2026-05/19_2598_v3行程day-node写操作真实化-修改接口-管理后台.md b/changelogs-v2/2026-05/19_2598_v3行程day-node写操作真实化-修改接口-管理后台.md new file mode 100644 index 0000000..6813ba5 --- /dev/null +++ b/changelogs-v2/2026-05/19_2598_v3行程day-node写操作真实化-修改接口-管理后台.md @@ -0,0 +1,562 @@ +# 【修改接口·管理后台】v3 行程 day/node 写操作真实化 + +> **更新时间**: 2026-05-19 +> **端类型**: 管理后台 +> **关联**: HL Issue [#2598](https://git.1814.love:8443/wx/HL/issues/2598) / PR [#2613](https://git.1814.love:8443/wx/HL/pulls/2613) +> **背景**: hl-order-service-v3 行程模块写接口从 Mock 替换为真实落库,7 个接口行为语义从假数据回显变为真实 DB 读写 + 审计日志。路径/方法/参数结构/响应结构全部不变。 + +--- + +## 1. 接口背景 + +行程编辑页(管理后台)包含天(Day)和节点(Node)两层叙事结构: + +- **天(Day)**:对应行程中的第 N 天,含标题、封面、描述、餐饮安排、集合解散地等叙事字段。 +- **节点(Node)**:每天下的具体活动节点,含节点类型(景点/餐厅/活动/服务/自定义)、时间、资源关联等。 + +之前 6 个写接口均为 Mock(返回硬编码 ID 或入参原样回显),不落库。本期(PR #2613)全部替换为真实 DB 操作,同时新增编辑历史分页接口(F7)。 + +**前端调用方式不变**——所有接口路径、方法、入参结构、出参结构与之前约定完全一致,变化的是后端行为从假数据变为真数据落库 + 审计。 + +--- + +## 2. 变更清单 + +| # | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|----------|------| +| F1 | POST | `/v3/admin/order/{orderId}/itinerary/days` | 行为变更 | Mock→真实:dayNumber/dayDate 后端派生,真实落库 + 写 edit_log | +| F2 | PUT | `/v3/admin/order/{orderId}/itinerary/days/{dayId}` | 行为变更 | Mock→真实:更新叙事字段,dayNumber/dayDate 不可改 | +| F3 | DELETE | `/v3/admin/order/{orderId}/itinerary/days/{dayId}` | 行为变更 | Mock→真实:级联软删 nodes + activity/custom_assignment | +| F4 | POST | `/v3/admin/order/{orderId}/itinerary/nodes` | 行为变更 | Mock→真实:ACTIVITY/CUSTOM 同事务 INSERT 实配,sortOrder 派生 | +| F5 | PUT | `/v3/admin/order/{orderId}/itinerary/nodes/{nodeId}` | 行为变更 | Mock→真实:仅叙事字段,实配不动 | +| F6 | DELETE | `/v3/admin/order/{orderId}/itinerary/nodes/{nodeId}` | 行为变更 | Mock→真实:软删节点,实配不动(下一期补) | +| F7 | GET | `/v3/admin/order/{orderId}/itinerary/edit-log/page` | 行为变更 | Mock→真实:6 字段筛选分页,真实查 order_itinerary_edit_log | + +**破坏兼容?** 无。入参字段、出参字段名/类型全部不变。 + +--- + +## 3. 接口详情 + +所有接口: +- **服务**: hl-order-service-v3(端口 8086,通过 Gateway :8080 路由) +- **认证**: JWT Bearer Token,Header `Authorization: Bearer {token}`,无效 token 返回 `code=401` +- **限流**: Gateway 全局限流,无接口级特殊限流 +- **幂等性**: 写接口非幂等(每次调用均落库 + 写 edit_log);建议前端防重提交(按钮 loading 态) + +### F1 新增天 +- **方法 + 路径**: `POST /v3/admin/order/{orderId}/itinerary/days` +- **描述**: 为指定订单新增一个行程天,dayNumber 由后端 MAX+1 派生 + +### F2 修改天 +- **方法 + 路径**: `PUT /v3/admin/order/{orderId}/itinerary/days/{dayId}` +- **描述**: 修改指定行程天的叙事字段,dayNumber/dayDate 不可改 + +### F3 删除天 +- **方法 + 路径**: `DELETE /v3/admin/order/{orderId}/itinerary/days/{dayId}` +- **描述**: 软删指定行程天,级联软删该天下所有节点及 activity/custom 实配 + +### F4 新增节点 +- **方法 + 路径**: `POST /v3/admin/order/{orderId}/itinerary/nodes` +- **描述**: 在指定天下新增节点,sortOrder 由后端 MAX+1 派生;ACTIVITY/CUSTOM 类型需带 assignmentPayload + +### F5 修改节点 +- **方法 + 路径**: `PUT /v3/admin/order/{orderId}/itinerary/nodes/{nodeId}` +- **描述**: 修改节点叙事字段,不修改实配数据(下一期) + +### F6 删除节点 +- **方法 + 路径**: `DELETE /v3/admin/order/{orderId}/itinerary/nodes/{nodeId}` +- **描述**: 软删指定节点,不级联删除实配(与 deleteDay 的级联行为不同) + +### F7 编辑历史分页 +- **方法 + 路径**: `GET /v3/admin/order/{orderId}/itinerary/edit-log/page` +- **描述**: 分页查询订单行程编辑历史,支持 6 字段筛选 + +--- + +## 4. 接口入参 + +### 4.1 路径参数(所有接口) + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| orderId | Long(String) | 是 | 订单 ID,雪花 ID 以字符串形式传递 | + +F2 / F3 额外: + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| dayId | Long(String) | 是 | 天 ID | + +F5 / F6 额外: + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| nodeId | Long(String) | 是 | 节点 ID | + +### 4.2 请求体字段 + +#### F1 新增天 / F2 修改天(请求体相同,均为 ItineraryDayUpsertReqVO) + +所有字段均为非必填: + +| 字段 | 类型 | 校验 | 说明 | +|------|------|------|------| +| dayTitle | String | 长度 ≤128 | 当天标题 | +| quoteText | String | 长度 ≤256 | 金句/小诗 | +| coverImageUrl | String | 长度 ≤512 | 封面图 OSS URL | +| description | String | 长度 ≤4096 | 当日描述(支持富文本) | +| breakfast | String | 枚举见第 6 节 | 早餐安排 | +| lunch | String | 枚举见第 6 节 | 午餐安排 | +| dinner | String | 枚举见第 6 节 | 晚餐安排 | +| diningRemark | String | 长度 ≤256 | 餐饮备注 | +| gatherPlace | Object | — | 集合地点(POI 对象,字段见下) | +| dismissalPlace | Object | — | 解散地点 | +| dailyMileage | Integer | 值 ≥0 | 当日里程(km) | +| dailyDuration | Integer | 值 ≥0 | 行驶时长(分钟) | +| mileageManualOverride | Boolean | — | 是否手动覆盖里程 | +| photoUrls | List\ | — | 图集 URL 列表 | +| editReason | String | 长度 ≤256 | 修改原因(写入 edit_log) | + +注意(F2 修改天): dayNumber 和 dayDate 后端管理,传了也不生效。 + +gatherPlace / dismissalPlace POI 对象结构(字段均可选): + +```json +{name:国家会展中心,address:上海市青浦区崧泽大道,longitude:121.2987,latitude:31.1598} +``` + +当前响应中 gatherPlace/dismissalPlace 以 JSON 字符串形式返回(非 Object),下一期统一处理。 + +#### F3 删除天 / F6 删除节点(请求体) + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| editReason | String | 否 | 删除原因(写入 edit_log) | + +#### F4 新增节点 / F5 修改节点(请求体 ItineraryNodeUpsertReqVO) + +| 字段 | 类型 | 必填 | 校验 | 说明 | +|------|------|------|------|------| +| nodeName | String | 是(F4/F5)| @NotBlank,长度 ≤128 | 节点名称 | +| dayId | Long(String) | 是(F4)| — | 所属天 ID,F5 修改时无需传 | +| nodeType | String | 是(F4) | 枚举见第 6 节 | 节点类型;F5 修改时不可改 | +| resourceType | String | 否 | 枚举见第 6 节 | 关联资源类型 | +| resourceId | Long(String) | 否 | — | 资源 ID;ACTIVITY 类型时后端校验 DB NOT NULL | +| startTime | String | 否 | HH:mm 格式 | 开始时间,如 "09:00" | +| timePeriod | String | 否 | — | 时段标签,如 "上午" | +| durationMinutes | Integer | 否 | 值 ≥0 | 持续时长(分钟) | +| description | String | 否 | 长度 ≤2000 | 节点描述 | +| images | List\ | 否 | — | 图集 URL 列表 | +| emojiIcon | String | 否 | 长度 ≤8 | Emoji 图标 | +| assignmentPayload | Object | nodeType=ACTIVITY/CUSTOM 时必填 | 见下 | 实配载荷(新增节点时使用) | +| editReason | String | 否 | 长度 ≤256 | 修改原因(写入 edit_log) | + +assignmentPayload 对象结构(NodeAssignmentPayload): + +| 字段 | 类型 | 必填 | 校验 | 说明 | +|------|------|------|------|------| +| unitPrice | BigDecimal | 是 | 值 ≥0 | 单价 | +| qty | Integer | 是 | 值 ≥1 | 数量 | +| costPrice | BigDecimal | 否 | — | 成本价 | +| supplierName | String | 否 | 长度 ≤128 | 供应商名称 | +| supplierContact | String | 否 | 长度 ≤64 | 供应商联系人 | +| supplierPhone | String | 否 | 长度 ≤32 | 供应商电话 | + +totalAmount = unitPrice x qty,后端自动计算,不接受前端传入。 + +#### F7 编辑历史分页(Query 参数) + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| pageNo | Integer | 是 | 页码,从 1 开始 | +| pageSize | Integer | 是 | 每页条数,建议 10-20 | +| editType | String | 否 | 枚举见第 6 节,单值筛选 | +| targetDay | Integer | 否 | 筛选指定天号(如 1、2、3) | +| operatorId | Long | 否 | 筛选指定操作人 ID | +| operatedFrom | String | 否 | 开始时间,**必须 ISO 格式** `2026-05-19T00:00:00` | +| operatedTo | String | 否 | 结束时间,**必须 ISO 格式** `2026-05-19T23:59:59` | + +注意:operatedFrom/operatedTo 必须使用 ISO 格式(含 T),空格格式(2026-05-19 00:00:00)会返回 400。 + +--- + +## 5. 出参字段 + +所有接口统一包装: + +```json +{"code":200,"msg":"success","data":{}} +``` + +### F1/F2/F3 响应体(ItineraryDayRespVO) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | String | 天 ID(Long 序列化为 String,防 JS 精度丢失) | +| orderId | String | 订单 ID | +| dayNumber | Integer | 第几天(后端派生,从 1 开始) | +| dayDate | String | 当日日期,格式 yyyy-MM-dd | +| dayTitle | String | 标题 | +| quoteText | String | 金句 | +| coverImageUrl | String | 封面图 URL | +| description | String | 描述 | +| breakfast | String | 早餐枚举值 | +| lunch | String | 午餐枚举值 | +| dinner | String | 晚餐枚举值 | +| diningRemark | String | 餐饮备注 | +| gatherPlace | String | 集合地点(当前为 JSON 字符串,非 Object) | +| dismissalPlace | String | 解散地点(同上) | +| dailyMileage | Integer | 当日里程 | +| dailyDuration | Integer | 行驶时长(分钟) | +| mileageManualOverride | Boolean | 是否手动覆盖里程 | +| photoUrls | List | 图集 | +| editLogId | String | 本次操作产生的 edit_log ID(Long→String) | +| createTime | String | 创建时间 | +| updateTime | String | 更新时间 | + +### F4/F5/F6 响应体(ItineraryNodeRespVO) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | String | 节点 ID(Long→String) | +| orderId | String | 订单 ID | +| dayId | String | 所属天 ID(Long→String) | +| nodeType | String | 节点类型枚举值 | +| sortOrder | Integer | 排序权重(后端派生,0-based max+1) | +| resourceType | String | 资源类型 | +| resourceId | String | 资源 ID(Long→String) | +| resourceName | String | 资源名称(Feign 反查,SERVICE/CUSTOM/无 resourceId 时为 null) | +| nodeName | String | 节点名称 | +| startTime | String | 开始时间(HH:mm) | +| timePeriod | String | 时段标签 | +| durationMinutes | Integer | 持续时长 | +| description | String | 描述 | +| images | List | 图集 | +| emojiIcon | String | Emoji | +| refType | String | 实配关联类型(枚举见第 6 节) | +| refId | String | 实配 ID(Long→String,无实配时 null) | +| assignment | Object | 实配数据(F4 ACTIVITY/CUSTOM 时有值,F5/F6 当前返回 null) | +| editLogId | String | 本次操作产生的 edit_log ID(Long→String) | +| createTime | String | 创建时间 | +| updateTime | String | 更新时间 | + +assignment 对象(仅 F4 ACTIVITY/CUSTOM 时非 null): + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | String | 实配 ID | +| unitPrice | BigDecimal | 单价 | +| qty | Integer | 数量 | +| totalAmount | BigDecimal | 总额(后端派生) | +| costPrice | BigDecimal | 成本价 | +| supplierName | String | 供应商 | +| status | String | 实配状态,新增时为 PLANNED | + +### F7 响应体(分页) + +```json +{code:200,data:{records:[],total:9,page:1,pageSize:10}} +``` + +EditLogVO 字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | String | edit_log ID(Long→String) | +| orderId | String | 订单 ID | +| editType | String | 操作类型枚举值(见第 6 节) | +| targetDay | Integer | 操作涉及的天号 | +| beforeSnapshot | String | 操作前快照(JSON 字符串) | +| afterSnapshot | String | 操作后快照(JSON 字符串) | +| amountDelta | BigDecimal | 金额变化(叙事层固定为 0) | +| editReason | String | 操作原因 | +| scope | String | 固定为 ITINERARY | +| operatorId | String | 操作人 ID(Long→String) | +| operatorName | String | 操作人姓名 | +| operatedAt | String | 操作时间(ISO 格式) | +| adjustmentId | String | 关联调整单 ID(暂未支持,固定 null) | + +--- + +## 6. 枚举 / 数据字典 + +### 餐饮枚举(breakfast / lunch / dinner) + +| 值 | 含义 | +|----|------| +| HOTEL | 酒店/旅馆餐 | +| CAMP | 营地餐 | +| SPECIAL | 特色餐 | +| SELF | 自理 | + +### 节点类型(nodeType) + +| 值 | 含义 | +|----|------| +| SCENIC | 景点游览 | +| RESTAURANT | 用餐 | +| ACTIVITY | 活动体验 | +| SERVICE | 服务项 | +| CUSTOM | 自定义 | + +### 资源类型(resourceType) + +| 值 | 含义 | +|----|------| +| SCENIC_SPOT | 景点 | +| RESTAURANT | 餐厅 | +| ACTIVITY | 活动 | + +### 实配关联类型(refType) + +| 值 | 含义 | +|----|------| +| NONE | 无实配 | +| SCENIC_ASSIGNMENT | 关联景点实配 | +| MEAL_ASSIGNMENT | 关联餐饮实配 | +| ACTIVITY_ASSIGNMENT | 关联活动实配 | +| CUSTOM_ASSIGNMENT | 关联自定义实配 | +| STAFF_ASSIGNMENT | 关联员工实配 | + +### edit_log 操作类型(editType) + +| 值 | 含义 | +|----|------| +| ADD_DAY | 新增天 | +| EDIT_DAY | 修改天 | +| REMOVE_DAY | 删除天 | +| ADD_NODE | 新增节点(本期新增枚举) | +| EDIT_NODE | 修改节点(本期新增枚举) | +| REMOVE_NODE | 删除节点(本期新增枚举) | + +--- + +## 7. 错误码 + +| 错误码 | 含义 | 触发场景 | +|--------|------|----------| +| 583001 | ORDER_NOT_FOUND 订单不存在 | orderId 无对应记录 | +| 583050 | DAY_NOT_FOUND 行程天不存在或已删除 | dayId 不属于该订单,或已软删 | +| 583051 | NODE_NOT_FOUND 节点不存在或已删除 | nodeId 不属于该订单,或已软删 | +| 583052 | ASSIGNMENT_PAYLOAD_REQUIRED 实配载荷必填 | nodeType=ACTIVITY/CUSTOM 但未传 assignmentPayload | +| 583053 | INVALID_NODE_TYPE 节点类型非法 | nodeType 传了非枚举值 | +| 583022 | RESOURCE_NOT_FOUND 资源不存在 | resourceId 对应资源在 resource-service 查不到 | +| 401 | 未认证 / Token 无效 | 无 Authorization Header 或 Token 过期 | + +--- + +## 8. 示例 + +### 8.1 典型成功 — 新增一天 + 新增节点 + +**Step 1:新增天** + +```bash +curl -X POST "http://localhost:8080/v3/admin/order/844477867747184641/itinerary/days" \n -H "Authorization: Bearer {token}" \n -H "Content-Type: application/json" \n -d '{"dayTitle":"第一天·布达拉宫","description":"抵达拉萨,参观布达拉宫","breakfast":"HOTEL","lunch":"SPECIAL","dinner":"SELF","editReason":"初次添加行程天"}' +``` + +响应: + +```json +{ + "code": 200, + "msg": "success", + "data": { + "id": "844800000000001001", + "orderId": "844477867747184641", + "dayNumber": 1, + "dayDate": "2026-06-01", + "dayTitle": "第一天·布达拉宫", + "breakfast": "HOTEL", + "lunch": "SPECIAL", + "dinner": "SELF", + "gatherPlace": null, + "editLogId": "844900000000000001", + "createTime": "2026-05-19T15:30:00" + } +} +``` + +说明:第一次新增天,dayNumber=1;dayDate 根据订单出发日自动计算。 + +**Step 2:新增景点节点** + +```bash +curl -X POST "http://localhost:8080/v3/admin/order/844477867747184641/itinerary/nodes" \n -H "Authorization: Bearer {token}" \n -H "Content-Type: application/json" \n -d '{"dayId":"844800000000001001","nodeType":"SCENIC","nodeName":"布达拉宫","resourceType":"SCENIC_SPOT","resourceId":"700000000000001","startTime":"10:00","durationMinutes":180}' +``` + +响应: + +```json +{ + "code": 200, + "data": { + "id": "844800000000002001", + "dayId": "844800000000001001", + "nodeType": "SCENIC", + "sortOrder": 0, + "resourceName": "布达拉宫", + "nodeName": "布达拉宫", + "startTime": "10:00", + "durationMinutes": 180, + "refType": "NONE", + "refId": null, + "assignment": null, + "editLogId": "844900000000000002" + } +} +``` + +说明:该天第一个节点,sortOrder=0;resourceName 由后端 Feign 反查。 + +### 8.2 边界情况 — 新增 ACTIVITY 节点(带实配载荷) + +```bash +curl -X POST "http://localhost:8080/v3/admin/order/844477867747184641/itinerary/nodes" \n -H "Authorization: Bearer {token}" \n -H "Content-Type: application/json" \n -d '{"dayId":"844800000000001001","nodeType":"ACTIVITY","nodeName":"骑马体验","resourceType":"ACTIVITY","resourceId":"710000000000002","startTime":"14:00","durationMinutes":60,"assignmentPayload":{"unitPrice":200.00,"qty":4,"costPrice":150.00,"supplierName":"高原牧场体验馆","supplierContact":"王师傅","supplierPhone":"18800001234"}}' +``` + +响应(节点 + 活动实配同事务写入): + +```json +{ + "code": 200, + "data": { + "id": "844800000000002002", + "dayId": "844800000000001001", + "nodeType": "ACTIVITY", + "sortOrder": 1, + "resourceName": "高原骑马体验", + "nodeName": "骑马体验", + "refType": "ACTIVITY_ASSIGNMENT", + "refId": "860000000000000001", + "assignment": {"id":"860000000000000001","unitPrice":200.00,"qty":4,"totalAmount":800.00,"costPrice":150.00,"supplierName":"高原牧场体验馆","status":"PLANNED"}, + "editLogId": "844900000000000003" + } +} +``` + +边界说明:第 2 个节点,sortOrder=1;totalAmount=200x4=800,后端派生;resourceName 由 Feign 反查不依赖前端传入。 + +### 8.3 业务失败 — ACTIVITY 节点未传 assignmentPayload + +```bash +curl -X POST "http://localhost:8080/v3/admin/order/844477867747184641/itinerary/nodes" \n -H "Authorization: Bearer {token}" \n -H "Content-Type: application/json" \n -d '{"dayId":"844800000000001001","nodeType":"ACTIVITY","nodeName":"骑马体验"}' +``` + +响应: + +```json +{"code":583052,"msg":"nodeType=ACTIVITY/CUSTOM 必须传 assignmentPayload","data":null} +``` + +--- + +## 9. 业务边界 + +### 适用场景 + +- 管理后台行程编辑页的新增天/删除天/编辑天/新增节点/修改节点/删除节点操作 +- 编辑历史面板查看审计日志 + +### 不适用场景 + +- 整体行程保存(batchSave,POST /itinerary/save)——该接口仍为 Mock,下一期处理 +- 小程序端行程查看(MpItineraryService.getMpItinerary)——只读接口,不涉及本期写操作 +- staff/scenic/meal 类节点的实配写入——下一期 + +### 特殊边界 + +| 场景 | 行为 | +|------|------| +| dayNumber 派生 | 后端 MAX(day_number)+1,同订单第一次加天时 dayNumber=1;前端不传不接收 | +| dayDate 派生 | 订单出发日+(dayNumber-1) 天自动计算;F2 修改天不可改 dayNumber,因此 dayDate 也不变 | +| sortOrder 派生 | addNode 时 MAX(sort_order)+1,该天首个节点 sortOrder=0;前端不传 | +| resourceName 派生 | nodeType=SCENIC_SPOT/RESTAURANT/ACTIVITY 且有 resourceId 时 Feign 反查;nodeType=SERVICE/CUSTOM 或 resourceId=null 时跳过返回 null | +| deleteDay 级联范围 | 软删该天所有节点 + 节点关联的 activity_assignment / custom_assignment;scenic_assignment / meal_assignment 暂不级联(留 TODO) | +| deleteNode 不级联 | 单独删节点不删实配(与 deleteDay 的级联不同),下一期补 | +| F5 editNode 实配不动 | 修改节点叙事字段时不同步修改 activity/custom_assignment,响应 assignment=null | +| 并发 addDay 防护 | 两人同时 addDay 触发 uk_order_day 唯一索引冲突时,后端 catch DuplicateKeyException 重试一次,仍失败返回业务错误码(不出 500) | +| 跨订单防护 | dayId/nodeId 校验 order_id 是否匹配,他人订单数据传进来返回 DAY_NOT_FOUND / NODE_NOT_FOUND | +| 已删除天再删 | 返回 DAY_NOT_FOUND(583050),不重复软删 | + +--- + +## 10. 修改前后对比 + +### 字段级对比(入参/出参结构不变) + +| 维度 | 修改前(Mock) | 修改后(真实) | +|------|--------|--------| +| F1 addDay 返回 id | 硬编码 7700000000008 | 真实雪花 ID,String 类型 | +| F2 editDay 返回数据 | 原 dayId 原样回显 | 真实更新后 DB 记录,含 updateTime | +| F3 deleteDay 返回 | 伪 deleted: true | 返回删除前的 day 完整数据 + editLogId | +| F4 addNode 返回 id | 硬编码 7800000000088 | 真实雪花 ID,refType/refId 真实回填 | +| F5 editNode / F6 deleteNode | 入参原样回显 | 真实 DB 操作后记录 | +| F7 edit-log/page | 固定返回 3 条假 log | 真实查询 order_itinerary_edit_log | +| edit_log 是否存在 | 否(Mock 不写 DB) | 是,每次写操作同事务 1 行 | + +### 行为级对比 + +| 操作 | 修改前 | 修改后 | +|------|--------|--------| +| 新增天两次 | 两次都返回同一 Mock ID | dayNumber 正确派生为 1、2 | +| 页面刷新后数据是否保留 | 否(Mock 不落库) | 是(真实落库) | +| 删除天后节点是否消失 | 否 | 是(级联软删) | +| 编辑历史能否查到 | 否 | 是(每个写操作写 edit_log) | + +--- + +## 11. 影响评估 / 回滚 + +### 对前端的影响 + +- 接口路径/方法/入参结构/出参字段名完全不变,前端代码无需修改 +- 行为变化:之前 Mock 数据刷新会丢失,现在真实落库;如果前端有基于 Mock 行为的临时兼容代码(如前端本地缓存假 ID 后续补偿),需检查是否有 workaround 要清理 + +### 前端需要验证的点 + +1. id / dayId / nodeId / editLogId / refId / operatorId 等所有 Long ID 字段均已序列化为 String,验证 JS 端是否正确处理(不要 parseInt) +2. editNode(F5)响应 assignment=null,如需展示实配数据需另调 /itinerary/full 接口 +3. gatherPlace / dismissalPlace 当前响应为 JSON 字符串,需 JSON.parse() 后渲染(下一期后端统一改为 Object 返回) +4. F7 edit-log/page 日期筛选必须 ISO 格式:2026-05-19T00:00:00,空格格式会 400 + +### 破坏性兼容 + +无破坏性兼容。 + +### 回滚方案 + +如需回滚,通过 Gitea 创建 revert PR,将 dev-v3 回到 PR #2613 合入前的状态。回滚不影响已落库的 order_itinerary_day / order_itinerary_node / order_itinerary_edit_log 数据(软删数据保留,不清库)。 + +--- + +## 12. 注意事项 + +1. **adjustmentId 暂未支持**:EditLogVO.adjustmentId 字段保留但 DDL 无对应列,固定返回 null,下一期 adjustment 流程接入时补齐。 + +2. **gatherPlace / dismissalPlace 返回 JSON 字符串**:当前接口返回的是 JSON 字符串而非 Object,前端需 JSON.parse() 后再使用。下一期统一改为 Object 返回。 + +3. **batchSave 仍为 Mock**:POST /v3/admin/order/{orderId}/itinerary/save 整体保存接口本期未真实化,仍返回 Mock 数据。 + +4. **staff/scenic/meal 实配 CRUD 仍 Mock**:nodeType=SERVICE/CUSTOM 的节点写入已真实,但 staff、scenic、meal 类型相关实配的增删改仍 Mock,下一期补齐。 + +5. **F5 editNode 不同步实配**:修改节点叙事字段时不修改关联实配,响应 assignment=null。如需实配数据调 /v3/admin/order/{orderId}/itinerary/full。 + +6. **deleteNode 不级联实配**:单独删节点(F6)不删关联的 activity/custom_assignment,与 deleteDay(F3)的级联行为不同,下一期补齐。 + +7. **resource-service 可用性**:nodeType=SCENIC/RESTAURANT/ACTIVITY 且传了 resourceId 时,后端会 Feign 调 resource-service 反查资源名。若 resource-service 不可用,返回 RESOURCE_NOT_FOUND(583022),写操作事务回滚。可通过不传 resourceId 规避(resourceName 会为 null)。 + +8. **Long ID 全为 String**:所有 Long 类型 ID 均序列化为 String(防 JS 精度丢失),前端赋值时不要做 parseInt。 + +--- + +## 13. 关联 / 联系人 + +- **Issue**: [#2598 v3 行程 day/node 写操作真实化](https://git.1814.love:8443/wx/HL/issues/2598) +- **PR**: [#2613](https://git.1814.love:8443/wx/HL/pulls/2613) +- **Commits(dev-v3 rebase 后)**: + - [12adf586](https://git.1814.love:8443/wx/HL/commit/12adf5861530f5af429d85aa38db9f801fd1d192) feat: Task 1-15 全量完成 + - [38c93408](https://git.1814.love:8443/wx/HL/commit/38c93408c2ca80784cd31cc1e6ee92b724814c9a) fix: Phase 4 QA BUG+WARN 修复 + - [d96c6575](https://git.1814.love:8443/wx/HL/commit/d96c657589838301739a47ab816bec2bbab9b042) fix: Phase 5 reviewer 必修项修复 +- **后端负责人**: @yaosutu