cb1492f2d6
1. _resolve_identity: permissions_for 未归一化 role → 使用 account_type+permission_role 2. rbac_org.py: 用 user.role 判断 operator → 改用 capabilities 3. membership_service: sync_user_affiliation 不同步 organization_members → 新增 _sync_org_members 4. repositories.to_profile: capabilities 只查旧表 → 增加 organization_members 查询 5. Header.RoleBadge: account_type_label 被 t() 重复翻译 → 优先直接显示中文
152 lines
6.7 KiB
Python
152 lines
6.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""成员关系服务:派生归属同步 + 转园迁移执行(users 单值归属的唯一合法写路径)。"""
|
|
from __future__ import annotations
|
|
|
|
from ..infrastructure.models import Organization, ParkCompany, ParkTenant
|
|
from ..infrastructure.repositories import Database, utcnow_iso
|
|
|
|
|
|
async def _ensure_org(db: Database, org_id: str, org_type: str, name: str = "") -> Organization:
|
|
"""确保 organization 记录存在,不存在则创建。"""
|
|
from sqlalchemy import select
|
|
|
|
org = await db.session.get(Organization, org_id)
|
|
if org is None:
|
|
org = Organization(
|
|
id=org_id, name=name or org_id, type=org_type,
|
|
region_id=None, parent_id=None, created_at=utcnow_iso(),
|
|
)
|
|
db.session.add(org)
|
|
await db.session.flush()
|
|
return org
|
|
|
|
|
|
async def _sync_org_members(db: Database, user_id: str) -> None:
|
|
"""把 park_members.admin / company_members.is_admin 同步到 organization_members。
|
|
|
|
统一成员关系表是权限计算的首选来源,必须与旧表保持一致。
|
|
"""
|
|
from sqlalchemy import select
|
|
from ..infrastructure.models import OrganizationMember
|
|
|
|
now = utcnow_iso()
|
|
|
|
# 同步园区管理员 → organization_members (type=carrier)
|
|
parks = await db.park_members.list_by_user(user_id)
|
|
for pm in parks:
|
|
if pm.get("member_type") != "admin" or pm.get("status") != "active":
|
|
continue
|
|
tenant = await db.session.scalar(select(ParkTenant).where(ParkTenant.id == pm["park_id"]))
|
|
await _ensure_org(db, pm["park_id"], "carrier", tenant.name if tenant else "")
|
|
existing = await db.session.get(OrganizationMember, (pm["park_id"], user_id))
|
|
if existing is None:
|
|
db.session.add(OrganizationMember(
|
|
org_id=pm["park_id"], user_id=user_id,
|
|
role="admin", is_admin=True, status="active", joined_at=now,
|
|
))
|
|
|
|
# 同步企业管理员 → organization_members (type=enterprise)
|
|
comps = await db.company_members.list_by_user(user_id)
|
|
for cm in comps:
|
|
if not cm.get("is_admin") or cm.get("status") != "active":
|
|
continue
|
|
comp = await db.session.scalar(select(ParkCompany).where(ParkCompany.id == cm["company_id"]))
|
|
await _ensure_org(db, cm["company_id"], "enterprise", comp.name if comp else "")
|
|
existing = await db.session.get(OrganizationMember, (cm["company_id"], user_id))
|
|
if existing is None:
|
|
db.session.add(OrganizationMember(
|
|
org_id=cm["company_id"], user_id=user_id,
|
|
role="admin", is_admin=True, status="active", joined_at=now,
|
|
))
|
|
|
|
await db.session.flush()
|
|
|
|
|
|
async def sync_user_affiliation(db: Database, user_id: str) -> None:
|
|
"""重算 users.affiliation / park_id / park_name(派生展示缓存),并同步 organization_members。
|
|
|
|
主归属规则:member_type=admin 优先,其次最早创建的 active 园区成员;
|
|
无任何园区成员 → independent。企业展示字段取 is_admin 企业名(否则最早一条)。
|
|
"""
|
|
from sqlalchemy import select
|
|
|
|
# 先同步统一成员关系表
|
|
await _sync_org_members(db, user_id)
|
|
|
|
parks = await db.park_members.list_by_user(user_id)
|
|
fields: dict = {}
|
|
if parks:
|
|
admin = next((p for p in parks if p.get("member_type") == "admin"), None)
|
|
primary = admin or parks[0]
|
|
tenant = await db.session.scalar(select(ParkTenant).where(ParkTenant.id == primary["park_id"]))
|
|
fields.update(affiliation="park", park_id=primary["park_id"],
|
|
park_name=(tenant.name if tenant else ""))
|
|
else:
|
|
fields.update(affiliation="independent", park_id="", park_name="")
|
|
|
|
comps = await db.company_members.list_by_user(user_id)
|
|
if comps:
|
|
comp_admin = next((c for c in comps if c.get("is_admin")), None)
|
|
comp = await db.session.scalar(
|
|
select(ParkCompany).where(ParkCompany.id == (comp_admin or comps[0])["company_id"]))
|
|
if comp:
|
|
fields["company"] = comp.name
|
|
|
|
await db.users.set_classification(user_id, **fields)
|
|
|
|
|
|
async def transfer_user_park(db: Database, user_id: str, from_park_id: str, to_park_id: str,
|
|
*, operator_id: str = "") -> None:
|
|
"""转园审批通过后的用户迁移:删旧园区成员、加新园区成员(不动其他园区身份)。"""
|
|
if from_park_id:
|
|
await db.park_members.remove(user_id, from_park_id)
|
|
await db.park_members.add(user_id, to_park_id, member_type="staff",
|
|
created_by=operator_id or "transfer")
|
|
await sync_user_affiliation(db, user_id)
|
|
|
|
|
|
async def transfer_company_park(db: Database, company_id: str, from_park_id: str, to_park_id: str,
|
|
*, operator_id: str = "") -> None:
|
|
"""企业整体转园:企业 tenant_id 迁移 + 成员批量迁移(仅移除 from 园区成员身份,
|
|
不影响成员在其他园区的身份)。"""
|
|
from sqlalchemy import select
|
|
|
|
comp = await db.session.scalar(select(ParkCompany).where(ParkCompany.id == company_id))
|
|
if comp is None:
|
|
return
|
|
comp.tenant_id = to_park_id
|
|
comp.company_kind = "park_entered"
|
|
await db.session.commit()
|
|
|
|
member_rows = await db.company_members.list_by_company(company_id)
|
|
for m in member_rows:
|
|
if from_park_id:
|
|
await db.park_members.remove(m["user_id"], from_park_id)
|
|
await db.park_members.add(m["user_id"], to_park_id, member_type="staff",
|
|
created_by=operator_id or "transfer")
|
|
await sync_user_affiliation(db, m["user_id"])
|
|
|
|
|
|
async def membership_overview(db: Database, user_id: str) -> dict:
|
|
"""用户归属总览(运营端/用户自己查看)。"""
|
|
from sqlalchemy import select
|
|
|
|
parks = []
|
|
for pm in await db.park_members.list_by_user(user_id):
|
|
tenant = await db.session.scalar(select(ParkTenant).where(ParkTenant.id == pm["park_id"]))
|
|
parks.append({**pm, "park_name": (tenant.name if tenant else "")})
|
|
companies = []
|
|
for cm in await db.company_members.list_by_user(user_id):
|
|
comp = await db.session.scalar(select(ParkCompany).where(ParkCompany.id == cm["company_id"]))
|
|
companies.append({**cm,
|
|
"company_name": (comp.name if comp else ""),
|
|
"company_kind": (comp.company_kind if comp else ""),
|
|
"tenant_id": (comp.tenant_id if comp else "")})
|
|
user = await db.users.get_by_id(user_id) or {}
|
|
return {
|
|
"user": {k: user.get(k, "") for k in ("id", "username", "nickname", "phone", "role", "affiliation")},
|
|
"user_kind": "park" if parks else "independent",
|
|
"parks": parks,
|
|
"companies": companies,
|
|
}
|