183 lines
5.2 KiB
Python
183 lines
5.2 KiB
Python
import uuid
|
|
from datetime import date, datetime, timezone
|
|
|
|
from pydantic import BaseModel, ConfigDict, field_validator
|
|
|
|
|
|
class MonitoringVisitIssueCreate(BaseModel):
|
|
issue_no: str | None = None
|
|
category: str
|
|
subject_code: str | None = None
|
|
monitor_item: str | None = None
|
|
monitor_type: str | None = None
|
|
status: str = "OPEN"
|
|
source: str | None = None
|
|
creator_name: str | None = None
|
|
recommendation: str | None = None
|
|
subject_name: str | None = None
|
|
description: str | None = None
|
|
action_taken: str | None = None
|
|
follow_up_progress: str | None = None
|
|
found_date: date | None = None
|
|
due_at: datetime | None = None
|
|
actual_resolve_date: date | None = None
|
|
closed_at: datetime | None = None
|
|
responsible_name: str | None = None
|
|
|
|
@field_validator("category", mode="before")
|
|
@classmethod
|
|
def _strip_required(cls, value: str) -> str:
|
|
text = (value or "").strip()
|
|
if not text:
|
|
raise ValueError("不能为空")
|
|
return text
|
|
|
|
@field_validator("issue_no", mode="before")
|
|
@classmethod
|
|
def _strip_issue_no(cls, value: str | None) -> str | None:
|
|
if value is None:
|
|
return None
|
|
text = value.strip()
|
|
return text or None
|
|
|
|
@field_validator(
|
|
"subject_code",
|
|
"monitor_item",
|
|
"monitor_type",
|
|
"source",
|
|
"creator_name",
|
|
"recommendation",
|
|
"subject_name",
|
|
"description",
|
|
"action_taken",
|
|
"follow_up_progress",
|
|
"responsible_name",
|
|
mode="before",
|
|
)
|
|
@classmethod
|
|
def _strip_optional(cls, value: str | None) -> str | None:
|
|
if value is None:
|
|
return None
|
|
text = value.strip()
|
|
return text or None
|
|
|
|
@field_validator("status", mode="before")
|
|
@classmethod
|
|
def _normalize_status(cls, value: str | None) -> str:
|
|
normalized = (value or "OPEN").strip().upper()
|
|
if normalized not in {"OPEN", "CLOSED"}:
|
|
raise ValueError("状态必须为 OPEN 或 CLOSED")
|
|
return normalized
|
|
|
|
@field_validator("due_at", "closed_at")
|
|
@classmethod
|
|
def _normalize_due_at(cls, value: datetime | None) -> datetime | None:
|
|
if value is None:
|
|
return None
|
|
return value if value.tzinfo else value.replace(tzinfo=timezone.utc)
|
|
|
|
|
|
class MonitoringVisitIssueRead(BaseModel):
|
|
id: uuid.UUID
|
|
study_id: uuid.UUID
|
|
issue_no: str
|
|
open_duration: str
|
|
category: str
|
|
subject_code: str | None
|
|
monitor_item: str | None
|
|
monitor_type: str | None
|
|
progress: str
|
|
status: str
|
|
source: str | None
|
|
creator_name: str | None
|
|
recommendation: str | None
|
|
subject_name: str | None
|
|
created_at: datetime
|
|
description: str | None
|
|
action_taken: str | None
|
|
follow_up_progress: str | None
|
|
found_date: date | None
|
|
overdue: bool
|
|
due_at: datetime | None
|
|
actual_resolve_date: date | None
|
|
closed_at: datetime | None
|
|
responsible_name: str | None
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class MonitoringVisitIssueImportSummary(BaseModel):
|
|
created_count: int
|
|
updated_count: int
|
|
skipped_count: int
|
|
skipped_rows: list[str]
|
|
|
|
|
|
class MonitoringVisitIssueUpdate(BaseModel):
|
|
issue_no: str | None = None
|
|
category: str | None = None
|
|
subject_code: str | None = None
|
|
monitor_item: str | None = None
|
|
monitor_type: str | None = None
|
|
status: str | None = None
|
|
source: str | None = None
|
|
creator_name: str | None = None
|
|
recommendation: str | None = None
|
|
subject_name: str | None = None
|
|
description: str | None = None
|
|
action_taken: str | None = None
|
|
follow_up_progress: str | None = None
|
|
found_date: date | None = None
|
|
due_at: datetime | None = None
|
|
actual_resolve_date: date | None = None
|
|
closed_at: datetime | None = None
|
|
responsible_name: str | None = None
|
|
|
|
@field_validator("issue_no", "category", mode="before")
|
|
@classmethod
|
|
def _strip_required(cls, value: str | None) -> str | None:
|
|
if value is None:
|
|
return None
|
|
text = value.strip()
|
|
if not text:
|
|
raise ValueError("不能为空")
|
|
return text
|
|
|
|
@field_validator(
|
|
"subject_code",
|
|
"monitor_item",
|
|
"monitor_type",
|
|
"source",
|
|
"creator_name",
|
|
"recommendation",
|
|
"subject_name",
|
|
"description",
|
|
"action_taken",
|
|
"follow_up_progress",
|
|
"responsible_name",
|
|
mode="before",
|
|
)
|
|
@classmethod
|
|
def _strip_optional(cls, value: str | None) -> str | None:
|
|
if value is None:
|
|
return None
|
|
text = value.strip()
|
|
return text or None
|
|
|
|
@field_validator("status", mode="before")
|
|
@classmethod
|
|
def _normalize_status(cls, value: str | None) -> str | None:
|
|
if value is None:
|
|
return None
|
|
normalized = value.strip().upper()
|
|
if normalized not in {"OPEN", "CLOSED"}:
|
|
raise ValueError("状态必须为 OPEN 或 CLOSED")
|
|
return normalized
|
|
|
|
@field_validator("due_at", "closed_at")
|
|
@classmethod
|
|
def _normalize_due_at(cls, value: datetime | None) -> datetime | None:
|
|
if value is None:
|
|
return None
|
|
return value if value.tzinfo else value.replace(tzinfo=timezone.utc)
|