移除特殊费用模块
This commit is contained in:
@@ -14,7 +14,6 @@ from app.crud import contract_fee as contract_fee_crud
|
||||
from app.crud import contract_fee_payment as payment_crud
|
||||
from app.crud import fee_attachment as fee_attachment_crud
|
||||
from app.crud import member as member_crud
|
||||
from app.crud import special_expense as special_crud
|
||||
from app.crud import user as user_crud
|
||||
from app.schemas.fee_attachment import FeeAttachmentRead
|
||||
from app.schemas.fee_common import FeeApiResponse
|
||||
@@ -24,11 +23,10 @@ router = APIRouter()
|
||||
|
||||
UPLOAD_ROOT = Path(__file__).resolve().parent.parent.parent / "uploads" / "fees"
|
||||
|
||||
ALLOWED_ENTITY_TYPES = {"contract_fee", "contract_payment", "special_expense"}
|
||||
ALLOWED_ENTITY_TYPES = {"contract_fee", "contract_payment"}
|
||||
ALLOWED_FILE_TYPES = {
|
||||
"contract_fee": {"contract", "voucher", "invoice"},
|
||||
"contract_payment": {"voucher", "invoice"},
|
||||
"special_expense": {"voucher", "invoice", "other"},
|
||||
}
|
||||
|
||||
|
||||
@@ -46,11 +44,6 @@ async def _resolve_project_id(db: AsyncSession, entity_type: str, entity_id: uui
|
||||
if not contract:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="合同费用不存在")
|
||||
return contract.project_id
|
||||
if entity_type == "special_expense":
|
||||
expense = await special_crud.get_special_expense(db, entity_id)
|
||||
if not expense:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="特殊费用不存在")
|
||||
return expense.project_id
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="不支持的附件类型")
|
||||
|
||||
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
import uuid
|
||||
from datetime import date
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, require_study_not_locked
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import member as member_crud
|
||||
from app.crud import special_expense as special_crud
|
||||
from app.crud import study as study_crud
|
||||
from app.crud import site as site_crud
|
||||
from app.schemas.fee_common import FeeApiResponse
|
||||
from app.schemas.special_expense import (
|
||||
SpecialExpenseCreate,
|
||||
SpecialExpenseListItem,
|
||||
SpecialExpenseRead,
|
||||
SpecialExpenseUpdate,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
ALLOWED_CATEGORIES = {"travel", "meal", "meeting", "supplies", "other"}
|
||||
|
||||
|
||||
async def _ensure_project_access(db: AsyncSession, project_id: uuid.UUID, current_user, write: bool = False):
|
||||
study = await study_crud.get(db, project_id)
|
||||
if not study:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
role_value = current_user.role.value if hasattr(current_user.role, "value") else current_user.role
|
||||
if role_value == "ADMIN":
|
||||
return None
|
||||
membership = await member_crud.get_member(db, project_id, current_user.id)
|
||||
if not membership or not membership.is_active:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不是项目成员")
|
||||
if write and membership.role_in_study != "PM":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="项目权限不足")
|
||||
return membership
|
||||
|
||||
|
||||
def _validate_special_rules(payload: SpecialExpenseCreate | SpecialExpenseUpdate):
|
||||
if payload.is_verified is True and payload.is_paid is False:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="核销需先打款")
|
||||
if payload.is_paid and not payload.paid_date:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="已打款需填写打款日期")
|
||||
if payload.is_verified and not payload.verified_date:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="已核销需填写核销日期")
|
||||
|
||||
|
||||
def _validate_category(value: str | None):
|
||||
if value and value not in ALLOWED_CATEGORIES:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="费用类别无效")
|
||||
|
||||
|
||||
async def _ensure_center_active(db: AsyncSession, project_id: uuid.UUID, center_id: uuid.UUID | None):
|
||||
if not center_id:
|
||||
return
|
||||
site = await site_crud.get_site(db, center_id)
|
||||
if not site or site.study_id != project_id or not site.is_active:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="中心已停用")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/special",
|
||||
response_model=FeeApiResponse[list[SpecialExpenseListItem]],
|
||||
dependencies=[Depends(get_current_user)],
|
||||
)
|
||||
async def list_special_expenses(
|
||||
project_id: uuid.UUID = Query(..., alias="projectId"),
|
||||
center_id: uuid.UUID | None = Query(None, alias="centerId"),
|
||||
category: str | None = None,
|
||||
date_from: date | None = Query(None, alias="dateFrom"),
|
||||
date_to: date | None = Query(None, alias="dateTo"),
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> FeeApiResponse[list[SpecialExpenseListItem]]:
|
||||
await _ensure_project_access(db, project_id, current_user, write=False)
|
||||
_validate_category(category)
|
||||
cra_scope = await get_cra_site_scope(db, project_id, current_user)
|
||||
center_ids = cra_scope[0] if cra_scope else None
|
||||
if center_id and center_ids is not None and center_id not in center_ids:
|
||||
return FeeApiResponse(data=[], meta={"total": 0})
|
||||
rows = await special_crud.list_special_expenses(
|
||||
db,
|
||||
project_id,
|
||||
center_id=center_id,
|
||||
center_ids=center_ids,
|
||||
category=category,
|
||||
date_from=date_from,
|
||||
date_to=date_to,
|
||||
)
|
||||
items: list[SpecialExpenseListItem] = []
|
||||
for expense, center_name, attachments_count in rows:
|
||||
items.append(
|
||||
SpecialExpenseListItem(
|
||||
id=expense.id,
|
||||
project_id=expense.project_id,
|
||||
center_id=expense.center_id,
|
||||
category=expense.category,
|
||||
amount=expense.amount,
|
||||
happen_date=expense.happen_date,
|
||||
description=expense.description,
|
||||
is_paid=expense.is_paid,
|
||||
paid_date=expense.paid_date,
|
||||
is_verified=expense.is_verified,
|
||||
verified_date=expense.verified_date,
|
||||
created_by=expense.created_by,
|
||||
created_at=expense.created_at,
|
||||
updated_at=expense.updated_at,
|
||||
center_name=center_name,
|
||||
attachments_count=attachments_count or 0,
|
||||
)
|
||||
)
|
||||
return FeeApiResponse(data=items, meta={"total": len(items)})
|
||||
|
||||
|
||||
@router.get(
|
||||
"/special/{expense_id}",
|
||||
response_model=FeeApiResponse[SpecialExpenseRead],
|
||||
dependencies=[Depends(get_current_user)],
|
||||
)
|
||||
async def get_special_expense(
|
||||
expense_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> FeeApiResponse[SpecialExpenseRead]:
|
||||
expense = await special_crud.get_special_expense(db, expense_id)
|
||||
if not expense:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="特殊费用不存在")
|
||||
await _ensure_project_access(db, expense.project_id, current_user, write=False)
|
||||
cra_scope = await get_cra_site_scope(db, expense.project_id, current_user)
|
||||
if cra_scope and expense.center_id not in cra_scope[0]:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
return FeeApiResponse(data=SpecialExpenseRead.model_validate(expense))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/special",
|
||||
response_model=FeeApiResponse[SpecialExpenseRead],
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(get_current_user), Depends(require_study_not_locked())],
|
||||
)
|
||||
async def create_special_expense(
|
||||
expense_in: SpecialExpenseCreate,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> FeeApiResponse[SpecialExpenseRead]:
|
||||
await _ensure_project_access(db, expense_in.project_id, current_user, write=True)
|
||||
_validate_category(expense_in.category)
|
||||
_validate_special_rules(expense_in)
|
||||
if expense_in.center_id:
|
||||
site = await site_crud.get_site(db, expense_in.center_id)
|
||||
if not site or site.study_id != expense_in.project_id or not site.is_active:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="中心不存在或已停用")
|
||||
await _ensure_center_active(db, expense_in.project_id, expense_in.center_id)
|
||||
expense = await special_crud.create_special_expense(db, expense_in, created_by=current_user.id)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
study_id=expense.project_id,
|
||||
entity_type="special_expense",
|
||||
entity_id=expense.id,
|
||||
action="CREATE_SPECIAL_EXPENSE",
|
||||
detail="特殊费用已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
return FeeApiResponse(data=SpecialExpenseRead.model_validate(expense))
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/special/{expense_id}",
|
||||
response_model=FeeApiResponse[SpecialExpenseRead],
|
||||
dependencies=[Depends(get_current_user), Depends(require_study_not_locked())],
|
||||
)
|
||||
async def update_special_expense(
|
||||
expense_id: uuid.UUID,
|
||||
expense_in: SpecialExpenseUpdate,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> FeeApiResponse[SpecialExpenseRead]:
|
||||
expense = await special_crud.get_special_expense(db, expense_id)
|
||||
if not expense:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="特殊费用不存在")
|
||||
await _ensure_project_access(db, expense.project_id, current_user, write=True)
|
||||
_validate_category(expense_in.category)
|
||||
merged = SpecialExpenseCreate(
|
||||
project_id=expense.project_id,
|
||||
center_id=expense_in.center_id if expense_in.center_id is not None else expense.center_id,
|
||||
category=expense_in.category if expense_in.category is not None else expense.category,
|
||||
amount=expense_in.amount if expense_in.amount is not None else expense.amount,
|
||||
happen_date=expense_in.happen_date if expense_in.happen_date is not None else expense.happen_date,
|
||||
description=expense_in.description if expense_in.description is not None else expense.description,
|
||||
is_paid=expense_in.is_paid if expense_in.is_paid is not None else expense.is_paid,
|
||||
paid_date=expense_in.paid_date if expense_in.paid_date is not None else expense.paid_date,
|
||||
is_verified=expense_in.is_verified if expense_in.is_verified is not None else expense.is_verified,
|
||||
verified_date=expense_in.verified_date if expense_in.verified_date is not None else expense.verified_date,
|
||||
)
|
||||
_validate_special_rules(merged)
|
||||
if expense_in.center_id:
|
||||
site = await site_crud.get_site(db, expense_in.center_id)
|
||||
if not site or site.study_id != expense.project_id or not site.is_active:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="中心不存在或已停用")
|
||||
await _ensure_center_active(db, expense.project_id, expense_in.center_id)
|
||||
else:
|
||||
await _ensure_center_active(db, expense.project_id, expense.center_id)
|
||||
expense = await special_crud.update_special_expense(db, expense, expense_in)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
study_id=expense.project_id,
|
||||
entity_type="special_expense",
|
||||
entity_id=expense_id,
|
||||
action="UPDATE_SPECIAL_EXPENSE",
|
||||
detail="特殊费用已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
return FeeApiResponse(data=SpecialExpenseRead.model_validate(expense))
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/special/{expense_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(get_current_user), Depends(require_study_not_locked())],
|
||||
)
|
||||
async def delete_special_expense(
|
||||
expense_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> None:
|
||||
expense = await special_crud.get_special_expense(db, expense_id)
|
||||
if not expense:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="特殊费用不存在")
|
||||
await _ensure_project_access(db, expense.project_id, current_user, write=True)
|
||||
await _ensure_center_active(db, expense.project_id, expense.center_id)
|
||||
await special_crud.delete_special_expense(db, expense)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
study_id=expense.project_id,
|
||||
entity_type="special_expense",
|
||||
entity_id=expense_id,
|
||||
action="DELETE_SPECIAL_EXPENSE",
|
||||
detail="特殊费用已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -1,177 +0,0 @@
|
||||
import uuid
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.deps import get_cra_site_scope, get_current_user, get_db_session, require_study_member, require_study_roles, require_study_not_locked
|
||||
from app.crud import audit as audit_crud
|
||||
from app.crud import finance_special as special_crud
|
||||
from app.crud import site as site_crud
|
||||
from app.crud import study as study_crud
|
||||
from app.schemas.finance_special import FinanceSpecialCreate, FinanceSpecialRead, FinanceSpecialUpdate
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
async def _ensure_study_exists(db: AsyncSession, study_id: uuid.UUID):
|
||||
study = await study_crud.get(db, study_id)
|
||||
if not study:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
async def _ensure_site_name_active(db: AsyncSession, study_id: uuid.UUID, site_name: str | None):
|
||||
if not site_name:
|
||||
return
|
||||
active_names = await site_crud.list_active_names(db, study_id)
|
||||
if site_name not in active_names:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="中心已停用")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/specials",
|
||||
response_model=FinanceSpecialRead,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(require_study_roles(["PM", "CRA"]))],
|
||||
)
|
||||
async def create_special(
|
||||
study_id: uuid.UUID,
|
||||
special_in: FinanceSpecialCreate,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> FinanceSpecialRead:
|
||||
await _ensure_study_exists(db, study_id)
|
||||
cra_scope = await get_cra_site_scope(db, study_id, current_user)
|
||||
if cra_scope and special_in.site_name not in cra_scope[1]:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
await _ensure_site_name_active(db, study_id, special_in.site_name)
|
||||
item = await special_crud.create_special(db, study_id, special_in, created_by=current_user.id)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
study_id=study_id,
|
||||
entity_type="finance_special",
|
||||
entity_id=item.id,
|
||||
action="CREATE_FINANCE_SPECIAL",
|
||||
detail=f"特殊费用 {item.id} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
return FinanceSpecialRead.model_validate(item)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/specials",
|
||||
response_model=list[FinanceSpecialRead],
|
||||
dependencies=[Depends(require_study_member())],
|
||||
)
|
||||
async def list_specials(
|
||||
study_id: uuid.UUID,
|
||||
site_name: str | None = None,
|
||||
fee_type: str | None = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> list[FinanceSpecialRead]:
|
||||
await _ensure_study_exists(db, study_id)
|
||||
cra_scope = await get_cra_site_scope(db, study_id, current_user)
|
||||
site_names = cra_scope[1] if cra_scope else None
|
||||
if site_name and site_names is not None and site_name not in site_names:
|
||||
return []
|
||||
items = await special_crud.list_specials(
|
||||
db,
|
||||
study_id,
|
||||
site_name=site_name,
|
||||
site_names=site_names,
|
||||
fee_type=fee_type,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
)
|
||||
return [FinanceSpecialRead.model_validate(item) for item in items]
|
||||
|
||||
|
||||
@router.get(
|
||||
"/specials/{special_id}",
|
||||
response_model=FinanceSpecialRead,
|
||||
dependencies=[Depends(require_study_member())],
|
||||
)
|
||||
async def get_special(
|
||||
study_id: uuid.UUID,
|
||||
special_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> FinanceSpecialRead:
|
||||
await _ensure_study_exists(db, study_id)
|
||||
item = await special_crud.get_special(db, special_id)
|
||||
if not item or item.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="特殊费用不存在")
|
||||
cra_scope = await get_cra_site_scope(db, study_id, current_user)
|
||||
if cra_scope and item.site_name not in cra_scope[1]:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
return FinanceSpecialRead.model_validate(item)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/specials/{special_id}",
|
||||
response_model=FinanceSpecialRead,
|
||||
dependencies=[Depends(require_study_roles(["PM", "CRA"]))],
|
||||
)
|
||||
async def update_special(
|
||||
study_id: uuid.UUID,
|
||||
special_id: uuid.UUID,
|
||||
special_in: FinanceSpecialUpdate,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> FinanceSpecialRead:
|
||||
await _ensure_study_exists(db, study_id)
|
||||
item = await special_crud.get_special(db, special_id)
|
||||
if not item or item.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="特殊费用不存在")
|
||||
cra_scope = await get_cra_site_scope(db, study_id, current_user)
|
||||
if cra_scope and item.site_name not in cra_scope[1]:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
await _ensure_site_name_active(db, study_id, item.site_name)
|
||||
item = await special_crud.update_special(db, item, special_in)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
study_id=study_id,
|
||||
entity_type="finance_special",
|
||||
entity_id=special_id,
|
||||
action="UPDATE_FINANCE_SPECIAL",
|
||||
detail=f"特殊费用 {special_id} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
return FinanceSpecialRead.model_validate(item)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/specials/{special_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(require_study_roles(["PM", "CRA"]))],
|
||||
)
|
||||
async def delete_special(
|
||||
study_id: uuid.UUID,
|
||||
special_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db_session),
|
||||
current_user=Depends(get_current_user),
|
||||
) -> None:
|
||||
await _ensure_study_exists(db, study_id)
|
||||
item = await special_crud.get_special(db, special_id)
|
||||
if not item or item.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="特殊费用不存在")
|
||||
cra_scope = await get_cra_site_scope(db, study_id, current_user)
|
||||
if cra_scope and item.site_name not in cra_scope[1]:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
await _ensure_site_name_active(db, study_id, item.site_name)
|
||||
await special_crud.delete_special(db, item)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
study_id=study_id,
|
||||
entity_type="finance_special",
|
||||
entity_id=special_id,
|
||||
action="DELETE_FINANCE_SPECIAL",
|
||||
detail=f"特殊费用 {special_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -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, material_equipments, project_milestones, startup, knowledge_notes, subject_histories, subject_pds, study_subject_pds, faq_categories, faqs, documents, overview, notifications, monitoring_visit_issues
|
||||
from app.api.v1 import auth, users, admin_users, studies, sites, members, attachments, audit_logs, dashboard, subjects, visits, aes, finance_dashboard, finance_contracts, fees_contracts, fees_attachments, drug_shipments, material_equipments, project_milestones, startup, knowledge_notes, subject_histories, subject_pds, study_subject_pds, faq_categories, faqs, documents, overview, notifications, monitoring_visit_issues
|
||||
|
||||
|
||||
api_router = APIRouter()
|
||||
@@ -21,9 +21,7 @@ api_router.include_router(visits.router, prefix="/studies/{study_id}/subjects/{s
|
||||
api_router.include_router(aes.router, prefix="/studies/{study_id}/aes", tags=["aes"])
|
||||
api_router.include_router(finance_dashboard.router, prefix="/studies/{study_id}/finance", tags=["finance"])
|
||||
api_router.include_router(finance_contracts.router, prefix="/studies/{study_id}/finance", tags=["finance-contracts"])
|
||||
api_router.include_router(finance_specials.router, prefix="/studies/{study_id}/finance", tags=["finance-specials"])
|
||||
api_router.include_router(fees_contracts.router, prefix="/fees", tags=["fees-contracts"])
|
||||
api_router.include_router(fees_specials.router, prefix="/fees", tags=["fees-specials"])
|
||||
api_router.include_router(fees_attachments.router, prefix="/fees", tags=["fees-attachments"])
|
||||
api_router.include_router(drug_shipments.router, prefix="/studies/{study_id}/drug", tags=["drug-shipments"])
|
||||
api_router.include_router(material_equipments.router, prefix="/studies/{study_id}/materials", tags=["material-equipments"])
|
||||
|
||||
Reference in New Issue
Block a user