# 第5步补充信息保存接口 - 融合「团队配置」，下线独立班期团队保存接口

**日期**: 2026-04-18
**PR**: #873 → 已合并到 dev，待部署测试环境
**Issue**: #871
**影响端**: 管理后台 Web (hl-ui-admin)
**影响接口**:
- `PUT /admin/product/item/{id}/supplement` — 新增 `scheduleTeams` 字段
- `PUT /admin/product/item/{id}/schedule/team` — **下线**（删除 PUT 方法；GET 同路径仍保留做团队查询）

## 背景

产品编辑第5步「团队与须知」页面左侧菜单新增「团队配置」，按班期（第1期…第N期）tab 切换，每个班期维护一组团队成员（领队/司机/摄影师等）。

此前页面保存需要并发触发两个请求：
- 第5步补充信息 → `PUT /admin/product/item/{id}/supplement`
- 每个班期的团队成员 → `PUT /admin/product/item/{id}/schedule/team`

本次改造将班期团队保存融合进第5步补充信息接口，**前端一次请求**即可保存整个第5步。

## 接口变化

### 1. `PUT /admin/product/item/{id}/supplement` 请求体新增字段

新增顶级字段 `scheduleTeams: List<ScheduleTeamItem>`，每项结构：

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `batchId` | Long | 是 | 班期ID（需属于当前产品，否则报"班期不属于该产品"） |
| `members` | List&lt;StaffItem&gt; | 否 | 团队成员列表；**空数组 `[]` = 清空该班期团队** |

`StaffItem` 结构与旧 `PUT /schedule/team` 请求体中的 `staffList` 项完全一致：

| 字段 | 类型 | 说明 |
|---|---|---|
| `staffId` | Long | 员工ID（可选） |
| `staffRole` | String | 员工角色：LEADER=领队, PHOTOGRAPHER=摄影师, DRIVER=司机, OTHER=其他 |
| `staffName` | String | 员工名称 |
| `staffPhone` | String | 员工电话 |
| `remark` | String | 备注 |
| `sortOrder` | Integer | 排序 |

### 2. `PUT /admin/product/item/{id}/schedule/team` 下线

Controller 方法 `saveScheduleTeam` 已删除，发送 `PUT` 请求到此路径会得到 Spring `HttpRequestMethodNotSupportedException`（项目统一包装成 `{code:500, message:"服务器内部错误"}`；HTTP 仍返 200）。

**前端请删除对该接口的所有调用**，改为在第5步整体保存时通过 `scheduleTeams` 字段一并提交。

### 3. `GET /admin/product/item/{id}/schedule/team?batchId=xxx` 保留

团队查询接口保持不变。第5步页面打开时仍按班期拉取现有团队成员用于渲染。

## 空值语义（重要）

| `scheduleTeams` 取值 | 后端行为 |
|---|---|
| `null`（字段不传）| 不修改任何班期的团队（用于前端分步保存草稿场景） |
| `[]`（空数组）| 不修改任何班期的团队 |
| `[{batchId:1, members:[...]}]` | 仅全量替换 `batchId=1` 的团队成员（全删全插） |
| `[{batchId:1, members:[]}]` | 清空 `batchId=1` 的团队成员 |
| `[{batchId:1,...}, {batchId:2,...}]` | 按班期逐个全删全插 |

## 请求体示例

```json
PUT /admin/product/item/2044306857534636034/supplement
Content-Type: application/json

{
  "includedFees": [...],
  "excludedFees": [...],
  "customFees": [...],
  "childTicket": "FREE",
  "insuranceNotice": "INCLUDED",
  "refundPolicyId": 1001,
  "scheduleTeams": [
    {
      "batchId": 9001,
      "members": [
        { "staffName": "张领队", "staffRole": "LEADER", "staffPhone": "13800000000", "sortOrder": 1 },
        { "staffName": "李司机", "staffRole": "DRIVER",  "staffPhone": "13900000000", "sortOrder": 2 }
      ]
    },
    {
      "batchId": 9002,
      "members": []
    }
  ]
}
```

上例：
- `batchId=9001` 的团队全量替换为 2 名成员
- `batchId=9002` 的团队清空
- 未出现在 `scheduleTeams` 里的其他班期团队不受影响

## 事务语义

第5步的费用 / 人群优惠 / 补充配置 / 所有班期团队**共用同一数据库事务**。任何一个班期保存失败（例如 `batchId` 不属于当前产品），整个第5步保存全部回滚——不会出现"补充信息保存成功但某些班期团队没写入"的中间状态。

## 前端迁移清单

- [ ] 移除所有 `PUT /admin/product/item/{id}/schedule/team` 的调用
- [ ] 第5步保存时把各班期的团队成员列表聚合到 `scheduleTeams` 数组里一并提交
- [ ] 团队查询（`GET /schedule/team`）保持原样调用
- [ ] 错误提示注意：跨产品 batchId 会报"班期不属于该产品"，产品草稿状态错误会报既有的"产品不可编辑"

## 不影响

- 第5步其他字段（费用 / 人群优惠 / 车辆 / 保险 / 须知 / 卖点 / 营销内容）结构完全不变
- 团队成员表 `group_batch_staff` 结构不变
- DB 无 DDL 变更
- `GroupBatchStaffRespVO`、内部 Feign 接口不变（订单服务读取团队成员的接口不受影响）

🤖 Generated with [Claude Code](https://claude.com/claude-code)
