# 出行人软删: DELETE → POST /traveler/{travelerId}/delete 路径迁移 + 真实业务化

> **服务**: hl-order-service-v3 (端口 8084 / 二期)
> **PR**: #2537
> **Issue**: #2520
> **日期**: 2026-05-18
> **影响范围**: 管理后台 F27 改人数场景(临时取消同行 1 人)
> **存放目录**: `changelogs-v2/2026-05/`(二期 v3 专属,带 -v2 后缀)
> **部署 commit**: dev-v3 `34c294465`
> **测试服已验证**: ✅(/@qa 通过 9443 网关 + 真 admin token round-trip)

---

## ⚠️ 关键变化

1. **破坏性路径 + 方法迁移**:
   - **方法**: DELETE → POST(对齐 v3 风格,所有写操作统一 POST)
   - **路径**: `/travelers/{travelerId}` → `/traveler/{travelerId}/delete`(对齐 V5.48 §2.4)
2. **从 Mock 占位变真实业务**: 4 道校验 + 软删 + 桥接表级联软删 + count-1 + status_log。
3. **新增 2 个错误码**: `581106`(已签合同禁删) / `581107`(最后 1 成人禁删)。
   - ⚠️ 注意码段: **不是 581140/581141**,大交通段位 581120+ 已被占用,出行人主段位空挡是 581106/581107 紧贴主块(文档原稿写的 581119/581120 已被 TRAVELER_ID_CARD_DUPLICATE / TRANSPORT_PLAN_NOT_FOUND 占用)。
4. **加幂等 + 锁整事务**: `@Idempotent(3s) + @Lock4j(30s)`。

---

## 一、背景

文档 V5.48 §2.4 定义: 软删除单行 `order_traveler`,同步:
1. UPDATE `order_main.<type>Count` -1
2. UPDATE `order_transport_plan_traveler.deleted=1`(级联解除其在大交通桥接表的所有关联)
3. INSERT `order_status_log`,reason="删除出行人"

并有 2 条业务硬约束:
- **已签电子合同(contract_status = SIGNED)的订单禁止删人**(合同已固定参与人,删除会破坏合同人员一致性)
- **订单最后 1 位成人禁止删除**(无成人订单逻辑上无效,会影响保险 / 大交通 / 房型分配)

---

## 二、变更接口清单

| # | 接口 | 方法 | 老路径 | 新路径 | 变更类型 |
|---|------|------|--------|--------|----------|
| 1 | 出行人软删 | DELETE → **POST** | `/v3/admin/order/{id}/travelers/{travelerId}` | `/v3/admin/order/{id}/traveler/{travelerId}/delete` | 破坏性方法 + 路径迁移 + 真实业务化 |

---

## 三、接口详情

### 1. 出行人软删 `POST /v3/admin/order/{id}/traveler/{travelerId}/delete`

#### 入参

| 字段 | 位置 | 类型 | 必填 | 说明 |
|------|------|------|------|------|
| `id` | Path | Long | ✅ | 订单 ID |
| `travelerId` | Path | Long | ✅ | 出行人 ID |

(请求体: 无)

#### 出参 `Result<Boolean>`

```json
{
  "code": 200,
  "data": true,
  "msg": "success"
}
```

#### 请求示例

```
POST /v3/admin/order/60123456789012/traveler/70123456789013/delete
Authorization: Bearer {admin_jwt}
```

#### 错误响应清单(本工单新增段位)

| 错误码 | 含义 |
|---|---|
| `581100` | 出行人不存在 |
| `581102` | 订单不存在,无法删除出行人 |
| `581106` | **已签电子合同,禁止删除出行人**(本 PR 新增) |
| `581107` | **出行人是订单最后 1 位成人,禁止删除**(本 PR 新增) |
| `581110` | 出行人 ID 不属于该订单 |

> 段位说明: 文档原稿 §2.4 期望 `581119 / 581120`,但 581119 已被 TRAVELER_ID_CARD_DUPLICATE 占用,581120 已被 TRANSPORT_PLAN_NOT_FOUND 占用。本 PR 落到出行人主段位空挡 **581106 / 581107**,紧贴主块,保留 581140-581144 给 §2.6 大交通后续工单。

