import uuid from datetime import date, datetime, timezone from typing import Sequence from sqlalchemy import select, update as sa_update from sqlalchemy.ext.asyncio import AsyncSession from app.models.issue import Issue from app.models.site import Site from app.models.subject import Subject from app.schemas.issue import IssueCreate, IssueUpdate async def _validate_site_subject(db: AsyncSession, study_id: uuid.UUID, site_id: uuid.UUID | None, subject_id: uuid.UUID | None): if site_id: result = await db.execute(select(Site).where(Site.id == site_id)) site = result.scalar_one_or_none() if not site or site.study_id != study_id: raise ValueError("Site not found in study") if subject_id: result = await db.execute(select(Subject).where(Subject.id == subject_id)) subj = result.scalar_one_or_none() if not subj or subj.study_id != study_id: raise ValueError("Subject not found in study") async def create_issue( db: AsyncSession, study_id: uuid.UUID, issue_in: IssueCreate, *, created_by: uuid.UUID, ) -> Issue: await _validate_site_subject(db, study_id, issue_in.site_id, issue_in.subject_id) issue = Issue( study_id=study_id, site_id=issue_in.site_id, subject_id=issue_in.subject_id, title=issue_in.title, description=issue_in.description, category=issue_in.category, level=issue_in.level, owner_id=issue_in.owner_id, due_date=issue_in.due_date, status="OPEN", capa=None, closed_at=None, created_by=created_by, ) db.add(issue) await db.commit() await db.refresh(issue) return issue async def get_issue(db: AsyncSession, issue_id: uuid.UUID) -> Issue | None: result = await db.execute(select(Issue).where(Issue.id == issue_id)) return result.scalar_one_or_none() async def list_issues( db: AsyncSession, study_id: uuid.UUID, status: str | None = None, level: str | None = None, category: str | None = None, overdue: bool | None = None, ) -> Sequence[Issue]: stmt = select(Issue).where(Issue.study_id == study_id) if status: stmt = stmt.where(Issue.status == status) if level: stmt = stmt.where(Issue.level == level) if category: stmt = stmt.where(Issue.category == category) if overdue is True: stmt = stmt.where(Issue.due_date < date.today(), Issue.status != "CLOSED") if overdue is False: stmt = stmt.where((Issue.due_date >= date.today()) | (Issue.due_date.is_(None)) | (Issue.status == "CLOSED")) result = await db.execute(stmt) return result.scalars().all() async def update_issue(db: AsyncSession, issue: Issue, issue_in: IssueUpdate) -> Issue: update_data = issue_in.model_dump(exclude_unset=True) if "status" in update_data: if update_data["status"] == "CLOSED": update_data["closed_at"] = datetime.now(timezone.utc) else: update_data["closed_at"] = None if update_data: await db.execute( sa_update(Issue) .where(Issue.id == issue.id) .values(**update_data) ) await db.commit() await db.refresh(issue) return issue