import uuid from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.deps import get_current_user, get_db_session, require_study_member, require_study_roles from app.crud import audit as audit_crud from app.crud import subject as subject_crud from app.crud import visit as visit_crud from app.schemas.visit import VisitRead, VisitUpdate router = APIRouter() async def _ensure_subject(db: AsyncSession, study_id: uuid.UUID, subject_id: uuid.UUID): subject = await subject_crud.get_subject(db, subject_id) if not subject or subject.study_id != study_id: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found") return subject @router.get( "/", response_model=list[VisitRead], dependencies=[Depends(require_study_member())], ) async def list_visits( study_id: uuid.UUID, subject_id: uuid.UUID, db: AsyncSession = Depends(get_db_session), ) -> list[VisitRead]: await _ensure_subject(db, study_id, subject_id) visits = await visit_crud.list_visits(db, subject_id) return list(visits) @router.patch( "/{visit_id}", response_model=VisitRead, dependencies=[Depends(require_study_roles(["PM", "CRA"]))], ) async def update_visit( study_id: uuid.UUID, subject_id: uuid.UUID, visit_id: uuid.UUID, visit_in: VisitUpdate, db: AsyncSession = Depends(get_db_session), current_user=Depends(get_current_user), ) -> VisitRead: await _ensure_subject(db, study_id, subject_id) visit = await visit_crud.get_visit(db, visit_id) if not visit or visit.subject_id != subject_id or visit.study_id != study_id: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Visit not found") if visit_in.status == "DONE" and not visit_in.actual_date: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="actual_date required when DONE") updated = await visit_crud.update_visit(db, visit, visit_in) detail = None if visit_in.status: detail = f"visit {visit.visit_code} {visit.status} -> {visit_in.status}" await audit_crud.log_action( db, study_id=study_id, entity_type="visit", entity_id=visit_id, action="VISIT_STATUS_CHANGE" if detail else "UPDATE_VISIT", detail=detail or "Visit updated", operator_id=current_user.id, operator_role=current_user.role, ) return updated