From 058fd11fb0c66a6f14d5eda06863087667663c8b Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 28 Aug 2026 15:34:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(gerp):=20role=5Fscope=E8=B4=9F=E8=B4=A3?= =?UTF-8?q?=E4=BA=BA/=E7=BB=84=E9=95=BF=E5=88=A4=E5=AE=9A=E6=94=B9?= =?UTF-8?q?=E8=AF=BB=E5=85=B3=E8=81=94=E8=A1=A8RoleType=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=A4=9A=E9=83=A8=E9=97=A8=E4=BD=9C=E7=94=A8=E5=9F=9F?= =?UTF-8?q?=E4=B8=8E=E5=85=AC=E5=8F=B8=E8=BF=87=E6=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除UserRole遗留字段(RoleType/DeptUUID/WorkgroupUUID)相关隐藏与防写入规则 - 同步UserRole/UserRoleDepartment列展示优化 --- gvsds/apps/gerp/form_service.py | 22 +++--- gvsds/apps/gerp/role_scope.py | 109 +++++++++++++++--------------- gvsds/apps/gerp/views/form_api.py | 10 +-- 3 files changed, 74 insertions(+), 67 deletions(-) diff --git a/gvsds/apps/gerp/form_service.py b/gvsds/apps/gerp/form_service.py index 78e41c9e..811b4666 100644 --- a/gvsds/apps/gerp/form_service.py +++ b/gvsds/apps/gerp/form_service.py @@ -154,7 +154,7 @@ class FormService(FormServiceBase): } # 列表页表格隐藏主键 UUID 列的表(UUID 无业务展示意义,仅前端隐藏,后端仍返回) - HIDE_PK_UUID_IN_TABLE = {'Company', 'Department', 'Workgroup'} + HIDE_PK_UUID_IN_TABLE = {'Company', 'Department', 'Workgroup', 'UserRoleDepartment', 'UserRole'} def __init__(self, user, current_company_uuid=None, admin_verify=False, current_role_uuid=None, current_tenant_uuid=None, ip_address='', role_scope=None): super().__init__(user=user) @@ -726,9 +726,18 @@ class FormService(FormServiceBase): col['type'] = 'readonly' col['editable'] = False col['hideInForm'] = True - # UserRole 遗留字段(DeptUUID/WorkgroupUUID):部门/工作组归属已迁移到 - # UserRoleDepartment/UserRoleWorkgroup 关联表,表单+列表均隐藏,禁止再次写入 - if table_name == 'UserRole' and pascal_name in ('DeptUUID', 'WorkgroupUUID'): + # UserRole 列表可读性:UserUUID/RoleUUID 值已由 _enrich_entity_labels 回显为 + # 用户名/角色名,列标题直接改"用户名"/"角色名"(避免误导为裸 UUID); + # CompanyUUID 为隔离冗余列,角色成员列表均按公司隔离展示,列表隐藏 + if table_name == 'UserRole' and pascal_name == 'UserUUID': + col['title'] = '用户名' + if table_name == 'UserRole' and pascal_name == 'RoleUUID': + col['title'] = '角色名' + if table_name == 'UserRole' and pascal_name == 'CompanyUUID': + col['visible'] = False + # UserRoleDepartment 关联表冗余字段(DeptUUID/TenantUUID/CompanyUUID): + # 仅用于租户/公司隔离过滤与详情页上下文注入,列表+表单均隐藏(详情页由 query 注入) + if table_name == 'UserRoleDepartment' and pascal_name in ('DeptUUID', 'TenantUUID', 'CompanyUUID'): col['editable'] = False col['hideInForm'] = True col['visible'] = False @@ -868,7 +877,7 @@ class FormService(FormServiceBase): # 系统级表(Menu/Permission)豁免隔离 field_names = {f.name for f in model._meta.get_fields() if hasattr(f, 'name')} # 租户管理(Tenant 表)仅租户管理员/跨租户管理员可读;普通用户无权限(菜单已隐藏,此处数据兜底) - if table_name == 'Tenant' and not self._is_admin(): + if table_name == 'Tenant' and not self._is_admin(self.user): return {'records': [], 'total': 0, 'page': 1, 'PageSize': 20} if table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES: # 公司隔离:有 CompanyUUID 字段的表按当前公司过滤 @@ -1464,9 +1473,6 @@ class FormService(FormServiceBase): _valid_values = [opt['value'] for opt in _opt_map[resolved]] if val not in _valid_values: return {'error': f"字段 '{resolved}' 值非法:只允许 {_valid_values}", 'field': resolved} - # 遗留字段防写入:UserRole 的 DeptUUID/WorkgroupUUID 已迁移到关联表,禁止再改 - if table_name == 'UserRole' and resolved in ('DeptUUID', 'WorkgroupUUID'): - continue # 订单受保护字段:状态机/审计字段禁止通用修改(状态流转必须走专用端点) if table_name == 'Order' and resolved in self.ORDER_PROTECTED_FIELDS: continue diff --git a/gvsds/apps/gerp/role_scope.py b/gvsds/apps/gerp/role_scope.py index 7c988217..b9432219 100644 --- a/gvsds/apps/gerp/role_scope.py +++ b/gvsds/apps/gerp/role_scope.py @@ -3,14 +3,17 @@ 作用域类型: admin —— 管理员(当前角色拥有 admin:super 或 admin:tenant 权限),看全公司 - dept_manager —— 部门负责人(UserRoleDepartment 关联 + RoleType=1),看本部门成员 - workgroup_lead —— 工作组长(UserRoleWorkgroup 关联 + RoleType=1),看本工作组成员 + dept_manager —— 部门负责人(UserRoleDepartment 关联行 RoleType=1),看本部门成员 + workgroup_lead —— 工作组长(UserRoleWorkgroup 关联行 RoleType=1),看本工作组成员 member —— 普通组员,只看自己 +负责人/组长判定一律以关联表 RoleType=1 为准(UserRole.RoleType / DeptUUID / +WorkgroupUUID 遗留字段已废弃删除,见 gvsdsdk 0002 迁移)。 + 用法: scope = GetUserScope(request) if scope.Type == 'dept_manager': - qs = qs.filter(DeptUUID=scope.DeptUUID) + qs = qs.filter(DeptUUID__in=scope.DeptUUIDs) """ import logging import uuid as _uuid @@ -21,10 +24,10 @@ logger = logging.getLogger(__name__) class RoleScope(NamedTuple): Type: str # admin / dept_manager / workgroup_lead / member - DeptUUID: Optional[bytes] # 部门 UUID(dept_manager 时非空) - WorkgroupUUID: Optional[bytes] # 工作组 UUID(workgroup_lead 时非空) + DeptUUIDs: Optional[tuple] # 部门 UUID 列表(dept_manager 时非空,可多部门) + WorkgroupUUIDs: Optional[tuple] # 工作组 UUID 列表(workgroup_lead 时非空) UserUUID: Optional[bytes] # 当前用户 UUID(member 时用于过滤) - RoleType: int # 当前角色类型 + RoleType: int # 0=成员 1=负责人/组长(以关联表 RoleType 为准) def _to_bytes(value): @@ -56,9 +59,12 @@ def GetUserScope(request) -> RoleScope: 优先级: 1. 管理员(IsAdmin=true)→ admin - 2. 当前 UserRole 的 RoleType=1 且 DeptUUID 非空 → dept_manager - 3. 当前 UserRole 的 RoleType=1 且 WorkgroupUUID 非空 → workgroup_lead + 2. 当前 (用户, 角色) 关联在 UserRoleDepartment 有 RoleType=1 的行 → dept_manager + 3. 当前 (用户, 角色) 关联在 UserRoleWorkgroup 有 RoleType=1 的行 → workgroup_lead 4. 其他 → member + + 负责人/组长判定以关联表 RoleType 为准(UserRole.RoleType / DeptUUID / + WorkgroupUUID 遗留字段已废弃删除),并按当前公司过滤(跨公司同名对不串)。 """ user = getattr(request, 'user', None) if request else None user_uuid = getattr(user, 'UserUUID', None) if user else None @@ -68,13 +74,13 @@ def GetUserScope(request) -> RoleScope: if IsAdmin(request): return RoleScope( Type='admin', - DeptUUID=None, - WorkgroupUUID=None, + DeptUUIDs=None, + WorkgroupUUIDs=None, UserUUID=user_uuid_bytes, RoleType=1, ) - # 获取当前角色关联的 UserRole 记录(含 DeptUUID/WorkgroupUUID/RoleType) + # 获取当前角色关联的 UserRole 记录(RoleType 等遗留字段已废弃,仅取关联 UUID) current_role = getattr(request, 'current_role', None) role_uuid = getattr(current_role, 'RoleUUID', None) if current_role else None current_company = getattr(request, 'current_company_uuid', None) @@ -90,8 +96,8 @@ def GetUserScope(request) -> RoleScope: if not role_uuid or not user_uuid_bytes: return RoleScope( Type='member', - DeptUUID=None, - WorkgroupUUID=None, + DeptUUIDs=None, + WorkgroupUUIDs=None, UserUUID=user_uuid_bytes, RoleType=0, ) @@ -105,60 +111,46 @@ def GetUserScope(request) -> RoleScope: if not ur: return RoleScope( Type='member', - DeptUUID=None, - WorkgroupUUID=None, + DeptUUIDs=None, + WorkgroupUUIDs=None, UserUUID=user_uuid_bytes, RoleType=0, ) - role_type = ur.RoleType or 0 ur_uuid_bytes = _to_bytes(ur.UserRoleUUID) - # 部门/工作组归属以关联表为准(UserRole.DeptUUID/WorkgroupUUID 已废弃清空): - # 部门负责人/工作组长通过 UserRoleDepartment / UserRoleWorkgroup 关联行判定 - dept_uuid = None - workgroup_uuid = None - if role_type == 1 and ur_uuid_bytes: - try: - urd = RoleManager.GetUserRoleDepartment(ur_uuid_bytes) - if urd: - dept_uuid = _to_bytes(urd.DeptUUID) - except Exception: - dept_uuid = None - if not dept_uuid: - try: - urw = RoleManager.GetUserRoleWorkgroup(ur_uuid_bytes) - if urw: - workgroup_uuid = _to_bytes(urw.WorkgroupUUID) - except Exception: - workgroup_uuid = None - - # 2. 部门负责人 - if role_type == 1 and dept_uuid: + # 2. 部门负责人:关联表 UserRoleDepartment 中 RoleType=1 的行(按当前公司过滤) + manager_depts = RoleManager.GetUserRoleDepartments( + ur_uuid_bytes, CompanyUUID=company_uuid_bytes, RoleType=1, + ) + if manager_depts: return RoleScope( Type='dept_manager', - DeptUUID=dept_uuid, - WorkgroupUUID=None, + DeptUUIDs=tuple(d for d in (_to_bytes(x.DeptUUID) for x in manager_depts) if d), + WorkgroupUUIDs=None, UserUUID=user_uuid_bytes, - RoleType=role_type, + RoleType=1, ) - # 3. 工作组长 - if role_type == 1 and workgroup_uuid: + # 3. 工作组长:关联表 UserRoleWorkgroup 中 RoleType=1 的行(按当前公司过滤) + manager_wgs = RoleManager.GetUserRoleWorkgroups( + ur_uuid_bytes, CompanyUUID=company_uuid_bytes, RoleType=1, + ) + if manager_wgs: return RoleScope( Type='workgroup_lead', - DeptUUID=None, - WorkgroupUUID=workgroup_uuid, + DeptUUIDs=None, + WorkgroupUUIDs=tuple(w for w in (_to_bytes(x.WorkgroupUUID) for x in manager_wgs) if w), UserUUID=user_uuid_bytes, - RoleType=role_type, + RoleType=1, ) # 4. 普通组员 return RoleScope( Type='member', - DeptUUID=None, - WorkgroupUUID=None, + DeptUUIDs=None, + WorkgroupUUIDs=None, UserUUID=user_uuid_bytes, - RoleType=role_type, + RoleType=0, ) @@ -245,12 +237,19 @@ def ApplyScopeToQueryset(qs, scope: RoleScope, table_name: str): def _get_scope_members(scope: RoleScope): """获取当前作用域下的成员 UUID 列表(委托 gvsdsdk RoleManager.GetScopeMembers) - dept_manager: 查询 UserRoleDepartment 中 DeptUUID = scope.DeptUUID 的用户 - workgroup_lead: 查询 UserRoleWorkgroup 中 WorkgroupUUID = scope.WorkgroupUUID 的用户 + dept_manager: 查询 UserRoleDepartment 中 DeptUUID ∈ scope.DeptUUIDs 的用户(多部门并集) + workgroup_lead: 查询 UserRoleWorkgroup 中 WorkgroupUUID ∈ scope.WorkgroupUUIDs 的用户 """ from gvsdsdk.auth.managers import RoleManager - if scope.Type == 'dept_manager' and scope.DeptUUID: - return RoleManager.GetScopeMembers('dept_manager', scope.DeptUUID) - if scope.Type == 'workgroup_lead' and scope.WorkgroupUUID: - return RoleManager.GetScopeMembers('workgroup_lead', scope.WorkgroupUUID) - return None + member_uuids = set() + if scope.Type == 'dept_manager' and scope.DeptUUIDs: + for dept_uuid in scope.DeptUUIDs: + members = RoleManager.GetScopeMembers('dept_manager', dept_uuid) + if members: + member_uuids.update(members) + elif scope.Type == 'workgroup_lead' and scope.WorkgroupUUIDs: + for wg_uuid in scope.WorkgroupUUIDs: + members = RoleManager.GetScopeMembers('workgroup_lead', wg_uuid) + if members: + member_uuids.update(members) + return list(member_uuids) if scope.Type in ('dept_manager', 'workgroup_lead') else None diff --git a/gvsds/apps/gerp/views/form_api.py b/gvsds/apps/gerp/views/form_api.py index 48040e1c..90d11ce6 100644 --- a/gvsds/apps/gerp/views/form_api.py +++ b/gvsds/apps/gerp/views/form_api.py @@ -418,10 +418,12 @@ class CheckAdminView(ELTAuthenticatedAPIView): scope = GetUserScope(request) scope_type = scope.Type scope_label = self.SCOPE_TYPE_LABEL.get(scope_type, '普通组员') - if scope.DeptUUID: - dept_uuid_str = UUIDObj(scope.DeptUUID).str - if scope.WorkgroupUUID: - workgroup_uuid_str = UUIDObj(scope.WorkgroupUUID).str + # 负责人/组长可挂多个部门/工作组:多个 UUID 用逗号拼接 + # (前端当前仅消费 Type/IsDeptManager/IsWorkgroupLead,列表值供扩展) + if scope.DeptUUIDs: + dept_uuid_str = ','.join(UUIDObj(d).str for d in scope.DeptUUIDs if d) + if scope.WorkgroupUUIDs: + workgroup_uuid_str = ','.join(UUIDObj(w).str for w in scope.WorkgroupUUIDs if w) except Exception: pass