dbc29eee59
- park_members/company_members 多对多成员表(0028),users.affiliation 为派生缓存由 sync_user_affiliation 刷新 - 转园两级审核状态机(0029):pending_from→pending_to→approved,支持用户/企业整体转园与运营端兜底 - 叠加制身份(0030):身份恒为运营/载体/OPC,企业管理员为能力叠加非身份,去除 enterprise/carrier 直写角色 - rbac 新增 memberships/park-transfers/企业管理员工作台 /admin/ent/* 接口 Co-Authored-By: Claude <noreply@anthropic.com>
67 lines
2.8 KiB
Python
67 lines
2.8 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""成员关系领域规则(用户↔园区 / 用户↔企业)—— 权限矩阵与归属铁律集中于此。
|
||
|
||
铁律:**任何端口不允许直接调整用户/企业的所属园区**,必须通过转园申请
|
||
(app/domain/park_transfer.py 审批通过后的 transfer 动作是唯一写路径)。
|
||
users.affiliation / park_id / park_name 仅作派生展示缓存,由 sync_user_affiliation 刷新。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
MEMBER_TYPES = ("admin", "staff", "owner", "migrated")
|
||
COMPANY_KINDS = ("park_entered", "independent") # 企业类型:园区入驻 | 独立
|
||
USER_KINDS = ("park", "independent") # 用户类型:园区入驻 | 独立(由园区成员派生)
|
||
|
||
|
||
class MembershipError(Exception):
|
||
"""成员/归属操作违规(由路由层转 400/403)。"""
|
||
|
||
|
||
def assert_not_changing_park(actor_role: str, target_park_id: str | None = None, new_park_id: str | None = None):
|
||
"""禁止任何端口直接改写所属园区(唯一豁免:转园申请审批通过后的内部迁移)。"""
|
||
raise MembershipError("不允许直接调整所属园区,请通过转园申请")
|
||
|
||
|
||
def can_create_user(actor_role: str) -> bool:
|
||
"""创建用户:运营端(全局) / 园区端(本园区)。企业管理员不可建号(防提权)。"""
|
||
return actor_role in ("operator", "carrier")
|
||
|
||
|
||
def can_create_company(actor_role: str, kind: str) -> bool:
|
||
"""创建企业:运营端(含独立企业)/ 园区端(仅园区入驻企业)。"""
|
||
if actor_role == "operator":
|
||
return kind in COMPANY_KINDS
|
||
if actor_role == "carrier":
|
||
return kind == "park_entered"
|
||
return False
|
||
|
||
|
||
def can_bind_park_admin(actor_role: str) -> bool:
|
||
"""绑定园区管理员:仅运营端。"""
|
||
return actor_role == "operator"
|
||
|
||
|
||
def can_bind_company_admin(actor_role: str, company_tenant_id: str, my_park_id: str = "") -> bool:
|
||
"""绑定企业管理员:运营端(任意企业)/ 园区端(本园区企业)。"""
|
||
if actor_role == "operator":
|
||
return True
|
||
if actor_role == "carrier":
|
||
return bool(company_tenant_id) and company_tenant_id == my_park_id
|
||
return False
|
||
|
||
|
||
def can_bind_company_member(actor_role: str, company_tenant_id: str, my_park_id: str = "",
|
||
is_company_admin: bool = False) -> bool:
|
||
"""绑定企业用户:运营端 / 园区端(本园区企业) / 企业管理员(本企业)。"""
|
||
if actor_role == "operator":
|
||
return True
|
||
if actor_role == "carrier":
|
||
return bool(company_tenant_id) and company_tenant_id == my_park_id
|
||
if actor_role == "enterprise":
|
||
return is_company_admin
|
||
return False
|
||
|
||
|
||
def can_view_park_scope(actor_role: str) -> bool:
|
||
"""园区端可查看/编辑本园区企业与用户(不可删除)。"""
|
||
return actor_role in ("operator", "carrier")
|