feat: refine subject visits and project workflows
Add early termination visit workflow with ordering, non-applicable visit handling, visit window display, and medication adherence support. Extend monitoring visit issue template fields, site scoping, setup draft project info handling, login security UI, attachment behavior, and related tests/migrations.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from datetime import date, datetime, time, timedelta, timezone
|
||||
from typing import Sequence
|
||||
|
||||
from sqlalchemy import and_, or_, select
|
||||
@@ -46,25 +46,55 @@ async def list_issues(
|
||||
db: AsyncSession,
|
||||
study_id: uuid.UUID,
|
||||
*,
|
||||
site_id: uuid.UUID | None = None,
|
||||
category: str | None = None,
|
||||
severity: str | None = None,
|
||||
mark: str | None = None,
|
||||
visit_cycle: str | None = None,
|
||||
status: str | None = None,
|
||||
overdue: bool | None = None,
|
||||
rectification_completed: bool | None = None,
|
||||
due_from: date | None = None,
|
||||
due_to: date | None = None,
|
||||
created_from: date | None = None,
|
||||
created_to: date | None = None,
|
||||
keyword: str | None = None,
|
||||
skip: int = 0,
|
||||
limit: int = 500,
|
||||
) -> Sequence[MonitoringVisitIssue]:
|
||||
stmt = select(MonitoringVisitIssue).where(MonitoringVisitIssue.study_id == study_id)
|
||||
|
||||
if site_id:
|
||||
stmt = stmt.where(MonitoringVisitIssue.site_id == site_id)
|
||||
if category:
|
||||
stmt = stmt.where(MonitoringVisitIssue.category == category)
|
||||
if severity:
|
||||
stmt = stmt.where(MonitoringVisitIssue.severity == severity)
|
||||
if mark:
|
||||
stmt = stmt.where(MonitoringVisitIssue.mark.ilike(f"%{mark}%"))
|
||||
if visit_cycle:
|
||||
stmt = stmt.where(MonitoringVisitIssue.visit_cycle == visit_cycle)
|
||||
if status:
|
||||
stmt = stmt.where(MonitoringVisitIssue.status == status)
|
||||
if rectification_completed is not None:
|
||||
stmt = stmt.where(MonitoringVisitIssue.rectification_completed == rectification_completed)
|
||||
if due_from:
|
||||
stmt = stmt.where(MonitoringVisitIssue.due_at >= datetime.combine(due_from, time.min, tzinfo=timezone.utc))
|
||||
if due_to:
|
||||
stmt = stmt.where(MonitoringVisitIssue.due_at < datetime.combine(due_to + timedelta(days=1), time.min, tzinfo=timezone.utc))
|
||||
if created_from:
|
||||
stmt = stmt.where(MonitoringVisitIssue.created_at >= datetime.combine(created_from, time.min, tzinfo=timezone.utc))
|
||||
if created_to:
|
||||
stmt = stmt.where(MonitoringVisitIssue.created_at < datetime.combine(created_to + timedelta(days=1), time.min, tzinfo=timezone.utc))
|
||||
if keyword:
|
||||
term = f"%{keyword}%"
|
||||
stmt = stmt.where(
|
||||
or_(
|
||||
MonitoringVisitIssue.issue_no.ilike(term),
|
||||
MonitoringVisitIssue.category.ilike(term),
|
||||
MonitoringVisitIssue.severity.ilike(term),
|
||||
MonitoringVisitIssue.mark.ilike(term),
|
||||
MonitoringVisitIssue.visit_cycle.ilike(term),
|
||||
MonitoringVisitIssue.subject_code.ilike(term),
|
||||
MonitoringVisitIssue.subject_name.ilike(term),
|
||||
MonitoringVisitIssue.monitor_item.ilike(term),
|
||||
@@ -72,6 +102,8 @@ async def list_issues(
|
||||
MonitoringVisitIssue.recommendation.ilike(term),
|
||||
MonitoringVisitIssue.action_taken.ilike(term),
|
||||
MonitoringVisitIssue.follow_up_progress.ilike(term),
|
||||
MonitoringVisitIssue.center_query.ilike(term),
|
||||
MonitoringVisitIssue.center_latest_reply.ilike(term),
|
||||
MonitoringVisitIssue.responsible_name.ilike(term),
|
||||
)
|
||||
)
|
||||
@@ -107,8 +139,12 @@ async def create_issue(
|
||||
closed_at = datetime.now(timezone.utc)
|
||||
item = MonitoringVisitIssue(
|
||||
study_id=study_id,
|
||||
site_id=issue_in.site_id,
|
||||
issue_no=issue_no,
|
||||
category=issue_in.category,
|
||||
severity=issue_in.severity,
|
||||
mark=issue_in.mark,
|
||||
visit_cycle=issue_in.visit_cycle,
|
||||
subject_code=issue_in.subject_code,
|
||||
monitor_item=issue_in.monitor_item,
|
||||
monitor_type=issue_in.monitor_type,
|
||||
@@ -120,6 +156,9 @@ async def create_issue(
|
||||
description=issue_in.description,
|
||||
action_taken=issue_in.action_taken,
|
||||
follow_up_progress=issue_in.follow_up_progress,
|
||||
center_query=issue_in.center_query,
|
||||
center_latest_reply=issue_in.center_latest_reply,
|
||||
rectification_completed=issue_in.rectification_completed,
|
||||
found_date=issue_in.found_date,
|
||||
due_at=issue_in.due_at,
|
||||
actual_resolve_date=issue_in.actual_resolve_date,
|
||||
|
||||
Reference in New Issue
Block a user