114 lines
4.2 KiB
Python
Executable File
114 lines
4.2 KiB
Python
Executable File
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, func, desc
|
|
from app.models.vehicle import Vehicle
|
|
from app.models.expense import VehicleEvent, ExpenseCategory
|
|
from app.models.social import ServiceProvider, SourceType, ModerationStatus
|
|
from app.schemas.fleet import EventCreate, TCOStats
|
|
|
|
async def add_vehicle_event(db: AsyncSession, vehicle_id: int, event_data: EventCreate, user_id: int):
|
|
"""
|
|
Összetett logika esemény rögzítésére:
|
|
1. Provider kezelés (DIY vs Existing vs New Ad-Hoc)
|
|
2. Odometer integritás ellenőrzés
|
|
3. Mentés és Jármű frissítés
|
|
"""
|
|
|
|
# 1. Jármű ellenőrzése
|
|
vehicle_res = await db.execute(select(Vehicle).where(Vehicle.id == vehicle_id))
|
|
vehicle = vehicle_res.scalars().first()
|
|
if not vehicle:
|
|
return {"error": "Vehicle not found"}
|
|
|
|
# 2. Provider Logika
|
|
final_provider_id = event_data.provider_id
|
|
|
|
if event_data.is_diy:
|
|
# Ha DIY, akkor nincs provider
|
|
final_provider_id = None
|
|
if not event_data.description:
|
|
event_data.description = "Saját javítás (DIY)"
|
|
|
|
elif event_data.provider_name and not final_provider_id:
|
|
# Ha nevet kaptunk, de ID-t nem -> Keresés vagy Létrehozás
|
|
# Megpróbáljuk megkeresni név alapján (case insensitive)
|
|
provider_res = await db.execute(select(ServiceProvider).where(func.lower(ServiceProvider.name) == event_data.provider_name.lower()))
|
|
existing_provider = provider_res.scalars().first()
|
|
|
|
if existing_provider:
|
|
final_provider_id = existing_provider.id
|
|
else:
|
|
# Nem létezik -> Létrehozunk egy "Fantom" szolgáltatót
|
|
new_provider = ServiceProvider(
|
|
name=event_data.provider_name,
|
|
address="Unknown (User Generated)",
|
|
status=ModerationStatus.pending, # Ellenőrzésre vár
|
|
source=SourceType.manual,
|
|
added_by_user_id=user_id
|
|
)
|
|
db.add(new_provider)
|
|
await db.flush() # Hogy kapjunk ID-t
|
|
final_provider_id = new_provider.id
|
|
print(f"--- INFO: Created Ad-Hoc Provider: {event_data.provider_name} (ID: {final_provider_id})")
|
|
|
|
# 3. Odometer Logika (Biztonság)
|
|
anomaly_detected = False
|
|
|
|
if event_data.odometer_value < vehicle.current_odometer:
|
|
# Figyelmeztetés: Csökkent a kilométerállás!
|
|
anomaly_detected = True
|
|
print(f"--- WARN: Odometer rollback detected for Vehicle {vehicle_id}!")
|
|
|
|
# 4. Esemény mentése
|
|
new_event = VehicleEvent(
|
|
vehicle_id=vehicle_id,
|
|
event_type=event_data.event_type,
|
|
date=event_data.date,
|
|
odometer_value=event_data.odometer_value,
|
|
odometer_anomaly=anomaly_detected,
|
|
cost_amount=event_data.cost_amount,
|
|
description=event_data.description,
|
|
is_diy=event_data.is_diy,
|
|
service_provider_id=final_provider_id
|
|
)
|
|
|
|
db.add(new_event)
|
|
|
|
# 5. Jármű frissítése (Ha nőtt a km, update-eljük a current-et)
|
|
if event_data.odometer_value > vehicle.current_odometer:
|
|
vehicle.current_odometer = event_data.odometer_value
|
|
|
|
await db.commit()
|
|
await db.refresh(new_event)
|
|
return new_event
|
|
|
|
async def calculate_tco(db: AsyncSession, vehicle_id: int) -> TCOStats:
|
|
"""Teljes költség elemzés (TCO)"""
|
|
|
|
# Költségek lekérése kategóriánként
|
|
result = await db.execute(
|
|
select(VehicleEvent.event_type, func.sum(VehicleEvent.cost_amount))
|
|
.where(VehicleEvent.vehicle_id == vehicle_id)
|
|
.group_by(VehicleEvent.event_type)
|
|
)
|
|
|
|
breakdown = {row[0]: row[1] for row in result.all()}
|
|
total_cost = sum(breakdown.values())
|
|
|
|
# Km futás számítás
|
|
vehicle_res = await db.execute(select(Vehicle).where(Vehicle.id == vehicle_id))
|
|
vehicle = vehicle_res.scalars().first()
|
|
|
|
km_driven = 0
|
|
cost_per_km = 0.0
|
|
|
|
if vehicle:
|
|
km_driven = max(0, vehicle.current_odometer - vehicle.initial_odometer)
|
|
if km_driven > 0:
|
|
cost_per_km = total_cost / km_driven
|
|
|
|
return TCOStats(
|
|
vehicle_id=vehicle_id,
|
|
total_cost=total_cost,
|
|
breakdown=breakdown,
|
|
cost_per_km=round(cost_per_km, 2)
|
|
) |