"""Appointment routes — list, cancel, and complete.""" from datetime import datetime from zoneinfo import ZoneInfo from fastapi import APIRouter, Depends, HTTPException from core import data_manager, google_services from core import settings_manager as settings from api.deps import get_current_session router = APIRouter(prefix="/api/appointments", tags=["appointments"]) @router.get("") def list_appointments(status: str | None = None, provider_id: str | None = None, session: dict = Depends(get_current_session)): appts = data_manager.get_all_appointments(status=status) if session.get("user_role") == "provider": # Providers see only their own appointments appts = [a for a in appts if a.get("provider_id") == session.get("provider_id")] elif provider_id: appts = [a for a in appts if a.get("provider_id") == provider_id] return appts @router.delete("/{appointment_id}") def cancel_appointment(appointment_id: str, session: dict = Depends(get_current_session)): if session.get("user_role") == "provider": raise HTTPException(status_code=403, detail="Providers cannot cancel appointments") appt = next((a for a in data_manager.get_all_appointments() if a.get("id") == appointment_id), None) if not appt: raise HTTPException(status_code=404, detail="Appointment not found") cancelled = data_manager.cancel_appointment(appointment_id) if not cancelled: raise HTTPException(status_code=500, detail="Cancellation failed") # Delete Google Calendar event (non-critical) if appt.get("google_event_id"): try: google_services.delete_calendar_event(appt["google_event_id"]) except Exception: pass # Send cancellation notifications (non-critical) try: provider = data_manager.get_provider_by_id(appt["provider_id"]) patient = data_manager.get_patient_by_id(appt["patient_id"]) appointment_details = { "patient_name": appt.get("patient_name", ""), "provider_name": appt.get("provider_name", ""), "date": appt.get("date", ""), "start_time": appt.get("start_time", ""), "end_time": appt.get("end_time", ""), "address": appt.get("visit_address", ""), } google_services.send_cancellation_notification( provider_email=provider.get("email", "") if provider else "", patient_email=patient.get("email", "") if patient else "", appointment_details=appointment_details, ) except Exception: pass data_manager.log_action( user=session["current_user"], action="CANCEL", details=( f"Cancelled {appt.get('visit_level', '')} visit for " f"{appt.get('patient_name', '')} with {appt.get('provider_name', '')} " f"on {appt.get('date', '')} at {appt.get('start_time', '')}" ), appointment_id=appointment_id, ) return {"ok": True} @router.post("/{appointment_id}/complete") def complete_appointment(appointment_id: str, session: dict = Depends(get_current_session)): """Mark an appointment as completed. Provider-only; only allowed once the appointment's start time has passed (compared in the platform timezone).""" appt = next((a for a in data_manager.get_all_appointments() if a.get("id") == appointment_id), None) if not appt: raise HTTPException(status_code=404, detail="Appointment not found") if session.get("user_role") == "provider" and session.get("provider_id") != appt.get("provider_id"): raise HTTPException(status_code=403, detail="Can only complete your own appointments") if appt.get("status") != "scheduled": raise HTTPException(status_code=400, detail=f"Cannot complete appointment with status '{appt.get('status')}'") # Compare in the platform timezone so a UTC server doesn't mis-evaluate. try: start_dt = datetime.strptime(f"{appt['date']} {appt['start_time']}", "%Y-%m-%d %H:%M") except (KeyError, ValueError): raise HTTPException(status_code=500, detail="Appointment has invalid date/time") now_local = datetime.now(ZoneInfo(settings.get_timezone())).replace(tzinfo=None) if now_local < start_dt: raise HTTPException(status_code=400, detail="Appointment has not started yet") completed = data_manager.complete_appointment(appointment_id) if not completed: raise HTTPException(status_code=500, detail="Failed to mark complete") data_manager.log_action( user=session["current_user"], action="COMPLETE", details=( f"Completed {appt.get('visit_level', '')} visit for " f"{appt.get('patient_name', '')} with {appt.get('provider_name', '')} " f"on {appt.get('date', '')} at {appt.get('start_time', '')}" ), appointment_id=appointment_id, ) return {"ok": True, "appointment": completed}