from datetime import datetime from typing import Any, List, Optional from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import or_ from sqlalchemy.orm import Session from app.core.auth import get_current_active_user from app.db.session import get_db from app.models.user import User from app.models.vehicle import Schedule, Vehicle, VehicleType from app.schemas.vehicle import ( Schedule as ScheduleSchema, ) from app.schemas.vehicle import ( ScheduleCreate, ScheduleUpdate, VehicleCreate, VehicleUpdate, ) from app.schemas.vehicle import ( Vehicle as VehicleSchema, ) router = APIRouter() # Vehicle endpoints @router.get("/", response_model=List[VehicleSchema]) def list_vehicles( *, db: Session = Depends(get_db), skip: int = 0, limit: int = 100, vehicle_type: Optional[VehicleType] = None, search: Optional[str] = None, _: User = Depends(get_current_active_user), ) -> Any: """ Retrieve vehicles with optional filtering. """ query = db.query(Vehicle).filter(Vehicle.is_active == True) if vehicle_type: query = query.filter(Vehicle.vehicle_type == vehicle_type) if search: query = query.filter( or_( Vehicle.vehicle_number.contains(search), ) ) vehicles = query.offset(skip).limit(limit).all() return vehicles @router.post("/", response_model=VehicleSchema, status_code=status.HTTP_201_CREATED) def create_vehicle( *, db: Session = Depends(get_db), vehicle_in: VehicleCreate, _: User = Depends(get_current_active_user), ) -> Any: """ Create new vehicle. """ # Check if vehicle with same number already exists db_vehicle = db.query(Vehicle).filter(Vehicle.vehicle_number == vehicle_in.vehicle_number).first() if db_vehicle: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Vehicle with this number already exists", ) vehicle = Vehicle( vehicle_number=vehicle_in.vehicle_number, vehicle_type=vehicle_in.vehicle_type, capacity=vehicle_in.capacity, is_active=True, ) db.add(vehicle) db.commit() db.refresh(vehicle) return vehicle @router.get("/{vehicle_id}", response_model=VehicleSchema) def get_vehicle( *, db: Session = Depends(get_db), vehicle_id: int, _: User = Depends(get_current_active_user), ) -> Any: """ Get vehicle by ID. """ vehicle = db.query(Vehicle).filter(Vehicle.id == vehicle_id, Vehicle.is_active == True).first() if not vehicle: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found" ) return vehicle @router.put("/{vehicle_id}", response_model=VehicleSchema) def update_vehicle( *, db: Session = Depends(get_db), vehicle_id: int, vehicle_in: VehicleUpdate, _: User = Depends(get_current_active_user), ) -> Any: """ Update a vehicle. """ vehicle = db.query(Vehicle).filter(Vehicle.id == vehicle_id, Vehicle.is_active == True).first() if not vehicle: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found" ) # Check if vehicle number is being updated and if it's already taken if vehicle_in.vehicle_number and vehicle_in.vehicle_number != vehicle.vehicle_number: db_vehicle = db.query(Vehicle).filter(Vehicle.vehicle_number == vehicle_in.vehicle_number).first() if db_vehicle: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Vehicle with this number already exists", ) vehicle_data = vehicle_in.dict(exclude_unset=True) for key, value in vehicle_data.items(): setattr(vehicle, key, value) db.add(vehicle) db.commit() db.refresh(vehicle) return vehicle @router.delete("/{vehicle_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) def delete_vehicle( *, db: Session = Depends(get_db), vehicle_id: int, _: User = Depends(get_current_active_user), ) -> None: """ Delete a vehicle (soft delete). """ vehicle = db.query(Vehicle).filter(Vehicle.id == vehicle_id, Vehicle.is_active == True).first() if not vehicle: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found" ) # Soft delete by setting is_active to False vehicle.is_active = False db.add(vehicle) db.commit() return None # Schedule endpoints @router.get("/schedules", response_model=List[ScheduleSchema]) def list_schedules( *, db: Session = Depends(get_db), skip: int = 0, limit: int = 100, vehicle_id: Optional[int] = None, vehicle_type: Optional[VehicleType] = None, departure_from: Optional[datetime] = None, departure_to: Optional[datetime] = None, _: User = Depends(get_current_active_user), ) -> Any: """ Retrieve schedules with optional filtering. """ query = db.query(Schedule).join(Vehicle).filter( Schedule.is_active == True, Vehicle.is_active == True ) if vehicle_id: query = query.filter(Schedule.vehicle_id == vehicle_id) if vehicle_type: query = query.filter(Vehicle.vehicle_type == vehicle_type) if departure_from: query = query.filter(Schedule.departure_time >= departure_from) if departure_to: query = query.filter(Schedule.departure_time <= departure_to) schedules = query.order_by(Schedule.departure_time).offset(skip).limit(limit).all() return schedules @router.post("/schedules", response_model=ScheduleSchema, status_code=status.HTTP_201_CREATED) def create_schedule( *, db: Session = Depends(get_db), schedule_in: ScheduleCreate, _: User = Depends(get_current_active_user), ) -> Any: """ Create new schedule. """ # Check if vehicle exists vehicle = db.query(Vehicle).filter( Vehicle.id == schedule_in.vehicle_id, Vehicle.is_active == True ).first() if not vehicle: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Vehicle not found", ) # Validate that arrival time is after departure time if schedule_in.arrival_time <= schedule_in.departure_time: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Arrival time must be after departure time", ) # Validate that available seats doesn't exceed vehicle capacity if schedule_in.available_seats > vehicle.capacity: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Available seats cannot exceed vehicle capacity ({vehicle.capacity})", ) schedule = Schedule( vehicle_id=schedule_in.vehicle_id, departure_location=schedule_in.departure_location, arrival_location=schedule_in.arrival_location, departure_time=schedule_in.departure_time, arrival_time=schedule_in.arrival_time, available_seats=schedule_in.available_seats, is_active=True, ) db.add(schedule) db.commit() db.refresh(schedule) return schedule @router.get("/schedules/{schedule_id}", response_model=ScheduleSchema) def get_schedule( *, db: Session = Depends(get_db), schedule_id: int, _: User = Depends(get_current_active_user), ) -> Any: """ Get schedule by ID. """ schedule = db.query(Schedule).filter( Schedule.id == schedule_id, Schedule.is_active == True ).first() if not schedule: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Schedule not found" ) return schedule @router.put("/schedules/{schedule_id}", response_model=ScheduleSchema) def update_schedule( *, db: Session = Depends(get_db), schedule_id: int, schedule_in: ScheduleUpdate, _: User = Depends(get_current_active_user), ) -> Any: """ Update a schedule. """ schedule = db.query(Schedule).filter( Schedule.id == schedule_id, Schedule.is_active == True ).first() if not schedule: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Schedule not found" ) # If updating available seats, check that it doesn't exceed vehicle capacity if schedule_in.available_seats is not None: vehicle = db.query(Vehicle).filter(Vehicle.id == schedule.vehicle_id).first() if schedule_in.available_seats > vehicle.capacity: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Available seats cannot exceed vehicle capacity ({vehicle.capacity})", ) # If updating times, validate that arrival is after departure new_departure = schedule_in.departure_time or schedule.departure_time new_arrival = schedule_in.arrival_time or schedule.arrival_time if new_arrival <= new_departure: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Arrival time must be after departure time", ) schedule_data = schedule_in.dict(exclude_unset=True) for key, value in schedule_data.items(): setattr(schedule, key, value) db.add(schedule) db.commit() db.refresh(schedule) return schedule @router.delete("/schedules/{schedule_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) def delete_schedule( *, db: Session = Depends(get_db), schedule_id: int, _: User = Depends(get_current_active_user), ) -> None: """ Delete a schedule (soft delete). """ schedule = db.query(Schedule).filter( Schedule.id == schedule_id, Schedule.is_active == True ).first() if not schedule: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Schedule not found" ) # Soft delete by setting is_active to False schedule.is_active = False db.add(schedule) db.commit() return None