RABC fejlesztése és beélesítése hibák kijavításával
This commit is contained in:
@@ -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}"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user