diff --git a/backend/app/crud/subject.py b/backend/app/crud/subject.py
index 923ccf00..a972bf45 100644
--- a/backend/app/crud/subject.py
+++ b/backend/app/crud/subject.py
@@ -34,6 +34,9 @@ def _standard_visit_codes(visit_schedule: list[dict] | None) -> set[str]:
def derive_subject_status(subject, visits: Sequence[Visit], visit_schedule: list[dict] | None = None) -> str:
+ if not getattr(subject, "screening_date", None):
+ return ""
+
if any(_visit_code(visit) == "提前终止" and getattr(visit, "actual_date", None) for visit in visits):
return "DROPPED"
@@ -116,7 +119,7 @@ async def create_subject(db: AsyncSession, study_id: uuid.UUID, subject_in: Subj
study_id=study_id,
site_id=subject_in.site_id,
subject_no=subject_in.subject_no,
- status="SCREENING",
+ status="SCREENING" if subject_in.screening_date else "",
screening_date=subject_in.screening_date,
consent_date=subject_in.consent_date,
enrollment_date=subject_in.enrollment_date,
@@ -194,8 +197,7 @@ async def generate_default_visits(db: AsyncSession, subject: Subject) -> None:
async def update_subject(db: AsyncSession, subject: Subject, subject_in: SubjectUpdate) -> Subject:
update_data = subject_in.model_dump(exclude_unset=True)
- update_data.pop("status", None)
- next_screening_date = subject.screening_date
+ next_screening_date = update_data.get("screening_date", subject.screening_date)
next_consent_date = update_data.get("consent_date", subject.consent_date)
next_enrollment_date = update_data.get("enrollment_date", subject.enrollment_date)
next_completion_date = update_data.get("completion_date", subject.completion_date)
diff --git a/backend/app/schemas/subject.py b/backend/app/schemas/subject.py
index 0fed17cf..669e24e1 100644
--- a/backend/app/schemas/subject.py
+++ b/backend/app/schemas/subject.py
@@ -15,7 +15,7 @@ class SubjectCreate(BaseModel):
class SubjectUpdate(BaseModel):
- status: Optional[str] = None
+ screening_date: Optional[date] = None
consent_date: Optional[date] = None
enrollment_date: Optional[date] = None
baseline_date: Optional[date] = None
diff --git a/backend/tests/test_subject_medication_adherence.py b/backend/tests/test_subject_medication_adherence.py
index b134f6e2..bb03083e 100644
--- a/backend/tests/test_subject_medication_adherence.py
+++ b/backend/tests/test_subject_medication_adherence.py
@@ -1,4 +1,4 @@
-from datetime import datetime
+from datetime import date, datetime
from types import SimpleNamespace
import uuid
@@ -12,6 +12,16 @@ def test_subject_update_accepts_actual_medication_count():
assert payload.actual_medication_count == 12
+def test_subject_update_accepts_screening_date():
+ payload = SubjectUpdate(screening_date=date(2026, 5, 25))
+
+ assert payload.screening_date == date(2026, 5, 25)
+
+
+def test_subject_update_does_not_expose_status_input():
+ assert "status" not in SubjectUpdate.model_fields
+
+
def test_subject_read_includes_actual_medication_count():
subject = SimpleNamespace(
id=uuid.UUID("00000000-0000-0000-0000-000000000001"),
diff --git a/backend/tests/test_visit_schedule.py b/backend/tests/test_visit_schedule.py
index 9666c0c1..33341c73 100644
--- a/backend/tests/test_visit_schedule.py
+++ b/backend/tests/test_visit_schedule.py
@@ -275,3 +275,9 @@ def test_derive_subject_status_marks_screening_until_enrollment_date_is_filled()
subject = SimpleNamespace(screening_date=date(2026, 5, 1), enrollment_date=None)
assert derive_subject_status(subject, [], []) == "SCREENING"
+
+
+def test_derive_subject_status_is_empty_before_screening_date_is_filled():
+ subject = SimpleNamespace(screening_date=None, enrollment_date=None)
+
+ assert derive_subject_status(subject, [], []) == ""
diff --git a/frontend/src/views/ia/SubjectManagement.vue b/frontend/src/views/ia/SubjectManagement.vue
index f097d7f2..4318785d 100644
--- a/frontend/src/views/ia/SubjectManagement.vue
+++ b/frontend/src/views/ia/SubjectManagement.vue
@@ -85,7 +85,7 @@
{{ displayDate(scope.row.consent_date) }}
- {{ displayEnum(TEXT.enums.subjectStatus, scope.row.status) }}
+ {{ displaySubjectStatus(scope.row) }}
{{ displayDate(scope.row.enrollment_date) }}
@@ -227,6 +227,10 @@ const getAeBadges = (row: any) => {
}
return badges;
};
+const displaySubjectStatus = (subject: any) => {
+ if (!subject?.screening_date) return TEXT.common.fallback;
+ return displayEnum(TEXT.enums.subjectStatus, subject.status);
+};
const filteredItems = computed(() => {
const keyword = filters.value.keyword.trim().toLowerCase();
return items.value.filter((item) => {
diff --git a/frontend/src/views/subjects/SubjectDetail.vue b/frontend/src/views/subjects/SubjectDetail.vue
index f507caa1..fb884f81 100644
--- a/frontend/src/views/subjects/SubjectDetail.vue
+++ b/frontend/src/views/subjects/SubjectDetail.vue
@@ -30,9 +30,18 @@
{{ detail.subject_no || TEXT.common.fallback }}
{{ siteMap[detail.site_id] || TEXT.common.fallback }}
- {{ displayEnum(TEXT.enums.subjectStatus, detail.status) }}
+ {{ displaySubjectStatus(detail) }}
+
+
+
+ {{ displayDate(detail.screening_date) }}
- {{ displayDate(detail.screening_date) }}
({
const subjectEditing = ref(false);
const subjectSaving = ref(false);
const subjectForm = reactive({
+ screening_date: "",
enrollment_date: "",
baseline_date: "",
consent_date: "",
@@ -665,6 +675,11 @@ const getErrorMessage = (error: any, fallback: string) => {
return fallback;
};
+const displaySubjectStatus = (subject: any) => {
+ if (!subject?.screening_date) return TEXT.common.fallback;
+ return displayEnum(TEXT.enums.subjectStatus, subject.status);
+};
+
const loadSites = async () => {
if (!studyId) return;
try {
@@ -702,6 +717,7 @@ const startSubjectEdit = () => {
ElMessage.warning("中心已停用");
return;
}
+ subjectForm.screening_date = detail.screening_date || "";
subjectForm.consent_date = detail.consent_date || "";
subjectForm.enrollment_date = detail.enrollment_date || "";
subjectForm.baseline_date = detail.baseline_date || "";
@@ -723,6 +739,7 @@ const saveSubjectEdit = async () => {
subjectSaving.value = true;
try {
const payload = {
+ screening_date: subjectForm.screening_date || null,
consent_date: subjectForm.consent_date || null,
enrollment_date: subjectForm.enrollment_date || null,
baseline_date: subjectForm.baseline_date || null,