diff --git a/changelogs-v2/2026-09/10_7455_团期十五个端点补判权-修改接口-管理后台.md b/changelogs-v2/2026-09/10_7455_团期十五个端点补判权-修改接口-管理后台.md new file mode 100644 index 00000000..2306f333 --- /dev/null +++ b/changelogs-v2/2026-09/10_7455_团期十五个端点补判权-修改接口-管理后台.md @@ -0,0 +1,1039 @@ +--- +schema: "hl-changelog/v2" +ticket: "7455" +title: "团期十五个 admin 端点补判权:写口 group-batch:manage / 详情名单 view / 列表看板 list" +consumer: "admin" +author: "jw(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-10" +status_note: "⚠️ 对外行为变更。团期域十五个 admin 端点此前零判权——任意能登录后台的账号都能调,其中六个是写口(物资增改删、确认物资会推进团期阶段、保存人员配置会扇出到团内全部活跃订单、设主次报账人),读口里子订单还带逐户出行人明细。现按三层收口:物资写与人员写要 group-batch:manage,详情/子订单/物资读/人员读要 group-batch:view,列表/看板/产品选择器要 group-batch:list。三个码取自权限种子 V20260831_002 的注册描述,不是新拍的,也不新增授权种子。管理员/超管完全不受影响;财务能看不能改;定制师全部被挡(他的入口在订单侧提需求,不碰团期域端点)。前端两条必看:① 入参出参既有错误码一律未变,不需要改请求代码,只需处理新出现的 589507;② 建议按码做菜单级/按钮级预判——list 决定团期菜单可见、view 决定详情可进、manage 决定编辑按钮可点。判权在进入业务逻辑之前,被拒时零写入、不写时间线、不触发扇出(已用 TEST SQL 佐证)。TEST 上十五个端点×三种角色 45 次调用逐条取证。" +updated_at: "2026-09-10" +base: "dev-v3" +--- + +# 团期十五个 admin 端点补判权 + +## 一、给前端的一句话 + +团期的**列表 / 看板 / 详情 / 子订单名单 / 物资 / 人员配置**这十五个端点,以前**任何能登录后台的账号都能调** +(一个权限都不判),现在按三层收口:看列表要 `group-batch:list`,看详情和名单要 `group-batch:view`, +改物资和人员配置要 `group-batch:manage`。**管理员 / 超级管理员不受影响**,财务**能看不能改**,其余角色返 `589507`。 + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|---|---|---|---|---| +| 1 | 管理员新增团期备品行 | POST | `/v3/admin/order/group-batch/:groupBatchId/supplies` | 修改 | 补判权 `group-batch:manage`,其余不变 | +| 2 | 调整团期备品数量 | PUT | `/v3/admin/order/group-batch/supplies/:batchSuppliesId/quantity` | 修改 | 补判权 `group-batch:manage`,其余不变 | +| 3 | 软删团期备品行 | DELETE | `/v3/admin/order/group-batch/supplies/:batchSuppliesId` | 修改 | 补判权 `group-batch:manage`,其余不变 | +| 4 | 确认团期物资 | POST | `/v3/admin/order/group-batch/:groupBatchId/confirm-material` | 修改 | 补判权 `group-batch:manage`,其余不变 | +| 5 | 全量保存团期人员配置 | PUT | `/v3/admin/group-batch/:productBatchId/staff` | 修改 | 补判权 `group-batch:manage`,其余不变 | +| 6 | 设置团期报账人等级 | PUT | `/v3/admin/group-batch/:productBatchId/staff/:staffId/reporter-rank` | 修改 | 补判权 `group-batch:manage`,其余不变 | +| 7 | 团期详情 | GET | `/v3/admin/order/group-batch/:groupBatchId` | 修改 | 补判权 `group-batch:view`,其余不变 | +| 8 | 团期下子订单列表 | GET | `/v3/admin/order/group-batch/:groupBatchId/orders` | 修改 | 补判权 `group-batch:view`,其余不变 | +| 9 | 查询团期备品列表 | GET | `/v3/admin/order/group-batch/:groupBatchId/supplies` | 修改 | 补判权 `group-batch:view`,其余不变 | +| 10 | 查询备品候选 | GET | `/v3/admin/order/group-batch/:groupBatchId/supplies/candidates` | 修改 | 补判权 `group-batch:view`,其余不变 | +| 11 | 查询团期人员配置列表 | GET | `/v3/admin/group-batch/:productBatchId/staff` | 修改 | 补判权 `group-batch:view`,其余不变 | +| 12 | 查询团期人员候选 | GET | `/v3/admin/group-batch/:productBatchId/staff/candidates` | 修改 | 补判权 `group-batch:view`,其余不变 | +| 13 | 团期分页列表 | GET | `/v3/admin/order/group-batch` | 修改 | 补判权 `group-batch:list`,其余不变 | +| 14 | 团期看板列表 | GET | `/v3/admin/order/group-batch/board` | 修改 | 补判权 `group-batch:list`,其余不变 | +| 15 | 团期控制台产品选择器 | GET | `/v3/admin/order/group-batch/products` | 修改 | 补判权 `group-batch:list`,其余不变 | +## 三、接口详情 + +> 十五个端点的**入参、出参、业务语义、既有错误码一律未变**,唯一变化是请求进入业务逻辑前多一道权限校验。 + +### 1. 管理员新增团期备品行 `POST /v3/admin/order/group-batch/:groupBatchId/supplies` + +**VO**: `AddSuppliesReqVO` + +#### 使用场景 + +团期详情「物资清单」页手工加一行备品。可从备品库选(带 `suppliesResourceId`),也可纯手填。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `groupBatchId` | path | Long | 是 | 雪花 ID,按字符串传 | 团期 ID | +| `suppliesName` | body | String | 否 | — | 备品名称;从库选时可由服务端回填 | +| `suppliesResourceId` | body | Long | 否 | — | 备品库资源 ID;传了就按库里口径取计费方式与单价 | +| `unitPrice` | body | BigDecimal | 否 | — | 单价;不传取库价 | +| `quantity` | body | Integer | **是** | 非空 | 数量 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | String | 新建的备品行 ID(雪花,按字符串下发) | + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/2097498512387104770/supplies +Authorization: Bearer +Content-Type: application/json + +{"suppliesName":"矿泉水","quantity":20,"unitPrice":2.00} +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": "2097963098638876675", "success": true } +``` + +#### 空数据 / 降级响应 + +写口无空数据形态。备品库不可用仍报 `589522`,所选备品不存在或已下架仍报 `589519`——**与本次变更无关,行为未改**。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:manage`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时**一行都不落库**(已用 TEST SQL 佐证:被拒后表里查不到该行)。 +- 既有的阶段门、备品重复校验(`589523`)全部保留,**顺序在判权之后**。 +- 权限码 `group-batch:manage`。 + +### 2. 调整团期备品数量 `PUT /v3/admin/order/group-batch/supplies/:batchSuppliesId/quantity` + +**VO**: `AdjustSuppliesQuantityReqVO` + +#### 使用场景 + +物资清单里改某一行的数量。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `batchSuppliesId` | path | Long | 是 | 雪花 ID | 备品行 ID | +| `quantity` | body | Integer | 是 | — | 新数量 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | null | 无返回体 | + +#### 请求示例 + +```http +PUT /v3/admin/order/group-batch/supplies/8801/quantity +Authorization: Bearer +Content-Type: application/json + +{"quantity":30} +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": null, "success": true } +``` + +#### 空数据 / 降级响应 + +写口无空数据形态。备品行不存在仍报 `589521`,行为未改。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:manage`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时数量**不被改写**。 +- 行存在性校验与阶段门保留,顺序在判权之后。 +- 权限码 `group-batch:manage`。 + +### 3. 软删团期备品行 `DELETE /v3/admin/order/group-batch/supplies/:batchSuppliesId` + +**VO**: `无请求体` + +#### 使用场景 + +物资清单里删掉一行。软删,不物理删除。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `batchSuppliesId` | path | Long | 是 | 雪花 ID | 备品行 ID | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | null | 无返回体 | + +#### 请求示例 + +```http +DELETE /v3/admin/order/group-batch/supplies/8801 +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": null, "success": true } +``` + +#### 空数据 / 降级响应 + +写口无空数据形态。行不存在仍报 `589521`,行为未改。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:manage`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时**不软删任何行**。 +- 软删语义与阶段门保留,顺序在判权之后。 +- 权限码 `group-batch:manage`。 + +### 4. 确认团期物资 `POST /v3/admin/order/group-batch/:groupBatchId/confirm-material` + +**VO**: `无请求体` + +#### 使用场景 + +物资备齐后点「确认物资清单」。这个动作会把团期从「物料准备中」推向「待出发」(需同时满足子订单全确认的双门)。**六个写口里它的破坏力最大——它推的是团期阶段。** + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `groupBatchId` | path | Long | 是 | 雪花 ID,按字符串传 | 团期 ID | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | null | 无返回体 | + +#### 请求示例 + +```http +POST /v3/admin/order/group-batch/2097498512387104770/confirm-material +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": null, "success": true } +``` + +#### 空数据 / 降级响应 + +写口无空数据形态。双门未同时满足时状态停在「物料准备中」并照常返回成功,这是既有语义,行为未改。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:manage`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时**团期阶段不被推进**,`material_confirmed` 不置位,时间线不写。 +- 团期不存在仍 `589500`、状态不符仍 `589501`;**顺序:先判权,再判状态**。 +- 权限码 `group-batch:manage`。 + +### 5. 全量保存团期人员配置 `PUT /v3/admin/group-batch/:productBatchId/staff` + +**VO**: `BatchStaffConfigReqVO` + +#### 使用场景 + +团期「导游 / 摄影」页整批保存人员配置。**传入列表即最终状态(全量覆盖)**,保存成功后**异步扇出到团内全部活跃订单**。⚠️ 路径前缀是 `/v3/admin/group-batch`(**不在 `/order/` 下**),路径变量是产品侧排期 ID。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `productBatchId` | path | Long | 是 | 雪花 ID;**产品侧排期 ID,不是团期主键** | 产品侧排期 ID | +| `staffList` | body | Array | **是** | 非空字段(`#7377` 起字段缺失一律 400;**要清空请显式传 `[]`**) | 人员配置列表,全量覆盖 | +| `staffList[].staffId` | body | Long | 是 | — | 员工 ID | +| `staffList[].staffRole` | body | String | 是 | 取值域 + 资源域实际 `staffType` 双校验(`582114`) | 角色位 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | Object | 保存后的配置快照(结构未变) | + +#### 请求示例 + +```http +PUT /v3/admin/group-batch/2097498511120478210/staff +Authorization: Bearer +Content-Type: application/json + +{"staffList":[{"staffId":3301,"staffRole":"GUIDE"}]} +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": { "staffList": [] }, "success": true } +``` + +#### 空数据 / 降级响应 + +显式传 `staffList: []` 表示清空配置,仍是合法请求(`#7377` 定的语义,未改);字段缺失一律 400,也未改。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:manage`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时**既不覆盖配置,也不触发向团内订单的扇出**。 +- `#7377` 的 `@NotNull`、`582114` 类型校验保留,顺序在判权之后。 +- 权限码 `group-batch:manage`。 + +### 6. 设置团期报账人等级 `PUT /v3/admin/group-batch/:productBatchId/staff/:staffId/reporter-rank` + +**VO**: `SetReporterRankReqVO` + +#### 使用场景 + +指定团期的主 / 次报账人。报账人决定核团阶段**谁来记账、成本记在谁头上**,不是无关紧要的标记。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `productBatchId` | path | Long | 是 | 雪花 ID;**产品侧排期 ID,不是团期主键** | 产品侧排期 ID | +| `staffId` | path | Long | 是 | 必须是该团期已配置的 staff | 员工 ID | +| `reporterRank` | body | String(枚举) | 是 | `PRIMARY` / `SECONDARY` / `NONE` | 报账人等级 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | null | 无返回体 | + +#### 请求示例 + +```http +PUT /v3/admin/group-batch/2097498511120478210/staff/3301/reporter-rank +Authorization: Bearer +Content-Type: application/json + +{"reporterRank":"PRIMARY"} +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": null, "success": true } +``` + +#### 空数据 / 降级响应 + +写口无空数据形态。团期尚未成团仍报 `589552`,行为未改。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:manage`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时**报账人一个字段都不被改动**(含「设新主报账人时原主自动降级」这条连带效果)。 +- 团期内 `PRIMARY` / `SECONDARY` 各唯一的既有语义保留。 +- 权限码 `group-batch:manage`。 + +### 7. 团期详情 `GET /v3/admin/order/group-batch/:groupBatchId` + +**VO**: `GroupBatchDetailRespVO` + +#### 使用场景 + +团期详情页主接口。出参含**整团应收 / 已收 / 已预支金额**、四项资源就绪位、成团门槛与报账人——比 `chips/*` 更该判权,而 `chips/*` 早就判了。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `groupBatchId` | path | Long | 是 | 雪花 ID,按字符串传 | 团期 ID | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | Object | 团期详情(字段结构未变) | +| `data.totalReceivable` / `totalReceived` | Number | 整团应收 / 已收(实时聚合) | +| `data.minToForm` | Integer | 成团门槛(户) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2097498512387104770 +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": { "groupBatchId": "2097498512387104770", "batchStatus": "RESOURCE_PREPARING" }, "success": true } +``` + +#### 空数据 / 降级响应 + +读口。团期不存在仍报 `589500`(不是空对象),行为未改。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:view`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时**不查团期、不返回任何金额字段**。 +- 团期不存在的 `589500` 排在判权之后。 +- ⚠️ `#7376` 的旁路探针 `GB_DETAIL_ACL_PROBE` 随本次判权落地**已删除**——判权之后它只会记录必然 `hasView=true` 的调用。 +- 权限码 `group-batch:view`。 + +### 8. 团期下子订单列表 `GET /v3/admin/order/group-batch/:groupBatchId/orders` + +**VO**: `GroupBatchOrderItemRespVO` + +#### 使用场景 + +团期详情「子订单」页。`includeTravelers=true` 时**带出逐户出行人明细**(证件号不返回),是本批里最敏感的读口。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `groupBatchId` | path | Long | 是 | 雪花 ID,按字符串传 | 团期 ID | +| `includeTravelers` | query | Boolean | 否 | 缺省 false | 是否附出行人明细 | +| `includeNeeds` | query | Boolean | 否 | 缺省 false | 是否附房数 / 房型 / 特殊需求 | +| `includeCancelled` | query | Boolean | 否 | 缺省 false | 是否含已取消子订单 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | Array | 子订单列表(字段结构未变) | +| `data[].travelers` | Array | 出行人明细;仅 `includeTravelers=true` 时出现 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2097498512387104770/orders?includeTravelers=true +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": [], "success": true } +``` + +#### 空数据 / 降级响应 + +团期无活跃子订单时返回空数组,不报错——既有语义,未改。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:view`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时**出行人明细一条都不返回**。 +- 活跃集口径(缺省排除已取消)未变。 +- 权限码 `group-batch:view`。 + +### 9. 查询团期备品列表 `GET /v3/admin/order/group-batch/:groupBatchId/supplies` + +**VO**: `GroupBatchSuppliesRespVO` + +#### 使用场景 + +团期详情「物资清单」页的列表。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `groupBatchId` | path | Long | 是 | 雪花 ID,按字符串传 | 团期 ID | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | Array | 备品行列表(字段结构未变) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2097498512387104770/supplies +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": [], "success": true } +``` + +#### 空数据 / 降级响应 + +无备品行时返回空数组,不报错——既有语义,未改。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:view`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时不查任何备品行。 +- 与写口同在一个页面,但**读走 `view`、写走 `manage`**:能看不等于能改。 +- 权限码 `group-batch:view`。 + +### 10. 查询备品候选 `GET /v3/admin/order/group-batch/:groupBatchId/supplies/candidates` + +**VO**: `SuppliesCandidateRespVO` + +#### 使用场景 + +加备品行时的备品库下拉候选,支持按分类与关键字过滤。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `groupBatchId` | path | Long | 是 | 雪花 ID,按字符串传 | 团期 ID | +| `categoryCode` | query | String | 否 | — | 分类过滤 | +| `keyword` | query | String | 否 | `#7377` 起 LIKE 通配符已转义 | 名称关键字 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | Array | 候选备品列表(字段结构未变) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/2097498512387104770/supplies/candidates?keyword=水 +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": [], "success": true } +``` + +#### 空数据 / 降级响应 + +无匹配候选时返回空数组。搜 `%` 或 `_` 从 `#7377` 起是零结果而非全库,行为本次未改。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:view`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时不查备品库。 +- `#7377` 的关键字转义保留。 +- 权限码 `group-batch:view`。 + +### 11. 查询团期人员配置列表 `GET /v3/admin/group-batch/:productBatchId/staff` + +**VO**: `BatchStaffConfigRespVO` + +#### 使用场景 + +团期「导游 / 摄影」页的配置列表,手机号脱敏(前 3 后 4)后返回。⚠️ 路径变量是产品侧排期 ID。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `productBatchId` | path | Long | 是 | 雪花 ID;**产品侧排期 ID,不是团期主键** | 产品侧排期 ID | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | Object | 人员配置(字段结构未变,手机号脱敏) | + +#### 请求示例 + +```http +GET /v3/admin/group-batch/2097498511120478210/staff +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": { "staffList": [] }, "success": true } +``` + +#### 空数据 / 降级响应 + +未配置人员时返回空列表,不报错——既有语义,未改。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:view`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时不查人员配置。 +- 手机号脱敏规则未变。 +- 权限码 `group-batch:view`。 + +### 12. 查询团期人员候选 `GET /v3/admin/group-batch/:productBatchId/staff/candidates` + +**VO**: `StaffCandidateRespVO` + +#### 使用场景 + +配人员时按角色位拉候选人。`#7028` 起勾选态按配置位收敛,`assignedRole` 仍带真实角色。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `productBatchId` | path | Long | 是 | 雪花 ID;**产品侧排期 ID,不是团期主键** | 产品侧排期 ID | +| `role` | query | String | 是 | 角色位 | 按角色位过滤候选人 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | Array | 候选人列表(字段结构未变) | + +#### 请求示例 + +```http +GET /v3/admin/group-batch/2097498511120478210/staff/candidates?role=GUIDE +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": [], "success": true } +``` + +#### 空数据 / 降级响应 + +无候选人时返回空数组。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:view`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时不查资源域候选人。 +- `#7028` 的勾选态收敛口径未变。 +- 权限码 `group-batch:view`。 + +### 13. 团期分页列表 `GET /v3/admin/order/group-batch` + +**VO**: `GroupBatchPageItemRespVO` + +#### 使用场景 + +团期列表页。出参含**整团已收合计**(实时聚合)。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `pageNum` / `pageSize` | query | Integer | 否 | — | 分页参数 | +| `batchStatus` 等筛选项 | query | String | 否 | — | 既有筛选项,未变 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data.records` | Array | 团期行(字段结构未变) | +| `data.total` | Number | 总条数 | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch?pageNum=1&pageSize=20 +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": { "records": [], "total": 0 }, "success": true } +``` + +#### 空数据 / 降级响应 + +无数据时 `records` 为空数组、`total` 为 0,不报错。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:list`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时不查列表。 +- ⚠️ **走 `group-batch:list` 不是 `view`**——权限种子把 GB-ADM-001 划给了 `list`。 +- 权限码 `group-batch:list`。 + +### 14. 团期看板列表 `GET /v3/admin/order/group-batch/board` + +**VO**: `GroupBatchBoardItemRespVO` + +#### 使用场景 + +团期看板。以产品全班期为基底左连订单侧团期记录,返回命中 / 未命中 / 孤儿三类行。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `productId` | query | Long | 是 | 雪花 ID | 产品 ID | +| `scope` | query | String | 否 | `ONGOING` / `FINISHED` / `ALL`,缺省 `ALL` | 班期范围 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | Array | 看板行(字段结构未变) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/board?productId=2097498511120478210 +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": [], "success": true } +``` + +#### 空数据 / 降级响应 + +产品无班期时返回空数组,不报错。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:list`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时不查看板。 +- `scope` 缺省行集与改前逐字一致(`#7189`),本次未动。 +- 权限码 `group-batch:list`。 + +### 15. 团期控制台产品选择器 `GET /v3/admin/order/group-batch/products` + +**VO**: `GroupBatchProductItemRespVO` + +#### 使用场景 + +看板上游的产品下拉。返回 PUBLISHED 产品 + 有活跃订单的非 PUBLISHED 产品。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `productType` | query | String | 否 | 缺省不限 | 产品类型 | +| `keyword` | query | String | 否 | — | 产品名称关键词 | + +#### 出参 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `data` | Array | 产品列表(字段结构未变) | + +#### 请求示例 + +```http +GET /v3/admin/order/group-batch/products +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ "code": 200, "message": "成功", "data": [], "success": true } +``` + +#### 空数据 / 降级响应 + +无匹配产品时返回空数组。 + +#### 错误响应 + +**本次新增的拒绝形态**——当前角色没有 `group-batch:list`: + +```json +{ "code": 589507, "message": "无操作权限(非团期管理员 / 非本定制师名下)", "data": null, "success": false } +``` + +缺少或无效的 token 由网关拦截返 `401`。本端点不新增其它业务错误码。 + +#### 业务边界 + +- 判权发生在**进入业务逻辑之前**;权限源不可用时**失败关闭**按拒绝处理。 +- 拒绝时不查产品。 +- 与看板同性质,同走 `list`。 +- 权限码 `group-batch:list`。 + +## 四、契约约束与正确调用方式 + +1. **前端不需要改任何请求代码。** 入参、出参、既有错误码一律未变,只是多了一种失败可能。 +2. **要处理 `589507`。** 这十五个口现在可能返 `589507`,按「无权限」提示即可,别当业务失败重试。 +3. **更好的做法是按钮级 / 菜单级预判**:能登录后台不等于能进团期。建议按当前角色是否持有 + `group-batch:list` 决定团期菜单可见性,`group-batch:view` 决定详情页可进, + `group-batch:manage` 决定物资与人员配置的编辑按钮可见 / 置灰。 +4. **三个码是分层的,不要混用**:`list`(列表 / 看板 / 产品选择器)→ `view`(详情 / 名单 / 物资读 / 人员读) + → `manage`(物资写 / 人员写)。这个划分取自权限种子 `V20260831_002` 的注册描述,不是本单新拍。 +5. **雪花 ID 一律按字符串处理**。 +6. ⚠️ **别把 `productBatchId` 当成 `groupBatchId`**:人员配置那四个口的路径变量是**产品侧排期 ID**, + 前缀也不在 `/order/` 下。这是既有形态,本次未改。 + +## 五、数据库行为 + +- **无表变更、无 Flyway、无数据迁移。** +- 三个权限码 `group-batch:list` / `view` / `manage` **早已存在**(`V20260831_002` #6902、`V20260906_002` #7158), + 本次**不新增授权种子**。 +- 拒绝路径**零写入**:判权在进入 Service 事务之前,被拒的请求不产生任何行变更、不写时间线、不触发扇出。 + 已用 TEST SQL 佐证(CUSTOMIZER 调「新增备品行」被拒后,表里查不到该行)。 + +## 六、边界行为 + +| 场景 | 行为 | +|---|---| +| 角色持有对应权限码 | 与改前完全一致 | +| 角色没有对应码 | `589507`,且**零副作用** | +| 缺少 / 无效 token | 网关拦截返 `401`(未到业务层) | +| 权限源(user-service)不可用 | **失败关闭 → 按无权限拒绝**,不放行 | +| 团期不存在 / 状态不对 / 备品行不存在 | 既有错误码不变,**但判权在前**——无权限时先返 `589507` | +| `confirm-material` 被拒 | 团期阶段不推进、`material_confirmed` 不置位、时间线不写 | +| `saveConfig` 被拒 | 配置不覆盖、**扇出不触发** | +| `reporter-rank` 被拒 | 报账人不改动,原主 / 次不降级 | +| 子订单列表被拒 | **出行人明细一条都不返回** | + +## 六.6、修改前后对比 + +| 端点组 | 改前 | 改后 | +|---|---|---| +| 物资 3 写 + `confirm-material` + 人员 2 写 | **零判权**:任意能登录后台的账号都可调 | `group-batch:manage` | +| 详情 / 子订单 / 物资读 2 / 人员读 2 | **零判权** | `group-batch:view` | +| 分页列表 / 看板 / 产品选择器 | **零判权** | `group-batch:list` | +| 判权位置 | 无 | Controller 方法体第一行(进 Service 事务之前) | +| 权限源不可用时 | 无判权,直接放行 | 失败关闭,按无权限拒绝 | + +改前的实测证据(`origin/dev-v3`,用 `role=CUSTOMIZER` 打真实网关):这十五个口一律 **200**; +而同团期的 `chips/*`、`finance`、`settlement/*`、`contracts`、`requirement/*`、`status-logs`、`itinerary` +一律 `589507`。**同一个 controller 包里有的加了有的没加,是漏加不是设计如此。** + +## 六.7、影响评估 + +TEST 库实测的授予关系: + +| 权限码 | 授予角色 | +|---|---| +| `group-batch:list` | `SUPER_ADMIN`、`ADMIN`、`FINANCE` | +| `group-batch:view` | `SUPER_ADMIN`、`ADMIN`、`FINANCE` | +| `group-batch:manage` | `SUPER_ADMIN`、`ADMIN` | + +系统里**没有独立的「团期管理员」角色**(`sys_role` 共 11 个),原型所说的团期管理员就是 `ADMIN`。 + +**完全不受影响**:`SUPER_ADMIN`、`ADMIN`。 +**读得到、改不了**:`FINANCE`——能看团期列表、详情、物资、人员配置,但不能改物资与人员配置。这是修正而非损失:改物资清单、改人员配置本来就不是财务的职责。 +**全部被挡**:`CUSTOMIZER`、`ROOM_MANAGER`、`VEHICLE_MANAGER`、`OPERATOR`、`CUSTOMER_SERVICE`、`MATERIAL_ADMIN`、`house_keeper_lead`、`order_console`。 + +三条判断依据: + +1. **`group-batch:manage` 早已在「成团」端点上跑通**(`#7158`),`list` / `view` 也早已在统计条、导出、 + 六芯片、合同面板上跑通。这几个码覆盖的就是真实操作人群,本次只是把漏网的十五个口接上同一套码。 +2. **定制师本来就不该有**:jw 2026-09-10 口径——定制师负责**提需求**,走订单侧 + `POST /v3/admin/order/:id/adjustment/submit`;团期管理员确认后再流转到房务 / 车务。 + 定制师不碰团期域端点。**TEST 探针日志实测印证**:`GB_DETAIL_ACL_PROBE` 采到 + `roleKey=CUSTOMIZER hasView=false`、`SUPER_ADMIN hasView=true`、`FINANCE hasView=true`,与种子完全一致。 +3. **房务 / 车务走自己的域**:房务在 `com.hulalv.house`(`#7322`~`#7327`),车务在 `fleet`(`#7439`~`#7444`), + 都有各自的权限体系,不依赖团期域这十五个口。 + +**如果上线后发现某个角色确实需要**:在 user-service 给该角色补对应码即可,**不需要改代码**。 + +⚠️ `order_console`(测试用角色)会被挡,TEST 上若有自动化脚本用它调这些口需改角色或补授权。 + +## 七、不影响范围 + +- 不改任何端点的路径、入参、出参、既有错误码。 +- **不改任何业务逻辑**:阶段门、双门推进、重复校验、扇出、时间线、活跃集口径全部原样保留,只是排在判权之后。 +- 无表变更、无 Flyway、无新增错误码(复用 `589507`)、无网关路由变更、无新增授权种子。 +- 只滚 `hl-order-service-v3` 一个服务。 + +## 八、测试环境已验证 + +TEST(`api.test.1814.love`)真实网关,**十五个端点 × 三种角色 = 45 次调用逐条取证**(2026-09-10)。 +角色 token 用 dev 默认 JWT 密钥自签,打的是真实网关与真实权限源。 + +| # | 端点 | 权限码 | `CUSTOMIZER` | `FINANCE` | `SUPER_ADMIN` | +|---|---|---|---|---|---| +| 1 | 物资 新增行 | manage | `589507` | `589507` | 越过判权(`589520` 业务前置) | +| 2 | 物资 改数量 | manage | `589507` | `589507` | 越过判权(`589521` 行不存在) | +| 3 | 物资 删行 | manage | `589507` | `589507` | 越过判权(`589521` 行不存在) | +| 4 | 确认物资 | manage | `589507` | `589507` | 越过判权(`589501` 状态不符) | +| 5 | 人员配置 保存 | manage | `589507` | `589507` | 越过判权(`589552` 尚未成团) | +| 6 | 设报账人 | manage | `589507` | `589507` | 越过判权(`589552` 尚未成团) | +| 7 | 团期详情 | view | `589507` | `200` | `200` | +| 8 | 子订单(带出行人明细) | view | `589507` | `200` | `200` | +| 9 | 物资列表 | view | `589507` | `200` | `200` | +| 10 | 备品候选 | view | `589507` | `200` | `200` | +| 11 | 人员配置列表 | view | `589507` | `200` | `200` | +| 12 | 人员候选 | view | `589507` | `200` | `200` | +| 13 | 分页列表 | list | `589507` | `200` | `200` | +| 14 | 看板 | list | `589507` | `200`(17 行) | `200`(17 行) | +| 15 | 产品选择器 | list | `589507` | `200` | `200` | + +**放行侧怎么证明的**:`manage` 组是写口,不能真跑(会造脏数据),改用「必然失败的业务前置」—— +`SUPER_ADMIN` 拿到的是各自的**业务错误码**而不是 `589507`,说明请求**已经越过判权层进入业务逻辑**。 + +**零写入的 SQL 佐证**:`CUSTOMIZER` 调「新增备品行」被拒后, +`SELECT count(*) FROM order_batch_supplies WHERE supplies_name LIKE '#7455探针%'` → **0**。两轮验收各查一次,均为 0。 + +**分层判权生效**:`FINANCE` 在 `view` / `list` 组全部 `200`、在 `manage` 组全部 `589507`—— +这正是「能看不能改」的设计意图,不是巧合。 + +**探针日志聚合(AC-1,删除探针前采集)**:`GB_DETAIL_ACL_PROBE` 采到 +`roleKey=CUSTOMIZER hasView=false`、`SUPER_ADMIN hasView=true`、`FINANCE hasView=true`, +与权限种子 `V20260831_002` 的角色集完全一致。 +⚠️ **如实标注**:样本是自造流量(TEST 无真实运营流量),只能证明探针工作正常与判定口径正确, +**不能代表生产调用方分布**;收口决策的主依据是职责链路论证(见「六.7 影响评估」第 2 条)。 + +**回归取证(AC-11)**:以 `SUPER_ADMIN` 走完看板 → 列表 → 详情 → 子订单 → 物资 → 人员配置 +一条完整路径,全部 `200`,团期页面主链路未被打断。 + +**本机全量**:`mvn -pl hl-order-service-v3 test` → **Tests run: 9657, Failures: 0, Errors: 0, Skipped: 7, BUILD SUCCESS** +(含 `MapperBoundaryArchTest` 26 例、`LayerEnforcementTest` 5 例——跨域引用 guard 未破边界)。 + +## 十、相关文档 + +- 工单:`#7455`(本单),前序 `#7376`(旁路探针,本单是它的收口) +- 同类先例:`#7316`(全团需求汇总补 `view`)、`#7411`(核单共享成本补判权,本单的单测范式来自它) +- 权限种子:`V20260831_002`(`list` / `view` / `export`,#6902)、`V20260906_002`(`manage`,#7158) +- 契约:《团期模块接口文档 v2.0》GB-ADM-000/001/002/003/014/024/029/035/036/037 + +## 关联 / 联系人 + +- 工单:https://git.1814.love:8443/wx/HL/issues/7455 +- PR:https://git.1814.love:8443/wx/HL/pulls/7483 +- 后端:jw;前端:mmg