管理后台前后端逻辑、UI美化

This commit is contained in:
Cheng Zhou
2026-01-16 13:50:08 +08:00
parent 05c1f9579a
commit 7fdcfdaadd
82 changed files with 3210 additions and 549 deletions
+132 -3
View File
@@ -1,7 +1,7 @@
import uuid
from typing import Sequence
from sqlalchemy import select, update as sa_update
from sqlalchemy import delete as sa_delete, select, update as sa_update
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.study import Study
@@ -9,10 +9,22 @@ from app.schemas.study import StudyCreate, StudyUpdate
async def create(db: AsyncSession, study_in: StudyCreate, *, created_by: uuid.UUID | None = None) -> Study:
import uuid as uuid_lib # Import uuid to generate unique code if needed
code = study_in.code
if not code:
# Generate a unique code if not provided
# Use first 8 chars of a new UUID, uppercase
code = f"P-{str(uuid_lib.uuid4())[:8].upper()}"
study = Study(
code=study_in.code,
code=code,
name=study_in.name,
sponsor=study_in.sponsor,
# sponsor is removed from schema, but model has it. Set to None/empty or keep existing value if schema had it (which it doesn't now)
# If we removed it from schema, study_in.sponsor will fail if we try to access it via .sponsor attribute directly if it's not in the pydantic model anymore.
# Since we removed `sponsor` field from StudyCreate, `study_in` object won't have `sponsor` attribute unless we access dict or use default.
# However, the Study MODEL still has sponsor. We should set it to None.
sponsor=None,
protocol_no=study_in.protocol_no,
phase=study_in.phase,
status=study_in.status,
@@ -73,3 +85,120 @@ async def list_studies_for_user(
)
result = await db.execute(stmt)
return result.scalars().all()
async def delete(db: AsyncSession, study_id: uuid.UUID) -> None:
"""硬删除项目及其所有关联数据"""
# 导入所有关联的模型
from app.models.study_member import StudyMember
from app.models.audit_log import AuditLog
from app.models.site import Site
from app.models.subject import Subject
from app.models.subject_history import SubjectHistory
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.finance_special import FinanceSpecial
from app.models.contract_fee import ContractFee
from app.models.special_expense import SpecialExpense
from app.models.milestone import Milestone
from app.models.document import Document
from app.models.attachment import Attachment
from app.models.drug_shipment import DrugShipment
from app.models.training_authorization import TrainingAuthorization
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.faq_category import FaqCategory
from app.models.faq_item import FaqItem
from app.models.faq_reply import FaqReply
from app.models.workflow_template import WorkflowTemplate
# 按依赖关系顺序删除关联数据
# 1. 删除审计日志
await db.execute(sa_delete(AuditLog).where(AuditLog.study_id == study_id))
# 2. 删除FAQ相关(可能有依赖关系)
await db.execute(sa_delete(FaqReply).where(FaqReply.study_id == study_id))
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))
# 4. 删除工作流模板
await db.execute(sa_delete(WorkflowTemplate).where(WorkflowTemplate.trial_id == study_id))
# 5. 删除启动相关
await db.execute(sa_delete(KickoffMeeting).where(KickoffMeeting.study_id == study_id))
await db.execute(sa_delete(StartupEthics).where(StartupEthics.study_id == study_id))
await db.execute(sa_delete(StartupFeasibility).where(StartupFeasibility.study_id == study_id))
await db.execute(sa_delete(TrainingAuthorization).where(TrainingAuthorization.study_id == study_id))
# 6. 删除药物配送
await db.execute(sa_delete(DrugShipment).where(DrugShipment.study_id == study_id))
# 7. 删除附件和文档
await db.execute(sa_delete(Attachment).where(Attachment.study_id == study_id))
await db.execute(sa_delete(Document).where(Document.trial_id == study_id))
# 8. 删除里程碑
await db.execute(sa_delete(Milestone).where(Milestone.study_id == study_id))
# 9. 删除财务相关
await db.execute(sa_delete(SpecialExpense).where(SpecialExpense.project_id == study_id))
await db.execute(sa_delete(ContractFee).where(ContractFee.project_id == study_id))
await db.execute(sa_delete(FinanceSpecial).where(FinanceSpecial.study_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. 删除不良事件
await db.execute(sa_delete(AdverseEvent).where(AdverseEvent.study_id == study_id))
# 11. 删除访视
await db.execute(sa_delete(Visit).where(Visit.study_id == study_id))
# 12. 删除受试者相关
await db.execute(sa_delete(SubjectHistory).where(SubjectHistory.study_id == study_id))
await db.execute(sa_delete(Subject).where(Subject.study_id == study_id))
# 13. 删除站点
await db.execute(sa_delete(Site).where(Site.study_id == study_id))
# 14. 删除成员
await db.execute(sa_delete(StudyMember).where(StudyMember.study_id == study_id))
# 15. 最后删除项目本身
await db.execute(sa_delete(Study).where(Study.id == study_id))
await db.commit()
async def lock(db: AsyncSession, study_id: uuid.UUID) -> Study | None:
"""锁定项目"""
await db.execute(
sa_update(Study)
.where(Study.id == study_id)
.values(is_locked=True)
)
await db.commit()
return await get(db, study_id)
async def unlock(db: AsyncSession, study_id: uuid.UUID) -> Study | None:
"""解锁项目"""
await db.execute(
sa_update(Study)
.where(Study.id == study_id)
.values(is_locked=False)
)
await db.commit()
return await get(db, study_id)
async def is_locked(db: AsyncSession, study_id: uuid.UUID) -> bool:
"""检查项目是否已锁定"""
study = await get(db, study_id)
return study.is_locked if study else False