changelog-filename-gate / validate (push) Failing after 2s
保存人员配置 staffList 必填、备品候选 keyword 转义、加备品行定点查三端点 行为变更,前端实查零改动:saveGroupBatchStaff 请求体恒带 staffList,备品 keyword 原样透传,加行 500 上限解除对调用方透明。
338 行
13 KiB
Markdown
338 行
13 KiB
Markdown
---
|
||
schema: "hl-changelog/v2"
|
||
ticket: "7377"
|
||
title: "团期人员配置 staffList 改必填 + 备品候选关键字转义 + 加备品行改定点查"
|
||
consumer: "admin"
|
||
author: "jw(GIT)"
|
||
change_type: "修改接口"
|
||
backend_status: "deployed"
|
||
gateway_status: "verified"
|
||
frontend_status: "not_required"
|
||
frontend_owner: "mmg"
|
||
frontend_ref: ""
|
||
target_release: ""
|
||
verified_at: "2026-09-09"
|
||
status_note: "not_required 2026-09-09 mmg:三端点行为变更,前端零改动。①保存人员 saveGroupBatchStaff(orderV2GroupBatch.js:186) 请求体恒为 {staffList},省略字段路径不存在,清空即传 [](GroupBatchStaffConfigModal handleSave 过滤后数组恒在)。②备品候选 keyword 由 SuppliesPickerModal 原样透传(仅 trim),无前端通配依赖。③加备品行 addGroupBatchSupplies 只传 {suppliesResourceId,quantity},500 上限解除对调用方透明,589518 已正确区分库挂/无备品。"
|
||
updated_at: "2026-09-09"
|
||
base: "dev-v3"
|
||
---
|
||
|
||
# 团期人员配置必填与备品库口径收口
|
||
|
||
## 一、给前端的一句话
|
||
|
||
两处**行为变更**要注意:保存团期人员配置**必须带 `staffList` 字段**(要清空就传 `[]`,别省略);备品候选列表搜 `%` / `_` 不再返回全库。另有一处纯修复不影响调用方——加备品行不再受「备品库超 500 条」的隐性上限。
|
||
|
||
## 二、变更接口清单
|
||
|
||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| 1 | 保存团期人员配置 | PUT | `/v3/admin/group-batch/:productBatchId/staff` | 修改接口 | `staffList` 由可省略改为**必填**,缺失返 400;显式传 `[]` 仍是清空 |
|
||
| 2 | 团期物资候选列表 | GET | `/v3/admin/order/group-batch/:groupBatchId/supplies/candidates` | 修改接口 | `keyword` 转义 LIKE 通配符,`%` / `_` 不再当通配符 |
|
||
| 3 | 团期物资新增行 | POST | `/v3/admin/order/group-batch/:groupBatchId/supplies` | 修改接口 | 取备品库快照改走定点查,解除「备品库超 500 条后加不进」的隐性上限 |
|
||
|
||
## 三、接口详情
|
||
|
||
### 1. 保存团期人员配置 `PUT /v3/admin/group-batch/:productBatchId/staff`
|
||
|
||
**VO**: `BatchStaffConfigReqVO / BatchStaffConfigRespVO`
|
||
|
||
#### 使用场景
|
||
|
||
团期详情的「导游 / 摄影」配置,全量覆盖式保存。
|
||
|
||
#### 入参
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `productBatchId` | Path | String | 是 | 正整数 | 产品侧班期 ID |
|
||
| `staffList` | Body | Array | **是(本次变更)** | 可为空数组 | 全量覆盖列表;**显式传 `[]` 即清空**,字段缺失一律 400 |
|
||
| `staffList[].staffId` | Body | String | 是 | 正整数 | 资源域人员 ID |
|
||
| `staffList[].staffRole` | Body | String | 是 | `LEADER` / `GUIDE` / `DRIVER` / `PHOTOGRAPHER` / `OTHER`,区分大小写 | 越界值返 400;与该人在资源域的实际类型不符返 582114 |
|
||
| `staffList[].sortOrder` | Body | Integer | 否 | 默认 0 | 展示排序 |
|
||
| `staffList[].remark` | Body | String | 否 | 最长 500 | 备注 |
|
||
|
||
#### 出参
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 |
|
||
| `data.staffList` | Array | 保存后的配置快照,含 `staffId` / `staffName` / `staffPhone` / `staffRole` 等 |
|
||
| `data.affectedOrderCount` | Integer | 已触发异步扇出的活跃订单数,仅作提示 |
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
{
|
||
"staffList": [
|
||
{
|
||
"staffId": "1002",
|
||
"staffRole": "GUIDE",
|
||
"sortOrder": 0
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"success": true,
|
||
"data": {
|
||
"staffList": [
|
||
{
|
||
"staffId": "1002",
|
||
"staffName": "李雪梅",
|
||
"staffPhone": "138****1002",
|
||
"staffRole": "GUIDE",
|
||
"sortOrder": 0
|
||
}
|
||
],
|
||
"affectedOrderCount": 2
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
显式传 `[]` 是合法请求:清空该团期全部人员配置并返回空 `staffList`,`code` 仍为 200。
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 400,
|
||
"message": "staff 配置列表不能缺失;确要清空请显式传空数组 []",
|
||
"data": null,
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
#### 业务边界
|
||
|
||
- **字段缺失或字段名写错一律 400,且现有配置零变动、不触发扇出**——这正是本次要堵的洞。
|
||
- 团期未创建返 589553、未成团返 589552;人员角色与资源域类型不符返 582114,整批拒绝不落库。
|
||
- 「显式传 `[]` 即清空」这条既有语义**没有改变**。
|
||
|
||
### 2. 团期物资候选列表 `GET /v3/admin/order/group-batch/:groupBatchId/supplies/candidates`
|
||
|
||
**VO**: `SuppliesCandidateRespVO`
|
||
|
||
#### 使用场景
|
||
|
||
团期物资清单的「从备品库选择」弹窗,支持按分类与关键字筛选。
|
||
|
||
#### 入参
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `groupBatchId` | Path | String | 是 | 正整数 | 团期主订单 ID |
|
||
| `categoryCode` | Query | String | 否 | 字典 `supplies_category` | 分类筛选 |
|
||
| `keyword` | Query | String | 否 | 无长度限制 | 匹配名称或副标题;**本次起 LIKE 通配符被转义** |
|
||
|
||
#### 出参
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 |
|
||
| `data[].suppliesResourceId` | String | 备品资源 ID |
|
||
| `data[].suppliesName` / `subtitle` / `category` | String | 备品名称、副标题、分类 |
|
||
| `data[].billingType` / `hasCost` / `basePrice` | String / Boolean / String | 计费方式、是否收费、库价 |
|
||
| `data[].added` / `batchSuppliesId` | Boolean / String | 是否已加入本团期清单及对应行 ID |
|
||
|
||
#### 请求示例
|
||
|
||
```http
|
||
GET /v3/admin/order/group-batch/2097500233511362561/supplies/candidates?keyword=%E5%B8%BD
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"suppliesResourceId": "2023438566624866305",
|
||
"suppliesName": "定制遮阳帽",
|
||
"subtitle": "呼籁旅行专属防晒鸭舌帽",
|
||
"category": "personal_gear",
|
||
"billingType": "PER_PERSON",
|
||
"hasCost": true,
|
||
"basePrice": "15.00",
|
||
"added": false,
|
||
"batchSuppliesId": null
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
无命中返回 `data: []`,`code` 仍为 200。
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 589500,
|
||
"message": "团期不存在",
|
||
"data": null,
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
#### 业务边界
|
||
|
||
- `keyword` 里的 `%` / `_` / `\` / `!` 一律按**字面字符**匹配,不再当通配符。
|
||
- 普通关键字的搜索结果**不受影响**(实测「帽」仍命中「定制遮阳帽」)。
|
||
- 只返上架备品,排序与分页口径未变。
|
||
|
||
### 3. 团期物资新增行 `POST /v3/admin/order/group-batch/:groupBatchId/supplies`
|
||
|
||
**VO**: `AddSuppliesReqVO`
|
||
|
||
#### 使用场景
|
||
|
||
团期物资清单新增一行,可从备品库选择(传 `suppliesResourceId`)或纯手填(传 `suppliesName`)。
|
||
|
||
#### 入参
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|---|---|---|---|---|---|
|
||
| `groupBatchId` | Path | String | 是 | 正整数 | 团期主订单 ID |
|
||
| `suppliesResourceId` | Body | String | 与 `suppliesName` 二选一 | 正整数 | 备品库资源 ID;传了则名称、分类、计费方式以库为准覆盖入参 |
|
||
| `suppliesName` | Body | String | 与 `suppliesResourceId` 二选一 | 最长 100 | 纯手填备品名 |
|
||
| `quantity` | Body | Integer | 是 | 大于 0 | 数量 |
|
||
| `unitPrice` | Body | String | 否 | 金额字符串 | 单价,**入参优先**;不传取库价 |
|
||
|
||
#### 出参
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 |
|
||
| `data` | String | 新建行主键 `batchSuppliesId` |
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
{
|
||
"suppliesResourceId": "2023438566624866305",
|
||
"quantity": 1
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"success": true,
|
||
"data": "2097600714757783553"
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
无空成功结果;任一校验失败返回 `data: null`、`success: false`。
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 589523,
|
||
"message": "该备品已在本团期物资清单中,请直接调整数量",
|
||
"data": null,
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
#### 业务边界
|
||
|
||
- 备品库不可用(资源域故障)与备品本身不可用(不存在 / 已下架)**分开报**:前者 589522,后者 589519。
|
||
- 同一 `suppliesResourceId` 重复加入返 589523;纯手填行不参与判重。
|
||
- 团期不在「物料准备中」返 589520,团期不存在返 589500。
|
||
|
||
## 四、契约约束与正确调用方式
|
||
|
||
- 保存人员配置**永远显式带上 `staffList`**:要清空传 `[]`,不要省略字段。省略在改前会静音清空整团配置,现在会 400。
|
||
- 备品搜索框不需要前端做任何转义,直接把用户输入原样传 `keyword` 即可。
|
||
- 加备品行时 `unitPrice` 不传即取库价;传了以入参为准(同团期可议价不同)。
|
||
|
||
## 五、数据库行为
|
||
|
||
- 人员配置:全量覆盖式保存——软删旧行 + 插入新行,随后异步扇出到该班期全部活跃订单的 `order_staff_assignment`。校验失败时**一行都不写**。
|
||
- 物资新增:向 `order_batch_supplies` 插一行;重复 `supplies_resource_id` 在插入前被拒。
|
||
- 本次**无表结构变更、无 Flyway 脚本**。
|
||
|
||
## 六、边界行为
|
||
|
||
- 人员配置越界角色值区分大小写,`guide` 不等于 `GUIDE`。
|
||
- 备品候选列表只返上架备品;已下架的既有行不受影响,仍留在清单里。
|
||
- 加备品行的库快照覆盖入参的字段是名称、分类、计费方式;单价例外,入参优先。
|
||
|
||
## 六.6、修改前后对比
|
||
|
||
| 场景 | 改前 | 改后 |
|
||
|---|---|---|
|
||
| 保存人员配置时不带 `staffList`(或字段名写错) | **静音软删整团人员配置 + 触发全团扇出,返 200** | 返 400,现有配置零变动 |
|
||
| 保存人员配置时显式传 `[]` | 清空 | 清空(**不变**) |
|
||
| 备品候选搜 `%` 或 `_` | 返回全库 | 返回零结果 |
|
||
| 备品候选搜普通关键字 | 正常命中 | 正常命中(**不变**) |
|
||
| 备品库超过 500 条时加第 501 条之后的备品 | 一律报「所选备品不存在或已下架」,实际是被 500 条上限截断 | 正常加入 |
|
||
| 资源域故障时加备品行 | 报「不存在或已下架」,与备品真下架无法区分 | 报 589522「备品库查询失败」,与 589519 分开 |
|
||
|
||
## 六.7、影响评估
|
||
|
||
- **需要前端配合的只有一条**:保存人员配置必须带 `staffList`。经排查管理后台现有调用是带该字段的,理论上不受影响;但字段名一旦写错,改前是静默清空、改后是 400,属"报错优于静默毁数据"。
|
||
- 备品搜索语义变化只影响把 `%` / `_` 当通配符用的用法,业务上没有这种用法。
|
||
- 加备品行的改动对调用方**完全透明**,契约与错误码不变,只是不再有隐性上限。
|
||
|
||
## 七、不影响范围
|
||
|
||
- 团期看板、详情、子订单、合同保险、财务、流团与退单等其余端点未改动。
|
||
- 备品清单的查询、改数量、删除、确认物资四个端点契约不变。
|
||
- 无表结构变更、无网关路由变更、无新增错误码(589519 / 589522 / 589523 / 582114 均为既有)。
|
||
|
||
## 八、测试环境已验证
|
||
|
||
真实网关(`api.test.1814.love`)逐条实测,`dev-v3` 分支已部署 `hl-resource-service` 与 `hl-order-service-v3`:
|
||
|
||
| 用例 | 结果 |
|
||
|---|---|
|
||
| 保存人员配置缺 `staffList`(模拟字段名写成 `items`) | 400「staff 配置列表不能缺失;确要清空请显式传空数组 []」 |
|
||
| 保存人员配置显式传 `[]` | 200,配置清空为 0 条 |
|
||
| GUIDE 类型的人配成 PHOTOGRAPHER | 582114,配置数仍为 0(零写入) |
|
||
| GUIDE 类型的人配成 GUIDE | 200,回显 `staffId` / `staffName` / `staffPhone` / `staffRole` 全字段 |
|
||
| 配完后看导游位候选 | `assigned=true`、`assignedRole=GUIDE` |
|
||
| 配完后看摄影位候选 | 该人不在候选内(勾选态按配置位收敛) |
|
||
| 备品候选 `keyword=%` / `keyword=_` | 均返 0 条(改前返回全库 15 条) |
|
||
| 备品候选 `keyword=帽` | 返 1 条「定制遮阳帽」 |
|
||
| 从备品库加行 | 200,库快照生效(`billingType=PER_PERSON`、`hasCost=true`、`unitPrice=15.00`) |
|
||
| 同一备品再加一次 | 589523,清单无重复行 |
|
||
| 三个写端点传不存在的 ID | 589500 / 589521 / 589553,一律零写入 |
|
||
|
||
单元测试:`hl-order-service-v3` 全量 9303 例、`hl-resource-service` 全量 2707 例,均 0 失败 0 错误。
|
||
|
||
## 九、相关历史 PR
|
||
|
||
- PR #7028(本次合入,含 2026-09-03 审计的 F-01 / F-02 / F-04 三项中危修复)
|
||
- 前序 #6950(团期人员配置候选列表)、#6986(团期物资备品库选择)
|
||
|
||
## 十、相关文档
|
||
|
||
- 审计记录:`dev-records/records/2026-09-03-local-group-tour-6950-6986-post-merge-audit.md`
|
||
- 实施单:`docs/group/实施单/07-人员-导游摄影司机.html`、`docs/group/实施单/10-物资清单.html`
|
||
|
||
## 关联 / 联系人
|
||
|
||
- 工单:#7377(收口审计余项)、#7028(PR)
|
||
- 后端:jw
|
||
- 前端:mmg —— 只需确认保存人员配置时始终带 `staffList` 字段
|