admin frontend elkezdése, járművek tisztázása, frontend fejleszts
This commit is contained in:
@@ -228,12 +228,34 @@ class AssetService:
|
||||
# --- MATCHER INTEGRÁCIÓ (HA NINCS CATALOG_ID DE VANNAK ADATOK) ---
|
||||
if not new_asset.catalog_id and new_asset.brand and new_asset.model:
|
||||
from app.services.asset_matcher_service import AssetMatcherService
|
||||
from app.models.vehicle.asset import AssetCatalog
|
||||
print(f"DEBUG: Matcher args: make={new_asset.brand}, model={new_asset.model}, year={new_asset.year_of_manufacture}")
|
||||
matched_result = await AssetMatcherService.find_best_match(db, new_asset)
|
||||
if matched_result:
|
||||
matched_def, conf = matched_result
|
||||
if matched_def:
|
||||
new_asset.catalog_id = matched_def.id
|
||||
# FIX: Asset.catalog_id FK references vehicle.vehicle_catalog.id (AssetCatalog),
|
||||
# NOT vehicle.vehicle_model_definitions.id (VehicleModelDefinition).
|
||||
# Look up or create the AssetCatalog entry for this definition.
|
||||
catalog_stmt = select(AssetCatalog).where(
|
||||
AssetCatalog.master_definition_id == matched_def.id
|
||||
).limit(1)
|
||||
catalog_entry = (await db.execute(catalog_stmt)).scalar_one_or_none()
|
||||
if not catalog_entry:
|
||||
# Create a new AssetCatalog entry from the matched definition
|
||||
catalog_entry = AssetCatalog(
|
||||
master_definition_id=matched_def.id,
|
||||
make=matched_def.make,
|
||||
model=matched_def.marketing_name,
|
||||
year_from=matched_def.year_from,
|
||||
year_to=matched_def.year_to,
|
||||
fuel_type=matched_def.fuel_type,
|
||||
power_kw=matched_def.power_kw if matched_def.power_kw and matched_def.power_kw > 0 else None,
|
||||
engine_capacity=matched_def.engine_capacity if matched_def.engine_capacity and matched_def.engine_capacity > 0 else None,
|
||||
)
|
||||
db.add(catalog_entry)
|
||||
await db.flush()
|
||||
new_asset.catalog_id = catalog_entry.id
|
||||
await AssetMatcherService.enrich_asset_from_definition(db, new_asset, matched_def, conf)
|
||||
await db.flush()
|
||||
|
||||
@@ -348,9 +370,15 @@ class AssetService:
|
||||
|
||||
@staticmethod
|
||||
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."""
|
||||
"""Get all distinct models for a given make, optionally filtered by vehicle_class.
|
||||
|
||||
Fuzzy matching: removes spaces and lowercases both the query and stored values
|
||||
so that 'CB 1000' matches 'CB1000'.
|
||||
"""
|
||||
stmt = select(distinct(VehicleModelDefinition.marketing_name)).where(
|
||||
VehicleModelDefinition.make == make
|
||||
func.replace(func.lower(VehicleModelDefinition.make), ' ', '').ilike(
|
||||
f'%{make.replace(" ", "").lower()}%'
|
||||
)
|
||||
)
|
||||
if vehicle_class:
|
||||
stmt = stmt.where(VehicleModelDefinition.vehicle_class == vehicle_class)
|
||||
@@ -384,6 +412,78 @@ class AssetService:
|
||||
engines = result.scalars().all()
|
||||
return engines
|
||||
|
||||
# --- CASCADING CATALOG METHODS (for wizard autocomplete) ---
|
||||
@staticmethod
|
||||
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.
|
||||
|
||||
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)
|
||||
if vehicle_class:
|
||||
stmt = stmt.where(VehicleModelDefinition.vehicle_class == vehicle_class)
|
||||
if query:
|
||||
clean_query = query.replace(' ', '').lower()
|
||||
stmt = stmt.where(
|
||||
func.replace(func.lower(VehicleModelDefinition.make), ' ', '').ilike(f'%{clean_query}%')
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
brands = result.scalars().all()
|
||||
return [b for b in brands if b]
|
||||
|
||||
@staticmethod
|
||||
async def get_catalog_models(db: AsyncSession, brand: str, vehicle_class: Optional[str] = None) -> List[Dict[str, Any]]:
|
||||
"""Get all distinct models for a given brand, returning id and marketing_name."""
|
||||
stmt = select(
|
||||
VehicleModelDefinition.id,
|
||||
VehicleModelDefinition.marketing_name
|
||||
).where(
|
||||
VehicleModelDefinition.make == brand
|
||||
).distinct(VehicleModelDefinition.marketing_name).order_by(VehicleModelDefinition.marketing_name)
|
||||
if vehicle_class:
|
||||
stmt = stmt.where(VehicleModelDefinition.vehicle_class == vehicle_class)
|
||||
result = await db.execute(stmt)
|
||||
rows = result.all()
|
||||
return [{"id": r.id, "name": r.marketing_name} for r in rows if r.marketing_name]
|
||||
|
||||
@staticmethod
|
||||
async def get_catalog_years(db: AsyncSession, brand: str, model: str) -> List[int]:
|
||||
"""Get all distinct years for a given brand and model."""
|
||||
stmt = select(distinct(VehicleModelDefinition.year_from)).where(
|
||||
VehicleModelDefinition.make == brand,
|
||||
VehicleModelDefinition.marketing_name == model,
|
||||
VehicleModelDefinition.year_from > 0
|
||||
).order_by(VehicleModelDefinition.year_from.desc())
|
||||
result = await db.execute(stmt)
|
||||
years = result.scalars().all()
|
||||
return [y for y in years if y]
|
||||
|
||||
@staticmethod
|
||||
async def get_catalog_trims(db: AsyncSession, brand: str, model: str, year: Optional[int] = None) -> List[Dict[str, Any]]:
|
||||
"""Get all trim/variant details for a given brand, model, and optional year."""
|
||||
stmt = select(VehicleModelDefinition).where(
|
||||
VehicleModelDefinition.make == brand,
|
||||
VehicleModelDefinition.marketing_name == model
|
||||
)
|
||||
if year and year > 0:
|
||||
stmt = stmt.where(VehicleModelDefinition.year_from == year)
|
||||
stmt = stmt.order_by(VehicleModelDefinition.trim_level, VehicleModelDefinition.engine_capacity)
|
||||
result = await db.execute(stmt)
|
||||
rows = result.scalars().all()
|
||||
return [
|
||||
{
|
||||
"id": r.id,
|
||||
"trim_level": r.trim_level or '',
|
||||
"engine_capacity": r.engine_capacity or 0,
|
||||
"power_kw": r.power_kw or 0,
|
||||
"fuel_type": r.fuel_type,
|
||||
"year_from": r.year_from,
|
||||
"body_type": r.body_type or '',
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
async def get_user_vehicle_limit(db: AsyncSession, user_id: int, org_id: int) -> int:
|
||||
"""
|
||||
@@ -596,15 +696,13 @@ class AssetService:
|
||||
user_badge = UserBadge(
|
||||
user_id=user_id,
|
||||
badge_id=badge.id,
|
||||
awarded_at=datetime.utcnow()
|
||||
earned_at=datetime.utcnow()
|
||||
)
|
||||
db.add(user_badge)
|
||||
await db.flush()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info(f"Awarded 'First Car' badge to user {user_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f"Error awarding first car badge: {e}")
|
||||
# Don't raise the error - badge awarding shouldn't break vehicle creation
|
||||
Reference in New Issue
Block a user