补齐业务路由与页面权限控制
This commit is contained in:
+66
-17
@@ -48,7 +48,7 @@ def _is_overdue(ae: AERead) -> bool:
|
||||
"/",
|
||||
response_model=AERead,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(require_api_permission("risk_issues:create")), Depends(require_study_not_locked())],
|
||||
dependencies=[Depends(require_api_permission("subject_aes:create")), Depends(require_study_not_locked())],
|
||||
)
|
||||
async def create_ae(
|
||||
study_id: uuid.UUID,
|
||||
@@ -86,20 +86,15 @@ async def create_ae(
|
||||
return data
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=list[AERead],
|
||||
dependencies=[Depends(require_api_permission("risk_issues:read"))],
|
||||
)
|
||||
async def list_ae(
|
||||
async def _list_ae_records(
|
||||
study_id: uuid.UUID,
|
||||
status_filter: str | None = None,
|
||||
seriousness: str | None = None,
|
||||
site_id: uuid.UUID | None = None,
|
||||
subject_id: uuid.UUID | None = None,
|
||||
overdue: bool | None = None,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
status_filter: str | None,
|
||||
seriousness: str | None,
|
||||
site_id: uuid.UUID | None,
|
||||
subject_id: uuid.UUID | None,
|
||||
overdue: bool | None,
|
||||
db: AsyncSession,
|
||||
current_user,
|
||||
) -> list[AERead]:
|
||||
await _ensure_study_exists(db, study_id)
|
||||
cra_scope = await get_cra_site_scope(db, study_id, current_user)
|
||||
@@ -124,10 +119,64 @@ async def list_ae(
|
||||
return result
|
||||
|
||||
|
||||
@router.get(
|
||||
"/summary",
|
||||
response_model=list[AERead],
|
||||
dependencies=[Depends(require_api_permission("subject_aes:list"))],
|
||||
)
|
||||
async def list_risk_issue_ae(
|
||||
study_id: uuid.UUID,
|
||||
status_filter: str | None = None,
|
||||
seriousness: str | None = None,
|
||||
site_id: uuid.UUID | None = None,
|
||||
subject_id: uuid.UUID | None = None,
|
||||
overdue: bool | None = None,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> list[AERead]:
|
||||
return await _list_ae_records(
|
||||
study_id,
|
||||
status_filter,
|
||||
seriousness,
|
||||
site_id,
|
||||
subject_id,
|
||||
overdue,
|
||||
db,
|
||||
current_user,
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=list[AERead],
|
||||
dependencies=[Depends(require_api_permission("subject_aes:list"))],
|
||||
)
|
||||
async def list_ae(
|
||||
study_id: uuid.UUID,
|
||||
status_filter: str | None = None,
|
||||
seriousness: str | None = None,
|
||||
site_id: uuid.UUID | None = None,
|
||||
subject_id: uuid.UUID | None = None,
|
||||
overdue: bool | None = None,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> list[AERead]:
|
||||
return await _list_ae_records(
|
||||
study_id,
|
||||
status_filter,
|
||||
seriousness,
|
||||
site_id,
|
||||
subject_id,
|
||||
overdue,
|
||||
db,
|
||||
current_user,
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{ae_id}",
|
||||
response_model=AERead,
|
||||
dependencies=[Depends(require_api_permission("risk_issues:read"))],
|
||||
dependencies=[Depends(require_api_permission("subject_aes:read"))],
|
||||
)
|
||||
async def get_ae(
|
||||
study_id: uuid.UUID,
|
||||
@@ -155,7 +204,7 @@ async def get_ae(
|
||||
@router.patch(
|
||||
"/{ae_id}",
|
||||
response_model=AERead,
|
||||
dependencies=[Depends(require_api_permission("risk_issues:update")), Depends(require_study_not_locked())],
|
||||
dependencies=[Depends(require_api_permission("subject_aes:update")), Depends(require_study_not_locked())],
|
||||
)
|
||||
async def update_ae(
|
||||
study_id: uuid.UUID,
|
||||
@@ -210,7 +259,7 @@ async def update_ae(
|
||||
@router.delete(
|
||||
"/{ae_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(require_api_permission("risk_issues:delete")), Depends(require_study_not_locked())],
|
||||
dependencies=[Depends(require_api_permission("subject_aes:delete")), Depends(require_study_not_locked())],
|
||||
)
|
||||
async def delete_ae(
|
||||
study_id: uuid.UUID,
|
||||
|
||||
@@ -70,7 +70,7 @@ async def list_lost_visits(
|
||||
|
||||
@router.get("/center-summary", response_model=list[CenterSummaryItem], dependencies=[
|
||||
Depends(require_study_member()),
|
||||
Depends(require_api_permission("dashboard:read"))
|
||||
Depends(require_api_permission("project_overview:read"))
|
||||
])
|
||||
async def get_center_summary(
|
||||
study_id: uuid.UUID,
|
||||
@@ -91,7 +91,7 @@ async def get_center_summary(
|
||||
|
||||
stage_order = [
|
||||
("institution_initiation_status", "机构立项"),
|
||||
("ethics_status", "伦理审批"),
|
||||
("ethics_status", "伦理记录"),
|
||||
("contract_sign_status", "合同签署"),
|
||||
("startup_status", "启动"),
|
||||
("enrollment_status", "入组"),
|
||||
|
||||
@@ -19,9 +19,9 @@ PROJECT_ROLE_RANK = {
|
||||
"ADMIN": 100,
|
||||
"PM": 80,
|
||||
"PV": 50,
|
||||
"MEDICAL_REVIEW": 50,
|
||||
"QA": 50,
|
||||
"CRA": 40,
|
||||
"IMP": 40,
|
||||
"CTA": 40,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ def _normalize_file_rows(filename: str, content: bytes) -> list[dict[str, object
|
||||
@router.get(
|
||||
"/issues",
|
||||
response_model=list[MonitoringVisitIssueRead],
|
||||
dependencies=[Depends(require_api_permission("monitoring_audit:read"))],
|
||||
dependencies=[Depends(require_api_permission("monitoring_issues:list"))],
|
||||
)
|
||||
async def list_monitoring_visit_issues(
|
||||
study_id: uuid.UUID,
|
||||
@@ -382,7 +382,7 @@ async def list_monitoring_visit_issues(
|
||||
"/issues",
|
||||
response_model=MonitoringVisitIssueRead,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(require_api_permission("monitoring_audit:create")), Depends(require_study_not_locked())],
|
||||
dependencies=[Depends(require_api_permission("monitoring_issues:create")), Depends(require_study_not_locked())],
|
||||
)
|
||||
async def create_monitoring_visit_issue(
|
||||
study_id: uuid.UUID,
|
||||
@@ -421,7 +421,7 @@ async def create_monitoring_visit_issue(
|
||||
@router.get(
|
||||
"/issues/export",
|
||||
response_class=StreamingResponse,
|
||||
dependencies=[Depends(require_api_permission("monitoring_audit:read"))],
|
||||
dependencies=[Depends(require_api_permission("monitoring_issues:list"))],
|
||||
)
|
||||
async def export_monitoring_visit_issues(
|
||||
study_id: uuid.UUID,
|
||||
@@ -553,7 +553,7 @@ async def export_monitoring_visit_issues(
|
||||
@router.get(
|
||||
"/issues/{issue_id}",
|
||||
response_model=MonitoringVisitIssueRead,
|
||||
dependencies=[Depends(require_api_permission("monitoring_audit:read"))],
|
||||
dependencies=[Depends(require_api_permission("monitoring_issues:read"))],
|
||||
)
|
||||
async def get_monitoring_visit_issue(
|
||||
study_id: uuid.UUID,
|
||||
@@ -570,7 +570,7 @@ async def get_monitoring_visit_issue(
|
||||
@router.patch(
|
||||
"/issues/{issue_id}",
|
||||
response_model=MonitoringVisitIssueRead,
|
||||
dependencies=[Depends(require_api_permission("monitoring_audit:update")), Depends(require_study_not_locked())],
|
||||
dependencies=[Depends(require_api_permission("monitoring_issues:update")), Depends(require_study_not_locked())],
|
||||
)
|
||||
async def update_monitoring_visit_issue(
|
||||
study_id: uuid.UUID,
|
||||
@@ -625,7 +625,7 @@ async def update_monitoring_visit_issue(
|
||||
@router.delete(
|
||||
"/issues/{issue_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(require_api_permission("monitoring_audit:delete")), Depends(require_study_not_locked())],
|
||||
dependencies=[Depends(require_api_permission("monitoring_issues:delete")), Depends(require_study_not_locked())],
|
||||
)
|
||||
async def delete_monitoring_visit_issue(
|
||||
study_id: uuid.UUID,
|
||||
@@ -655,7 +655,7 @@ async def delete_monitoring_visit_issue(
|
||||
@router.post(
|
||||
"/issues/import",
|
||||
response_model=MonitoringVisitIssueImportSummary,
|
||||
dependencies=[Depends(require_api_permission("monitoring_audit:create")), Depends(require_study_not_locked())],
|
||||
dependencies=[Depends(require_api_permission("monitoring_issues:create")), Depends(require_study_not_locked())],
|
||||
)
|
||||
async def import_monitoring_visit_issues(
|
||||
study_id: uuid.UUID,
|
||||
|
||||
@@ -25,7 +25,7 @@ async def get_project_overview(
|
||||
|
||||
返回项目各中心的进度情况,包括:
|
||||
- 机构立项状态
|
||||
- 伦理审批状态
|
||||
- 伦理记录状态
|
||||
- 合同签署状态
|
||||
- 启动会状态
|
||||
- 入组进度
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.v1 import auth, users, admin_users, studies, sites, members, attachments, audit_logs, dashboard, subjects, visits, aes, finance_dashboard, finance_contracts, fees_contracts, fees_attachments, drug_shipments, material_equipments, project_milestones, startup, knowledge_notes, subject_histories, subject_pds, study_subject_pds, faq_categories, faqs, documents, overview, notifications, monitoring_visit_issues, api_permissions, permission_monitoring, permission_templates, system_permissions, study_active_roles
|
||||
from app.api.v1 import auth, users, admin_users, studies, sites, members, attachments, audit_logs, dashboard, subjects, visits, aes, finance_dashboard, fees_contracts, drug_shipments, material_equipments, project_milestones, startup, precautions, subject_histories, subject_pds, study_subject_pds, faq_categories, faqs, documents, etmf, overview, notifications, monitoring_visit_issues, api_permissions, permission_monitoring, permission_templates, system_permissions, study_active_roles
|
||||
|
||||
|
||||
api_router = APIRouter()
|
||||
@@ -22,14 +22,12 @@ api_router.include_router(subjects.router, prefix="/studies/{study_id}/subjects"
|
||||
api_router.include_router(visits.router, prefix="/studies/{study_id}/subjects/{subject_id}/visits", tags=["visits"])
|
||||
api_router.include_router(aes.router, prefix="/studies/{study_id}/aes", tags=["aes"])
|
||||
api_router.include_router(finance_dashboard.router, prefix="/studies/{study_id}/finance", tags=["finance"])
|
||||
api_router.include_router(finance_contracts.router, prefix="/studies/{study_id}/finance", tags=["finance-contracts"])
|
||||
api_router.include_router(fees_contracts.router, prefix="/fees", tags=["fees-contracts"])
|
||||
api_router.include_router(fees_attachments.router, prefix="/fees", tags=["fees-attachments"])
|
||||
api_router.include_router(drug_shipments.router, prefix="/studies/{study_id}/drug", tags=["drug-shipments"])
|
||||
api_router.include_router(material_equipments.router, prefix="/studies/{study_id}/materials", tags=["material-equipments"])
|
||||
api_router.include_router(project_milestones.router, prefix="/studies/{study_id}/project", tags=["project-milestones"])
|
||||
api_router.include_router(startup.router, prefix="/studies/{study_id}/startup", tags=["startup"])
|
||||
api_router.include_router(knowledge_notes.router, prefix="/studies/{study_id}/knowledge", tags=["knowledge"])
|
||||
api_router.include_router(precautions.router, prefix="/studies/{study_id}/shared-library", tags=["precautions"])
|
||||
api_router.include_router(monitoring_visit_issues.router, prefix="/studies/{study_id}/monitoring", tags=["monitoring-visit-issues"])
|
||||
api_router.include_router(subject_histories.router, prefix="/studies/{study_id}/subjects/{subject_id}", tags=["subject-histories"])
|
||||
api_router.include_router(subject_pds.router, prefix="/studies/{study_id}/subjects/{subject_id}", tags=["subject-pds"])
|
||||
@@ -37,6 +35,7 @@ api_router.include_router(study_subject_pds.router, prefix="/studies/{study_id}"
|
||||
api_router.include_router(faq_categories.router, prefix="/faqs/categories", tags=["faq-categories"])
|
||||
api_router.include_router(faqs.router, prefix="/faqs/items", tags=["faqs"])
|
||||
api_router.include_router(documents.router, prefix="", tags=["documents"])
|
||||
api_router.include_router(etmf.router, prefix="/etmf", tags=["etmf"])
|
||||
api_router.include_router(permission_monitoring.router)
|
||||
api_router.include_router(permission_templates.router)
|
||||
api_router.include_router(permission_templates.study_router, prefix="/studies/{study_id}", tags=["permission-templates"])
|
||||
|
||||
@@ -79,7 +79,7 @@ async def create_site(
|
||||
module="sites",
|
||||
action="read",
|
||||
description="查询中心列表",
|
||||
default_roles=["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP"],
|
||||
default_roles=["PM", "CRA", "PV", "QA", "CTA"],
|
||||
)
|
||||
async def list_sites(
|
||||
study_id: uuid.UUID,
|
||||
|
||||
@@ -53,7 +53,7 @@ async def _ensure_site_active(db: AsyncSession, site_id: uuid.UUID | None):
|
||||
"/feasibility",
|
||||
response_model=StartupFeasibilityRead,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(require_api_permission("ethics:create"))],
|
||||
dependencies=[Depends(require_api_permission("startup_initiation:create"))],
|
||||
)
|
||||
async def create_feasibility(
|
||||
study_id: uuid.UUID,
|
||||
@@ -83,7 +83,7 @@ async def create_feasibility(
|
||||
@router.get(
|
||||
"/feasibility",
|
||||
response_model=list[StartupFeasibilityRead],
|
||||
dependencies=[Depends(require_api_permission("ethics:read"))],
|
||||
dependencies=[Depends(require_api_permission("startup_initiation:read"))],
|
||||
)
|
||||
async def list_feasibilities(
|
||||
study_id: uuid.UUID,
|
||||
@@ -102,7 +102,7 @@ async def list_feasibilities(
|
||||
@router.get(
|
||||
"/feasibility/{record_id}",
|
||||
response_model=StartupFeasibilityRead,
|
||||
dependencies=[Depends(require_api_permission("ethics:read"))],
|
||||
dependencies=[Depends(require_api_permission("startup_initiation:read"))],
|
||||
)
|
||||
async def get_feasibility(
|
||||
study_id: uuid.UUID,
|
||||
@@ -123,7 +123,7 @@ async def get_feasibility(
|
||||
@router.patch(
|
||||
"/feasibility/{record_id}",
|
||||
response_model=StartupFeasibilityRead,
|
||||
dependencies=[Depends(require_api_permission("ethics:update"))],
|
||||
dependencies=[Depends(require_api_permission("startup_initiation:update"))],
|
||||
)
|
||||
async def update_feasibility(
|
||||
study_id: uuid.UUID,
|
||||
@@ -157,7 +157,7 @@ async def update_feasibility(
|
||||
@router.delete(
|
||||
"/feasibility/{record_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(require_api_permission("ethics:delete"))],
|
||||
dependencies=[Depends(require_api_permission("startup_initiation:delete"))],
|
||||
)
|
||||
async def delete_feasibility(
|
||||
study_id: uuid.UUID,
|
||||
@@ -190,7 +190,7 @@ async def delete_feasibility(
|
||||
"/ethics",
|
||||
response_model=StartupEthicsRead,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(require_api_permission("ethics:create"))],
|
||||
dependencies=[Depends(require_api_permission("startup_ethics:create"))],
|
||||
)
|
||||
async def create_ethics(
|
||||
study_id: uuid.UUID,
|
||||
@@ -220,7 +220,7 @@ async def create_ethics(
|
||||
@router.get(
|
||||
"/ethics",
|
||||
response_model=list[StartupEthicsRead],
|
||||
dependencies=[Depends(require_api_permission("ethics:read"))],
|
||||
dependencies=[Depends(require_api_permission("startup_ethics:read"))],
|
||||
)
|
||||
async def list_ethics(
|
||||
study_id: uuid.UUID,
|
||||
@@ -239,7 +239,7 @@ async def list_ethics(
|
||||
@router.get(
|
||||
"/ethics/{record_id}",
|
||||
response_model=StartupEthicsRead,
|
||||
dependencies=[Depends(require_api_permission("ethics:read"))],
|
||||
dependencies=[Depends(require_api_permission("startup_ethics:read"))],
|
||||
)
|
||||
async def get_ethics(
|
||||
study_id: uuid.UUID,
|
||||
@@ -260,7 +260,7 @@ async def get_ethics(
|
||||
@router.patch(
|
||||
"/ethics/{record_id}",
|
||||
response_model=StartupEthicsRead,
|
||||
dependencies=[Depends(require_api_permission("ethics:update"))],
|
||||
dependencies=[Depends(require_api_permission("startup_ethics:update"))],
|
||||
)
|
||||
async def update_ethics(
|
||||
study_id: uuid.UUID,
|
||||
@@ -294,7 +294,7 @@ async def update_ethics(
|
||||
@router.delete(
|
||||
"/ethics/{record_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(require_api_permission("ethics:delete"))],
|
||||
dependencies=[Depends(require_api_permission("startup_ethics:delete"))],
|
||||
)
|
||||
async def delete_ethics(
|
||||
study_id: uuid.UUID,
|
||||
|
||||
@@ -3,7 +3,7 @@ import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, require_study_member
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, require_api_permission
|
||||
from app.crud import study as study_crud
|
||||
from app.crud import subject_pd as subject_pd_crud
|
||||
from app.schemas.subject_pd import SubjectPdSummaryRead
|
||||
@@ -21,7 +21,7 @@ async def _ensure_study_exists(db: AsyncSession, study_id: uuid.UUID):
|
||||
@router.get(
|
||||
"/subject-pds",
|
||||
response_model=list[SubjectPdSummaryRead],
|
||||
dependencies=[Depends(require_study_member())],
|
||||
dependencies=[Depends(require_api_permission("subject_pds:list"))],
|
||||
)
|
||||
async def list_study_subject_pds(
|
||||
study_id: uuid.UUID,
|
||||
|
||||
@@ -66,7 +66,7 @@ async def create_history(
|
||||
@router.get(
|
||||
"/histories",
|
||||
response_model=list[SubjectHistoryRead],
|
||||
dependencies=[Depends(require_api_permission("subject_histories:read"))],
|
||||
dependencies=[Depends(require_api_permission("subject_histories:list"))],
|
||||
)
|
||||
async def list_histories(
|
||||
study_id: uuid.UUID,
|
||||
|
||||
@@ -64,7 +64,7 @@ def _normalize_choice(value: str | None, allowed: set[str], field_name: str) ->
|
||||
@router.get(
|
||||
"/pds",
|
||||
response_model=list[SubjectPdRead],
|
||||
dependencies=[Depends(require_api_permission("subject_pds:read"))],
|
||||
dependencies=[Depends(require_api_permission("subject_pds:list"))],
|
||||
)
|
||||
async def list_subject_pds(
|
||||
study_id: uuid.UUID,
|
||||
|
||||
@@ -33,7 +33,7 @@ async def _ensure_subject_active(db: AsyncSession, subject) -> None:
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=list[VisitRead],
|
||||
dependencies=[Depends(require_api_permission("visits:read"))],
|
||||
dependencies=[Depends(require_api_permission("visits:list"))],
|
||||
)
|
||||
async def list_visits(
|
||||
study_id: uuid.UUID,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.models.site import Site
|
||||
from app.models.startup_feasibility import StartupFeasibility
|
||||
from app.models.startup_ethics import StartupEthics
|
||||
from app.models.finance_contract import FinanceContract
|
||||
from app.models.contract_fee import ContractFee
|
||||
from app.models.kickoff_meeting import KickoffMeeting
|
||||
from app.models.subject import Subject
|
||||
|
||||
@@ -59,7 +59,7 @@ async def get_project_overview(db: AsyncSession, study_id: uuid.UUID) -> Dict:
|
||||
institution_status = "COMPLETED" if feas.get("approved_date") else "IN_PROGRESS" if feas.get("submit_date") else "NOT_STARTED"
|
||||
institution_completed = feas.get("approved_date")
|
||||
|
||||
# 伦理审批状态 - 依赖机构立项完成
|
||||
# 伦理记录状态 - 依赖机构立项完成
|
||||
eth = ethics_data.get(site_id_str, {})
|
||||
if institution_status == "COMPLETED":
|
||||
ethics_status = "COMPLETED" if eth.get("approved_date") else "IN_PROGRESS" if eth.get("submit_date") else "NOT_STARTED"
|
||||
@@ -68,7 +68,7 @@ async def get_project_overview(db: AsyncSession, study_id: uuid.UUID) -> Dict:
|
||||
ethics_status = "NOT_STARTED"
|
||||
ethics_completed = eth.get("approved_date") if institution_status == "COMPLETED" else None
|
||||
|
||||
# 合同签署状态 - 依赖伦理审批完成
|
||||
# 合同签署状态 - 依赖伦理记录完成
|
||||
contract = contract_data.get(site_id_str, {})
|
||||
if ethics_status == "COMPLETED":
|
||||
contract_status = "COMPLETED" if contract.get("signed_date") else "NOT_STARTED"
|
||||
@@ -202,28 +202,22 @@ async def _get_ethics_data(db: AsyncSession, study_id: uuid.UUID, site_ids: List
|
||||
|
||||
async def _get_contract_data(db: AsyncSession, study_id: uuid.UUID, site_ids: List[uuid.UUID]) -> Dict:
|
||||
"""获取各中心的合同签署数据"""
|
||||
# FinanceContract 通过 site_name 关联,需要先获取site名称映射
|
||||
stmt_sites = select(Site.id, Site.name).where(Site.id.in_(site_ids))
|
||||
result = await db.execute(stmt_sites)
|
||||
site_name_map = {row.name: str(row.id) for row in result.all()}
|
||||
|
||||
stmt = select(
|
||||
FinanceContract.site_name,
|
||||
func.min(FinanceContract.signed_date).label("signed_date")
|
||||
ContractFee.center_id,
|
||||
func.min(ContractFee.signed_date).label("signed_date")
|
||||
).where(
|
||||
FinanceContract.study_id == study_id
|
||||
).group_by(FinanceContract.site_name)
|
||||
ContractFee.project_id == study_id,
|
||||
ContractFee.center_id.in_(site_ids),
|
||||
).group_by(ContractFee.center_id)
|
||||
|
||||
result = await db.execute(stmt)
|
||||
rows = result.all()
|
||||
|
||||
data = {}
|
||||
for row in rows:
|
||||
site_id_str = site_name_map.get(row.site_name)
|
||||
if site_id_str:
|
||||
data[site_id_str] = {
|
||||
"signed_date": row.signed_date.isoformat() if row.signed_date else None,
|
||||
}
|
||||
data[str(row.center_id)] = {
|
||||
"signed_date": row.signed_date.isoformat() if row.signed_date else None,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
+11
-25
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Sequence
|
||||
@@ -16,9 +18,8 @@ from app.models.distribution import Distribution
|
||||
from app.models.drug_shipment import DrugShipment
|
||||
from app.models.fee_attachment import FeeAttachment
|
||||
from app.models.finance import FinanceItem
|
||||
from app.models.finance_contract import FinanceContract
|
||||
from app.models.kickoff_meeting import KickoffMeeting
|
||||
from app.models.knowledge_note import KnowledgeNote
|
||||
from app.models.precaution import Precaution
|
||||
from app.models.milestone import Milestone
|
||||
from app.models.monitoring_visit_issue import MonitoringVisitIssue
|
||||
from app.models.site import Site
|
||||
@@ -177,19 +178,11 @@ async def delete_site_and_related(db: AsyncSession, site: Site) -> None:
|
||||
)
|
||||
)
|
||||
).scalars().all()
|
||||
finance_contract_ids = (
|
||||
precaution_ids = (
|
||||
await db.execute(
|
||||
select(FinanceContract.id).where(
|
||||
FinanceContract.study_id == study_id,
|
||||
FinanceContract.site_name == site_name,
|
||||
)
|
||||
)
|
||||
).scalars().all()
|
||||
knowledge_note_ids = (
|
||||
await db.execute(
|
||||
select(KnowledgeNote.id).where(
|
||||
KnowledgeNote.study_id == study_id,
|
||||
KnowledgeNote.site_name == site_name,
|
||||
select(Precaution.id).where(
|
||||
Precaution.study_id == study_id,
|
||||
Precaution.site_name == site_name,
|
||||
)
|
||||
)
|
||||
).scalars().all()
|
||||
@@ -220,8 +213,7 @@ async def delete_site_and_related(db: AsyncSession, site: Site) -> None:
|
||||
"startup_kickoff_signin": kickoff_ids,
|
||||
"startup_kickoff_ppt": kickoff_ids,
|
||||
"training_authorization": training_ids,
|
||||
"finance_contract": finance_contract_ids,
|
||||
"knowledge_note": knowledge_note_ids,
|
||||
"precaution": precaution_ids,
|
||||
"drug_shipment": drug_shipment_ids,
|
||||
}
|
||||
attachment_paths: list[str] = []
|
||||
@@ -311,15 +303,9 @@ async def delete_site_and_related(db: AsyncSession, site: Site) -> None:
|
||||
)
|
||||
)
|
||||
await db.execute(
|
||||
delete(FinanceContract).where(
|
||||
FinanceContract.study_id == study_id,
|
||||
FinanceContract.site_name == site_name,
|
||||
)
|
||||
)
|
||||
await db.execute(
|
||||
delete(KnowledgeNote).where(
|
||||
KnowledgeNote.study_id == study_id,
|
||||
KnowledgeNote.site_name == site_name,
|
||||
delete(Precaution).where(
|
||||
Precaution.study_id == study_id,
|
||||
Precaution.site_name == site_name,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Sequence
|
||||
|
||||
@@ -99,7 +101,6 @@ async def delete(db: AsyncSession, study_id: uuid.UUID) -> None:
|
||||
from app.models.visit import Visit
|
||||
from app.models.ae import AdverseEvent
|
||||
from app.models.finance import FinanceItem
|
||||
from app.models.finance_contract import FinanceContract
|
||||
from app.models.contract_fee import ContractFee
|
||||
from app.models.contract_fee_payment import ContractFeePayment
|
||||
from app.models.milestone import Milestone
|
||||
@@ -111,7 +112,7 @@ async def delete(db: AsyncSession, study_id: uuid.UUID) -> None:
|
||||
from app.models.startup_feasibility import StartupFeasibility
|
||||
from app.models.startup_ethics import StartupEthics
|
||||
from app.models.kickoff_meeting import KickoffMeeting
|
||||
from app.models.knowledge_note import KnowledgeNote
|
||||
from app.models.precaution import Precaution
|
||||
from app.models.faq_category import FaqCategory
|
||||
from app.models.faq_item import FaqItem
|
||||
from app.models.faq_reply import FaqReply
|
||||
@@ -129,8 +130,8 @@ async def delete(db: AsyncSession, study_id: uuid.UUID) -> None:
|
||||
await db.execute(sa_delete(FaqItem).where(FaqItem.study_id == study_id))
|
||||
await db.execute(sa_delete(FaqCategory).where(FaqCategory.study_id == study_id))
|
||||
|
||||
# 3. 删除知识库
|
||||
await db.execute(sa_delete(KnowledgeNote).where(KnowledgeNote.study_id == study_id))
|
||||
# 3. 删除注意事项
|
||||
await db.execute(sa_delete(Precaution).where(Precaution.study_id == study_id))
|
||||
|
||||
# 4. 删除启动相关
|
||||
await db.execute(sa_delete(KickoffMeeting).where(KickoffMeeting.study_id == study_id))
|
||||
@@ -158,7 +159,6 @@ async def delete(db: AsyncSession, study_id: uuid.UUID) -> None:
|
||||
)
|
||||
)
|
||||
await db.execute(sa_delete(ContractFee).where(ContractFee.project_id == study_id))
|
||||
await db.execute(sa_delete(FinanceContract).where(FinanceContract.study_id == study_id))
|
||||
await db.execute(sa_delete(FinanceItem).where(FinanceItem.study_id == study_id))
|
||||
|
||||
# 10. 删除不良事件
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Sequence
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from app.models.site import Site # noqa: F401
|
||||
from app.models.study_member import StudyMember # noqa: F401
|
||||
from app.models.attachment import Attachment # noqa: F401
|
||||
from app.models.audit_log import AuditLog # noqa: F401
|
||||
from app.models.etmf import EtmfNode # noqa: F401
|
||||
from app.models.document import Document # noqa: F401
|
||||
from app.models.document_version import DocumentVersion # noqa: F401
|
||||
from app.models.distribution import Distribution # noqa: F401
|
||||
@@ -17,7 +18,6 @@ from app.models.subject_pd import SubjectPd # noqa: F401
|
||||
from app.models.visit import Visit # noqa: F401
|
||||
from app.models.ae import AdverseEvent # noqa: F401
|
||||
from app.models.finance import FinanceItem # noqa: F401
|
||||
from app.models.finance_contract import FinanceContract # noqa: F401
|
||||
from app.models.contract_fee import ContractFee # noqa: F401
|
||||
from app.models.contract_fee_payment import ContractFeePayment # noqa: F401
|
||||
from app.models.fee_attachment import FeeAttachment # noqa: F401
|
||||
@@ -28,7 +28,7 @@ from app.models.startup_feasibility import StartupFeasibility # noqa: F401
|
||||
from app.models.startup_ethics import StartupEthics # noqa: F401
|
||||
from app.models.kickoff_meeting import KickoffMeeting # noqa: F401
|
||||
from app.models.training_authorization import TrainingAuthorization # noqa: F401
|
||||
from app.models.knowledge_note import KnowledgeNote # noqa: F401
|
||||
from app.models.precaution import Precaution # noqa: F401
|
||||
from app.models.subject_history import SubjectHistory # noqa: F401
|
||||
from app.models.faq_category import FaqCategory # noqa: F401
|
||||
from app.models.faq_item import FaqItem # noqa: F401
|
||||
|
||||
@@ -18,7 +18,7 @@ class KickoffMeeting(Base):
|
||||
study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False)
|
||||
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)
|
||||
attendees: Mapped[Optional[list[str]]] = mapped_column(JSON, 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(
|
||||
|
||||
@@ -22,8 +22,8 @@ class StudySetupConfig(Base):
|
||||
version: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
||||
config: Mapped[dict] = mapped_column(JSONB_TYPE, nullable=False)
|
||||
publish_status: Mapped[str] = mapped_column(default="DRAFT", nullable=False)
|
||||
published_config: Mapped[dict | None] = mapped_column(JSONB_TYPE, nullable=True)
|
||||
published_project_snapshot: Mapped[dict | None] = mapped_column(JSONB_TYPE, nullable=True)
|
||||
published_config: Mapped[Optional[dict]] = mapped_column(JSONB_TYPE, nullable=True)
|
||||
published_project_snapshot: Mapped[Optional[dict]] = mapped_column(JSONB_TYPE, nullable=True)
|
||||
current_branch_name: Mapped[str] = mapped_column(nullable=False, default="main")
|
||||
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
|
||||
|
||||
@@ -38,7 +38,7 @@ class StudySetupConfigVersion(Base):
|
||||
UUID(as_uuid=True), ForeignKey("study_setup_config_versions.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
config: Mapped[dict] = mapped_column(JSONB_TYPE, nullable=False)
|
||||
published_project_snapshot: Mapped[dict | None] = mapped_column(JSONB_TYPE, nullable=True)
|
||||
published_project_snapshot: Mapped[Optional[dict]] = mapped_column(JSONB_TYPE, 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,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
Reference in New Issue
Block a user