文件版本管理-初步优化

This commit is contained in:
Cheng Zhou
2026-01-14 11:35:37 +08:00
parent ef1e67218c
commit f9ef5c109f
46 changed files with 4603 additions and 30 deletions
+214
View File
@@ -0,0 +1,214 @@
import uuid
from typing import Optional
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile, status
from fastapi.responses import FileResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_current_user, get_db_session
from app.crud import document_version as version_crud
from app.schemas.acknowledgement import AcknowledgementCreate, AcknowledgementRead
from app.schemas.common import PaginatedResponse
from app.schemas.distribution import DistributionCreate, DistributionRead
from app.schemas.document import DocumentCreate, DocumentDetail, DocumentSummary
from app.schemas.document_version import DocumentVersionRead, VersionActionRequest, VersionSubmitRequest
from app.schemas.workflow_template import WorkflowTemplateRead
from app.services import document_service
from app.utils.pagination import paginate
router = APIRouter()
@router.post(
"/documents",
response_model=DocumentSummary,
status_code=status.HTTP_201_CREATED,
)
async def create_document(
payload: DocumentCreate,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> DocumentSummary:
doc = await document_service.create_document(db, payload, current_user)
return DocumentSummary.model_validate(doc)
@router.get("/documents", response_model=PaginatedResponse[DocumentSummary])
async def list_documents(
trial_id: uuid.UUID = Query(...),
site_id: Optional[uuid.UUID] = Query(None),
doc_type: Optional[str] = Query(None),
scope_type: Optional[str] = Query(None),
status_value: Optional[str] = Query(None, alias="status"),
skip: int = 0,
limit: int = 100,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> PaginatedResponse[DocumentSummary]:
items = await document_service.list_documents(
db,
trial_id=trial_id,
site_id=site_id,
doc_type=doc_type,
status=status_value,
scope_type=scope_type,
skip=skip,
limit=limit,
current_user=current_user,
)
return paginate(items, total=len(items))
@router.get("/documents/{document_id}", response_model=DocumentDetail)
async def get_document_detail(
document_id: uuid.UUID,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> DocumentDetail:
return await document_service.get_document_detail(db, document_id, current_user)
@router.delete("/documents/{document_id}", response_model=DocumentSummary)
async def delete_document(
document_id: uuid.UUID,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> DocumentSummary:
doc = await document_service.delete_document(db, document_id, current_user)
return DocumentSummary.model_validate(doc)
@router.post(
"/documents/{document_id}/versions",
response_model=DocumentVersionRead,
status_code=status.HTTP_201_CREATED,
)
async def create_version(
document_id: uuid.UUID,
version_no: str = Form(...),
change_summary: Optional[str] = Form(None),
parent_version_id: Optional[uuid.UUID] = Form(None),
file: UploadFile = File(...),
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> DocumentVersionRead:
version = await document_service.create_version(
db,
document_id,
version_no=version_no,
change_summary=change_summary,
parent_version_id=parent_version_id,
file=file,
current_user=current_user,
)
return DocumentVersionRead.model_validate(version)
@router.post("/versions/{version_id}/submit", response_model=DocumentVersionRead)
async def submit_version(
version_id: uuid.UUID,
payload: VersionSubmitRequest,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> DocumentVersionRead:
await document_service.submit_version(db, version_id, payload.template_id, payload.comment, current_user)
version = await version_crud.get(db, version_id)
if not version:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="版本不存在")
return DocumentVersionRead.model_validate(version)
@router.post("/versions/{version_id}/approve", response_model=DocumentVersionRead)
async def approve_version(
version_id: uuid.UUID,
payload: VersionActionRequest,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> DocumentVersionRead:
await document_service.approve_version(db, version_id, payload.comment, current_user)
version = await version_crud.get(db, version_id)
if not version:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="版本不存在")
return DocumentVersionRead.model_validate(version)
@router.post("/versions/{version_id}/reject", response_model=DocumentVersionRead)
async def reject_version(
version_id: uuid.UUID,
payload: VersionActionRequest,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> DocumentVersionRead:
await document_service.reject_version(db, version_id, payload.comment, current_user)
version = await version_crud.get(db, version_id)
if not version:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="版本不存在")
return DocumentVersionRead.model_validate(version)
@router.post("/versions/{version_id}/make-effective", response_model=DocumentVersionRead)
async def make_version_effective(
version_id: uuid.UUID,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> DocumentVersionRead:
version = await document_service.make_version_effective(db, version_id, current_user)
return DocumentVersionRead.model_validate(version)
@router.get("/versions/{version_id}/download", response_class=FileResponse)
async def download_version(
version_id: uuid.UUID,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
):
return await document_service.get_version_download_response(db, version_id, current_user)
@router.get("/workflow-templates", response_model=list[WorkflowTemplateRead])
async def list_workflow_templates(
trial_id: uuid.UUID | None = None,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> list[WorkflowTemplateRead]:
templates = await document_service.list_workflow_templates(db, trial_id, current_user)
return [WorkflowTemplateRead.model_validate(t) for t in templates]
@router.post(
"/versions/{version_id}/distributions",
response_model=list[DistributionRead],
status_code=status.HTTP_201_CREATED,
)
async def create_distributions(
version_id: uuid.UUID,
payload: DistributionCreate,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> list[DistributionRead]:
distributions = await document_service.create_distributions(db, version_id, payload, current_user)
return [DistributionRead.model_validate(d) for d in distributions]
@router.get("/versions/{version_id}/distributions", response_model=list[DistributionRead])
async def list_distributions(
version_id: uuid.UUID,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> list[DistributionRead]:
return await document_service.list_distributions(db, version_id, current_user)
@router.post(
"/distributions/{distribution_id}/acknowledgements",
response_model=AcknowledgementRead,
status_code=status.HTTP_201_CREATED,
)
async def create_acknowledgement(
distribution_id: uuid.UUID,
payload: AcknowledgementCreate,
db: AsyncSession = Depends(get_db_session),
current_user=Depends(get_current_user),
) -> AcknowledgementRead:
ack = await document_service.create_acknowledgement(db, distribution_id, payload, current_user)
return AcknowledgementRead.model_validate(ack)
+2 -1
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter
from app.api.v1 import auth, users, admin_users, studies, sites, members, attachments, audit_logs, dashboard, subjects, visits, aes, finance_dashboard, finance_contracts, finance_specials, fees_contracts, fees_specials, fees_attachments, drug_shipments, startup, knowledge_notes, subject_histories, faq_categories, faqs
from app.api.v1 import auth, users, admin_users, studies, sites, members, attachments, audit_logs, dashboard, subjects, visits, aes, finance_dashboard, finance_contracts, finance_specials, fees_contracts, fees_specials, fees_attachments, drug_shipments, startup, knowledge_notes, subject_histories, faq_categories, faqs, documents
api_router = APIRouter()
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
@@ -28,3 +28,4 @@ api_router.include_router(knowledge_notes.router, prefix="/studies/{study_id}/kn
api_router.include_router(subject_histories.router, prefix="/studies/{study_id}/subjects/{subject_id}", tags=["subject-histories"])
api_router.include_router(faq_categories.router, prefix="/faqs/categories", tags=["faq-categories"])
api_router.include_router(faqs.router, prefix="/faqs/items", tags=["faqs"])
api_router.include_router(documents.router, prefix="", tags=["documents"])