错误响应体示例:

```json
{
  "code": 581106,
  "msg": "已签电子合同,禁止删除出行人",
  "data": null
}
```

---

## 四、契约约束

### 校验顺序(任一失败整事务回滚,4 道闸)

1. **订单存在性**(581102): 查 `order_main` by `id`
2. **出行人归属**(581110): 出行人的 `order_id` 必须等于 path `id`(防越权 / 错传)
3. **合同冻结**(581106): 订单 `contract_status = SIGNED` → 拒绝
4. **最后 1 成人**(581107): 当前操作是 ADULT 且订单未软删的 ADULT 行数 = 1 → 拒绝

### 并发控制

- `@Idempotent(timeout=3s)`: 同 `(orderId, travelerId)` 3 秒重复提交直接拒绝(防快速双击)
- `@Lock4j(expire=30000ms)`: 同订单 30 秒锁,与 batch-edit / add 互斥,防 count 字段 race

---

## 五、数据库行为

整事务:

| 操作 | 表 | 说明 |
|------|------|------|
| UPDATE | `order_traveler` | `deleted=1` `deleted_at=NOW()` |
| UPDATE | `order_transport_plan_traveler` | 该出行人所有未软删的桥接行 `deleted=1`(级联解除大交通关联) |
| UPDATE | `order_main.<type>Count` | -1(按 `travelerType` 选择: adultCount / childCount / youngChildCount / babyCount) |
| INSERT | `order_status_log` | reason="删除出行人",flowStatus 不变(from == to) |

---

## 六、边界行为

- 未登录 → 401(网关拦截)
- 订单不存在 → 581102
- 出行人不属于订单(越权) → 581110
- 合同 SIGNED → 581106(优先于人数校验)
- 删最后 1 成人 → 581107(允许删 CHILD / YOUNG_CHILD / BABY 即使他们也是最后 1 个)
- 出行人有大交通关联 → 自动级联软删桥接表,不阻断主流程
- 出行人已被软删过(重复删) → 581100(查不到行)

---

## 七、不影响范围

- **仅影响**: 管理后台 F27 改人数(删除)场景
- **零影响**:
  - 批量编辑 `/traveler/batch-edit`(#2518 独立接口)
  - 新增 `/traveler/add`(#2519 独立接口)
  - 列表 `/traveler/list`(只读)
  - 大交通错误码段位 581120-581129(保留)
  - 老路径 `DELETE /travelers/{travelerId}` 已下线,前端调用必须改用 `POST /traveler/{travelerId}/delete`

---

## 八、测试环境已验证

```
POST https://web.test.1814.love:9443/v3/admin/order/60123456789012/traveler/70123456789013/delete
  Authorization: Bearer {admin_jwt}
  → 200 + data: true ✓
  → order_traveler.deleted=1 ✓
  → order_transport_plan_traveler 级联 deleted=1 ✓
  → order_main.child_count -1 ✓
  → order_status_log 新增 1 行 reason=删除出行人 ✓
反例:
  - 合同 SIGNED 删人 → 581106 ✓
  - 删最后 1 成人 → 581107 ✓
  - travelerId 不属于该订单 → 581110 ✓
  - DELETE 老方法老路径 → 404 / 405 ✓
```

---

## 九、相关历史 PR

| PR | Issue | 说明 | 是否仍有效 |
|----|-------|------|------------|
| #1984 | - | [§2 traveler skeleton] 空骨架 Mock | ❌ 被本 PR 真实化覆盖 |
| #2535 | #2518 | batch-edit 真实化(同期工单,共用 581100-581114) | ✅ 有效 |
| #2536 | #2519 | add 真实化(同期工单,581115-581118) | ✅ 有效 |
| **本 PR #2537** | **#2520** | delete 接通真实业务 + 方法+路径迁移 + 2 错误码(581106/581107) | ✅ 最新 |

---

## 十、相关文档

- API SPEC: `D:/work2/HL-v3/docs/order-v3/api/API-SPEC-V5.48.html` §2.4
- 关联 Issue: [wx/HL#2520](https://git.1814.love:8443/wx/HL/issues/2520)
- 关联 PR: [wx/HL#2537](https://git.1814.love:8443/wx/HL/pulls/2537)
