egyedi jármű szerkesztés előtti mentés
This commit is contained in:
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import uuid
|
||||
from typing import List, Optional, Dict, Any, TYPE_CHECKING
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, and_, distinct
|
||||
from sqlalchemy.orm import selectinload
|
||||
@@ -62,10 +62,21 @@ class AssetService:
|
||||
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 == target_org_id,
|
||||
Asset.status == "active"
|
||||
)
|
||||
# FIX: Count only the CURRENT USER's active vehicles, not ALL vehicles in the org
|
||||
if target_org_id is not None:
|
||||
# Organization mode: count vehicles owned by this user within the org
|
||||
count_stmt = select(func.count(Asset.id)).where(
|
||||
Asset.current_organization_id == target_org_id,
|
||||
Asset.owner_person_id == user.person_id,
|
||||
Asset.status == "active"
|
||||
)
|
||||
else:
|
||||
# Personal mode: count vehicles owned personally by this user
|
||||
count_stmt = select(func.count(Asset.id)).where(
|
||||
Asset.current_organization_id.is_(None),
|
||||
Asset.owner_person_id == user.person_id,
|
||||
Asset.status == "active"
|
||||
)
|
||||
current_count = (await db.execute(count_stmt)).scalar()
|
||||
|
||||
# Determine status based on data completeness (use Pydantic validator's logic)
|
||||
@@ -86,7 +97,15 @@ class AssetService:
|
||||
else:
|
||||
status = "active"
|
||||
|
||||
# SAFETY: Ensure limit is at least 1 — no registered user should ever have 0 limit
|
||||
allowed_limit = max(allowed_limit or 1, 1)
|
||||
logger.info(
|
||||
f"Limit check for user {user_id} (org={target_org_id}): "
|
||||
f"current_count={current_count}, allowed_limit={allowed_limit}, status={status}"
|
||||
)
|
||||
|
||||
if current_count >= allowed_limit and status == "active":
|
||||
logger.error(f"QUOTA BLOCK TRIGGERED - User Person ID: {user.person_id}, Active Count: {current_count}, Limit: {allowed_limit}")
|
||||
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)
|
||||
@@ -400,7 +419,11 @@ class AssetService:
|
||||
# Get user-specific limit (based on role or subscription plan)
|
||||
user_limit = limits.get(user_role)
|
||||
if user_limit is None:
|
||||
user_limit = limits.get(subscription_plan.lower(), 100)
|
||||
# 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)
|
||||
|
||||
# Get organization-specific limit (if configured)
|
||||
org_limit = None
|
||||
@@ -431,6 +454,98 @@ class AssetService:
|
||||
# Fallback to a reasonable default
|
||||
return 100
|
||||
|
||||
@staticmethod
|
||||
async def archive_vehicle(
|
||||
db: AsyncSession,
|
||||
asset_id: uuid.UUID,
|
||||
user_id: int,
|
||||
final_mileage: int,
|
||||
archive_reason: str,
|
||||
) -> Asset:
|
||||
"""
|
||||
Strict Soft Delete — Jármű biztonságos kivezetése a garázsból.
|
||||
|
||||
A művelet:
|
||||
1. Frissíti a current_mileage értékét a megadott final_mileage-ra
|
||||
2. Állítja: status = 'archived'
|
||||
3. Nullázza az owner_person_id és owner_org_id mezőket
|
||||
4. Elmenti az archive_info metaadatokat az individual_equipment JSONB-be
|
||||
5. Naplózza a biztonsági auditba
|
||||
"""
|
||||
from app.models.identity import User
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
# 1. Lekérjük a járművet — eager load catalog reláció a MissingGreenlet hiba elkerülésére
|
||||
stmt = select(Asset).where(Asset.id == asset_id).options(selectinload(Asset.catalog))
|
||||
result = await db.execute(stmt)
|
||||
asset = result.scalar_one_or_none()
|
||||
|
||||
if not asset:
|
||||
raise HTTPException(status_code=404, detail="Jármű nem található")
|
||||
|
||||
# 2. Jogosultság ellenőrzése — csak a tulajdonos vagy admin archiválhat
|
||||
user_stmt = select(User).where(User.id == user_id)
|
||||
user = (await db.execute(user_stmt)).scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="Felhasználó nem található")
|
||||
|
||||
is_owner = (asset.owner_person_id == user.person_id)
|
||||
is_admin = user.role in ("admin", "superadmin") if hasattr(user, 'role') else False
|
||||
|
||||
if not is_owner and not is_admin:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Nincs jogosultságod a jármű eltávolításához"
|
||||
)
|
||||
|
||||
# 3. Már archiválva van?
|
||||
if asset.status == "archived":
|
||||
raise HTTPException(status_code=400, detail="A jármű már archiválva van")
|
||||
|
||||
# 4. Mentjük az archive_info metaadatokat
|
||||
now_iso = datetime.now(timezone.utc).isoformat()
|
||||
equipment = dict(asset.individual_equipment or {})
|
||||
equipment['archive_info'] = {
|
||||
'reason': archive_reason,
|
||||
'archived_at': now_iso,
|
||||
'previous_owner_person_id': asset.owner_person_id,
|
||||
'previous_owner_org_id': asset.owner_org_id,
|
||||
'final_mileage': final_mileage,
|
||||
}
|
||||
|
||||
# 5. Frissítjük a jármű adatait
|
||||
asset.current_mileage = final_mileage
|
||||
asset.status = "archived"
|
||||
asset.owner_person_id = None
|
||||
asset.owner_org_id = None
|
||||
asset.individual_equipment = equipment
|
||||
asset.updated_at = datetime.now(timezone.utc)
|
||||
|
||||
# 6. Biztonsági audit naplózás
|
||||
await security_service.log_event(
|
||||
db,
|
||||
user_id=user_id,
|
||||
action="VEHICLE_ARCHIVED",
|
||||
severity=LogSeverity.warning,
|
||||
target_type="Asset",
|
||||
target_id=str(asset.id),
|
||||
new_data={
|
||||
"reason": archive_reason,
|
||||
"final_mileage": final_mileage,
|
||||
"archived_at": now_iso,
|
||||
}
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(asset)
|
||||
|
||||
logger.info(
|
||||
f"Vehicle {asset.id} ({asset.license_plate}) archived by user {user_id}. "
|
||||
f"Reason: {archive_reason}, final_mileage: {final_mileage}"
|
||||
)
|
||||
|
||||
return asset
|
||||
|
||||
@staticmethod
|
||||
async def _award_first_car_badge(db: AsyncSession, user_id: int, org_id: int):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user