Files
ctms/backend/app/crud/subject_pd.py
T
2026-02-28 14:33:57 +08:00

121 lines
3.4 KiB
Python

import re
import uuid
from typing import Sequence
from sqlalchemy import select, update as sa_update
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.subject import Subject
from app.models.subject_pd import SubjectPd
from app.schemas.subject_pd import SubjectPdCreate, SubjectPdUpdate
_PD_NO_PATTERN = re.compile(r"PD(\d+)$", re.IGNORECASE)
def _extract_pd_sequence(pd_no: str | None) -> int:
if not pd_no:
return 0
matched = _PD_NO_PATTERN.search(str(pd_no).strip())
if not matched:
return 0
try:
return int(matched.group(1))
except ValueError:
return 0
async def _next_pd_no(db: AsyncSession, study_id: uuid.UUID) -> str:
rows = await db.execute(select(SubjectPd.pd_no).where(SubjectPd.study_id == study_id))
max_seq = 0
for (pd_no,) in rows.all():
max_seq = max(max_seq, _extract_pd_sequence(pd_no))
return f"PD{max_seq + 1:04d}"
async def list_subject_pds(
db: AsyncSession,
study_id: uuid.UUID,
subject_id: uuid.UUID,
*,
skip: int = 0,
limit: int = 200,
) -> Sequence[SubjectPd]:
stmt = (
select(SubjectPd)
.where(SubjectPd.study_id == study_id, SubjectPd.subject_id == subject_id)
.order_by(SubjectPd.pd_no.asc(), SubjectPd.created_at.asc())
.offset(skip)
.limit(limit)
)
rows = await db.execute(stmt)
return rows.scalars().all()
async def list_study_subject_pds(
db: AsyncSession,
study_id: uuid.UUID,
*,
skip: int = 0,
limit: int = 500,
site_ids: set[uuid.UUID] | None = None,
) -> list[tuple[SubjectPd, str, uuid.UUID]]:
if site_ids is not None and not site_ids:
return []
stmt = (
select(SubjectPd, Subject.subject_no, Subject.site_id)
.join(Subject, Subject.id == SubjectPd.subject_id)
.where(
SubjectPd.study_id == study_id,
Subject.study_id == study_id,
)
.order_by(SubjectPd.pd_no.asc(), SubjectPd.created_at.asc())
.offset(skip)
.limit(limit)
)
if site_ids is not None:
stmt = stmt.where(Subject.site_id.in_(site_ids))
rows = await db.execute(stmt)
return [(row[0], row[1], row[2]) for row in rows.all()]
async def get_subject_pd(db: AsyncSession, pd_id: uuid.UUID) -> SubjectPd | None:
row = await db.execute(select(SubjectPd).where(SubjectPd.id == pd_id))
return row.scalar_one_or_none()
async def create_subject_pd(
db: AsyncSession,
study_id: uuid.UUID,
pd_in: SubjectPdCreate,
*,
created_by: uuid.UUID | None = None,
) -> SubjectPd:
payload = pd_in.model_dump(exclude_unset=True)
# PD编号统一由后端按项目全局顺序生成,避免被外部覆盖。
payload.pop("pd_no", None)
pd_no = await _next_pd_no(db, study_id)
item = SubjectPd(study_id=study_id, pd_no=pd_no, created_by=created_by, **payload)
db.add(item)
await db.commit()
await db.refresh(item)
return item
async def update_subject_pd(db: AsyncSession, item: SubjectPd, pd_in: SubjectPdUpdate) -> SubjectPd:
update_data = pd_in.model_dump(exclude_unset=True)
if update_data:
await db.execute(
sa_update(SubjectPd)
.where(SubjectPd.id == item.id)
.values(**update_data)
)
await db.commit()
await db.refresh(item)
return item
async def delete_subject_pd(db: AsyncSession, item: SubjectPd) -> None:
await db.delete(item)
await db.commit()