from __future__ import annotations from fastapi import APIRouter, Depends, Header, HTTPException, Query, Response from sqlalchemy import select from sqlalchemy.orm import Session from app.api.deps import get_current_user, get_db from app.models.booking import Booking from app.models.customer import Customer from app.models.vehicle import Vehicle from app.schemas import ( BookingOut, CurrentUser, NextBookingRisk, RegisterReturnRequest, ReturnPreviewResult, ) from app.services.returns import preview_vehicle_return, register_vehicle_return router = APIRouter(prefix="/api/v1/bookings", tags=["bookings"]) def _to_out(booking: Booking, customer: Customer, vehicle: Vehicle) -> BookingOut: return BookingOut( public_ref=booking.public_ref, customer_ref=customer.public_ref, vehicle_ref=vehicle.public_ref, starts_at=booking.starts_at, ends_at=booking.ends_at, status=booking.status, start_odometer_km=booking.start_odometer_km, end_odometer_km=booking.end_odometer_km, requirements_complete=booking.requirements_complete, customer_name=f"{customer.first_name} {customer.last_name}", ) @router.get("", response_model=list[BookingOut]) def list_bookings( status: str | None = Query(default=None), vehicle_ref: str | None = Query(default=None), db: Session = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ) -> list[BookingOut]: stmt = select(Booking).order_by(Booking.starts_at.desc()) if status: stmt = stmt.where(Booking.status == status) if vehicle_ref: vehicle = db.scalar(select(Vehicle).where(Vehicle.public_ref == vehicle_ref)) if vehicle is None: return [] stmt = stmt.where(Booking.vehicle_id == vehicle.id) bookings = db.scalars(stmt).all() customers = {c.id: c for c in db.scalars(select(Customer)).all()} vehicles = {v.id: v for v in db.scalars(select(Vehicle)).all()} return [_to_out(b, customers[b.customer_id], vehicles[b.vehicle_id]) for b in bookings] @router.get("/{public_ref}", response_model=BookingOut) def get_booking( public_ref: str, db: Session = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ) -> BookingOut: booking = db.scalar(select(Booking).where(Booking.public_ref == public_ref)) if booking is None: raise HTTPException(status_code=404, detail="Booking not found") customer = db.get(Customer, booking.customer_id) vehicle = db.get(Vehicle, booking.vehicle_id) if customer is None or vehicle is None: raise HTTPException(status_code=500, detail="Booking references a missing record") return _to_out(booking, customer, vehicle) @router.post("/{public_ref}/return-preview", response_model=ReturnPreviewResult) def preview_return( public_ref: str, body: RegisterReturnRequest, db: Session = Depends(get_db), _user: CurrentUser = Depends(get_current_user), ) -> ReturnPreviewResult: booking, vehicle, evaluation = preview_vehicle_return(db, public_ref, body) return ReturnPreviewResult( booking_ref=booking.public_ref, vehicle_ref=vehicle.public_ref, canonical_odometer_km=evaluation.canonical_odometer_km, submitted_odometer_km=evaluation.submitted_odometer_km, odometer_regression=evaluation.odometer_regression, resulting_odometer_km=evaluation.resulting_odometer_km, resulting_vehicle_status=evaluation.resulting_vehicle_status, status_reason=evaluation.status_reason, status_reason_code=evaluation.status_reason_code, status_reason_params=evaluation.status_reason_params, would_create_quality_issue=evaluation.would_create_quality_issue, attention_reasons=evaluation.attention_reasons, next_booking_risk=( NextBookingRisk(**evaluation.next_booking_risk) if evaluation.next_booking_risk is not None else None ), ) @router.post("/{public_ref}/return") def register_return( public_ref: str, body: RegisterReturnRequest, response: Response, idempotency_key: str = Header(..., alias="Idempotency-Key", min_length=8, max_length=128), db: Session = Depends(get_db), user: CurrentUser = Depends(get_current_user), ) -> dict: status_code, result = register_vehicle_return(db, public_ref, body, idempotency_key, user) response.status_code = status_code return result