Files
server-core/app/services/membership_service.py
T
Pine cb1492f2d6 fix: 完整审查修复 - 5个问题
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() 重复翻译 → 优先直接显示中文
2026-09-03 09:43:34 +08:00

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,
}