Phase 6: Admin appointments view and cancellation with UI and backend tests
This commit is contained in:
parent
5108a620e7
commit
b3e00b0745
12 changed files with 814 additions and 548 deletions
|
|
@ -203,6 +203,13 @@ async def create_booking(
|
|||
appointments_router = APIRouter(prefix="/api/appointments", tags=["appointments"])
|
||||
|
||||
|
||||
async def _get_user_email(db: AsyncSession, user_id: int) -> str:
|
||||
"""Get user email by ID."""
|
||||
result = await db.execute(select(User.email).where(User.id == user_id))
|
||||
email = result.scalar_one_or_none()
|
||||
return email or "unknown"
|
||||
|
||||
|
||||
@appointments_router.get("", response_model=list[AppointmentResponse])
|
||||
async def get_my_appointments(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
|
|
@ -278,3 +285,84 @@ async def cancel_my_appointment(
|
|||
cancelled_at=appointment.cancelled_at,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Admin Appointments Endpoints
|
||||
# =============================================================================
|
||||
|
||||
admin_appointments_router = APIRouter(prefix="/api/admin/appointments", tags=["admin-appointments"])
|
||||
|
||||
|
||||
@admin_appointments_router.get("", response_model=list[AppointmentResponse])
|
||||
async def get_all_appointments(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_current_user: User = Depends(require_permission(Permission.VIEW_ALL_APPOINTMENTS)),
|
||||
) -> list[AppointmentResponse]:
|
||||
"""Get all appointments (admin only), sorted by date descending."""
|
||||
result = await db.execute(
|
||||
select(Appointment)
|
||||
.order_by(Appointment.slot_start.desc())
|
||||
)
|
||||
appointments = result.scalars().all()
|
||||
|
||||
responses = []
|
||||
for apt in appointments:
|
||||
user_email = await _get_user_email(db, apt.user_id)
|
||||
responses.append(AppointmentResponse(
|
||||
id=apt.id,
|
||||
user_id=apt.user_id,
|
||||
user_email=user_email,
|
||||
slot_start=apt.slot_start,
|
||||
slot_end=apt.slot_end,
|
||||
note=apt.note,
|
||||
status=apt.status.value,
|
||||
created_at=apt.created_at,
|
||||
cancelled_at=apt.cancelled_at,
|
||||
))
|
||||
|
||||
return responses
|
||||
|
||||
|
||||
@admin_appointments_router.post("/{appointment_id}/cancel", response_model=AppointmentResponse)
|
||||
async def admin_cancel_appointment(
|
||||
appointment_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_current_user: User = Depends(require_permission(Permission.CANCEL_ANY_APPOINTMENT)),
|
||||
) -> AppointmentResponse:
|
||||
"""Cancel any appointment (admin only)."""
|
||||
# Get the appointment
|
||||
result = await db.execute(
|
||||
select(Appointment).where(Appointment.id == appointment_id)
|
||||
)
|
||||
appointment = result.scalar_one_or_none()
|
||||
|
||||
if not appointment:
|
||||
raise HTTPException(status_code=404, detail="Appointment not found")
|
||||
|
||||
# Check if already cancelled
|
||||
if appointment.status != AppointmentStatus.BOOKED:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Cannot cancel appointment with status '{appointment.status.value}'"
|
||||
)
|
||||
|
||||
# Cancel the appointment
|
||||
appointment.status = AppointmentStatus.CANCELLED_BY_ADMIN
|
||||
appointment.cancelled_at = datetime.now(timezone.utc)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(appointment)
|
||||
|
||||
user_email = await _get_user_email(db, appointment.user_id)
|
||||
|
||||
return AppointmentResponse(
|
||||
id=appointment.id,
|
||||
user_id=appointment.user_id,
|
||||
user_email=user_email,
|
||||
slot_start=appointment.slot_start,
|
||||
slot_end=appointment.slot_end,
|
||||
note=appointment.note,
|
||||
status=appointment.status.value,
|
||||
created_at=appointment.created_at,
|
||||
cancelled_at=appointment.cancelled_at,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue