from datetime import timedelta from typing import Any, List, Optional from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from app import crud, models, schemas from app.api import deps from app.models.appointment import AppointmentStatus router = APIRouter() @router.get("/", response_model=List[schemas.appointment.Appointment]) def read_appointments( db: Session = Depends(deps.get_db), doctor_id: Optional[int] = Query(None, description="Filter appointments by doctor"), patient_id: Optional[int] = Query(None, description="Filter appointments by patient"), skip: int = 0, limit: int = 100, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Retrieve appointments. """ # Determine what appointments the user can see if crud.crud_user.user.is_superuser(current_user): # Superusers can see all appointments with optional filtering if doctor_id: appointments = crud.crud_appointment.appointment.get_by_doctor( db, doctor_id=doctor_id, skip=skip, limit=limit ) elif patient_id: appointments = crud.crud_appointment.appointment.get_by_patient( db, patient_id=patient_id, skip=skip, limit=limit ) else: appointments = crud.crud_appointment.appointment.get_multi( db, skip=skip, limit=limit ) else: # Regular users can only see their own appointments if current_user.doctor: # Doctors can see all their appointments appointments = crud.crud_appointment.appointment.get_by_doctor( db, doctor_id=current_user.doctor.id, skip=skip, limit=limit ) elif current_user.patient: # Patients can see all their appointments appointments = crud.crud_appointment.appointment.get_by_patient( db, patient_id=current_user.patient.id, skip=skip, limit=limit ) else: # Users without doctor or patient profiles can't see any appointments appointments = [] return appointments @router.post("/", response_model=schemas.appointment.Appointment) def create_appointment( *, db: Session = Depends(deps.get_db), appointment_in: schemas.appointment.AppointmentCreate, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Create new appointment. """ # Check if doctor exists doctor = crud.crud_doctor.doctor.get(db, id=appointment_in.doctor_id) if not doctor: raise HTTPException( status_code=404, detail="Doctor not found", ) # Check if patient exists patient = crud.crud_patient.patient.get(db, id=appointment_in.patient_id) if not patient: raise HTTPException( status_code=404, detail="Patient not found", ) # Check permissions if not crud.crud_user.user.is_superuser(current_user): # Only the patient or the doctor involved can create an appointment if current_user.patient: if current_user.patient.id != appointment_in.patient_id: raise HTTPException( status_code=403, detail="Not enough permissions to create an appointment for another patient", ) elif current_user.doctor: if current_user.doctor.id != appointment_in.doctor_id: raise HTTPException( status_code=403, detail="Not enough permissions to create an appointment for another doctor", ) else: raise HTTPException( status_code=403, detail="Not enough permissions to create an appointment", ) # Check for scheduling conflicts appointment_end = appointment_in.appointment_datetime + timedelta(minutes=appointment_in.duration_minutes) doctor_appointments = crud.crud_appointment.appointment.get_by_doctor_and_date_range( db, doctor_id=appointment_in.doctor_id, start_date=appointment_in.appointment_datetime - timedelta(hours=1), # Buffer before end_date=appointment_end + timedelta(hours=1), # Buffer after ) for existing_appointment in doctor_appointments: # Skip cancelled appointments if existing_appointment.status == AppointmentStatus.CANCELLED: continue existing_end = existing_appointment.appointment_datetime + timedelta(minutes=existing_appointment.duration_minutes) # Check for overlap if (appointment_in.appointment_datetime < existing_end and existing_appointment.appointment_datetime < appointment_end): raise HTTPException( status_code=400, detail=f"Scheduling conflict with an existing appointment at {existing_appointment.appointment_datetime}", ) appointment = crud.crud_appointment.appointment.create(db, obj_in=appointment_in) return appointment @router.get("/{appointment_id}", response_model=schemas.appointment.Appointment) def read_appointment( *, db: Session = Depends(deps.get_db), appointment_id: int, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Get appointment by ID. """ appointment = crud.crud_appointment.appointment.get(db, id=appointment_id) if not appointment: raise HTTPException(status_code=404, detail="Appointment not found") # Check permissions if not crud.crud_user.user.is_superuser(current_user): # Regular users can only see their own appointments if current_user.patient: if current_user.patient.id != appointment.patient_id: raise HTTPException(status_code=403, detail="Not enough permissions") elif current_user.doctor: if current_user.doctor.id != appointment.doctor_id: raise HTTPException(status_code=403, detail="Not enough permissions") else: raise HTTPException(status_code=403, detail="Not enough permissions") return appointment @router.put("/{appointment_id}", response_model=schemas.appointment.Appointment) def update_appointment( *, db: Session = Depends(deps.get_db), appointment_id: int, appointment_in: schemas.appointment.AppointmentUpdate, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Update an appointment. """ appointment = crud.crud_appointment.appointment.get(db, id=appointment_id) if not appointment: raise HTTPException(status_code=404, detail="Appointment not found") # Check permissions if not crud.crud_user.user.is_superuser(current_user): # Regular users can only update their own appointments if current_user.patient: if current_user.patient.id != appointment.patient_id: raise HTTPException(status_code=403, detail="Not enough permissions") elif current_user.doctor: if current_user.doctor.id != appointment.doctor_id: raise HTTPException(status_code=403, detail="Not enough permissions") else: raise HTTPException(status_code=403, detail="Not enough permissions") # If appointment_datetime is updated, check for scheduling conflicts if appointment_in.appointment_datetime and appointment_in.appointment_datetime != appointment.appointment_datetime: appointment_end = appointment_in.appointment_datetime + timedelta( minutes=appointment_in.duration_minutes if appointment_in.duration_minutes else appointment.duration_minutes ) doctor_appointments = crud.crud_appointment.appointment.get_by_doctor_and_date_range( db, doctor_id=appointment.doctor_id, start_date=appointment_in.appointment_datetime - timedelta(hours=1), end_date=appointment_end + timedelta(hours=1), ) for existing_appointment in doctor_appointments: # Skip the current appointment and cancelled appointments if existing_appointment.id == appointment_id or existing_appointment.status == AppointmentStatus.CANCELLED: continue existing_end = existing_appointment.appointment_datetime + timedelta(minutes=existing_appointment.duration_minutes) # Check for overlap if (appointment_in.appointment_datetime < existing_end and existing_appointment.appointment_datetime < appointment_end): raise HTTPException( status_code=400, detail=f"Scheduling conflict with an existing appointment at {existing_appointment.appointment_datetime}", ) appointment = crud.crud_appointment.appointment.update( db, db_obj=appointment, obj_in=appointment_in ) return appointment @router.delete("/{appointment_id}", response_model=schemas.appointment.Appointment) def delete_appointment( *, db: Session = Depends(deps.get_db), appointment_id: int, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Delete an appointment. """ appointment = crud.crud_appointment.appointment.get(db, id=appointment_id) if not appointment: raise HTTPException(status_code=404, detail="Appointment not found") # Check permissions if not crud.crud_user.user.is_superuser(current_user): # Regular users can only delete their own appointments if current_user.patient: if current_user.patient.id != appointment.patient_id: raise HTTPException(status_code=403, detail="Not enough permissions") elif current_user.doctor: if current_user.doctor.id != appointment.doctor_id: raise HTTPException(status_code=403, detail="Not enough permissions") else: raise HTTPException(status_code=403, detail="Not enough permissions") appointment = crud.crud_appointment.appointment.remove(db, id=appointment_id) return appointment @router.post("/{appointment_id}/status", response_model=schemas.appointment.Appointment) def update_appointment_status( *, db: Session = Depends(deps.get_db), appointment_id: int, status: AppointmentStatus, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Update appointment status. """ appointment = crud.crud_appointment.appointment.get(db, id=appointment_id) if not appointment: raise HTTPException(status_code=404, detail="Appointment not found") # Check permissions if not crud.crud_user.user.is_superuser(current_user): # Regular users can only update their own appointments if current_user.patient: if current_user.patient.id != appointment.patient_id: raise HTTPException(status_code=403, detail="Not enough permissions") # Patients can only cancel their appointments if status not in [AppointmentStatus.CANCELLED]: raise HTTPException( status_code=403, detail="Patients can only cancel appointments" ) elif current_user.doctor: if current_user.doctor.id != appointment.doctor_id: raise HTTPException(status_code=403, detail="Not enough permissions") else: raise HTTPException(status_code=403, detail="Not enough permissions") appointment = crud.crud_appointment.appointment.update_status( db, db_obj=appointment, status=status ) return appointment