立项配置数据入库、审计日志数据入库、编辑页UI界面美化、设备管理内容补充、审计日志显示优化
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import uuid
|
||||
from typing import Sequence
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.material_equipment import MaterialEquipment
|
||||
from app.schemas.material_equipment import MaterialEquipmentCreate, MaterialEquipmentUpdate
|
||||
|
||||
|
||||
async def create_equipment(
|
||||
db: AsyncSession,
|
||||
study_id: uuid.UUID,
|
||||
equipment_in: MaterialEquipmentCreate,
|
||||
created_by: uuid.UUID | None,
|
||||
) -> MaterialEquipment:
|
||||
item = MaterialEquipment(
|
||||
study_id=study_id,
|
||||
name=equipment_in.name,
|
||||
spec_model=equipment_in.spec_model,
|
||||
unit=equipment_in.unit,
|
||||
brand=equipment_in.brand,
|
||||
origin=equipment_in.origin,
|
||||
production_permit_file_name=equipment_in.production_permit_file_name,
|
||||
tech_index_file_name=equipment_in.tech_index_file_name,
|
||||
need_calibration=equipment_in.need_calibration,
|
||||
calibration_cycle_days=equipment_in.calibration_cycle_days,
|
||||
created_by=created_by,
|
||||
)
|
||||
db.add(item)
|
||||
await db.commit()
|
||||
await db.refresh(item)
|
||||
return item
|
||||
|
||||
|
||||
async def get_equipment(db: AsyncSession, equipment_id: uuid.UUID) -> MaterialEquipment | None:
|
||||
result = await db.execute(select(MaterialEquipment).where(MaterialEquipment.id == equipment_id))
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def list_equipments(
|
||||
db: AsyncSession,
|
||||
study_id: uuid.UUID,
|
||||
name: str | None = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> Sequence[MaterialEquipment]:
|
||||
stmt = select(MaterialEquipment).where(MaterialEquipment.study_id == study_id)
|
||||
if name:
|
||||
stmt = stmt.where(MaterialEquipment.name.ilike(f"%{name}%"))
|
||||
stmt = stmt.order_by(MaterialEquipment.created_at.desc()).offset(skip).limit(limit)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
async def update_equipment(
|
||||
db: AsyncSession,
|
||||
equipment: MaterialEquipment,
|
||||
equipment_in: MaterialEquipmentUpdate,
|
||||
) -> MaterialEquipment:
|
||||
update_data = equipment_in.model_dump(exclude_unset=True)
|
||||
for key, value in update_data.items():
|
||||
setattr(equipment, key, value)
|
||||
await db.commit()
|
||||
await db.refresh(equipment)
|
||||
return equipment
|
||||
|
||||
|
||||
async def delete_equipment(db: AsyncSession, equipment: MaterialEquipment) -> None:
|
||||
await db.delete(equipment)
|
||||
await db.commit()
|
||||
@@ -0,0 +1,54 @@
|
||||
import uuid
|
||||
from typing import Sequence
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.milestone import Milestone
|
||||
from app.schemas.project_milestone import ProjectMilestoneUpdate
|
||||
|
||||
PROJECT_MILESTONE_TYPE = "SETUP_PROJECT_MILESTONE"
|
||||
|
||||
|
||||
async def list_project_milestones(db: AsyncSession, study_id: uuid.UUID) -> Sequence[Milestone]:
|
||||
stmt = (
|
||||
select(Milestone)
|
||||
.where(
|
||||
Milestone.study_id == study_id,
|
||||
Milestone.type == PROJECT_MILESTONE_TYPE,
|
||||
)
|
||||
.order_by(Milestone.planned_date.asc().nulls_last(), Milestone.created_at.asc())
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
async def get_project_milestone(db: AsyncSession, study_id: uuid.UUID, milestone_id: uuid.UUID) -> Milestone | None:
|
||||
result = await db.execute(
|
||||
select(Milestone).where(
|
||||
Milestone.id == milestone_id,
|
||||
Milestone.study_id == study_id,
|
||||
Milestone.type == PROJECT_MILESTONE_TYPE,
|
||||
)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def update_project_milestone(db: AsyncSession, milestone: Milestone, data: ProjectMilestoneUpdate) -> Milestone:
|
||||
milestone.adjusted_start_date = data.adjusted_start_date
|
||||
milestone.adjusted_end_date = data.adjusted_end_date
|
||||
milestone.actual_start_date = data.actual_start_date
|
||||
milestone.actual_end_date = data.actual_end_date
|
||||
milestone.actual_date = data.actual_end_date
|
||||
milestone.notes = data.notes
|
||||
|
||||
if milestone.actual_end_date:
|
||||
milestone.status = "DONE"
|
||||
elif milestone.actual_start_date:
|
||||
milestone.status = "IN_PROGRESS"
|
||||
else:
|
||||
milestone.status = "NOT_STARTED"
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(milestone)
|
||||
return milestone
|
||||
@@ -112,6 +112,7 @@ async def delete(db: AsyncSession, study_id: uuid.UUID) -> None:
|
||||
from app.models.document import Document
|
||||
from app.models.attachment import Attachment
|
||||
from app.models.drug_shipment import DrugShipment
|
||||
from app.models.material_equipment import MaterialEquipment
|
||||
from app.models.training_authorization import TrainingAuthorization
|
||||
from app.models.startup_feasibility import StartupFeasibility
|
||||
from app.models.startup_ethics import StartupEthics
|
||||
@@ -145,6 +146,7 @@ async def delete(db: AsyncSession, study_id: uuid.UUID) -> None:
|
||||
|
||||
# 6. 删除药物配送
|
||||
await db.execute(sa_delete(DrugShipment).where(DrugShipment.study_id == study_id))
|
||||
await db.execute(sa_delete(MaterialEquipment).where(MaterialEquipment.study_id == study_id))
|
||||
|
||||
# 7. 删除附件和文档
|
||||
await db.execute(sa_delete(Attachment).where(Attachment.study_id == study_id))
|
||||
|
||||
@@ -28,6 +28,7 @@ async def upsert(
|
||||
expected_version: int | None,
|
||||
data: StudySetupConfigData | dict,
|
||||
saved_by: uuid.UUID | None,
|
||||
force_draft: bool = False,
|
||||
) -> tuple[StudySetupConfig | None, bool]:
|
||||
existing = await get_by_study(db, study_id)
|
||||
payload = _to_storage(data)
|
||||
@@ -37,7 +38,13 @@ async def upsert(
|
||||
existing.version = existing.version + 1
|
||||
existing.config = payload
|
||||
existing.saved_by = saved_by
|
||||
existing.publish_status = "PUBLISHED" if existing.published_config == payload else "DRAFT"
|
||||
if force_draft:
|
||||
existing.publish_status = "DRAFT"
|
||||
elif existing.publish_status == "DRAFT":
|
||||
# Keep draft state sticky until explicit publish API is called.
|
||||
existing.publish_status = "DRAFT"
|
||||
else:
|
||||
existing.publish_status = "PUBLISHED" if existing.published_config == payload else "DRAFT"
|
||||
existing.updated_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
await db.refresh(existing)
|
||||
|
||||
Reference in New Issue
Block a user