from fastapi import APIRouter, Depends, HTTPException, status, Query from sqlalchemy.orm import Session from typing import List, Optional from datetime import datetime, timezone from database import get_db from models.reservation import Reservation from models.table import Table from schemas.reservation import ( ReservationCreate, ReservationUpdate, ReservationStatusUpdate, ReservationOut, ) from routers.deps import get_current_user, require_manager from models.user import User router = APIRouter() VALID_STATUSES = {"pending", "arrived", "cancelled", "no_show"} def _enrich(r: Reservation) -> ReservationOut: out = ReservationOut.model_validate(r) if r.table: out.table_label = r.table.label or f"T{r.table.number}" if r.created_by: out.created_by_name = ( r.created_by.full_name or r.created_by.nickname or r.created_by.username ) return out @router.post("/", response_model=ReservationOut, status_code=status.HTTP_201_CREATED) def create_reservation( body: ReservationCreate, db: Session = Depends(get_db), user: User = Depends(get_current_user), ): if body.table_id: if not db.query(Table).filter(Table.id == body.table_id, Table.is_active == True).first(): raise HTTPException(status_code=400, detail="Table not found or inactive") r = Reservation( guest_name=body.guest_name, party_size=body.party_size, phone=body.phone, email=body.email, note=body.note, reserved_for=body.reserved_for, table_id=body.table_id, source=body.source, online_customer_id=body.online_customer_id, created_by_user_id=user.id, status="pending", ) db.add(r) db.commit() db.refresh(r) return _enrich(r) @router.get("/", response_model=List[ReservationOut]) def list_reservations( tab: Optional[str] = Query(None), # "upcoming" | "history" — if omitted returns all date: Optional[str] = Query(None), # ISO date string YYYY-MM-DD to filter by day db: Session = Depends(get_db), user: User = Depends(get_current_user), ): q = db.query(Reservation) if date: try: day = datetime.fromisoformat(date).date() except ValueError: raise HTTPException(status_code=400, detail="Invalid date format") from sqlalchemy import func q = q.filter(func.date(Reservation.reserved_for) == day) if tab == "upcoming": # pending + no_show (actionable), ordered soonest first q = q.filter(Reservation.status.in_(["pending", "no_show"])) q = q.order_by(Reservation.reserved_for.asc()) elif tab == "history": # arrived, cancelled, plus no_show/pending that are clearly in the past now = datetime.now(timezone.utc) q = q.filter( (Reservation.status.in_(["arrived", "cancelled"])) | ( Reservation.status.in_(["no_show", "pending"]) & (Reservation.reserved_for < now) ) ) q = q.order_by(Reservation.reserved_for.desc()) else: q = q.order_by(Reservation.reserved_for.asc()) reservations = q.all() return [_enrich(r) for r in reservations] @router.get("/{reservation_id}", response_model=ReservationOut) def get_reservation( reservation_id: int, db: Session = Depends(get_db), user: User = Depends(get_current_user), ): r = db.query(Reservation).filter(Reservation.id == reservation_id).first() if not r: raise HTTPException(status_code=404, detail="Reservation not found") return _enrich(r) @router.patch("/{reservation_id}", response_model=ReservationOut) def update_reservation( reservation_id: int, body: ReservationUpdate, db: Session = Depends(get_db), user: User = Depends(get_current_user), ): r = db.query(Reservation).filter(Reservation.id == reservation_id).first() if not r: raise HTTPException(status_code=404, detail="Reservation not found") updates = body.model_dump(exclude_none=True) if "table_id" in updates and updates["table_id"] is not None: if not db.query(Table).filter(Table.id == updates["table_id"], Table.is_active == True).first(): raise HTTPException(status_code=400, detail="Table not found or inactive") if "status" in updates and updates["status"] not in VALID_STATUSES: raise HTTPException(status_code=400, detail=f"Invalid status: {updates['status']}") for field, value in updates.items(): setattr(r, field, value) r.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(r) return _enrich(r) @router.patch("/{reservation_id}/status", response_model=ReservationOut) def update_reservation_status( reservation_id: int, body: ReservationStatusUpdate, db: Session = Depends(get_db), user: User = Depends(get_current_user), ): if body.status not in VALID_STATUSES: raise HTTPException(status_code=400, detail=f"Invalid status: {body.status}") r = db.query(Reservation).filter(Reservation.id == reservation_id).first() if not r: raise HTTPException(status_code=404, detail="Reservation not found") r.status = body.status r.updated_at = datetime.now(timezone.utc) db.commit() db.refresh(r) return _enrich(r) @router.delete("/{reservation_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_reservation( reservation_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager), ): r = db.query(Reservation).filter(Reservation.id == reservation_id).first() if not r: raise HTTPException(status_code=404, detail="Reservation not found") db.delete(r) db.commit()