185 lines
7.3 KiB
Python
185 lines
7.3 KiB
Python
# /opt/docker/dev/service_finder/backend/app/services/logbook_service.py
|
|
"""
|
|
Logbook Service - GPS, OBDII és előfizetési szűrő kezelése.
|
|
"""
|
|
import logging
|
|
from typing import Optional, Tuple, Any
|
|
from decimal import Decimal
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
|
|
from app.models.asset import VehicleLogbook
|
|
from app.models.gamification import UserStats
|
|
from app.models.identity import User
|
|
from app.models.system import SystemParameter
|
|
|
|
logger = logging.getLogger("Logbook-Service-2.0")
|
|
|
|
class LogbookService:
|
|
"""
|
|
Útnyilvántartás kezelése GPS koordinátákkal, OBDII adatokkal és előfizetési szintű jogosultságokkal.
|
|
"""
|
|
|
|
@staticmethod
|
|
async def get_system_parameter(db: AsyncSession, key: str, default: Any = None) -> Any:
|
|
"""
|
|
Lekéri a rendszerparamétert a system.system_parameters táblából.
|
|
Elsőként a global scope-ot keresi (scope_level='global', scope_id=NULL).
|
|
Ha nem talál, visszaadja a default értéket.
|
|
"""
|
|
stmt = select(SystemParameter).where(
|
|
SystemParameter.key == key,
|
|
SystemParameter.scope_level == 'global',
|
|
SystemParameter.scope_id.is_(None),
|
|
SystemParameter.is_active == True
|
|
).order_by(SystemParameter.updated_at.desc())
|
|
result = await db.execute(stmt)
|
|
param = result.scalar_one_or_none()
|
|
if param and 'value' in param.value:
|
|
return param.value['value']
|
|
return default
|
|
|
|
@staticmethod
|
|
async def get_user_rank(db: AsyncSession, user_id: int) -> int:
|
|
"""
|
|
Lekérdezi a felhasználó aktuális rankját (current_level) a UserStats táblából.
|
|
Ha nincs rekord, alapértelmezett 0 (ingyenes szint).
|
|
"""
|
|
stmt = select(UserStats.current_level).where(UserStats.user_id == user_id)
|
|
result = await db.execute(stmt)
|
|
rank = result.scalar_one_or_none()
|
|
return rank if rank is not None else 0
|
|
|
|
@staticmethod
|
|
async def check_subscription_guard(
|
|
db: AsyncSession,
|
|
user_id: int,
|
|
wants_gps: bool = False,
|
|
wants_obd: bool = False
|
|
) -> Tuple[bool, str]:
|
|
"""
|
|
Ellenőrzi, hogy a felhasználó előfizetési szintje engedélyezi-e a GPS/OBDII adatok rögzítését.
|
|
|
|
Szabályok:
|
|
- Rank >= LOGBOOK_GPS_MIN_RANK (alapértelmezett 50): engedélyezett a GPS távolság és koordináták
|
|
- Rank >= 90 (VIP/Admin): minden engedélyezett (GPS, OBDII, gyorsulás)
|
|
- Rank < LOGBOOK_GPS_MIN_RANK: csak manuális distance_km és trip_type rögzíthető
|
|
|
|
Visszatérés: (allowed: bool, message: str)
|
|
"""
|
|
rank = await LogbookService.get_user_rank(db, user_id)
|
|
gps_min_rank = await LogbookService.get_system_parameter(db, 'LOGBOOK_GPS_MIN_RANK', 50)
|
|
vip_min_rank = 90 # Fix VIP küszöb
|
|
|
|
if rank >= vip_min_rank:
|
|
return True, "VIP/Admin szint: minden adat rögzíthető"
|
|
|
|
if rank >= gps_min_rank:
|
|
if wants_gps or wants_obd:
|
|
return True, f"PREMIUM szint (rank {rank} >= {gps_min_rank}): GPS és OBDII adatok rögzíthetők"
|
|
return True, "PREMIUM szint"
|
|
|
|
# Ingyenes felhasználó
|
|
if wants_gps or wants_obd:
|
|
return False, f"Ingyenes felhasználók (rank {rank} < {gps_min_rank}) nem rögzíthetnek GPS koordinátákat vagy OBDII adatokat. Csak manuális distance_km és trip_type engedélyezett."
|
|
|
|
return True, "Ingyenes szint: csak manuális adatok"
|
|
|
|
@staticmethod
|
|
async def create_logbook_entry(
|
|
db: AsyncSession,
|
|
asset_id: str,
|
|
driver_id: int,
|
|
trip_type: str,
|
|
start_mileage: int,
|
|
end_mileage: Optional[int] = None,
|
|
distance_km: Optional[float] = None,
|
|
start_lat: Optional[float] = None,
|
|
start_lng: Optional[float] = None,
|
|
end_lat: Optional[float] = None,
|
|
end_lng: Optional[float] = None,
|
|
gps_calculated_distance: Optional[float] = None,
|
|
obd_verified: bool = False,
|
|
max_acceleration: Optional[float] = None,
|
|
average_speed: Optional[float] = None,
|
|
) -> VehicleLogbook:
|
|
"""
|
|
Új útnyilvántartás bejegyzés létrehozása előfizetési szűrővel.
|
|
|
|
Automatikusan ellenőrzi, hogy a felhasználó rankja engedélyezi-e a GPS/OBDII mezők kitöltését.
|
|
Ha nem, a GPS és OBDII mezők null-ra állnak, és csak a manuális distance_km marad.
|
|
"""
|
|
# Ellenőrizzük a jogosultságot
|
|
wants_gps = any([start_lat, start_lng, end_lat, end_lng, gps_calculated_distance])
|
|
wants_obd = obd_verified or max_acceleration is not None or average_speed is not None
|
|
|
|
allowed, message = await LogbookService.check_subscription_guard(
|
|
db, driver_id, wants_gps, wants_obd
|
|
)
|
|
|
|
if not allowed:
|
|
# Ha nem engedélyezett, nullázzuk a tiltott mezőket
|
|
logger.warning(f"User {driver_id} attempted to log GPS/OBDII without permission. {message}")
|
|
start_lat = start_lng = end_lat = end_lng = gps_calculated_distance = None
|
|
obd_verified = False
|
|
max_acceleration = average_speed = None
|
|
|
|
# Új bejegyzés létrehozása
|
|
new_entry = VehicleLogbook(
|
|
asset_id=asset_id,
|
|
driver_id=driver_id,
|
|
trip_type=trip_type,
|
|
start_mileage=start_mileage,
|
|
end_mileage=end_mileage,
|
|
distance_km=distance_km,
|
|
start_lat=start_lat,
|
|
start_lng=start_lng,
|
|
end_lat=end_lat,
|
|
end_lng=end_lng,
|
|
gps_calculated_distance=gps_calculated_distance,
|
|
obd_verified=obd_verified,
|
|
max_acceleration=max_acceleration,
|
|
average_speed=average_speed,
|
|
)
|
|
|
|
db.add(new_entry)
|
|
await db.commit()
|
|
await db.refresh(new_entry)
|
|
|
|
logger.info(f"Logbook entry created for asset {asset_id}, driver {driver_id}, trip_type {trip_type}")
|
|
return new_entry
|
|
|
|
@staticmethod
|
|
async def calculate_official_distance(
|
|
start_coords: Tuple[float, float],
|
|
end_coords: Tuple[float, float]
|
|
) -> Optional[float]:
|
|
"""
|
|
TODO: OSRM/Google Maps API hívással számolja ki a legrövidebb útvonal távolságát.
|
|
Egyelőre placeholder, később implementálandó.
|
|
|
|
Visszatérés: távolság kilométerben (float) vagy None, ha nem sikerült.
|
|
"""
|
|
# TODO: Integrálni OSRM vagy Google Maps Distance Matrix API-t
|
|
# Példa: https://project-osrm.org/docs/v5.24.0/api/#route-service
|
|
# Jelenleg egyszerű haversine formula alapján számolunk
|
|
from math import radians, sin, cos, sqrt, atan2
|
|
|
|
lat1, lon1 = start_coords
|
|
lat2, lon2 = end_coords
|
|
|
|
R = 6371.0 # Föld sugara km-ben
|
|
|
|
lat1_rad = radians(lat1)
|
|
lon1_rad = radians(lon1)
|
|
lat2_rad = radians(lat2)
|
|
lon2_rad = radians(lon2)
|
|
|
|
dlon = lon2_rad - lon1_rad
|
|
dlat = lat2_rad - lat1_rad
|
|
|
|
a = sin(dlat / 2)**2 + cos(lat1_rad) * cos(lat2_rad) * sin(dlon / 2)**2
|
|
c = 2 * atan2(sqrt(a), sqrt(1 - a))
|
|
|
|
distance_km = R * c
|
|
return round(distance_km, 2) |