121 lines
3.4 KiB
Python
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()
|