admin felület különválasztva
This commit is contained in:
109
backend/app/services/capability_service.py
Normal file
109
backend/app/services/capability_service.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
P0 3D Capability Matrix — Capability Resolver Service
|
||||
|
||||
Resolves the effective feature capabilities for a given organization by merging:
|
||||
1. The SubscriptionTier's feature_capabilities (tier defaults)
|
||||
2. The Organization's custom_features (org-specific overrides)
|
||||
|
||||
Superadmin "Fast-Pass": If the user's role is SUPERADMIN, all capabilities
|
||||
are bypassed and a special {"_is_superadmin": True} marker is returned.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from app.models.identity import User, UserRole
|
||||
from app.models.marketplace.organization import Organization
|
||||
from app.models.core_logic import SubscriptionTier
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def resolve_org_capabilities(
|
||||
user: User,
|
||||
org_id: Optional[int],
|
||||
db: AsyncSession,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Resolve the effective 3D capability matrix for a user within an organization.
|
||||
|
||||
Business Logic:
|
||||
1. Fast-Pass: If user.role is SUPERADMIN, return {"_is_superadmin": True}.
|
||||
2. If no org_id is provided, return an empty dict (no capabilities).
|
||||
3. Fetch the Organization by org_id, joined with its SubscriptionTier.
|
||||
4. tier_features = tier.feature_capabilities (or {} if None/missing).
|
||||
5. org_features = org.custom_features (or {} if None/missing).
|
||||
6. Merge: {**tier_features, **org_features} (org overrides tier).
|
||||
7. Return the merged dict.
|
||||
|
||||
Args:
|
||||
user: The authenticated User instance.
|
||||
org_id: The active organization ID (may be None).
|
||||
db: Async SQLAlchemy session.
|
||||
|
||||
Returns:
|
||||
A dict of resolved capability flags.
|
||||
"""
|
||||
# ── Fast-Pass: SUPERADMIN bypasses everything ──
|
||||
if user.role == UserRole.SUPERADMIN:
|
||||
logger.debug(
|
||||
"Capability Fast-Pass for user=%s (SUPERADMIN)",
|
||||
user.id,
|
||||
)
|
||||
return {"_is_superadmin": True}
|
||||
|
||||
# ── No active organization → no capabilities ──
|
||||
if org_id is None:
|
||||
logger.debug(
|
||||
"No active org for user=%s, returning empty capabilities",
|
||||
user.id,
|
||||
)
|
||||
return {}
|
||||
|
||||
# ── Fetch Organization + SubscriptionTier ──
|
||||
stmt = (
|
||||
select(Organization)
|
||||
.options(
|
||||
joinedload(Organization.subscription_tier)
|
||||
)
|
||||
.where(Organization.id == org_id)
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
org = result.unique().scalar_one_or_none()
|
||||
|
||||
if not org:
|
||||
logger.warning(
|
||||
"Organization id=%s not found for user=%s, returning empty capabilities",
|
||||
org_id,
|
||||
user.id,
|
||||
)
|
||||
return {}
|
||||
|
||||
# ── Extract tier features ──
|
||||
tier_features: Dict[str, Any] = {}
|
||||
if org.subscription_tier_id is not None:
|
||||
# The subscription_tier is loaded via joinedload
|
||||
tier = org.subscription_tier # type: Optional[SubscriptionTier]
|
||||
if tier and tier.feature_capabilities:
|
||||
tier_features = dict(tier.feature_capabilities)
|
||||
|
||||
# ── Extract org custom features ──
|
||||
org_features: Dict[str, Any] = {}
|
||||
if org.custom_features:
|
||||
org_features = dict(org.custom_features)
|
||||
|
||||
# ── Merge: org custom_features override tier defaults ──
|
||||
merged = {**tier_features, **org_features}
|
||||
|
||||
logger.debug(
|
||||
"Resolved capabilities for org=%s user=%s: %s",
|
||||
org_id,
|
||||
user.id,
|
||||
merged,
|
||||
)
|
||||
|
||||
return merged
|
||||
@@ -141,6 +141,7 @@ async def search_providers(
|
||||
category: Optional[str] = None,
|
||||
city: Optional[str] = None,
|
||||
category_ids: Optional[List[int]] = None,
|
||||
org_type: Optional[str] = None,
|
||||
limit: int = 20,
|
||||
offset: int = 0,
|
||||
) -> ProviderSearchResponse:
|
||||
@@ -174,6 +175,7 @@ async def search_providers(
|
||||
category: Kategória szűrő
|
||||
city: Város szűrő — AND kapcsolat a q-val
|
||||
category_ids: Kategória ID-k listája (SZIGORÚ AND szűrés)
|
||||
org_type: Szervezet típus szűrő (pl. 'service_provider', 'insurance')
|
||||
limit: Lapozási limit
|
||||
offset: Lapozási offset
|
||||
|
||||
@@ -190,9 +192,12 @@ async def search_providers(
|
||||
|
||||
# --- 1. LEKÉRDEZÉS: Szervezetek (fleet.organizations) ---
|
||||
org_conditions = [
|
||||
Organization.org_type == OrgType.service_provider,
|
||||
Organization.is_deleted == False,
|
||||
]
|
||||
if org_type:
|
||||
org_conditions.append(Organization.org_type == org_type)
|
||||
else:
|
||||
org_conditions.append(Organization.org_type == OrgType.service_provider)
|
||||
if q_tokens:
|
||||
token_conditions = []
|
||||
for token in q_tokens:
|
||||
@@ -630,25 +635,10 @@ async def quick_add_provider(
|
||||
db.add(branch)
|
||||
|
||||
# =====================================================================
|
||||
# 6. 👤 ORGANIZATION MEMBER LÉTREHOZÁSA (P0 CRITICAL BUGFIX 2026-06-17)
|
||||
# 6. 👤 ORGANIZATION MEMBER LÉTREHOZÁSA (KIHAGYVA - P0 ARCHITECTURE FIX)
|
||||
# =====================================================================
|
||||
# A létrehozó felhasználó ADMIN szerepkörű tag lesz (NEM OWNER).
|
||||
# Crowdsourcingból felvett szolgáltatóknak nincs tulajdonosa (owner_id=NULL).
|
||||
# Az ADMIN jogosultság elegendő a későbbi szerkesztéshez, de a cég
|
||||
# nem jelenik meg a "Cégeim" garázsválasztóban.
|
||||
member = OrganizationMember(
|
||||
organization_id=org.id,
|
||||
user_id=user_id,
|
||||
role=OrgUserRole.ADMIN,
|
||||
is_permanent=True,
|
||||
is_verified=True,
|
||||
)
|
||||
db.add(member)
|
||||
await db.flush()
|
||||
logger.info(
|
||||
f"OrganizationMember created: org_id={org.id}, user_id={user_id}, "
|
||||
f"role=ADMIN (crowdsourced provider has no owner)"
|
||||
)
|
||||
# DÖNTÉS: A létrehozó felhasználó NEM lehet tagja a beszállító szervezetnek.
|
||||
# A szerviz jöjjön létre owner_id=None beállítással, tagok nélkül.
|
||||
# =====================================================================
|
||||
|
||||
# =====================================================================
|
||||
|
||||
Reference in New Issue
Block a user