import uuid from typing import Sequence from sqlalchemy import delete as sa_delete, select, update as sa_update from sqlalchemy.ext.asyncio import AsyncSession from app.models.study import Study 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=code, name=study_in.name, # 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, visit_interval_days=study_in.visit_interval_days, visit_total=study_in.visit_total, visit_window_start_offset=study_in.visit_window_start_offset, visit_window_end_offset=study_in.visit_window_end_offset, created_by=created_by, ) db.add(study) await db.commit() await db.refresh(study) return study async def get(db: AsyncSession, study_id: uuid.UUID) -> Study | None: result = await db.execute(select(Study).where(Study.id == study_id)) return result.scalar_one_or_none() async def get_by_code(db: AsyncSession, code: str) -> Study | None: result = await db.execute(select(Study).where(Study.code == code)) return result.scalar_one_or_none() async def update(db: AsyncSession, study: Study, study_in: StudyUpdate) -> Study: update_data = study_in.model_dump(exclude_unset=True) if update_data: await db.execute( sa_update(Study) .where(Study.id == study.id) .values(**update_data) ) await db.commit() await db.refresh(study) return study async def list_studies(db: AsyncSession, skip: int = 0, limit: int = 100) -> Sequence[Study]: result = await db.execute(select(Study).offset(skip).limit(limit)) return result.scalars().all() async def list_studies_for_user( db: AsyncSession, user_id: uuid.UUID, skip: int = 0, limit: int = 100, ) -> Sequence[Study]: from app.models.study_member import StudyMember # local import to avoid cycles stmt = ( select(Study) .join(StudyMember, StudyMember.study_id == Study.id) .where(StudyMember.user_id == user_id, StudyMember.is_active.is_(True)) .offset(skip) .limit(limit) ) 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 # 按依赖关系顺序删除关联数据 # 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(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