# 【修改接口·管理后台】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\<String\> | — | 图集 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\<String\> | 否 | — | 图集 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<String> | 图集 |
| 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<String> | 图集 |
| 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
