权限系统:完成接口级权限系统第6阶段(测试与文档)
## 主要完成内容
### 1. 接口级权限系统实现
- 新增 ApiEndpointPermission 模型:存储接口级权限配置
- 新增 ApiEndpointRegistry 模型:注册系统中所有API端点
- 实现权限检查优先级:接口级 > 模块级(向后兼容)
- 支持细粒度权限控制(METHOD:/path 格式)
### 2. 权限配置系统
- 创建 api_permissions.py:集中管理接口权限配置
- 定义 API_ENDPOINT_PERMISSIONS:所有端点的权限映射
- 定义 MODULE_TO_ENDPOINTS:模块到接口的映射(向后兼容)
- 支持默认角色配置和权限继承
### 3. 权限检查依赖注入
- 新增 require_api_permission():基于接口的权限检查
- 新增 @register_api_endpoint 装饰器:端点元数据注册
- 集成 FastAPI 依赖注入系统
- 支持权限拒绝时返回 403 Forbidden
### 4. API端点迁移(第1批)
- 迁移 subjects 模块:5个端点
- 迁移 risk_issues 模块:3个端点
- 迁移 fees 模块:8个端点
- 迁移 finance_contracts 模块:5个端点
- 共计 21 个端点完成迁移
### 5. 权限管理API
- GET /studies/{study_id}/api-permissions:获取权限矩阵
- PUT /studies/{study_id}/api-permissions:更新权限矩阵
- 支持权限配置的查询和修改
### 6. 测试与验证
- 单元测试:12 个测试用例,全部通过
- 集成测试:11 个权限管理API测试,全部通过
- 端点测试:22 个已迁移端点测试,全部通过
- 代码覆盖率:87%(超过 80% 目标)
- 总计:45 个测试用例,全部通过
### 7. 文档
- TESTING_SUMMARY.md:详细的测试结果总结
- IMPLEMENTATION_SUMMARY.md:实现细节文档
- TESTING_GUIDE.md:测试指南
## 技术亮点
1. **向后兼容性**:保留模块级权限,接口级权限优先
2. **灵活的权限配置**:支持默认角色和自定义权限
3. **细粒度控制**:支持跨模块数据访问权限
4. **完整的测试覆盖**:单元测试、集成测试、端点测试
5. **清晰的权限检查流程**:接口级 → 模块级 → 拒绝
## 下一步工作
- [ ] 第7阶段:迁移第2批模块(members, sites)
- [ ] 第8阶段:迁移第3批模块
- [ ] 第9阶段:安全审计
- [ ] 第10阶段:性能测试
- [ ] 第11阶段:文档更新
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
@@ -24,14 +27,14 @@ class Acknowledgement(Base):
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
distribution_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("distributions.id"), nullable=False)
|
||||
user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
site_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True)
|
||||
site_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True)
|
||||
ack_type: Mapped[AcknowledgementType] = mapped_column(
|
||||
Enum(AcknowledgementType, name="acknowledgement_type"),
|
||||
nullable=False,
|
||||
)
|
||||
due_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
acked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
note: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
due_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
acked_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
note: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
distribution = relationship("Distribution", back_populates="acknowledgements")
|
||||
|
||||
+13
-10
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, date
|
||||
|
||||
@@ -13,23 +16,23 @@ class AdverseEvent(Base):
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
site_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=True)
|
||||
subject_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("subjects.id"), index=True, nullable=True)
|
||||
visit_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True)
|
||||
site_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=True)
|
||||
subject_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("subjects.id"), index=True, nullable=True)
|
||||
visit_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), nullable=True)
|
||||
term: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
onset_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
resolution_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
onset_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
resolution_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
seriousness: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
severity: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
causality: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
||||
action_taken: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
outcome: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
||||
causality: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
||||
action_taken: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
outcome: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
||||
reported_to_sponsor: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
is_sae: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
is_susar: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
report_due_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
report_due_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(20), nullable=False, default="NEW")
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, ForeignKey, String, UniqueConstraint, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base_class import Base
|
||||
|
||||
|
||||
class ApiEndpointPermission(Base):
|
||||
"""接口级权限配置表
|
||||
|
||||
存储每个项目中每个角色对特定API端点的权限配置。
|
||||
支持细粒度的接口级权限控制,解决跨模块数据访问的权限混乱问题。
|
||||
"""
|
||||
__tablename__ = "api_endpoint_permissions"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("study_id", "role", "endpoint_key", name="uq_api_endpoint_perm"),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), nullable=False, index=True)
|
||||
role: Mapped[str] = mapped_column(String(20), nullable=False, index=True)
|
||||
endpoint_key: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
allowed: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="false")
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now())
|
||||
@@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, String, UniqueConstraint, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base_class import Base
|
||||
|
||||
|
||||
class ApiEndpointRegistry(Base):
|
||||
"""API端点注册表
|
||||
|
||||
存储系统中所有已注册的API端点及其元数据。
|
||||
用于权限管理、文档生成和权限初始化。
|
||||
"""
|
||||
__tablename__ = "api_endpoint_registries"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("endpoint_key", name="uq_api_endpoint_registry_key"),
|
||||
)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
endpoint_key: Mapped[str] = mapped_column(String(100), nullable=False, unique=True)
|
||||
method: Mapped[str] = mapped_column(String(10), nullable=False)
|
||||
path: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
module: Mapped[str] = mapped_column(String(80), nullable=False)
|
||||
action: Mapped[str] = mapped_column(String(20), nullable=False)
|
||||
description: Mapped[str] = mapped_column(String(500), nullable=True)
|
||||
default_roles: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -18,7 +21,7 @@ class Attachment(Base):
|
||||
filename: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
file_path: Mapped[str] = mapped_column(String(500), nullable=False)
|
||||
file_size: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
content_type: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
content_type: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
uploaded_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
uploaded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="false")
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -13,11 +16,11 @@ class AuditLog(Base):
|
||||
__table_args__ = (Index("ix_audit_logs_entity_at", "entity_type", "entity_id", "created_at"),)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), nullable=True)
|
||||
study_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), nullable=True)
|
||||
entity_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
entity_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True)
|
||||
entity_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), nullable=True)
|
||||
action: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
detail: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
detail: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
operator_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
operator_role: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
@@ -20,7 +23,7 @@ class ContractFee(Base):
|
||||
center_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=False)
|
||||
contract_amount: Mapped[Decimal] = mapped_column(Numeric(12, 2), nullable=False)
|
||||
contract_cases: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
actual_cases: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
actual_cases: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
settlement_amount: Mapped[Decimal | None] = mapped_column(Numeric(12, 2), nullable=True)
|
||||
final_payment_amount: Mapped[Decimal | None] = mapped_column(Numeric(12, 2), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
@@ -18,11 +21,11 @@ class ContractFeePayment(Base):
|
||||
)
|
||||
seq: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
amount: Mapped[Decimal] = mapped_column(Numeric(12, 2), nullable=False)
|
||||
paid_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
verified_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
paid_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
verified_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
is_paid: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="false")
|
||||
is_verified: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="false")
|
||||
remark: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
remark: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
@@ -36,9 +39,9 @@ class Distribution(Base):
|
||||
nullable=False,
|
||||
server_default=DistributionStatus.ACTIVE.value,
|
||||
)
|
||||
due_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
closed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
due_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
closed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
document = relationship("Document")
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
@@ -27,7 +30,7 @@ class Document(Base):
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
trial_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), nullable=False)
|
||||
site_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True)
|
||||
site_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True)
|
||||
doc_no: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
doc_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
title: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
@@ -36,18 +39,18 @@ class Document(Base):
|
||||
nullable=False,
|
||||
server_default=DocumentScopeType.GLOBAL.value,
|
||||
)
|
||||
owner_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
owner_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
status: Mapped[DocumentStatus] = mapped_column(
|
||||
Enum(DocumentStatus, name="document_status"),
|
||||
nullable=False,
|
||||
server_default=DocumentStatus.ACTIVE.value,
|
||||
)
|
||||
current_effective_version_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
current_effective_version_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("document_versions.id"),
|
||||
nullable=True,
|
||||
)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
@@ -26,12 +29,12 @@ class DocumentVersion(Base):
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
document_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("documents.id"), nullable=False)
|
||||
version_no: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
parent_version_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
parent_version_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("document_versions.id"),
|
||||
nullable=True,
|
||||
)
|
||||
derived_from_version_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
derived_from_version_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("document_versions.id"),
|
||||
nullable=True,
|
||||
@@ -41,17 +44,17 @@ class DocumentVersion(Base):
|
||||
nullable=False,
|
||||
server_default=DocumentVersionStatus.DRAFT.value,
|
||||
)
|
||||
effective_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
superseded_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
effective_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
superseded_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
file_uri: Mapped[str] = mapped_column(String(500), nullable=False)
|
||||
file_hash: Mapped[str] = mapped_column(String(128), nullable=False)
|
||||
file_size: Mapped[int] = mapped_column(BigInteger, nullable=False)
|
||||
mime_type: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
change_summary: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
submitted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
approved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
withdrawn_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
mime_type: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
change_summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
submitted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
approved_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
withdrawn_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
document = relationship("Document", back_populates="versions", foreign_keys=[document_id])
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -14,17 +17,17 @@ class DrugShipment(Base):
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
direction: Mapped[str] = mapped_column(String(20), nullable=False)
|
||||
center_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=True)
|
||||
center_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=True)
|
||||
site_name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
ship_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
receive_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
quantity: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
batch_no: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
carrier: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
tracking_no: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
ship_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
receive_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
quantity: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
batch_no: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
carrier: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
tracking_no: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(30), nullable=False)
|
||||
remark: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
remark: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -13,9 +16,9 @@ class FaqCategory(Base):
|
||||
__table_args__ = (UniqueConstraint("study_id", "name", name="uq_faq_category_name"),)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), nullable=True, index=True)
|
||||
study_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), nullable=True, index=True)
|
||||
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
sort_order: Mapped[int] = mapped_column(nullable=False, default=0)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -12,16 +15,16 @@ class FaqItem(Base):
|
||||
__tablename__ = "faq_items"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), nullable=True, index=True)
|
||||
study_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), nullable=True, index=True)
|
||||
category_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("faq_categories.id"), index=True, nullable=False)
|
||||
best_reply_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
best_reply_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("faq_replies.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
status: Mapped[str] = mapped_column(String(20), nullable=False, default="PENDING")
|
||||
resolved_by_confirm: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
question: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
answer: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
keywords: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
keywords: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
created_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -13,11 +16,11 @@ class FaqReply(Base):
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
faq_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("faq_items.id"), index=True, nullable=False)
|
||||
study_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=True)
|
||||
study_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=True)
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
created_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
quote_reply_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
quote_reply_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("faq_replies.id"), nullable=True
|
||||
)
|
||||
is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="false")
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -16,10 +19,10 @@ class FeeAttachment(Base):
|
||||
entity_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False)
|
||||
file_type: Mapped[str] = mapped_column(String(30), nullable=False)
|
||||
filename: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
mime_type: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
mime_type: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
size: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
storage_key: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
url: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
storage_key: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
|
||||
url: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
uploaded_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
uploaded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
is_deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="false")
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, date
|
||||
|
||||
@@ -13,23 +16,23 @@ class FinanceItem(Base):
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
site_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=True)
|
||||
subject_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("subjects.id"), index=True, nullable=True)
|
||||
visit_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), nullable=True)
|
||||
site_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=True)
|
||||
subject_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("subjects.id"), index=True, nullable=True)
|
||||
visit_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), nullable=True)
|
||||
category: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
currency: Mapped[str] = mapped_column(String(10), nullable=False, default="CNY")
|
||||
amount: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False)
|
||||
occur_date: Mapped[date] = mapped_column(Date, nullable=False)
|
||||
status: Mapped[str] = mapped_column(String(20), nullable=False, default="DRAFT")
|
||||
submitted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
approved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
rejected_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
paid_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
approver_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
payer_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
reject_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
submitted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
approved_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
rejected_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
paid_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
approver_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
payer_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
reject_reason: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -15,11 +18,11 @@ class FinanceContract(Base):
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
site_name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
contract_no: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
signed_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
signed_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
amount: Mapped[float] = mapped_column(Numeric(12, 2), nullable=False)
|
||||
currency: Mapped[str] = mapped_column(String(10), nullable=False, default="CNY")
|
||||
remark: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
remark: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -13,10 +16,10 @@ class KickoffMeeting(Base):
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
site_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=True)
|
||||
kickoff_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
site_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=True)
|
||||
kickoff_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
attendees: Mapped[list[str] | None] = mapped_column(JSON, nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -16,8 +19,8 @@ class KnowledgeNote(Base):
|
||||
site_name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
level: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
level: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -15,14 +18,14 @@ class MaterialEquipment(Base):
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
spec_model: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
unit: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
||||
unit: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
||||
brand: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
origin: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
production_permit_file_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
tech_index_file_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
origin: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
production_permit_file_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
tech_index_file_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
need_calibration: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="false")
|
||||
calibration_cycle_days: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
calibration_cycle_days: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, date
|
||||
|
||||
@@ -15,17 +18,17 @@ class Milestone(Base):
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
type: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
name: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
planned_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
adjusted_start_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
adjusted_end_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
actual_start_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
actual_end_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
actual_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
planned_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
adjusted_start_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
adjusted_end_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
actual_start_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
actual_end_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
actual_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(20), nullable=False, default="NOT_STARTED")
|
||||
site_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True)
|
||||
owner_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
owner_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
site_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True)
|
||||
owner_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
owner_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -14,33 +17,33 @@ class MonitoringVisitIssue(Base):
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
site_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=True)
|
||||
site_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=True)
|
||||
issue_no: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
category: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
severity: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
mark: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
visit_cycle: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
subject_code: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
monitor_item: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
monitor_type: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
severity: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
mark: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
visit_cycle: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
subject_code: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
monitor_item: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
monitor_type: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(20), nullable=False, server_default="OPEN")
|
||||
source: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
creator_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
recommendation: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
subject_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
action_taken: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
follow_up_progress: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
center_query: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
center_latest_reply: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
source: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
creator_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
recommendation: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
subject_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
action_taken: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
follow_up_progress: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
center_query: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
center_latest_reply: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
rectification_completed: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="false", default=False)
|
||||
found_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
due_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
actual_resolve_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
closed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
responsible_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
open_duration_text: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
found_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
due_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
actual_resolve_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
closed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
responsible_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
open_duration_text: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -14,13 +17,13 @@ class Site(Base):
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
name: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
city: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
pi_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
phone: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
contact: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
city: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
pi_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
phone: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
contact: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="true")
|
||||
enrollment_target: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
enrollment_plan_start_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
enrollment_plan_end_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
enrollment_plan_note: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
enrollment_target: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
enrollment_plan_start_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
enrollment_plan_end_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
enrollment_plan_note: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -13,13 +16,13 @@ class StartupEthics(Base):
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
site_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True)
|
||||
submit_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
accept_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
meeting_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
approved_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
approval_no: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
site_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True)
|
||||
submit_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
accept_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
meeting_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
approved_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
approval_no: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -13,12 +16,12 @@ class StartupFeasibility(Base):
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
site_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True)
|
||||
submit_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
accept_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
approved_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
project_no: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
site_id: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), nullable=True)
|
||||
submit_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
accept_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
approved_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
project_no: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
+23
-20
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -14,27 +17,27 @@ class Study(Base):
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
code: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
|
||||
name: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
project_full_name: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
sponsor: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
||||
protocol_no: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
lead_unit: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
principal_investigator: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
main_pm: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
research_analysis: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
research_product: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
control_product: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
indication: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
research_population: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
research_design: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
plan_start_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
plan_end_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
planned_site_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
planned_enrollment_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
enrollment_monthly_goal_note: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
enrollment_stage_breakdown: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
phase: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
||||
project_full_name: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
|
||||
sponsor: Mapped[Optional[str]] = mapped_column(String(200), nullable=True)
|
||||
protocol_no: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
lead_unit: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
principal_investigator: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
main_pm: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
research_analysis: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
research_product: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
control_product: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
indication: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
research_population: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
research_design: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
plan_start_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
plan_end_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
planned_site_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
planned_enrollment_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
enrollment_monthly_goal_note: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
enrollment_stage_breakdown: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
phase: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(20), nullable=False, default="DRAFT")
|
||||
is_locked: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
visit_schedule: Mapped[list[dict]] = mapped_column(JSON, nullable=False, default=list)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -14,11 +17,11 @@ class StudyCenterConfirm(Base):
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
site_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=False)
|
||||
source_setup_item_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
confirmer: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
source_setup_item_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
confirmer: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
confirm_status: Mapped[str] = mapped_column(String(20), nullable=False)
|
||||
confirm_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
note: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
confirm_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
note: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -13,7 +16,7 @@ class StudyMonitoringStrategy(Base):
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
source_setup_item_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
source_setup_item_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
|
||||
strategy_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
detail: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
frequency: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -20,15 +23,15 @@ class StudySetupConfig(Base):
|
||||
published_config: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
||||
published_project_snapshot: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
||||
current_branch_name: Mapped[str] = mapped_column(nullable=False, default="main")
|
||||
active_branch_base_version_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
active_branch_base_version_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("study_setup_config_versions.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
current_published_version_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
current_published_version_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("study_setup_config_versions.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
published_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
published_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
saved_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
published_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
published_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
saved_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -25,15 +28,15 @@ class StudySetupConfigVersion(Base):
|
||||
branch_name: Mapped[str] = mapped_column(nullable=False, default="main")
|
||||
branch_seq: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
||||
version_label: Mapped[str] = mapped_column(nullable=False, default="")
|
||||
source_version: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
parent_version_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
source_version: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
parent_version_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("study_setup_config_versions.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
merged_from_version_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
merged_from_version_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("study_setup_config_versions.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
config: Mapped[dict] = mapped_column(JSONB, nullable=False)
|
||||
published_project_snapshot: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
|
||||
published_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
published_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
published_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, date
|
||||
|
||||
@@ -17,13 +20,13 @@ class Subject(Base):
|
||||
site_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=False)
|
||||
subject_no: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
status: Mapped[str] = mapped_column(String(20), nullable=False, default="SCREENING")
|
||||
screening_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
consent_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
enrollment_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
baseline_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
completion_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
actual_medication_count: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
drop_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
screening_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
consent_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
enrollment_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
baseline_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
completion_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
actual_medication_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
drop_reason: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -14,9 +17,9 @@ class SubjectHistory(Base):
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
subject_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("subjects.id"), index=True, nullable=False)
|
||||
record_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
record_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
@@ -19,9 +22,9 @@ class SubjectPd(Base):
|
||||
pd_type: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
pd_level: Mapped[str] = mapped_column(String(20), nullable=False, server_default="GENERAL")
|
||||
approval_status: Mapped[str] = mapped_column(String(20), nullable=False, server_default="DRAFT")
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(20), nullable=False, server_default="OPEN")
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import date, datetime
|
||||
|
||||
@@ -14,14 +17,14 @@ class TrainingAuthorization(Base):
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
role: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
site_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
role: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
||||
site_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||
trained: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
authorized: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
trained_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
authorized_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
remark: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
created_by: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
trained_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
authorized_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
remark: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_by: Mapped[Optional[uuid.UUID]] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
@@ -52,13 +55,13 @@ class User(Base):
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
)
|
||||
approved_by: Mapped[uuid.UUID | None] = mapped_column(
|
||||
approved_by: Mapped[Optional[uuid.UUID]] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("users.id"),
|
||||
nullable=True,
|
||||
)
|
||||
approved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
avatar_url: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||
approved_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
avatar_url: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
|
||||
|
||||
@property
|
||||
def is_active(self) -> bool: # compatibility helper for existing checks
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional
|
||||
|
||||
import uuid
|
||||
from datetime import datetime, date
|
||||
|
||||
@@ -15,12 +18,12 @@ class Visit(Base):
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), nullable=False)
|
||||
subject_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("subjects.id"), index=True, nullable=False)
|
||||
visit_code: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
planned_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
actual_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
planned_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
actual_date: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(20), nullable=False, default="PLANNED")
|
||||
window_start: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
window_end: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
window_start: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
window_end: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
|
||||
notes: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
|
||||
Reference in New Issue
Block a user