egyedi jármű szerkesztés előtti mentés
This commit is contained in:
@@ -4,6 +4,7 @@ import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, desc, text, or_
|
||||
from sqlalchemy.orm import selectinload
|
||||
@@ -17,11 +18,81 @@ from app.services.asset_service import AssetService
|
||||
from app.schemas.asset_cost import AssetCostCreate, AssetCostResponse
|
||||
from app.schemas.asset import AssetResponse, AssetCreate, AssetUpdate
|
||||
|
||||
|
||||
class ArchiveVehicleRequest(BaseModel):
|
||||
"""Payload a jármű archiválásához (Soft Delete)."""
|
||||
final_mileage: int = Field(..., ge=0, description="Utolsó km óra állás")
|
||||
archive_reason: str = Field(..., min_length=1, max_length=100, description="Eltávolítás oka")
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.get("/vehicles/quota-status")
|
||||
async def get_vehicle_quota_status(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Preemptive quota check endpoint for the frontend.
|
||||
Returns whether the user can add a new vehicle BEFORE they start the wizard.
|
||||
|
||||
GET /api/v1/assets/vehicles/quota-status
|
||||
|
||||
Returns:
|
||||
{ "can_add": bool, "current_count": int, "limit": int }
|
||||
"""
|
||||
try:
|
||||
# Determine organization ID based on user's active scope (garage isolation)
|
||||
org_id = None
|
||||
if current_user.scope_id is not None:
|
||||
try:
|
||||
org_id = int(current_user.scope_id)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Get the user's vehicle limit
|
||||
allowed_limit = await AssetService.get_user_vehicle_limit(
|
||||
db, current_user.id, org_id
|
||||
)
|
||||
# SAFETY: Ensure limit is at least 1
|
||||
allowed_limit = max(allowed_limit or 1, 1)
|
||||
|
||||
# Count only active vehicles (draft vehicles don't count toward the limit)
|
||||
from sqlalchemy import func
|
||||
if org_id is not None:
|
||||
count_stmt = select(func.count(Asset.id)).where(
|
||||
Asset.current_organization_id == org_id,
|
||||
Asset.owner_person_id == current_user.person_id,
|
||||
Asset.status == "active"
|
||||
)
|
||||
else:
|
||||
count_stmt = select(func.count(Asset.id)).where(
|
||||
Asset.current_organization_id.is_(None),
|
||||
Asset.owner_person_id == current_user.person_id,
|
||||
Asset.status == "active"
|
||||
)
|
||||
current_count = (await db.execute(count_stmt)).scalar() or 0
|
||||
|
||||
can_add = current_count < allowed_limit
|
||||
|
||||
logger.info(
|
||||
f"Quota check for user {current_user.id} (org={org_id}): "
|
||||
f"current_count={current_count}, allowed_limit={allowed_limit}, can_add={can_add}"
|
||||
)
|
||||
|
||||
return {
|
||||
"can_add": can_add,
|
||||
"current_count": current_count,
|
||||
"limit": allowed_limit
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Quota status check error for user {current_user.id}: {e}")
|
||||
# Fail open — if we can't check quota, allow the user to proceed
|
||||
return {"can_add": True, "current_count": 0, "limit": 9999}
|
||||
|
||||
|
||||
@router.get("/vehicles/check")
|
||||
async def check_vehicle_exists(
|
||||
license_plate: str,
|
||||
@@ -286,14 +357,42 @@ async def create_or_claim_vehicle(
|
||||
f"Setting data_status='draft' for transfer scenario."
|
||||
)
|
||||
|
||||
# Determine organization ID based on user's active scope (garage isolation)
|
||||
org_id = None
|
||||
if current_user.scope_id is not None:
|
||||
# ── 🛡️ KETTŐS VÉDELEM: Szervezet hozzárendelés ──
|
||||
# 1. PRIORITÁS: A frontend által küldött organization_id (ha van)
|
||||
# 2. FALLBACK: A felhasználó aktív scope-ja (scope_id)
|
||||
# 3. VASÁJTÓ: Ha egyik sincs, lekérdezzük a fleet.organization_members táblából
|
||||
org_id = payload.organization_id
|
||||
|
||||
if org_id is None and current_user.scope_id is not None:
|
||||
try:
|
||||
org_id = int(current_user.scope_id)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# VASÁJTÓ FALLBACK: Ha még mindig nincs org_id, keressük meg a felhasználó
|
||||
# első elérhető szervezetét a fleet.organization_members táblában
|
||||
if org_id is None:
|
||||
from app.models.marketplace.organization import OrganizationMember
|
||||
from sqlalchemy import select
|
||||
member_stmt = (
|
||||
select(OrganizationMember.organization_id)
|
||||
.where(OrganizationMember.user_id == current_user.id)
|
||||
.limit(1)
|
||||
)
|
||||
member_result = await db.execute(member_stmt)
|
||||
member_row = member_result.first()
|
||||
if member_row:
|
||||
org_id = member_row[0]
|
||||
logger.info(
|
||||
f"Iron Door fallback: assigned org_id={org_id} from "
|
||||
f"organization_members for user {current_user.id}"
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"Iron Door fallback: user {current_user.id} has no "
|
||||
f"organization memberships — asset will be created without org"
|
||||
)
|
||||
|
||||
asset = await AssetService.create_or_claim_vehicle(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
@@ -591,4 +690,46 @@ async def create_maintenance_record(
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Internal server error while creating maintenance record"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/vehicles/{vehicle_id}/archive", response_model=AssetResponse)
|
||||
async def archive_vehicle(
|
||||
vehicle_id: uuid.UUID,
|
||||
payload: ArchiveVehicleRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Jármű biztonságos kivezetése (Strict Soft Delete).
|
||||
|
||||
POST /api/v1/assets/vehicles/{vehicle_id}/archive
|
||||
|
||||
A végpont:
|
||||
1. Frissíti a jármű current_mileage értékét a megadott final_mileage-ra
|
||||
2. Átállítja a státuszt 'archived'-ra
|
||||
3. Nullázza a tulajdonosi mezőket (owner_person_id, owner_org_id)
|
||||
4. Elmenti az archive_info metaadatokat az individual_equipment JSONB-be
|
||||
5. Naplózza a biztonsági auditba
|
||||
|
||||
Payload:
|
||||
- final_mileage: int (utolsó km óra állás)
|
||||
- archive_reason: string (Eladás, Gazdasági totálkár / Bontás, Lízing/Bérlet lejárta, Téves rögzítés, Egyéb)
|
||||
"""
|
||||
try:
|
||||
asset = await AssetService.archive_vehicle(
|
||||
db=db,
|
||||
asset_id=vehicle_id,
|
||||
user_id=current_user.id,
|
||||
final_mileage=payload.final_mileage,
|
||||
archive_reason=payload.archive_reason,
|
||||
)
|
||||
return asset
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Vehicle archive error for {vehicle_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Belső szerverhiba a jármű archiválásakor"
|
||||
)
|
||||
Reference in New Issue
Block a user