RABC fejlesztése és beélesítése hibák kijavításával

This commit is contained in:
Roo
2026-06-18 18:09:51 +00:00
parent 611307a24b
commit fe3c32597d
57 changed files with 4689 additions and 655 deletions

View File

@@ -492,10 +492,12 @@ class AssetService:
async def get_user_vehicle_limit(db: AsyncSession, user_id: int, org_id: int) -> int:
"""
Get the vehicle limit for a user, checking:
1. Config-based limits (user role, subscription plan, org-specific)
2. Subscription tier JSONB rules['allowances']['max_vehicles']
1. Subscription tier JSONB rules['allowances']['max_vehicles'] (PRIMARY source of truth)
2. Organization-level base_asset_limit (fallback if no user subscription)
3. Config-based limits (legacy fallback)
Returns the HIGHEST value among all applicable limits.
P0: The subscription_tier JSONB rules are now the Single Source of Truth.
Legacy string-based subscription_plan lookups have been removed.
Args:
db: AsyncSession
@@ -503,10 +505,11 @@ class AssetService:
org_id: Organization ID
Returns:
Maximum allowed vehicles (highest of all applicable limits)
Maximum allowed vehicles
"""
from app.models.identity import User
from app.models.core_logic import UserSubscription, SubscriptionTier
from app.models.marketplace.organization import Organization
from app.services.config_service import config
try:
@@ -514,34 +517,11 @@ class AssetService:
user_stmt = select(User).where(User.id == user_id)
user = (await db.execute(user_stmt)).scalar_one()
# ── 1. CONFIG-BASED LIMIT ──
limits = await config.get_setting(db, "VEHICLE_LIMIT")
if limits is None:
logger.error(f"VEHICLE_LIMIT configuration not found in database for user {user_id}")
limits = {"admin": 9999, "superadmin": 9999, "user": 100, "free": 100, "premium": 100, "vip": 100, "service_pro": 100}
user_role = user.role.value if hasattr(user.role, 'value') else str(user.role)
subscription_plan = user.subscription_plan or "free"
config_limit = limits.get(user_role)
if config_limit is None:
config_limit = limits.get(subscription_plan.lower())
if config_limit is None:
config_limit = limits.get("free", 1)
# ── 2. ORGANIZATION-SPECIFIC LIMIT ──
org_limit = None
try:
org_limits = await config.get_setting(db, "VEHICLE_LIMIT", org_id=org_id)
if org_limits and isinstance(org_limits, dict):
org_limit = org_limits.get(user_role) or org_limits.get(subscription_plan.lower())
if org_limit is None and "default" in org_limits:
org_limit = org_limits["default"]
except Exception as e:
logger.debug(f"No organization-specific VEHICLE_LIMIT found for org {org_id}: {e}")
# ── 3. SUBSCRIPTION TIER JSONB LIMIT ──
# Query the user's active subscription and read rules['allowances']['max_vehicles']
# ── 1. SUBSCRIPTION TIER JSONB LIMIT (PRIMARY) ──
# P0: This is now the Single Source of Truth for vehicle limits.
# Reads rules['allowances']['max_vehicles'] from the user's active subscription tier.
subscription_limit = None
try:
sub_stmt = (
@@ -564,12 +544,53 @@ class AssetService:
if max_vehicles is not None and isinstance(max_vehicles, (int, float)):
subscription_limit = int(max_vehicles)
logger.info(
f"Subscription tier limit for user {user_id}: "
f"max_vehicles={subscription_limit} (from rules={tier_rules})"
f"[P0] Subscription tier limit for user {user_id}: "
f"max_vehicles={subscription_limit} (from JSONB rules)"
)
except Exception as e:
logger.debug(f"Could not read subscription tier limit for user {user_id}: {e}")
# ── 2. ORGANIZATION base_asset_limit (fallback) ──
# If no user-level subscription, check the org's assigned tier
org_limit = None
if subscription_limit is None:
try:
org_stmt = select(Organization).where(Organization.id == org_id)
org = (await db.execute(org_stmt)).scalar_one_or_none()
if org and org.subscription_tier_id:
# Read from the org's subscription tier
tier_stmt = select(SubscriptionTier).where(SubscriptionTier.id == org.subscription_tier_id)
tier = (await db.execute(tier_stmt)).scalar_one_or_none()
if tier and tier.rules:
allowances = tier.rules.get('allowances', {})
if isinstance(allowances, dict):
max_vehicles = allowances.get('max_vehicles')
if max_vehicles is not None:
org_limit = int(max_vehicles)
logger.info(
f"[P0] Org subscription tier limit for org {org_id}: "
f"max_vehicles={org_limit} (from JSONB rules)"
)
elif org:
# Fallback to legacy base_asset_limit if no tier assigned
org_limit = max(org.base_asset_limit or 1, 1)
except Exception as e:
logger.debug(f"Could not read org subscription tier limit for org {org_id}: {e}")
# ── 3. CONFIG-BASED LIMIT (legacy fallback, role-based only) ──
config_limit = None
try:
limits = await config.get_setting(db, "VEHICLE_LIMIT")
if limits and isinstance(limits, dict):
config_limit = limits.get(user_role)
if config_limit is None:
config_limit = limits.get("default")
except Exception as e:
logger.debug(f"Could not read VEHICLE_LIMIT config: {e}")
if config_limit is None:
config_limit = 1 # absolute fallback
# ── 4. FINAL: Take the HIGHEST of all applicable limits ──
final_limit = config_limit
if org_limit is not None:
@@ -578,8 +599,8 @@ class AssetService:
final_limit = max(final_limit, subscription_limit)
logger.info(
f"Vehicle limit for user {user_id} (role={user_role}, plan={subscription_plan}): "
f"config={config_limit}, org={org_limit}, subscription={subscription_limit}, "
f"[P0] Vehicle limit for user {user_id} (role={user_role}): "
f"subscription_tier={subscription_limit}, org={org_limit}, config={config_limit}, "
f"final={final_limit}"
)

View File

@@ -107,7 +107,7 @@ class AuthService:
await db.flush()
# Szerepkör dinamikus feloldása
assigned_role = UserRole[default_role_name] if default_role_name in UserRole.__members__ else UserRole.user
assigned_role = UserRole[default_role_name] if default_role_name in UserRole.__members__ else UserRole.USER
# Referral kód generálása
referral_code = generate_secure_slug(8).upper()
@@ -273,7 +273,15 @@ class AuthService:
# Infrastruktúra elemek
db.add(Branch(organization_id=new_org.id, address_id=addr_id, name="Home Base", is_main=True))
db.add(OrganizationMember(organization_id=new_org.id, user_id=user.id, role="OWNER"))
db.add(OrganizationMember(
organization_id=new_org.id,
user_id=user.id,
person_id=user.person_id,
role="OWNER",
is_permanent=True,
is_verified=True,
status="active"
))
db.add(Wallet(user_id=user.id, currency=kyc_in.preferred_currency or base_cur))
# db.add(UserStats(user_id=user.id)) # GamificationService kezeli

View File

@@ -51,18 +51,14 @@ class PricingCalculator:
# RBAC rank discounts (higher rank = bigger discount)
# Map the actual UserRole enum values to discount percentages
# RBAC Phase 1: Tisztított rendszerszintű szerepkörök
RBAC_DISCOUNTS = {
UserRole.superadmin: 0.5, # 50% discount
UserRole.admin: 0.3, # 30% discount
UserRole.fleet_manager: 0.2, # 20% discount
UserRole.user: 0.0, # 0% discount
# Add other roles as needed
UserRole.region_admin: 0.25, # 25% discount
UserRole.country_admin: 0.25, # 25% discount
UserRole.moderator: 0.15, # 15% discount
UserRole.sales_agent: 0.1, # 10% discount
UserRole.service_owner: 0.1, # 10% discount
UserRole.driver: 0.0, # 0% discount
UserRole.SUPERADMIN: 0.5, # 50% discount
UserRole.ADMIN: 0.3, # 30% discount
UserRole.MODERATOR: 0.15, # 15% discount
UserRole.SERVICE_MGR: 0.1, # 10% discount
UserRole.SALES_REP: 0.1, # 10% discount
UserRole.USER: 0.0, # 0% discount
}
@classmethod
@@ -71,7 +67,7 @@ class PricingCalculator:
db: AsyncSession,
base_amount: float,
country_code: str = "HU",
user_role: UserRole = UserRole.user,
user_role: UserRole = UserRole.USER,
individual_discounts: Optional[List[Dict[str, Any]]] = None
) -> float:
"""
@@ -663,7 +659,7 @@ async def calculate_price(
db: AsyncSession,
base_amount: float,
country_code: str = "HU",
user_role: UserRole = UserRole.user,
user_role: UserRole = UserRole.USER,
individual_discounts: Optional[List[Dict[str, Any]]] = None
) -> float:
"""
@@ -806,6 +802,95 @@ async def upgrade_subscription(
}
async def upgrade_org_subscription(
db: AsyncSession,
org_id: int,
tier_id: int,
actor_user_id: int
) -> Dict[str, Any]:
"""
Szervezet előfizetési csomagjának beállítása (org-level subscription assignment).
P0 Feature: Subscription & Package Assignment Bridge.
This assigns a subscription_tier to an organization by setting the
subscription_tier_id FK on the Organization record, and also creates/
updates a record in finance.org_subscriptions for audit trail.
Args:
db: Database session
org_id: Organization ID
tier_id: SubscriptionTier ID
actor_user_id: User ID performing the action (for audit)
Returns:
Dict: Result with tier details
"""
from app.models.core_logic import SubscriptionTier, OrganizationSubscription
from app.models.marketplace.organization import Organization
# 1. Ellenőrizze, hogy a tier létezik-e
stmt = select(SubscriptionTier).where(SubscriptionTier.id == tier_id)
result = await db.execute(stmt)
tier = result.scalar_one_or_none()
if not tier:
raise ValueError(f"Subscription tier id={tier_id} not found")
# 2. Ellenőrizze, hogy a szervezet létezik-e
stmt = select(Organization).where(Organization.id == org_id)
result = await db.execute(stmt)
org = result.scalar_one_or_none()
if not org:
raise ValueError(f"Organization id={org_id} not found")
# 3. Állítsa be a subscription_tier_id-t a szervezeten
org.subscription_tier_id = tier_id
org.subscription_plan = tier.name
# 4. Extract max_vehicles from tier rules to update base_asset_limit
max_vehicles = tier.rules.get("allowances", {}).get("max_vehicles", 1) if tier.rules else 1
org.base_asset_limit = max_vehicles
# 5. Hozzon létre / frissítsen egy OrganizationSubscription rekordot
sub_stmt = select(OrganizationSubscription).where(
OrganizationSubscription.org_id == org_id,
OrganizationSubscription.is_active == True
)
sub_result = await db.execute(sub_stmt)
existing_sub = sub_result.scalar_one_or_none()
if existing_sub:
# Deaktiváljuk a régit
existing_sub.is_active = False
existing_sub.valid_until = datetime.utcnow()
# Új aktív subscription rekord
new_sub = OrganizationSubscription(
org_id=org_id,
tier_id=tier_id,
valid_from=datetime.utcnow(),
is_active=True
)
db.add(new_sub)
logger.info(
f"Org subscription upgraded: org_id={org_id}, "
f"tier={tier.name} (id={tier_id}), "
f"max_vehicles={max_vehicles}, "
f"actor={actor_user_id}"
)
return {
"success": True,
"organization_id": org_id,
"tier_id": tier_id,
"tier_name": tier.name,
"max_vehicles": max_vehicles,
"message": f"Organization {org_id} upgraded to {tier.name}"
}
async def record_ledger_entry(
db: AsyncSession,
user_id: int,

View File

@@ -38,7 +38,7 @@ class SearchService:
user_tier = current_user.tier_name
if current_user.role in [UserRole.superadmin, UserRole.admin]:
if current_user.role in [UserRole.SUPERADMIN, UserRole.ADMIN]:
user_tier = "vip"
ranking_rules = await config.get_setting(

View File

@@ -30,7 +30,7 @@ class SocialAuthService:
db.add(new_person)
await db.flush()
user = User(email=email, person_id=new_person.id, role=UserRole.user, is_active=False)
user = User(email=email, person_id=new_person.id, role=UserRole.USER, is_active=False)
db.add(user)
await db.flush()