szolgáltatók beálltásai, szerkesztése , létrehozása

This commit is contained in:
Roo
2026-06-17 11:52:25 +00:00
parent 213ba3b0f1
commit bf3a971ff1
56 changed files with 14421 additions and 1512 deletions

View File

@@ -287,7 +287,7 @@ class AssetService:
except Exception as e:
await db.rollback()
logger.error(f"Asset Creation Error: {e}")
logger.error(f"Asset Creation Error: {e}", exc_info=True)
raise e
@staticmethod
@@ -360,8 +360,11 @@ class AssetService:
# --- CATALOG METHODS ---
@staticmethod
async def get_makes(db: AsyncSession, vehicle_class: Optional[str] = None) -> List[str]:
"""Get all distinct makes from vehicle model definitions, optionally filtered by vehicle_class."""
stmt = select(distinct(VehicleModelDefinition.make)).order_by(VehicleModelDefinition.make)
"""Get all distinct makes from vehicle model definitions, optionally filtered by vehicle_class.
Uses func.upper() to normalize brand casing and deduplicate case-insensitively.
"""
stmt = select(distinct(func.upper(VehicleModelDefinition.make))).order_by(func.upper(VehicleModelDefinition.make))
if vehicle_class:
stmt = stmt.where(VehicleModelDefinition.vehicle_class == vehicle_class)
result = await db.execute(stmt)
@@ -417,10 +420,11 @@ class AssetService:
async def get_catalog_brands(db: AsyncSession, vehicle_class: Optional[str] = None, query: Optional[str] = None) -> List[str]:
"""Get all distinct brands (makes) from vehicle_model_definitions, with optional class filter and search query.
Uses func.upper() to normalize brand casing and deduplicate case-insensitively.
Fuzzy matching: removes spaces and lowercases both the query and stored values
so that 'BMW' matches 'bmw' and 'CB 1000' matches 'CB1000'.
"""
stmt = select(distinct(VehicleModelDefinition.make)).order_by(VehicleModelDefinition.make)
stmt = select(distinct(func.upper(VehicleModelDefinition.make))).order_by(func.upper(VehicleModelDefinition.make))
if vehicle_class:
stmt = stmt.where(VehicleModelDefinition.vehicle_class == vehicle_class)
if query:
@@ -487,8 +491,11 @@ class AssetService:
@staticmethod
async def get_user_vehicle_limit(db: AsyncSession, user_id: int, org_id: int) -> int:
"""
Get the vehicle limit for a user, checking both user-specific AND organization limits.
Returns the HIGHER value of the two as per requirements.
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']
Returns the HIGHEST value among all applicable limits.
Args:
db: AsyncSession
@@ -496,9 +503,10 @@ class AssetService:
org_id: Organization ID
Returns:
Maximum allowed vehicles (higher of user limit and organization limit)
Maximum allowed vehicles (highest of all applicable limits)
"""
from app.models.identity import User
from app.models.core_logic import UserSubscription, SubscriptionTier
from app.services.config_service import config
try:
@@ -506,46 +514,74 @@ class AssetService:
user_stmt = select(User).where(User.id == user_id)
user = (await db.execute(user_stmt)).scalar_one()
# Get global vehicle limits configuration
# ── 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}")
# Fallback to very high limit instead of restricting users
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"
# Get user-specific limit (based on role or subscription plan)
user_limit = limits.get(user_role)
if user_limit is None:
# FIX: If role not found (e.g. "user" not in dict), try subscription plan
user_limit = limits.get(subscription_plan.lower())
if user_limit is None:
# FIX: Ultimate fallback - safe default for free/basic users
user_limit = limits.get("free", 1)
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)
# Get organization-specific limit (if configured)
# ── 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):
# Organization might have different limit structure
# Try to get limit for user's role or use a default org limit
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}")
org_limit = None
# Log the calculated limit for debugging
final_limit = user_limit
# ── 3. SUBSCRIPTION TIER JSONB LIMIT ──
# Query the user's active subscription and read rules['allowances']['max_vehicles']
subscription_limit = None
try:
sub_stmt = (
select(SubscriptionTier.rules)
.select_from(UserSubscription)
.join(SubscriptionTier, UserSubscription.tier_id == SubscriptionTier.id)
.where(
UserSubscription.user_id == user_id,
UserSubscription.is_active == True
)
.limit(1)
)
sub_result = await db.execute(sub_stmt)
tier_rules = sub_result.scalar_one_or_none()
if tier_rules and isinstance(tier_rules, dict):
allowances = tier_rules.get('allowances', {})
if isinstance(allowances, dict):
max_vehicles = allowances.get('max_vehicles')
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})"
)
except Exception as e:
logger.debug(f"Could not read subscription tier limit for user {user_id}: {e}")
# ── 4. FINAL: Take the HIGHEST of all applicable limits ──
final_limit = config_limit
if org_limit is not None:
final_limit = max(user_limit, org_limit)
logger.info(f"Calculated limit for user {user_id} (role: {user_role}, plan: {subscription_plan}): user_limit={user_limit}, org_limit={org_limit}, final={final_limit}")
else:
logger.info(f"Calculated limit for user {user_id} (role: {user_role}, plan: {subscription_plan}): user_limit={user_limit}, org_limit=None, final={final_limit}")
final_limit = max(final_limit, org_limit)
if subscription_limit is not None:
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"final={final_limit}"
)
return final_limit