Files
track-LICA/backend/app/schemas/group.py

79 lines
2.7 KiB
Python
Raw Normal View History

feat(分组权限): 分组管理接口 + 管理页 + 端到端验收 后端 endpoints/groups.py(**仅 SUPER_ADMIN**): · 组的 CRUD(两级,子组不配范围则继承父组 —— 生产大组配一次,下面的 生产/测试小组都不用再配) · 成员增删 / 设组长 / 候选人下拉(复用 MOM 查询口径,部门已钉死为 ORG_DEPARTMENT) · **删组仅限空组**:级联删是一次静默的批量权限变更,误点一下一批人就突然 看不到数据了;强制「先移人再删组」多一步,但出错时是可见的 · 停用组的语义写死在接口文档:成员**立即**退回未分组状态 为什么只有超管能管分组(不是偏好,是必须): 被显式分进组的 SUPERVISOR 会从「全厂」降级为只看本组;若允许主管管理分组, 他把自己移出组就能恢复全厂视野 —— 这是一条现成的提权路径,分组对他无效。 前端: · AdminGroupsPage:组列表 + 可见范围勾选 + 成员管理 + 组长标记 + 二次确认 · AdminLayout 加菜单项,页头显示数据范围徽标 —— 空范围(未分组)用橙色显眼 提示,否则用户看到空列表会以为系统坏了,这是最难排查的一类反馈 · AuthContext 登录后补拉一次 /auth/me 拿 scope(登录接口不查库、不返回它) · constants/task.ts 新增 isSuperAdmin,不手写 === 比较 端到端验收(实测):先造 1 生产 + 1 售后产品,然后 生产组员 → 1 条,全 PRODUCTION;范围经「生产小组 → 生产大组」继承而来 维修组员 → 1 条,全 AFTER_SALES 超管 → 2 条,全量 任务列表 total 与 returned 一致(验证 count/select 双过滤) 扫码跨组仍 200(符合「能看、不能操作」的既定决策) 停用维修大组 → 成员立即退回未分组 上述测试数据已还原
2026-09-21 17:10:33 +08:00
"""业务分组 Schemas"""
from pydantic import BaseModel, Field
class PhaseOption(BaseModel):
"""可选的生命周期阶段 —— 由服务端下发,前端不要写死这两个值"""
value: str
label: str
class GroupMemberOut(BaseModel):
user_id: str # MOM 短账号(与 Task.assignee_id 同口径)
display_name: str
is_leader: bool
class GroupOut(BaseModel):
id: int
name: str
parent_id: int | None = None
parent_name: str | None = None
description: str | None = None
sort_order: int = 0
is_active: bool = True
phases: list[str] = [] # 该组自己的可见范围(不含继承)
effective_phases: list[str] = [] # 实际生效范围(自己没配则取父组的)
phase_labels: list[str] = [] # 生效范围的中文标签
member_count: int = 0
feat(权限): 业务分组对全员开放(操作分层)+ 操作审计仅超管可见 需求:业务分组开放给所有人,主管可操作、其余人只读;操作审计前端不显示。 ⚠️ 这里有个必须收窄的边界:按数据范围规则,被分进组的 SUPERVISOR 会从 「全厂」降级为只看本组。若允许他改可见范围,他把自己那组改成「生产+售后」 就恢复全厂视野;若允许建组,他新建一个组再把自己塞进去,同样绕过。 **「能管成员」与「能配范围」必须分开** —— 前者安全(组长给自己加组会被 唯一约束挡住、移出自己只是失去权限),后者是提权入口。 因此分层如下: | 谁 | 看 | 改 | |------------------|--------------|------------------------------------| | SUPER_ADMIN | 所有组 | 全部(建/改/删组、配范围、管成员) | | 主管 / 本组组长 | 自己所属的组 | **仅本组成员**(加人/移人/设组长) | | 普通成员 | 自己所属的组 | 无(只读) | 后端(groups.py): · 去掉路由级的 require_roles(SUPER_ADMIN),改为逐端点校验 · list_groups / get_group:非超管只返回自己所属的组;看别组详情 403 (连「这个组存在但你没份」都不暴露,避免被拿来推测组织架构) · create/update/delete:_require_super —— 这三个 + 配范围是提权入口 · add/update/remove member:_can_manage_members(超管 / 主管 / 本组组长) · GroupOut 新增 can_manage_members / can_manage_group / is_my_leader —— 由服务端算好下发,前端不按 role 自行推导(组长身份是按组算的) 前端: · AdminGroupsPage 去掉整页超管门禁,改为按能力显示按钮: 无权限时「组长」列退化成纯展示而非可点按钮 没有任何组时给「你还没有被分配到任何业务分组」的引导提示 · AdminLayout 的 MENU 支持 superOnly 标记,「操作审计」只对超管显示 (后端 /audit/* 本就挂了 require_admin,这里只是不给入口, 否则普通用户点进去只会看到一堆 403) 实测(四类账号): 建组/改范围:仅超管通过,其余全 403 管成员:超管/主管/本组组长通过;非本组 403;跨组组长 403 可见性:主管未分组看到 0 个组、维修组长只看到维修大组、生产组员只看到生产小组 详情页能力标记正确下发;越权查别组详情 403
2026-09-21 17:38:07 +08:00
# ---- 当前用户对这个组能做什么(服务端算好,前端别自己推导)----
can_manage_members: bool = False # 能加/移成员、设组长
can_manage_group: bool = False # 能改组名/范围/启停/删除 —— 仅超管
is_my_leader: bool = False # 我是这个组的组长
feat(分组权限): 分组管理接口 + 管理页 + 端到端验收 后端 endpoints/groups.py(**仅 SUPER_ADMIN**): · 组的 CRUD(两级,子组不配范围则继承父组 —— 生产大组配一次,下面的 生产/测试小组都不用再配) · 成员增删 / 设组长 / 候选人下拉(复用 MOM 查询口径,部门已钉死为 ORG_DEPARTMENT) · **删组仅限空组**:级联删是一次静默的批量权限变更,误点一下一批人就突然 看不到数据了;强制「先移人再删组」多一步,但出错时是可见的 · 停用组的语义写死在接口文档:成员**立即**退回未分组状态 为什么只有超管能管分组(不是偏好,是必须): 被显式分进组的 SUPERVISOR 会从「全厂」降级为只看本组;若允许主管管理分组, 他把自己移出组就能恢复全厂视野 —— 这是一条现成的提权路径,分组对他无效。 前端: · AdminGroupsPage:组列表 + 可见范围勾选 + 成员管理 + 组长标记 + 二次确认 · AdminLayout 加菜单项,页头显示数据范围徽标 —— 空范围(未分组)用橙色显眼 提示,否则用户看到空列表会以为系统坏了,这是最难排查的一类反馈 · AuthContext 登录后补拉一次 /auth/me 拿 scope(登录接口不查库、不返回它) · constants/task.ts 新增 isSuperAdmin,不手写 === 比较 端到端验收(实测):先造 1 生产 + 1 售后产品,然后 生产组员 → 1 条,全 PRODUCTION;范围经「生产小组 → 生产大组」继承而来 维修组员 → 1 条,全 AFTER_SALES 超管 → 2 条,全量 任务列表 total 与 returned 一致(验证 count/select 双过滤) 扫码跨组仍 200(符合「能看、不能操作」的既定决策) 停用维修大组 → 成员立即退回未分组 上述测试数据已还原
2026-09-21 17:10:33 +08:00
class GroupDetailOut(GroupOut):
members: list[GroupMemberOut] = []
class GroupCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=50, description="分组显示名")
parent_id: int | None = Field(None, description="上级大组ID为空则建的是大组")
description: str | None = Field(None, max_length=200)
sort_order: int = 0
phases: list[str] = Field(default_factory=list, description="可见的生命周期阶段")
class GroupUpdate(BaseModel):
name: str | None = Field(None, min_length=1, max_length=50)
parent_id: int | None = None
description: str | None = Field(None, max_length=200)
sort_order: int | None = None
is_active: bool | None = None
phases: list[str] | None = Field(
None, description="整组覆盖式更新;传 [] 表示清空(此时继承父组)",
)
class GroupMembersAdd(BaseModel):
"""批量加成员 —— 界面是多选下拉,一次提交一批"""
user_ids: list[str] = Field(
..., min_length=1, max_length=200, description="要加入的 MOM 账号列表",
)
feat(分组权限): 分组管理接口 + 管理页 + 端到端验收 后端 endpoints/groups.py(**仅 SUPER_ADMIN**): · 组的 CRUD(两级,子组不配范围则继承父组 —— 生产大组配一次,下面的 生产/测试小组都不用再配) · 成员增删 / 设组长 / 候选人下拉(复用 MOM 查询口径,部门已钉死为 ORG_DEPARTMENT) · **删组仅限空组**:级联删是一次静默的批量权限变更,误点一下一批人就突然 看不到数据了;强制「先移人再删组」多一步,但出错时是可见的 · 停用组的语义写死在接口文档:成员**立即**退回未分组状态 为什么只有超管能管分组(不是偏好,是必须): 被显式分进组的 SUPERVISOR 会从「全厂」降级为只看本组;若允许主管管理分组, 他把自己移出组就能恢复全厂视野 —— 这是一条现成的提权路径,分组对他无效。 前端: · AdminGroupsPage:组列表 + 可见范围勾选 + 成员管理 + 组长标记 + 二次确认 · AdminLayout 加菜单项,页头显示数据范围徽标 —— 空范围(未分组)用橙色显眼 提示,否则用户看到空列表会以为系统坏了,这是最难排查的一类反馈 · AuthContext 登录后补拉一次 /auth/me 拿 scope(登录接口不查库、不返回它) · constants/task.ts 新增 isSuperAdmin,不手写 === 比较 端到端验收(实测):先造 1 生产 + 1 售后产品,然后 生产组员 → 1 条,全 PRODUCTION;范围经「生产小组 → 生产大组」继承而来 维修组员 → 1 条,全 AFTER_SALES 超管 → 2 条,全量 任务列表 total 与 returned 一致(验证 count/select 双过滤) 扫码跨组仍 200(符合「能看、不能操作」的既定决策) 停用维修大组 → 成员立即退回未分组 上述测试数据已还原
2026-09-21 17:10:33 +08:00
is_leader: bool = False
class GroupMembersAddResult(BaseModel):
added: list["GroupMemberOut"] = [] # 本次真正加进去的
skipped: list[str] = [] # 已在组内、被跳过的账号
skipped_names: list[str] = [] # 上面那些账号的中文名,便于前端提示
feat(分组权限): 分组管理接口 + 管理页 + 端到端验收 后端 endpoints/groups.py(**仅 SUPER_ADMIN**): · 组的 CRUD(两级,子组不配范围则继承父组 —— 生产大组配一次,下面的 生产/测试小组都不用再配) · 成员增删 / 设组长 / 候选人下拉(复用 MOM 查询口径,部门已钉死为 ORG_DEPARTMENT) · **删组仅限空组**:级联删是一次静默的批量权限变更,误点一下一批人就突然 看不到数据了;强制「先移人再删组」多一步,但出错时是可见的 · 停用组的语义写死在接口文档:成员**立即**退回未分组状态 为什么只有超管能管分组(不是偏好,是必须): 被显式分进组的 SUPERVISOR 会从「全厂」降级为只看本组;若允许主管管理分组, 他把自己移出组就能恢复全厂视野 —— 这是一条现成的提权路径,分组对他无效。 前端: · AdminGroupsPage:组列表 + 可见范围勾选 + 成员管理 + 组长标记 + 二次确认 · AdminLayout 加菜单项,页头显示数据范围徽标 —— 空范围(未分组)用橙色显眼 提示,否则用户看到空列表会以为系统坏了,这是最难排查的一类反馈 · AuthContext 登录后补拉一次 /auth/me 拿 scope(登录接口不查库、不返回它) · constants/task.ts 新增 isSuperAdmin,不手写 === 比较 端到端验收(实测):先造 1 生产 + 1 售后产品,然后 生产组员 → 1 条,全 PRODUCTION;范围经「生产小组 → 生产大组」继承而来 维修组员 → 1 条,全 AFTER_SALES 超管 → 2 条,全量 任务列表 total 与 returned 一致(验证 count/select 双过滤) 扫码跨组仍 200(符合「能看、不能操作」的既定决策) 停用维修大组 → 成员立即退回未分组 上述测试数据已还原
2026-09-21 17:10:33 +08:00
class GroupMemberUpdate(BaseModel):
is_leader: bool
class MemberCandidate(BaseModel):
username: str # 短账号,存进组的就是它
full_name: str # 真实姓名,仅供界面展示