admin felület különválasztva

This commit is contained in:
Roo
2026-06-24 11:29:45 +00:00
parent 71ef33bb85
commit 80a5d67f79
462 changed files with 87873 additions and 312 deletions

View 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

View File

@@ -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.
# =====================================================================
# =====================================================================