frontend kínlódás
This commit is contained in:
@@ -7,10 +7,12 @@ from datetime import datetime
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, and_, distinct
|
||||
from sqlalchemy.orm import selectinload
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.models import Asset, AssetAssignment, AssetTelemetry, AssetFinancials, VehicleModelDefinition
|
||||
from app.models.identity import User
|
||||
from app.models.vehicle.history import LogSeverity
|
||||
from app.schemas.asset import AssetCreate
|
||||
from app.services.config_service import config
|
||||
from app.services.gamification_service import GamificationService
|
||||
from app.services.security_service import security_service
|
||||
@@ -33,20 +35,23 @@ class AssetService:
|
||||
db: AsyncSession,
|
||||
user_id: int,
|
||||
org_id: int,
|
||||
vin: Optional[str] = None,
|
||||
license_plate: Optional[str] = None,
|
||||
catalog_id: int = None,
|
||||
asset_data: AssetCreate,
|
||||
draft: bool = False
|
||||
):
|
||||
"""
|
||||
Intelligens Jármű Rögzítés:
|
||||
Ha új: létrehozza.
|
||||
Intelligens Jármű Rögzítés - Thick Digital Twin támogatással:
|
||||
Ha új: létrehozza a teljes technikai adatokkal.
|
||||
Ha már létezik: Transzfer folyamatot indít.
|
||||
Ha draft=True vagy VIN hiányzik: draft státuszban hozza létre.
|
||||
Automatikus státusz meghatározás az adatkomplettség alapján.
|
||||
Catalog Snapshot Sync: Ha catalog_id van, betölti a hiányzó technikai adatokat.
|
||||
"""
|
||||
try:
|
||||
vin_clean = vin.strip().upper() if vin else None
|
||||
license_plate_clean = license_plate.strip().upper() if license_plate else None
|
||||
# Clean input data
|
||||
vin_clean = asset_data.vin.strip().upper() if asset_data.vin else None
|
||||
license_plate_clean = asset_data.license_plate.strip().upper()
|
||||
|
||||
# Use organization_id from asset_data if provided, otherwise use the passed org_id
|
||||
target_org_id = asset_data.organization_id or org_id
|
||||
|
||||
# 1. ADMIN LIMIT ELLENŐRZÉS (csak aktív járművek számítanak)
|
||||
user_stmt = select(User).where(User.id == user_id)
|
||||
@@ -54,17 +59,35 @@ class AssetService:
|
||||
|
||||
# Get vehicle limit using the new function that checks both user AND organization limits
|
||||
# Returns the HIGHER value of user-specific and organization-specific limits
|
||||
allowed_limit = await AssetService.get_user_vehicle_limit(db, user_id, org_id)
|
||||
allowed_limit = await AssetService.get_user_vehicle_limit(db, user_id, target_org_id)
|
||||
|
||||
# Csak aktív járművek számítanak a limitbe (draft-ok nem)
|
||||
count_stmt = select(func.count(Asset.id)).where(
|
||||
Asset.current_organization_id == org_id,
|
||||
Asset.current_organization_id == target_org_id,
|
||||
Asset.status == "active"
|
||||
)
|
||||
current_count = (await db.execute(count_stmt)).scalar()
|
||||
|
||||
if current_count >= allowed_limit and not draft:
|
||||
raise ValueError(f"Limit túllépés! A csomagod {allowed_limit} autót engedélyez.")
|
||||
# Determine status based on data completeness (use Pydantic validator's logic)
|
||||
# Check the 5 core fields: license_plate, brand, model, vehicle_class, fuel_type
|
||||
core_fields_complete = all([
|
||||
asset_data.license_plate and asset_data.license_plate.strip(),
|
||||
asset_data.brand and asset_data.brand.strip(),
|
||||
asset_data.model and asset_data.model.strip(),
|
||||
asset_data.vehicle_class and asset_data.vehicle_class.strip(),
|
||||
asset_data.fuel_type and asset_data.fuel_type.strip()
|
||||
])
|
||||
|
||||
# Determine final status
|
||||
if draft:
|
||||
status = "draft"
|
||||
elif not core_fields_complete:
|
||||
status = "draft"
|
||||
else:
|
||||
status = "active"
|
||||
|
||||
if current_count >= allowed_limit and status == "active":
|
||||
raise ValueError(f"Limit túllépés! A csomagod {allowed_limit} aktív autót engedélyez.")
|
||||
|
||||
# 2. LÉTEZIK-E MÁR A JÁRMŰ? (csak ha van VIN)
|
||||
existing_asset = None
|
||||
@@ -74,41 +97,95 @@ class AssetService:
|
||||
|
||||
if existing_asset:
|
||||
# HA MÁR A JELENLEGI SZERVEZETNÉL VAN
|
||||
if existing_asset.current_organization_id == org_id:
|
||||
if existing_asset.current_organization_id == target_org_id:
|
||||
raise ValueError("Ez a jármű már a te garázsodban van.")
|
||||
|
||||
# TRANSZFER FOLYAMAT INDÍTÁSA
|
||||
return await AssetService.initiate_ownership_transfer(
|
||||
db, existing_asset, user_id, org_id, license_plate_clean or ""
|
||||
db, existing_asset, user_id, target_org_id, license_plate_clean or ""
|
||||
)
|
||||
|
||||
# 3. ÚJ JÁRMŰ LÉTREHOZÁSA (Standard vagy Draft Flow)
|
||||
# Dynamic Gatekeeper Logic: If both vin and catalog_id are missing, status = 'draft'
|
||||
# If core data is provided (either vin OR catalog_id), status = 'active'
|
||||
# Also respect the draft parameter if explicitly set
|
||||
if draft:
|
||||
status = "draft"
|
||||
elif not vin_clean and not catalog_id:
|
||||
status = "draft"
|
||||
else:
|
||||
status = "active"
|
||||
# 3. CATALOG SNAPSHOT SYNC - Ha catalog_id van, betöltjük a hiányzó technikai adatokat
|
||||
catalog_data = {}
|
||||
if asset_data.catalog_id:
|
||||
catalog_stmt = select(VehicleModelDefinition).where(
|
||||
VehicleModelDefinition.id == asset_data.catalog_id
|
||||
)
|
||||
catalog = (await db.execute(catalog_stmt)).scalar_one_or_none()
|
||||
if catalog:
|
||||
# Map catalog fields to asset fields (only if not already provided by user)
|
||||
catalog_data = {
|
||||
'brand': catalog.make if not asset_data.brand else None,
|
||||
'model': catalog.marketing_name if not asset_data.model else None,
|
||||
'vehicle_class': catalog.vehicle_class if not asset_data.vehicle_class else None,
|
||||
'fuel_type': catalog.fuel_type if not asset_data.fuel_type else None,
|
||||
'power_kw': catalog.power_kw if not asset_data.power_kw else None,
|
||||
'engine_capacity': catalog.engine_capacity if not asset_data.engine_capacity else None,
|
||||
'euro_classification': catalog.euro_class if not asset_data.euro_classification else None,
|
||||
'body_type': catalog.body_type if not asset_data.trim_level else None,
|
||||
}
|
||||
# Remove None values
|
||||
catalog_data = {k: v for k, v in catalog_data.items() if v is not None}
|
||||
|
||||
# 4. ÚJ JÁRMŰ LÉTREHOZÁSA - Thick Digital Twin
|
||||
# Először összeállítjuk az összes adatot (user input + catalog snapshot)
|
||||
# Get default vehicle class from config if not provided
|
||||
default_vehicle_class = await config.get_setting(db, "DEFAULT_VEHICLE_CLASS", default="car")
|
||||
|
||||
asset_fields = {
|
||||
'vin': vin_clean,
|
||||
'license_plate': license_plate_clean,
|
||||
'catalog_id': asset_data.catalog_id,
|
||||
'current_organization_id': target_org_id,
|
||||
'owner_person_id': user.person_id,
|
||||
'owner_org_id': asset_data.owner_org_id or target_org_id,
|
||||
'operator_org_id': asset_data.operator_org_id,
|
||||
'status': status,
|
||||
'individual_equipment': asset_data.individual_equipment or {},
|
||||
'created_at': datetime.utcnow(),
|
||||
|
||||
new_asset = Asset(
|
||||
vin=vin_clean,
|
||||
license_plate=license_plate_clean,
|
||||
catalog_id=catalog_id,
|
||||
current_organization_id=org_id,
|
||||
owner_person_id=user.person_id,
|
||||
owner_org_id=org_id,
|
||||
status=status,
|
||||
individual_equipment={},
|
||||
created_at=datetime.utcnow()
|
||||
)
|
||||
# Classification
|
||||
'brand': asset_data.brand or catalog_data.get('brand'),
|
||||
'model': asset_data.model or catalog_data.get('model'),
|
||||
'vehicle_class': asset_data.vehicle_class or catalog_data.get('vehicle_class') or default_vehicle_class,
|
||||
'trim_level': asset_data.trim_level,
|
||||
|
||||
# Technical Specs
|
||||
'fuel_type': asset_data.fuel_type or catalog_data.get('fuel_type'),
|
||||
'engine_capacity': asset_data.engine_capacity or catalog_data.get('engine_capacity'),
|
||||
'power_kw': asset_data.power_kw or catalog_data.get('power_kw'),
|
||||
'torque_nm': asset_data.torque_nm,
|
||||
'cylinder_layout': asset_data.cylinder_layout,
|
||||
'transmission_type': asset_data.transmission_type,
|
||||
'drive_type': asset_data.drive_type,
|
||||
'euro_classification': asset_data.euro_classification or catalog_data.get('euro_classification'),
|
||||
|
||||
# Physical Dimensions
|
||||
'curb_weight': asset_data.curb_weight,
|
||||
'max_weight': asset_data.max_weight,
|
||||
'cargo_volume_x': asset_data.cargo_volume_x,
|
||||
'cargo_volume_y': asset_data.cargo_volume_y,
|
||||
'door_count': asset_data.door_count,
|
||||
'seat_count': asset_data.seat_count,
|
||||
|
||||
# Equipment
|
||||
'roof_type': asset_data.roof_type,
|
||||
'audio_system_type': asset_data.audio_system_type,
|
||||
|
||||
# Timeline
|
||||
'year_of_manufacture': asset_data.year_of_manufacture,
|
||||
'first_registration_date': asset_data.first_registration_date,
|
||||
}
|
||||
|
||||
# Remove None values from the dictionary
|
||||
asset_fields = {k: v for k, v in asset_fields.items() if v is not None}
|
||||
|
||||
new_asset = Asset(**asset_fields)
|
||||
db.add(new_asset)
|
||||
await db.flush()
|
||||
|
||||
# Digitális Iker Alapmodulok
|
||||
db.add(AssetAssignment(asset_id=new_asset.id, organization_id=org_id, status="active"))
|
||||
db.add(AssetAssignment(asset_id=new_asset.id, organization_id=target_org_id, status="active"))
|
||||
db.add(AssetTelemetry(asset_id=new_asset.id))
|
||||
db.add(AssetFinancials(
|
||||
asset_id=new_asset.id,
|
||||
@@ -122,7 +199,7 @@ class AssetService:
|
||||
await GamificationService.award_points(db, user_id, int(reward), "NEW_ASSET_REG")
|
||||
|
||||
# Check if this is user's first vehicle and award "First Car" badge
|
||||
await AssetService._award_first_car_badge(db, user_id, org_id)
|
||||
await AssetService._award_first_car_badge(db, user_id, target_org_id)
|
||||
|
||||
await db.commit()
|
||||
return new_asset
|
||||
@@ -207,11 +284,14 @@ class AssetService:
|
||||
return [make for make in makes if make] # Filter out None/empty
|
||||
|
||||
@staticmethod
|
||||
async def get_models(db: AsyncSession, make: str) -> List[str]:
|
||||
"""Get all distinct models for a given make."""
|
||||
async def get_models(db: AsyncSession, make: str, vehicle_class: str = None) -> List[str]:
|
||||
"""Get all distinct models for a given make, optionally filtered by vehicle_class."""
|
||||
stmt = select(distinct(VehicleModelDefinition.marketing_name)).where(
|
||||
VehicleModelDefinition.make == make
|
||||
).order_by(VehicleModelDefinition.marketing_name)
|
||||
)
|
||||
if vehicle_class:
|
||||
stmt = stmt.where(VehicleModelDefinition.vehicle_class == vehicle_class)
|
||||
stmt = stmt.order_by(VehicleModelDefinition.marketing_name)
|
||||
result = await db.execute(stmt)
|
||||
models = result.scalars().all()
|
||||
return [model for model in models if model]
|
||||
|
||||
Reference in New Issue
Block a user