diff --git a/changelogs-v2/2026-10/03_8750_团期子订单封掉订单级行程写口-修改接口-管理后台.md b/changelogs-v2/2026-10/03_8750_团期子订单封掉订单级行程写口-修改接口-管理后台.md new file mode 100644 index 00000000..c3e79932 --- /dev/null +++ b/changelogs-v2/2026-10/03_8750_团期子订单封掉订单级行程写口-修改接口-管理后台.md @@ -0,0 +1,490 @@ +--- +schema: "hl-changelog/v2" +ticket: "8750" +title: "团期子订单封掉订单级行程写口:改行程天 / 增改删节点 4 个接口对团期子订单返回 583066,调整快照不再提示行程与出行日期可编辑" +consumer: "admin" +author: "jw(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "v2.1" +verified_at: "2026-10-03" +status_note: "PR #8762 已合并 dev-v3(e8d83b0cb)并滚动部署 TEST 双实例。订单行程 4 个写口(改行程天、新增 / 修改 / 删除节点)对团期子订单一律返回新码 583066,零写入;团期管理员仍先返回 581008;散客单不变。调整快照 editableTabLocksHint 对团期子订单去掉 ITINERARY / SCHEDULE。TEST 网关实测:团期子订单 4 个写口 583066 且三表逐字一致、散客单增改删回写 200、团期管理员 581008、快照两侧取值符合。前端待办:团期子订单详情行程页签隐藏编辑 / 新增 / 删除按钮;团期行程汇总下钻去掉「跳去改」入口,保留跳转查看。" +updated_at: "2026-10-03" +base: "dev-v3" +--- + +# order-v3: 团期子订单封掉订单级行程写口 + +**服务**: hl-order-service-v3 +**PR**: `#8762`(已合入 `dev-v3`,合并提交 `e8d83b0cb`);文档回写 `#8763` +**Issue**: #8750 + +--- + +## ⚠️ 关键变化 + +🔴 **团期子订单不能再在订单详情里改行程。** 改行程天、新增 / 修改 / 删除节点 4 个接口,对团期子订单一律返回新码 `583066`,整笔零写入。原来定制师还能改成功,现在改不了。 + +🟢 **散客单完全不变。** 请求体、响应体结构都没改。 + +🟢 **调整快照的 `editableTabLocksHint` 对团期子订单不再含 `ITINERARY` / `SCHEDULE`。** 这两个页签在调整里一提交就是 `587045`(#8350),提示与实际行为现在一致。 + +--- + +## 一、背景 + +#8350(2026-09-24)定案:团期子订单不能在子订单里调整出行日期和行程,但当时只封了 `POST /v3/admin/order/:id/adjustment/submit` 一个入口。订单详情行程页签直接调的 4 个写接口仍然开着:团期管理员被按角色拦住,定制师仍能改团期子订单的行程。团期层本来就不提供行程编辑(#7378 AC-I7)。jw 2026-10-03 定:封。 + +封口后,团期子订单的行程只来自下单时的产品快照,随团期转期、改期同步,没有人工编辑通路。订单调整、转期重排、改期同步、下单物化这些内部路径不走这 4 个接口,不受影响。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 修改行程天 | PUT | `/v3/admin/order/:orderId/itinerary/days/:dayId` | 行为变更 + 新增错误码 | 团期子订单返回 583066 | +| 2 | 新增行程节点 | POST | `/v3/admin/order/:orderId/itinerary/nodes` | 行为变更 + 新增错误码 | 团期子订单返回 583066 | +| 3 | 修改行程节点 | PUT | `/v3/admin/order/:orderId/itinerary/nodes/:nodeId` | 行为变更 + 新增错误码 | 团期子订单返回 583066 | +| 4 | 删除行程节点 | DELETE | `/v3/admin/order/:orderId/itinerary/nodes/:nodeId` | 行为变更 + 新增错误码 | 团期子订单返回 583066 | +| 5 | 调整快照 | GET | `/v3/admin/order/:id/adjustment/snapshot` | 返回值变更 | 团期子订单 `editableTabLocksHint` 去掉 `ITINERARY` / `SCHEDULE` | + +--- + +## 三、接口详情 + +### 1. 修改行程天 `PUT /v3/admin/order/:orderId/itinerary/days/:dayId` + +**VO**: `ItineraryDayUpsertReqVO` → `Result` + +#### 使用场景 + +订单详情行程页签改某一天的叙事内容(标题、描述、封面图、三餐等)。本次只改变团期子订单的放行规则。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Path | Long | ✅ | 订单 ID | **不变**;团期子订单直接返回 583066 | +| dayId | Path | Long | ✅ | 该订单的行程天 ID | **不变** | +| dayTitle 等叙事字段 | Body | — | ❌ | 同改前 | **不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | ItineraryDayRespVO | **不变**(散客单);团期子订单返回 583066,`data` 为 null | + +#### 请求示例 + +```json +{ + "dayTitle": "海拉尔—额尔古纳 湿地观景", + "description": "上午出发前往额尔古纳湿地,午后登观景台" +} +``` + +#### 响应示例 + +散客单(不变): + +```json +{ + "code": 200, + "message": "成功", + "data": { "id": "7700000000001", "dayNumber": 2, "dayDate": "2026-10-12", "dayTitle": "海拉尔—额尔古纳 湿地观景" }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +写接口无空数据场景。 + +#### 错误响应 + +团期子订单(**本次新增**): + +```json +{ "code": 583066, "message": "团期子订单的行程随团期产品,不支持在子订单中修改", "data": null, "success": false } +``` + +团期管理员角色(存量,先于 583066 判定): + +```json +{ "code": 581008, "message": "无权查看此订单", "data": null, "success": false } +``` + +#### 业务边界 + +- 判团口径与 #8350 相同:订单归属团期(`group_batch_id` 非空)即为团期子订单。 +- 先判团期管理员角色(581008),再判团期子订单(583066),都在任何写之前,拒绝即零写入。 +- 订单不存在时不在这里拦,仍按原逻辑返回 583050(行程天不存在)。 + +--- + +### 2. 新增行程节点 `POST /v3/admin/order/:orderId/itinerary/nodes` + +**VO**: `ItineraryNodeUpsertReqVO` → `Result` + +#### 使用场景 + +订单详情行程页签在某一天新增一个节点(景点 / 餐厅 / 活动 / 服务)。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Path | Long | ✅ | 订单 ID | **不变**;团期子订单直接返回 583066 | +| dayId | Body | Long | ✅ | 该订单的行程天 ID | **不变** | +| nodeType | Body | String | ✅ | SCENIC / RESTAURANT / ACTIVITY / SERVICE | **不变** | +| nodeName | Body | String | ✅ | ≤128 | **不变** | +| 其余字段 | Body | — | ❌ | 同改前 | **不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | ItineraryNodeRespVO | **不变**(散客单);团期子订单返回 583066,`data` 为 null | + +#### 请求示例 + +```json +{ + "dayId": "7700000000001", + "nodeType": "SCENIC", + "nodeName": "呼伦湖", + "startTime": "09:30" +} +``` + +#### 响应示例 + +散客单(不变): + +```json +{ + "code": 200, + "message": "成功", + "data": { "id": "8800000000001", "dayId": "7700000000001", "nodeType": "SCENIC", "sortOrder": 3, "nodeName": "呼伦湖" }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +写接口无空数据场景。 + +#### 错误响应 + +团期子订单(**本次新增**): + +```json +{ "code": 583066, "message": "团期子订单的行程随团期产品,不支持在子订单中修改", "data": null, "success": false } +``` + +#### 业务边界 + +- 同接口 1:团期管理员 581008 先判,团期子订单 583066 后判,都在写之前。 +- 散客单的节点类型校验、资源名反查、自费售价校验(583064)等全部不变。 + +--- + +### 3. 修改行程节点 `PUT /v3/admin/order/:orderId/itinerary/nodes/:nodeId` + +**VO**: `ItineraryNodeUpsertReqVO` → `Result` + +#### 使用场景 + +订单详情行程页签修改某个节点的叙事字段和结算字段。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Path | Long | ✅ | 订单 ID | **不变**;团期子订单直接返回 583066 | +| nodeId | Path | Long | ✅ | 该订单的节点 ID | **不变** | +| nodeName | Body | String | ✅ | ≤128 | **不变** | +| 其余字段 | Body | — | ❌ | 同改前 | **不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | ItineraryNodeRespVO | **不变**(散客单);团期子订单返回 583066,`data` 为 null | + +#### 请求示例 + +```json +{ + "nodeType": "SCENIC", + "nodeName": "呼伦湖", + "description": "湖畔栈道步行约 40 分钟" +} +``` + +#### 响应示例 + +散客单(不变): + +```json +{ + "code": 200, + "message": "成功", + "data": { "id": "8800000000001", "nodeName": "呼伦湖", "description": "湖畔栈道步行约 40 分钟" }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +写接口无空数据场景。 + +#### 错误响应 + +团期子订单(**本次新增**): + +```json +{ "code": 583066, "message": "团期子订单的行程随团期产品,不支持在子订单中修改", "data": null, "success": false } +``` + +#### 业务边界 + +- 同接口 1。 +- 订单调整(`adjustment/submit`)内部改节点不走本接口,不受 583066 影响;它对团期子订单带行程已由 587045 拦截(#8350)。 + +--- + +### 4. 删除行程节点 `DELETE /v3/admin/order/:orderId/itinerary/nodes/:nodeId` + +**VO**: `ItineraryNodeDeleteReqVO`(可不传)→ `Result` + +#### 使用场景 + +订单详情行程页签删除某个节点(软删)。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Path | Long | ✅ | 订单 ID | **不变**;团期子订单直接返回 583066 | +| nodeId | Path | Long | ✅ | 该订单的节点 ID | **不变** | +| editReason | Body | String | ❌ | ≤500 | **不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data | ItineraryNodeRespVO | **不变**(散客单,删除前快照);团期子订单返回 583066,`data` 为 null | + +#### 请求示例 + +```http +DELETE /v3/admin/order/2100743225424621570/itinerary/nodes/8800000000001 +Authorization: Bearer <管理端令牌> +``` + +#### 响应示例 + +散客单(不变): + +```json +{ + "code": 200, + "message": "成功", + "data": { "id": "8800000000001", "nodeName": "呼伦湖" }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +写接口无空数据场景。 + +#### 错误响应 + +团期子订单(**本次新增**): + +```json +{ "code": 583066, "message": "团期子订单的行程随团期产品,不支持在子订单中修改", "data": null, "success": false } +``` + +#### 业务边界 + +- 同接口 1。 +- 散客单删除节点时的应付款台账联动(599602 锁定拦截等)不变。 + +--- + +### 5. 调整快照 `GET /v3/admin/order/:id/adjustment/snapshot` + +**VO**: `Result` + +#### 使用场景 + +「调整订单」弹窗打开时取快照;`editableTabLocksHint` 告诉前端哪些页签可编辑。 + +#### 入参字段表 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| id | Path | Long | ✅ | 订单 ID | **不变** | +| scope | Query | String | ❌ | 逗号分隔子域 | **不变** | + +#### 出参字段表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| data.editableTabLocksHint | Array\ | 🔁 **团期子订单不再含 `ITINERARY` / `SCHEDULE`**;散客单不变 | +| 其余字段 | — | **不变** | + +#### 请求示例 + +```http +GET /v3/admin/order/2100743225424621570/adjustment/snapshot +Authorization: Bearer <管理端令牌> +``` + +#### 响应示例 + +团期子订单(资源准备中): + +```json +{ + "code": 200, + "message": "成功", + "data": { + "basic": { "orderNo": "HL20260924171304411" }, + "editableTabLocksHint": ["BASIC", "PEOPLE", "HOTEL_REQ", "VEHICLE_REQ", "FEE"] + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +无变化:订单不存在等错误与改前一致。 + +```json +{ "code": 200, "message": "成功", "data": { "editableTabLocksHint": ["BASIC"] }, "success": true } +``` + +#### 错误响应 + +非法 scope(存量): + +```json +{ "code": 587003, "message": "调整范围取值非法", "data": null, "success": false } +``` + +#### 业务边界 + +- 只对团期子订单从集合里去掉 `ITINERARY` / `SCHEDULE`,其余页签按原来的流程状态规则给出。 +- 传单个 scope 时原来是原样回显该 scope;团期子订单传 `ITINERARY` 或 `SCHEDULE` 现在返回空集合。 + +--- + +## 四、契约约束与正确调用方式 + +| 场景 | 调用 | 结果 | +|------|------|------| +| ❌ 团期子订单改行程天 / 增改删节点 | 接口 1~4 | 583066,零写入 | +| ❌ 团期管理员改任意订单行程 | 接口 1~4 | 581008(存量,先于 583066) | +| ✅ 散客单改行程 | 接口 1~4 | 同改前 | +| ✅ 团期子订单打开调整弹窗 | 接口 5 | `editableTabLocksHint` 不含 `ITINERARY` / `SCHEDULE` | + +- 前端对团期子订单应隐藏行程页签的编辑、新增、删除按钮,避免用户点了才看到 583066。 +- 团期行程汇总下钻(GB-ADM-019)不再提供「跳去改」入口,保留跳转查看即可。 + +--- + +## 五、数据库行为 + +- 无 Flyway migration、无 DDL、无新增写入。 +- 团期子订单调接口 1~4:在任何写之前拒绝,`order_itinerary_day` / `order_itinerary_node` / `order_main` 零写入。 +- 散客单:写入行为与改前一致。 +- 接口 5 只读。 + +--- + +## 六、边界行为 + +- 团期子订单调接口 1~4 → 583066。 +- 团期管理员调接口 1~4 → 581008(不论散客还是团期子订单)。 +- 订单不存在 → 不在守卫拦,按原逻辑报 583050 / 583051。 +- 团期子订单调接口 5 → `editableTabLocksHint` 不含 `ITINERARY` / `SCHEDULE`。 +- 散客单 → 全部不变。 + +--- + +## 六.6、修改前后对比 + +| 行为(团期子订单) | 改前 | 改后 | +|------|------|------| +| 定制师 / 管理员改行程天、增改删节点 | 200,写入成功 | 583066,零写入 | +| 团期管理员改行程 | 581008 | 581008(不变) | +| 调整快照 `editableTabLocksHint`(全量) | 含 `ITINERARY`,流程状态早时含 `SCHEDULE` | 两者都不含 | +| 调整快照单 scope=`ITINERARY` / `SCHEDULE` | 回显该 scope | 空集合 | + +--- + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 请求 / 响应结构不变;团期子订单原来能成功的行程编辑现在返回 583066。 +- **前端是否必须同步上线**: 建议同步。行程页签对团期子订单要隐藏编辑、新增、删除按钮;团期行程汇总下钻去掉「跳去改」入口。不改前端也不会写坏数据,只是用户点了会看到 583066 提示。 +- **前端 workaround 清理点**: 调整弹窗里对团期子订单置灰出行日期 / 行程页签的自判逻辑,现在可以直接读 `editableTabLocksHint`。 + +--- + +## 七、不影响范围 + +- 散客(非团期)订单的全部行程编辑与调整行为。 +- 订单调整统一提交(`adjustment/submit`),团期子订单仍按 #8350 返回 587045。 +- 团期转期、改期同步、下单物化行程等内部路径。 +- 团期用餐口径(#8230 / #8343)、团期行程汇总与下钻(GB-ADM-018 / 019)的读取结果。 +- `POST /v3/admin/order/:id/confirm-itinerary`(只确认状态、不改行程内容)。 + +--- + +## 八、测试环境已验证 + +真实网关(TEST,`https://api.test.1814.love`,自签 admin token),部署检出 `b9f22e3ad`(含合并提交 `e8d83b0cb`),造数:团期子订单 `HL20260929154809598`(团号 T26-0352,资源准备中)、散客单 `HL20260810141518677`(资源准备中): + +| 用例 | 请求 | 结果 | 库内读数 | +|------|------|------|----------| +| 团期子订单改行程天 / 增 / 改 / 删节点 | 接口 1~4,管理员 token | 583066 ×4 | 订单主表、行程天 7 行、节点 28 行前后逐字一致 | +| 散客单原值回写行程天 | 接口 1 | 200 | 该天整行未变 | +| 散客单新增「莫日格勒河」→ 改描述 → 删除 | 接口 2 → 3 → 4 | 200 ×3 | 新节点写入、描述更新、软删;原有 14 个节点无变化 | +| 团期管理员改散客单行程 | 接口 1~4,团期管理员 token | 581008 ×4 | 三表前后逐字一致 | +| 团期管理员改团期子订单行程天 | 接口 1,团期管理员 token | 581008 | 角色守卫先于 583066 | +| 团期子订单调整快照 | 接口 5 | 200 | `editableTabLocksHint` = BASIC / PEOPLE / HOTEL_REQ / VEHICLE_REQ / FEE | +| 散客单调整快照 | 接口 5 | 200 | `editableTabLocksHint` 七项齐全,与改前规则一致 | +| 不带令牌 | 接口 1 | 401 | 零写入 | + +单元测试:定向集(行程 / 调整 / 错误码 / 架构包 + 转期改期回归)67 类 825 例,失败 2、错误 0;2 条失败在干净基底上逐条复现,属 #8714 既有,与本次无关。本次新增 13 例全部通过,含 ArchTest:订单行程写端点必须调团期子订单守卫、守卫只许该控制器调用。 + +部署: PR #8762 已合并 `dev-v3`(合并提交 `e8d83b0cb`),TEST 双实例滚动部署完成。 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#8750](https://git.1814.love/wx/HL/issues/8750) +- 关联 PR: [wx/HL#8762](https://git.1814.love/wx/HL/pulls/8762)、文档 [wx/HL#8763](https://git.1814.love/wx/HL/pulls/8763) +- 前置定案: [wx/HL#8350](https://git.1814.love/wx/HL/issues/8350) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#8750](https://git.1814.love/wx/HL/issues/8750) +- **PR**: [#8762](https://git.1814.love/wx/HL/pulls/8762) +- **Merge commit**: [e8d83b0cb](https://git.1814.love/wx/HL/commit/e8d83b0cb) + +### 联系人 + +- **后端负责人**: @jw