未知(继上次中断)
This commit is contained in:
@@ -2,7 +2,7 @@ import uuid
|
||||
from datetime import date, timedelta
|
||||
from typing import Sequence
|
||||
|
||||
from sqlalchemy import select, update as sa_update
|
||||
from sqlalchemy import and_, func, or_, select, update as sa_update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.subject import Subject
|
||||
@@ -43,6 +43,54 @@ async def list_visits(db: AsyncSession, subject_id: uuid.UUID) -> Sequence[Visit
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
async def mark_overdue_as_lost(db: AsyncSession, subject_id: uuid.UUID) -> None:
|
||||
today = func.current_date()
|
||||
deadline = func.coalesce(Visit.window_end, Visit.planned_date)
|
||||
await db.execute(
|
||||
sa_update(Visit)
|
||||
.where(
|
||||
Visit.subject_id == subject_id,
|
||||
Visit.actual_date.is_(None),
|
||||
deadline.is_not(None),
|
||||
deadline < today,
|
||||
Visit.status.not_in(["DONE", "CANCELLED", "LOST"]),
|
||||
)
|
||||
.values(status="LOST")
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def mark_overdue_as_lost_global(db: AsyncSession) -> None:
|
||||
today = func.current_date()
|
||||
deadline = func.coalesce(Visit.window_end, Visit.planned_date)
|
||||
await db.execute(
|
||||
sa_update(Visit)
|
||||
.where(
|
||||
Visit.actual_date.is_(None),
|
||||
deadline.is_not(None),
|
||||
deadline < today,
|
||||
Visit.status.not_in(["DONE", "CANCELLED", "LOST"]),
|
||||
)
|
||||
.values(status="LOST")
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def get_next_visit_code(db: AsyncSession, subject_id: uuid.UUID) -> str:
|
||||
result = await db.execute(select(Visit.visit_code).where(Visit.subject_id == subject_id))
|
||||
used: set[int] = set()
|
||||
for (code,) in result.all():
|
||||
if not code or not code.startswith("V"):
|
||||
continue
|
||||
num = code[1:]
|
||||
if num.isdigit():
|
||||
used.add(int(num))
|
||||
next_num = 1
|
||||
while next_num in used:
|
||||
next_num += 1
|
||||
return f"V{next_num}"
|
||||
|
||||
|
||||
async def get_visit(db: AsyncSession, visit_id: uuid.UUID) -> Visit | None:
|
||||
result = await db.execute(select(Visit).where(Visit.id == visit_id))
|
||||
return result.scalar_one_or_none()
|
||||
@@ -72,6 +120,36 @@ async def delete_visit(db: AsyncSession, visit: Visit) -> None:
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def list_lost_visits(
|
||||
db: AsyncSession,
|
||||
study_id: uuid.UUID,
|
||||
*,
|
||||
site_ids: set[uuid.UUID] | None = None,
|
||||
limit: int = 50,
|
||||
) -> Sequence[tuple[Visit, str, uuid.UUID | None]]:
|
||||
if site_ids is not None and not site_ids:
|
||||
return []
|
||||
today = func.current_date()
|
||||
deadline = func.coalesce(Visit.window_end, Visit.planned_date)
|
||||
lost_condition = and_(
|
||||
Visit.actual_date.is_(None),
|
||||
deadline.is_not(None),
|
||||
deadline < today,
|
||||
Visit.status.not_in(["DONE", "CANCELLED"]),
|
||||
)
|
||||
stmt = (
|
||||
select(Visit, Subject.subject_no, Subject.site_id)
|
||||
.join(Subject, Subject.id == Visit.subject_id)
|
||||
.where(Visit.study_id == study_id, lost_condition)
|
||||
.order_by(Visit.updated_at.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
if site_ids is not None:
|
||||
stmt = stmt.where(Subject.site_id.in_(site_ids))
|
||||
result = await db.execute(stmt)
|
||||
return result.all()
|
||||
|
||||
|
||||
async def create_followup_visits(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user