全局中文化
This commit is contained in:
@@ -24,7 +24,7 @@ async def list_users_for_review(
|
||||
async def _get_review_user(db: AsyncSession, user_id: uuid.UUID) -> User:
|
||||
user = await user_crud.get_by_id(db, user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
if user.role == UserRole.ADMIN:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="管理员账号不允许审核")
|
||||
return user
|
||||
|
||||
+13
-13
@@ -21,7 +21,7 @@ ALLOWED_UPDATE_ROLES = {"PM", "PV", "CRA"}
|
||||
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="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -48,10 +48,10 @@ async def create_ae(
|
||||
) -> AERead:
|
||||
await _ensure_study_exists(db, study_id)
|
||||
if ae_in.onset_date and ae_in.onset_date > date.today():
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Onset date cannot be in the future")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="发生日期不能晚于今天")
|
||||
member_role = await _get_member_role(db, study_id, current_user.id)
|
||||
if current_user.role != "ADMIN" and member_role not in ALLOWED_CREATE_ROLES:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
try:
|
||||
ae = await ae_crud.create_ae(db, study_id, ae_in, created_by=current_user.id)
|
||||
except ValueError as exc:
|
||||
@@ -62,7 +62,7 @@ async def create_ae(
|
||||
entity_type="ae",
|
||||
entity_id=ae.id,
|
||||
action="CREATE_AE",
|
||||
detail=f"AE {ae.id} created",
|
||||
detail=f"AE {ae.id} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -108,7 +108,7 @@ async def get_ae(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
ae = await ae_crud.get_ae(db, ae_id)
|
||||
if not ae or ae.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="AE not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="AE 不存在")
|
||||
data = AERead.model_validate(ae)
|
||||
data.is_overdue = _is_overdue(data)
|
||||
return data
|
||||
@@ -128,10 +128,10 @@ async def update_ae(
|
||||
) -> AERead:
|
||||
await _ensure_study_exists(db, study_id)
|
||||
if ae_in.onset_date and ae_in.onset_date > date.today():
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Onset date cannot be in the future")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="发生日期不能晚于今天")
|
||||
ae = await ae_crud.get_ae(db, ae_id)
|
||||
if not ae or ae.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="AE not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="AE 不存在")
|
||||
|
||||
member_role = await _get_member_role(db, study_id, current_user.id)
|
||||
if current_user.role == "ADMIN":
|
||||
@@ -140,11 +140,11 @@ async def update_ae(
|
||||
pass
|
||||
elif member_role == "CRA":
|
||||
if ae.created_by != current_user.id:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only creator can update AE")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="仅创建人可更新 AE")
|
||||
if ae_in.status == "CLOSED":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="CRA cannot close AE")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="CRA 无法关闭 AE")
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
|
||||
old_status = ae.status
|
||||
detail_before = {
|
||||
@@ -195,10 +195,10 @@ async def delete_ae(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
ae = await ae_crud.get_ae(db, ae_id)
|
||||
if not ae or ae.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="AE not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="AE 不存在")
|
||||
member_role = await _get_member_role(db, study_id, current_user.id)
|
||||
if current_user.role != "ADMIN" and member_role not in {"PM", "PV"}:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
await ae_crud.delete_ae(db, ae)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -206,7 +206,7 @@ async def delete_ae(
|
||||
entity_type="ae",
|
||||
entity_id=ae_id,
|
||||
action="DELETE_AE",
|
||||
detail=f"AE {ae_id} deleted",
|
||||
detail=f"AE {ae_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ UPLOAD_ROOT = Path(__file__).resolve().parent.parent.parent / "uploads"
|
||||
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="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ async def upload_attachment(
|
||||
entity_type=entity_type,
|
||||
entity_id=entity_id,
|
||||
action="UPLOAD_FILE",
|
||||
detail=f"File uploaded: {file.filename}",
|
||||
detail=f"文件已上传:{file.filename}",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -130,9 +130,9 @@ async def download_attachment(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
attachment = await attachment_crud.get_attachment(db, attachment_id)
|
||||
if not attachment or attachment.study_id != study_id or attachment.entity_id != entity_id or attachment.entity_type != entity_type:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Attachment not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="附件不存在")
|
||||
if not os.path.exists(attachment.file_path):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="File missing on server")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="服务器未找到文件")
|
||||
return FileResponse(
|
||||
path=attachment.file_path,
|
||||
filename=attachment.filename,
|
||||
@@ -148,16 +148,16 @@ async def _authorize_global(request: Request, db: AsyncSession, study_id: uuid.U
|
||||
if not token:
|
||||
token = request.query_params.get("token")
|
||||
if not token:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="未登录")
|
||||
payload = decode_token(token)
|
||||
user = await user_crud.get_by_id(db, uuid.UUID(str(payload.get("sub"))))
|
||||
if not user or not user.is_active:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Inactive or missing user")
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="账号不存在或已停用")
|
||||
if user.role == "ADMIN":
|
||||
return user, None
|
||||
membership = await member_crud.get_member(db, study_id, user.id)
|
||||
if not membership or not membership.is_active:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not project member")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不是项目成员")
|
||||
return user, membership
|
||||
|
||||
|
||||
@@ -172,11 +172,11 @@ async def global_download_attachment(
|
||||
):
|
||||
attachment = await attachment_crud.get_attachment(db, attachment_id)
|
||||
if not attachment:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Attachment not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="附件不存在")
|
||||
await _ensure_study_exists(db, attachment.study_id)
|
||||
user, _ = await _authorize_global(request, db, attachment.study_id)
|
||||
if not os.path.exists(attachment.file_path):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="File missing on server")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="服务器未找到文件")
|
||||
return FileResponse(
|
||||
path=attachment.file_path,
|
||||
filename=attachment.filename,
|
||||
@@ -195,7 +195,7 @@ async def global_delete_attachment(
|
||||
):
|
||||
attachment = await attachment_crud.get_attachment(db, attachment_id)
|
||||
if not attachment:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Attachment not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="附件不存在")
|
||||
await _ensure_study_exists(db, attachment.study_id)
|
||||
user, membership = await _authorize_global(request, db, attachment.study_id)
|
||||
can_delete = (
|
||||
@@ -204,7 +204,7 @@ async def global_delete_attachment(
|
||||
or (membership and getattr(membership, "role_in_study", None) == "PM")
|
||||
)
|
||||
if not can_delete:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="No permission to delete attachment")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="无权限删除附件")
|
||||
await attachment_crud.soft_delete_attachment(db, attachment)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -212,7 +212,7 @@ async def global_delete_attachment(
|
||||
entity_type=attachment.entity_type,
|
||||
entity_id=attachment.entity_id,
|
||||
action="DELETE_ATTACHMENT",
|
||||
detail=f"File deleted: {attachment.filename}",
|
||||
detail=f"文件已删除:{attachment.filename}",
|
||||
operator_id=user.id,
|
||||
operator_role=user.role,
|
||||
)
|
||||
@@ -240,7 +240,7 @@ async def delete_attachment(
|
||||
or attachment.entity_type != entity_type
|
||||
or attachment.is_deleted
|
||||
):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Attachment not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="附件不存在")
|
||||
|
||||
membership = None
|
||||
if current_user.role != "ADMIN":
|
||||
@@ -252,7 +252,7 @@ async def delete_attachment(
|
||||
or (membership and getattr(membership, "role_in_study", None) == "PM")
|
||||
)
|
||||
if not can_delete:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="No permission to delete attachment")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="无权限删除附件")
|
||||
|
||||
await attachment_crud.soft_delete_attachment(db, attachment)
|
||||
await audit_crud.log_action(
|
||||
@@ -261,7 +261,7 @@ async def delete_attachment(
|
||||
entity_type=entity_type,
|
||||
entity_id=entity_id,
|
||||
action="DELETE_ATTACHMENT",
|
||||
detail=f"File deleted: {attachment.filename}",
|
||||
detail=f"文件已删除:{attachment.filename}",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
@@ -50,5 +50,5 @@ async def delete_audit_log(
|
||||
):
|
||||
log = await audit_crud.get_log(db, log_id)
|
||||
if not log or log.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Audit log not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="审计日志不存在")
|
||||
await audit_crud.delete_log(db, log)
|
||||
|
||||
@@ -124,5 +124,5 @@ async def upload_avatar(
|
||||
async def get_avatar(user_id: str, filename: str):
|
||||
file_path = AVATAR_ROOT / user_id / filename
|
||||
if not file_path.exists():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Avatar not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="头像不存在")
|
||||
return FileResponse(file_path)
|
||||
|
||||
@@ -15,7 +15,7 @@ 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="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ async def create_shipment(
|
||||
entity_type="drug_shipment",
|
||||
entity_id=shipment.id,
|
||||
action="CREATE_DRUG_SHIPMENT",
|
||||
detail=f"Drug shipment {shipment.id} created",
|
||||
detail=f"药品运输 {shipment.id} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -80,7 +80,7 @@ async def get_shipment(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
shipment = await shipment_crud.get_shipment(db, shipment_id)
|
||||
if not shipment or shipment.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Shipment not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="运输记录不存在")
|
||||
return DrugShipmentRead.model_validate(shipment)
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ async def update_shipment(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
shipment = await shipment_crud.get_shipment(db, shipment_id)
|
||||
if not shipment or shipment.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Shipment not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="运输记录不存在")
|
||||
shipment = await shipment_crud.update_shipment(db, shipment, shipment_in)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -107,7 +107,7 @@ async def update_shipment(
|
||||
entity_type="drug_shipment",
|
||||
entity_id=shipment_id,
|
||||
action="UPDATE_DRUG_SHIPMENT",
|
||||
detail=f"Drug shipment {shipment_id} updated",
|
||||
detail=f"药品运输 {shipment_id} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -128,7 +128,7 @@ async def delete_shipment(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
shipment = await shipment_crud.get_shipment(db, shipment_id)
|
||||
if not shipment or shipment.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Shipment not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="运输记录不存在")
|
||||
await shipment_crud.delete_shipment(db, shipment)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -136,7 +136,7 @@ async def delete_shipment(
|
||||
entity_type="drug_shipment",
|
||||
entity_id=shipment_id,
|
||||
action="DELETE_DRUG_SHIPMENT",
|
||||
detail=f"Drug shipment {shipment_id} deleted",
|
||||
detail=f"药品运输 {shipment_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ router = APIRouter()
|
||||
|
||||
def _check_permission_for_scope(study_id: uuid.UUID, current_user, member_role: str | None):
|
||||
if current_user.role != "ADMIN" and member_role != "PM":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -33,14 +33,14 @@ async def create_category(
|
||||
current_user=Depends(get_current_user),
|
||||
) -> CategoryRead:
|
||||
if not payload.study_id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="study_id is required")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="必须提供项目 ID")
|
||||
existing = await category_crud.get_category_by_name(db, payload.study_id, payload.name)
|
||||
if existing:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="分类名称已存在")
|
||||
member_role = None
|
||||
member = await member_crud.get_member(db, payload.study_id, current_user.id)
|
||||
if not member and current_user.role != "ADMIN":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not a study member")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不是该项目成员")
|
||||
member_role = member.role_in_study if member else None
|
||||
_check_permission_for_scope(payload.study_id, current_user, member_role)
|
||||
category = await category_crud.create_category(db, payload)
|
||||
@@ -50,7 +50,7 @@ async def create_category(
|
||||
entity_type="faq_category",
|
||||
entity_id=category.id,
|
||||
action="CREATE_FAQ_CATEGORY",
|
||||
detail=f"FAQ category {category.name} created",
|
||||
detail=f"FAQ 分类 {category.name} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -70,11 +70,11 @@ async def list_categories(
|
||||
current_user=Depends(get_current_user),
|
||||
) -> list[CategoryRead]:
|
||||
if not study_id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="study_id is required")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="必须提供项目 ID")
|
||||
if study_id:
|
||||
member = await member_crud.get_member(db, study_id, current_user.id)
|
||||
if not member and current_user.role != "ADMIN":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not a study member")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不是该项目成员")
|
||||
categories = await category_crud.list_categories(db, study_id, include_global=False, is_active=is_active)
|
||||
return paginate([CategoryRead.model_validate(c) for c in categories], total=len(categories))
|
||||
|
||||
@@ -93,22 +93,22 @@ async def update_category(
|
||||
) -> CategoryRead:
|
||||
category = await category_crud.get_category(db, category_id)
|
||||
if not category:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Category not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="分类不存在")
|
||||
member_role = None
|
||||
update_data = payload.model_dump(exclude_unset=True)
|
||||
target_study_id = update_data.get("study_id", category.study_id)
|
||||
target_name = update_data.get("name", category.name)
|
||||
if "study_id" in update_data and update_data["study_id"] != category.study_id and current_user.role != "ADMIN":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only admin can change category scope")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="仅管理员可修改分类范围")
|
||||
existing = await category_crud.get_category_by_name(db, target_study_id, target_name)
|
||||
if existing and existing.id != category.id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="分类名称已存在")
|
||||
if not target_study_id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="study_id is required")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="必须提供项目 ID")
|
||||
member = await member_crud.get_member(db, target_study_id, current_user.id)
|
||||
member_role = member.role_in_study if member else None
|
||||
if not member and current_user.role != "ADMIN":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not a study member")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不是该项目成员")
|
||||
_check_permission_for_scope(target_study_id, current_user, member_role)
|
||||
updated = await category_crud.update_category(db, category, payload)
|
||||
await audit_crud.log_action(
|
||||
@@ -117,7 +117,7 @@ async def update_category(
|
||||
entity_type="faq_category",
|
||||
entity_id=category_id,
|
||||
action="UPDATE_FAQ_CATEGORY",
|
||||
detail=f"FAQ category {category_id} updated",
|
||||
detail=f"FAQ 分类 {category_id} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -137,9 +137,9 @@ async def delete_category(
|
||||
) -> None:
|
||||
category = await category_crud.get_category(db, category_id)
|
||||
if not category:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Category not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="分类不存在")
|
||||
if current_user.role != "ADMIN":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Only admin can delete FAQ category")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="仅管理员可删除 FAQ 分类")
|
||||
item_count = await item_crud.count_items_by_category(db, category_id)
|
||||
if item_count > 0:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="分类下存在 FAQ,无法删除")
|
||||
@@ -151,7 +151,7 @@ async def delete_category(
|
||||
entity_type="faq_category",
|
||||
entity_id=category_id,
|
||||
action="DELETE_FAQ_CATEGORY",
|
||||
detail=f"FAQ category {category_id} deleted",
|
||||
detail=f"FAQ 分类 {category_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
+34
-34
@@ -27,12 +27,12 @@ router = APIRouter()
|
||||
|
||||
def _check_write_permission(study_id: uuid.UUID, current_user, member_role: str | None):
|
||||
if current_user.role != "ADMIN" and member_role != "PM":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
|
||||
|
||||
def _check_create_permission(current_user, is_member: bool):
|
||||
if current_user.role != "ADMIN" and not is_member:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not a study member")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不是该项目成员")
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -48,12 +48,12 @@ async def create_faq(
|
||||
current_user=Depends(get_current_user),
|
||||
) -> FaqRead:
|
||||
if not payload.study_id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="study_id is required")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="必须提供项目 ID")
|
||||
cat = await category_crud.get_category(db, payload.category_id)
|
||||
if not cat:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Category not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="分类不存在")
|
||||
if cat.study_id != payload.study_id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Category scope mismatch")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="分类范围不匹配")
|
||||
member_role = None
|
||||
is_member = False
|
||||
member = await member_crud.get_member(db, payload.study_id, current_user.id)
|
||||
@@ -79,7 +79,7 @@ async def create_faq(
|
||||
entity_type="faq_item",
|
||||
entity_id=item.id,
|
||||
action="CREATE_FAQ_ITEM",
|
||||
detail="FAQ created",
|
||||
detail="FAQ 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -111,16 +111,16 @@ async def list_faqs(
|
||||
return role
|
||||
|
||||
if not study_id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="study_id is required")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="必须提供项目 ID")
|
||||
if current_user.role != "ADMIN":
|
||||
role = await _get_member_role(study_id)
|
||||
if not role:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not a study member")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不是该项目成员")
|
||||
|
||||
if is_active is False and current_user.role != "ADMIN":
|
||||
role = await _get_member_role(study_id)
|
||||
if role != "PM":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
|
||||
items = await faq_crud.list_items(
|
||||
db,
|
||||
@@ -158,18 +158,18 @@ async def get_faq(
|
||||
) -> FaqRead:
|
||||
item = await faq_crud.get_item(db, item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
if not item.study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
if current_user.role != "ADMIN":
|
||||
member = await member_crud.get_member(db, item.study_id, current_user.id)
|
||||
if not member:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not a study member")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不是该项目成员")
|
||||
if not item.is_active and current_user.role not in {"ADMIN"}:
|
||||
member = await member_crud.get_member(db, item.study_id, current_user.id)
|
||||
member_role = member.role_in_study if member else None
|
||||
if member_role != "PM":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Inactive FAQ")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="FAQ 已停用")
|
||||
return FaqRead.model_validate(item)
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ async def update_faq(
|
||||
) -> FaqRead:
|
||||
item = await faq_crud.get_item(db, item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
if item.created_by != current_user.id:
|
||||
member_role = None
|
||||
if item.study_id:
|
||||
@@ -228,16 +228,16 @@ async def update_faq_status(
|
||||
) -> FaqRead:
|
||||
item = await faq_crud.get_item(db, item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
if payload.status != "RESOLVED":
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only RESOLVED is allowed")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="仅允许设置为已解决")
|
||||
if item.created_by != current_user.id and current_user.role != "ADMIN":
|
||||
member_role = None
|
||||
if item.study_id:
|
||||
member = await member_crud.get_member(db, item.study_id, current_user.id)
|
||||
member_role = member.role_in_study if member else None
|
||||
if member_role != "PM":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
await faq_crud.set_status(db, item.id, "RESOLVED", resolved_by_confirm=True)
|
||||
updated = await faq_crud.get_item(db, item_id)
|
||||
return FaqRead.model_validate(updated)
|
||||
@@ -257,9 +257,9 @@ async def set_best_reply(
|
||||
) -> FaqRead:
|
||||
item = await faq_crud.get_item(db, item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
if not item.study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
is_member = False
|
||||
member = await member_crud.get_member(db, item.study_id, current_user.id)
|
||||
is_member = member is not None
|
||||
@@ -267,9 +267,9 @@ async def set_best_reply(
|
||||
if payload.best_reply_id:
|
||||
reply = await reply_crud.get_reply(db, payload.best_reply_id)
|
||||
if not reply or reply.faq_id != item.id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid reply")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="回复无效")
|
||||
if reply.is_deleted:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Reply deleted")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="回复已删除")
|
||||
await faq_crud.set_best_reply(db, item.id, reply.id)
|
||||
await faq_crud.set_status(db, item.id, "RESOLVED", resolved_by_confirm=item.resolved_by_confirm)
|
||||
else:
|
||||
@@ -299,11 +299,11 @@ async def list_replies(
|
||||
) -> list[FaqReplyRead]:
|
||||
item = await faq_crud.get_item(db, item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
if item.study_id and current_user.role != "ADMIN":
|
||||
member = await member_crud.get_member(db, item.study_id, current_user.id)
|
||||
if not member:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not a study member")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="不是该项目成员")
|
||||
replies = await reply_crud.list_replies(db, item_id)
|
||||
reply_map = {r.id: r for r in replies}
|
||||
result: list[FaqReplyRead] = []
|
||||
@@ -339,11 +339,11 @@ async def create_reply(
|
||||
) -> FaqReplyRead:
|
||||
item = await faq_crud.get_item(db, item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
if not item.study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
if not payload.content.strip():
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Reply content is required")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="回复内容不能为空")
|
||||
is_member = False
|
||||
member = await member_crud.get_member(db, item.study_id, current_user.id)
|
||||
is_member = member is not None
|
||||
@@ -352,7 +352,7 @@ async def create_reply(
|
||||
if payload.quote_reply_id:
|
||||
quote = await reply_crud.get_reply(db, payload.quote_reply_id)
|
||||
if not quote or quote.faq_id != item.id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid quote reply")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="引用回复无效")
|
||||
reply = await reply_crud.create_reply(
|
||||
db,
|
||||
faq_id=item.id,
|
||||
@@ -369,7 +369,7 @@ async def create_reply(
|
||||
entity_type="faq_reply",
|
||||
entity_id=reply.id,
|
||||
action="CREATE_FAQ_REPLY",
|
||||
detail="FAQ replied",
|
||||
detail="FAQ 已回复",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -400,7 +400,7 @@ async def delete_faq(
|
||||
) -> None:
|
||||
item = await faq_crud.get_item(db, item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
if item.created_by != current_user.id:
|
||||
member_role = None
|
||||
if item.study_id:
|
||||
@@ -416,7 +416,7 @@ async def delete_faq(
|
||||
entity_type="faq_item",
|
||||
entity_id=item_id,
|
||||
action="DELETE_FAQ_ITEM",
|
||||
detail="FAQ deleted",
|
||||
detail="FAQ 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -436,17 +436,17 @@ async def delete_reply(
|
||||
) -> None:
|
||||
item = await faq_crud.get_item(db, item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="FAQ 不存在")
|
||||
reply = await reply_crud.get_reply(db, reply_id)
|
||||
if not reply or reply.faq_id != item.id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Reply not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="回复不存在")
|
||||
if reply.created_by != current_user.id and current_user.role != "ADMIN":
|
||||
member_role = None
|
||||
if item.study_id:
|
||||
member = await member_crud.get_member(db, item.study_id, current_user.id)
|
||||
member_role = member.role_in_study if member else None
|
||||
if member_role != "PM":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="权限不足")
|
||||
if item.best_reply_id == reply.id:
|
||||
await faq_crud.set_best_reply(db, item.id, None)
|
||||
ref_count = await reply_crud.count_quote_references(db, reply.id)
|
||||
@@ -472,7 +472,7 @@ async def delete_reply(
|
||||
entity_type="faq_reply",
|
||||
entity_id=reply_id,
|
||||
action="DELETE_FAQ_REPLY",
|
||||
detail="FAQ reply deleted",
|
||||
detail="FAQ 回复已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ 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="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ async def create_contract(
|
||||
entity_type="finance_contract",
|
||||
entity_id=contract.id,
|
||||
action="CREATE_FINANCE_CONTRACT",
|
||||
detail=f"Finance contract {contract.contract_no} created",
|
||||
detail=f"合同费用 {contract.contract_no} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -77,7 +77,7 @@ async def get_contract(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
contract = await contract_crud.get_contract(db, contract_id)
|
||||
if not contract or contract.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Contract not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="合同不存在")
|
||||
return FinanceContractRead.model_validate(contract)
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ async def update_contract(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
contract = await contract_crud.get_contract(db, contract_id)
|
||||
if not contract or contract.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Contract not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="合同不存在")
|
||||
contract = await contract_crud.update_contract(db, contract, contract_in)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -104,7 +104,7 @@ async def update_contract(
|
||||
entity_type="finance_contract",
|
||||
entity_id=contract_id,
|
||||
action="UPDATE_FINANCE_CONTRACT",
|
||||
detail=f"Finance contract {contract_id} updated",
|
||||
detail=f"合同费用 {contract_id} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -125,7 +125,7 @@ async def delete_contract(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
contract = await contract_crud.get_contract(db, contract_id)
|
||||
if not contract or contract.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Contract not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="合同不存在")
|
||||
await contract_crud.delete_contract(db, contract)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -133,7 +133,7 @@ async def delete_contract(
|
||||
entity_type="finance_contract",
|
||||
entity_id=contract_id,
|
||||
action="DELETE_FINANCE_CONTRACT",
|
||||
detail=f"Finance contract {contract_id} deleted",
|
||||
detail=f"合同费用 {contract_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ 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="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ async def create_special(
|
||||
entity_type="finance_special",
|
||||
entity_id=item.id,
|
||||
action="CREATE_FINANCE_SPECIAL",
|
||||
detail=f"Finance special {item.id} created",
|
||||
detail=f"特殊费用 {item.id} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -77,7 +77,7 @@ async def get_special(
|
||||
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="Special fee not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="特殊费用不存在")
|
||||
return FinanceSpecialRead.model_validate(item)
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ async def update_special(
|
||||
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="Special fee not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="特殊费用不存在")
|
||||
item = await special_crud.update_special(db, item, special_in)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -104,7 +104,7 @@ async def update_special(
|
||||
entity_type="finance_special",
|
||||
entity_id=special_id,
|
||||
action="UPDATE_FINANCE_SPECIAL",
|
||||
detail=f"Finance special {special_id} updated",
|
||||
detail=f"特殊费用 {special_id} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -125,7 +125,7 @@ async def delete_special(
|
||||
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="Special fee not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="特殊费用不存在")
|
||||
await special_crud.delete_special(db, item)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -133,7 +133,7 @@ async def delete_special(
|
||||
entity_type="finance_special",
|
||||
entity_id=special_id,
|
||||
action="DELETE_FINANCE_SPECIAL",
|
||||
detail=f"Finance special {special_id} deleted",
|
||||
detail=f"特殊费用 {special_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ 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="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ async def create_note(
|
||||
entity_type="knowledge_note",
|
||||
entity_id=note.id,
|
||||
action="CREATE_KNOWLEDGE_NOTE",
|
||||
detail=f"Knowledge note {note.title} created",
|
||||
detail=f"注意事项 {note.title} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -77,7 +77,7 @@ async def get_note(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
note = await note_crud.get_note(db, note_id)
|
||||
if not note or note.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Note not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在")
|
||||
return KnowledgeNoteRead.model_validate(note)
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ async def update_note(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
note = await note_crud.get_note(db, note_id)
|
||||
if not note or note.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Note not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在")
|
||||
note = await note_crud.update_note(db, note, note_in)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -104,7 +104,7 @@ async def update_note(
|
||||
entity_type="knowledge_note",
|
||||
entity_id=note_id,
|
||||
action="UPDATE_KNOWLEDGE_NOTE",
|
||||
detail=f"Knowledge note {note_id} updated",
|
||||
detail=f"注意事项 {note_id} 已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -125,7 +125,7 @@ async def delete_note(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
note = await note_crud.get_note(db, note_id)
|
||||
if not note or note.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Note not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="注意事项不存在")
|
||||
await note_crud.delete_note(db, note)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -133,7 +133,7 @@ async def delete_note(
|
||||
entity_type="knowledge_note",
|
||||
entity_id=note_id,
|
||||
action="DELETE_KNOWLEDGE_NOTE",
|
||||
detail=f"Knowledge note {note_id} deleted",
|
||||
detail=f"注意事项 {note_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ 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="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ async def add_member(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
existing = await member_crud.get_member(db, study_id, member_in.user_id)
|
||||
if existing:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Member already exists")
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="成员已存在")
|
||||
member = await member_crud.add_member(db, study_id, member_in)
|
||||
return member
|
||||
|
||||
@@ -90,7 +90,7 @@ async def update_member(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
member = await member_crud.get_member_by_id(db, member_id)
|
||||
if not member or member.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Member not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="成员不存在")
|
||||
updated = await member_crud.update_member(db, member, member_in)
|
||||
return updated
|
||||
|
||||
@@ -108,6 +108,6 @@ async def remove_member(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
member = await member_crud.get_member_by_id(db, member_id)
|
||||
if not member or member.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Member not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="成员不存在")
|
||||
removed = await member_crud.remove_member(db, member)
|
||||
return removed
|
||||
|
||||
@@ -14,7 +14,7 @@ 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="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -64,6 +64,6 @@ async def update_site(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
site = await site_crud.get_site(db, site_id)
|
||||
if not site or site.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Site not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="分中心不存在")
|
||||
updated = await site_crud.update_site(db, site, site_in)
|
||||
return updated
|
||||
|
||||
@@ -28,7 +28,7 @@ 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="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ async def create_feasibility(
|
||||
entity_type="startup_feasibility",
|
||||
entity_id=record.id,
|
||||
action="CREATE_STARTUP_FEASIBILITY",
|
||||
detail="Startup feasibility record created",
|
||||
detail="立项记录已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -88,7 +88,7 @@ async def get_feasibility(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
record = await startup_crud.get_feasibility(db, record_id)
|
||||
if not record or record.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Feasibility record not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="立项记录不存在")
|
||||
return StartupFeasibilityRead.model_validate(record)
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ async def update_feasibility(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
record = await startup_crud.get_feasibility(db, record_id)
|
||||
if not record or record.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Feasibility record not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="立项记录不存在")
|
||||
record = await startup_crud.update_feasibility(db, record, record_in)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -115,7 +115,7 @@ async def update_feasibility(
|
||||
entity_type="startup_feasibility",
|
||||
entity_id=record_id,
|
||||
action="UPDATE_STARTUP_FEASIBILITY",
|
||||
detail="Startup feasibility record updated",
|
||||
detail="立项记录已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -136,7 +136,7 @@ async def delete_feasibility(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
record = await startup_crud.get_feasibility(db, record_id)
|
||||
if not record or record.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Feasibility record not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="立项记录不存在")
|
||||
await startup_crud.delete_feasibility(db, record)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -144,7 +144,7 @@ async def delete_feasibility(
|
||||
entity_type="startup_feasibility",
|
||||
entity_id=record_id,
|
||||
action="DELETE_STARTUP_FEASIBILITY",
|
||||
detail="Startup feasibility record deleted",
|
||||
detail="立项记录已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -170,7 +170,7 @@ async def create_ethics(
|
||||
entity_type="startup_ethics",
|
||||
entity_id=record.id,
|
||||
action="CREATE_STARTUP_ETHICS",
|
||||
detail="Startup ethics record created",
|
||||
detail="伦理记录已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -206,7 +206,7 @@ async def get_ethics(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
record = await startup_crud.get_ethics(db, record_id)
|
||||
if not record or record.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ethics record not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="伦理记录不存在")
|
||||
return StartupEthicsRead.model_validate(record)
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ async def update_ethics(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
record = await startup_crud.get_ethics(db, record_id)
|
||||
if not record or record.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ethics record not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="伦理记录不存在")
|
||||
record = await startup_crud.update_ethics(db, record, record_in)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -233,7 +233,7 @@ async def update_ethics(
|
||||
entity_type="startup_ethics",
|
||||
entity_id=record_id,
|
||||
action="UPDATE_STARTUP_ETHICS",
|
||||
detail="Startup ethics record updated",
|
||||
detail="伦理记录已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -254,7 +254,7 @@ async def delete_ethics(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
record = await startup_crud.get_ethics(db, record_id)
|
||||
if not record or record.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ethics record not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="伦理记录不存在")
|
||||
await startup_crud.delete_ethics(db, record)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -262,7 +262,7 @@ async def delete_ethics(
|
||||
entity_type="startup_ethics",
|
||||
entity_id=record_id,
|
||||
action="DELETE_STARTUP_ETHICS",
|
||||
detail="Startup ethics record deleted",
|
||||
detail="伦理记录已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -288,7 +288,7 @@ async def create_kickoff(
|
||||
entity_type="startup_kickoff",
|
||||
entity_id=meeting.id,
|
||||
action="CREATE_KICKOFF_MEETING",
|
||||
detail="Kickoff meeting created",
|
||||
detail="启动会记录已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -324,7 +324,7 @@ async def get_kickoff(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
meeting = await startup_crud.get_kickoff(db, meeting_id)
|
||||
if not meeting or meeting.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Kickoff meeting not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="启动会记录不存在")
|
||||
return KickoffMeetingRead.model_validate(meeting)
|
||||
|
||||
|
||||
@@ -343,7 +343,7 @@ async def update_kickoff(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
meeting = await startup_crud.get_kickoff(db, meeting_id)
|
||||
if not meeting or meeting.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Kickoff meeting not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="启动会记录不存在")
|
||||
meeting = await startup_crud.update_kickoff(db, meeting, meeting_in)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -351,7 +351,7 @@ async def update_kickoff(
|
||||
entity_type="startup_kickoff",
|
||||
entity_id=meeting_id,
|
||||
action="UPDATE_KICKOFF_MEETING",
|
||||
detail="Kickoff meeting updated",
|
||||
detail="启动会记录已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -372,7 +372,7 @@ async def delete_kickoff(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
meeting = await startup_crud.get_kickoff(db, meeting_id)
|
||||
if not meeting or meeting.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Kickoff meeting not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="启动会记录不存在")
|
||||
await startup_crud.delete_kickoff(db, meeting)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -380,7 +380,7 @@ async def delete_kickoff(
|
||||
entity_type="startup_kickoff",
|
||||
entity_id=meeting_id,
|
||||
action="DELETE_KICKOFF_MEETING",
|
||||
detail="Kickoff meeting deleted",
|
||||
detail="启动会记录已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -406,7 +406,7 @@ async def create_training_authorization(
|
||||
entity_type="training_authorization",
|
||||
entity_id=record.id,
|
||||
action="CREATE_TRAINING_AUTH",
|
||||
detail="Training authorization created",
|
||||
detail="培训授权人员已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -442,7 +442,7 @@ async def get_training_authorization(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
record = await startup_crud.get_training_authorization(db, record_id)
|
||||
if not record or record.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Training authorization not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="培训授权人员不存在")
|
||||
return TrainingAuthorizationRead.model_validate(record)
|
||||
|
||||
|
||||
@@ -461,7 +461,7 @@ async def update_training_authorization(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
record = await startup_crud.get_training_authorization(db, record_id)
|
||||
if not record or record.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Training authorization not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="培训授权人员不存在")
|
||||
record = await startup_crud.update_training_authorization(db, record, record_in)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -469,7 +469,7 @@ async def update_training_authorization(
|
||||
entity_type="training_authorization",
|
||||
entity_id=record_id,
|
||||
action="UPDATE_TRAINING_AUTH",
|
||||
detail="Training authorization updated",
|
||||
detail="培训授权人员已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -490,7 +490,7 @@ async def delete_training_authorization(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
record = await startup_crud.get_training_authorization(db, record_id)
|
||||
if not record or record.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Training authorization not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="培训授权人员不存在")
|
||||
await startup_crud.delete_training_authorization(db, record)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -498,7 +498,7 @@ async def delete_training_authorization(
|
||||
entity_type="training_authorization",
|
||||
entity_id=record_id,
|
||||
action="DELETE_TRAINING_AUTH",
|
||||
detail="Training authorization deleted",
|
||||
detail="培训授权人员已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ async def create_study(
|
||||
) -> StudyRead:
|
||||
existing = await study_crud.get_by_code(db, study_in.code)
|
||||
if existing:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Study code already exists")
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="项目编号已存在")
|
||||
study = await study_crud.create(db, study_in, created_by=current_user.id)
|
||||
return study
|
||||
|
||||
@@ -48,7 +48,7 @@ async def get_study(
|
||||
) -> StudyRead:
|
||||
study = await study_crud.get(db, study_id)
|
||||
if not study:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -64,6 +64,6 @@ async def update_study(
|
||||
) -> StudyRead:
|
||||
study = await study_crud.get(db, study_id)
|
||||
if not study:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
updated = await study_crud.update(db, study, study_in)
|
||||
return updated
|
||||
|
||||
@@ -15,7 +15,7 @@ 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="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ async def create_history(
|
||||
) -> SubjectHistoryRead:
|
||||
await _ensure_study_exists(db, study_id)
|
||||
if history_in.subject_id != subject_id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Subject mismatch")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="受试者不匹配")
|
||||
history = await history_crud.create_history(db, study_id, history_in, created_by=current_user.id)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -42,7 +42,7 @@ async def create_history(
|
||||
entity_type="subject_history",
|
||||
entity_id=history.id,
|
||||
action="CREATE_SUBJECT_HISTORY",
|
||||
detail="Subject history created",
|
||||
detail="病史记录已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -80,7 +80,7 @@ async def get_history(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
history = await history_crud.get_history(db, history_id)
|
||||
if not history or history.study_id != study_id or history.subject_id != subject_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="History not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="病史记录不存在")
|
||||
return SubjectHistoryRead.model_validate(history)
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ async def update_history(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
history = await history_crud.get_history(db, history_id)
|
||||
if not history or history.study_id != study_id or history.subject_id != subject_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="History not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="病史记录不存在")
|
||||
history = await history_crud.update_history(db, history, history_in)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -108,7 +108,7 @@ async def update_history(
|
||||
entity_type="subject_history",
|
||||
entity_id=history_id,
|
||||
action="UPDATE_SUBJECT_HISTORY",
|
||||
detail="Subject history updated",
|
||||
detail="病史记录已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -130,7 +130,7 @@ async def delete_history(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
history = await history_crud.get_history(db, history_id)
|
||||
if not history or history.study_id != study_id or history.subject_id != subject_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="History not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="病史记录不存在")
|
||||
await history_crud.delete_history(db, history)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -138,7 +138,7 @@ async def delete_history(
|
||||
entity_type="subject_history",
|
||||
entity_id=history_id,
|
||||
action="DELETE_SUBJECT_HISTORY",
|
||||
detail="Subject history deleted",
|
||||
detail="病史记录已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ 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="Study not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="项目不存在")
|
||||
return study
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ async def create_subject(
|
||||
entity_type="subject",
|
||||
entity_id=subject.id,
|
||||
action="CREATE_SUBJECT",
|
||||
detail=f"Subject {subject.subject_no} created",
|
||||
detail=f"受试者 {subject.subject_no} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -81,7 +81,7 @@ async def get_subject(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
subject = await subject_crud.get_subject(db, subject_id)
|
||||
if not subject or subject.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="受试者不存在")
|
||||
return subject
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ async def update_subject(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
subject = await subject_crud.get_subject(db, subject_id)
|
||||
if not subject or subject.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="受试者不存在")
|
||||
old_status = subject.status
|
||||
updated = await subject_crud.update_subject(db, subject, subject_in)
|
||||
# auto-generate visits when enrolled
|
||||
@@ -108,14 +108,14 @@ async def update_subject(
|
||||
await subject_crud.generate_default_visits(db, updated)
|
||||
detail = None
|
||||
if subject_in.status and subject_in.status != old_status:
|
||||
detail = f"subject {updated.subject_no} status {old_status} -> {subject_in.status}"
|
||||
detail = f"受试者 {updated.subject_no} 状态 {old_status} -> {subject_in.status}"
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
study_id=study_id,
|
||||
entity_type="subject",
|
||||
entity_id=subject_id,
|
||||
action="SUBJECT_STATUS_CHANGE" if detail else "UPDATE_SUBJECT",
|
||||
detail=detail or "Subject updated",
|
||||
detail=detail or "受试者已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -136,7 +136,7 @@ async def delete_subject(
|
||||
await _ensure_study_exists(db, study_id)
|
||||
subject = await subject_crud.get_subject(db, subject_id)
|
||||
if not subject or subject.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="受试者不存在")
|
||||
await subject_crud.delete_subject(db, subject)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -144,7 +144,7 @@ async def delete_subject(
|
||||
entity_type="subject",
|
||||
entity_id=subject_id,
|
||||
action="DELETE_SUBJECT",
|
||||
detail=f"Subject {subject_id} deleted",
|
||||
detail=f"受试者 {subject_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
@@ -36,7 +36,7 @@ async def create_user(
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Email already exists",
|
||||
detail="邮箱已存在",
|
||||
)
|
||||
user = await user_crud.create_user(db, user_in)
|
||||
return user
|
||||
@@ -51,7 +51,7 @@ async def update_user(
|
||||
) -> UserRead:
|
||||
db_user = await user_crud.get_by_id(db, user_id)
|
||||
if not db_user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
if db_user.role.value == "ADMIN":
|
||||
requested_role = user_in.role
|
||||
requested_status = user_in.status
|
||||
@@ -79,7 +79,7 @@ async def delete_user(
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="管理员密码错误")
|
||||
db_user = await user_crud.get_by_id(db, user_id)
|
||||
if not db_user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
if db_user.id == current_user.id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="不允许删除自己")
|
||||
if db_user.role.value == "ADMIN" and db_user.status.value == "ACTIVE":
|
||||
|
||||
@@ -15,7 +15,7 @@ router = APIRouter()
|
||||
async def _ensure_subject(db: AsyncSession, study_id: uuid.UUID, subject_id: uuid.UUID):
|
||||
subject = await subject_crud.get_subject(db, subject_id)
|
||||
if not subject or subject.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="受试者不存在")
|
||||
return subject
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ async def create_visit(
|
||||
) -> VisitRead:
|
||||
subject = await _ensure_subject(db, study_id, subject_id)
|
||||
if visit_in.subject_id != subject_id:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Subject mismatch")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="受试者不匹配")
|
||||
visit = await visit_crud.create_visit(
|
||||
db,
|
||||
study_id=study_id,
|
||||
@@ -65,7 +65,7 @@ async def create_visit(
|
||||
entity_type="visit",
|
||||
entity_id=visit.id,
|
||||
action="CREATE_VISIT",
|
||||
detail=f"Visit {visit.visit_code} created",
|
||||
detail=f"访视 {visit.visit_code} 已创建",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -88,20 +88,20 @@ async def update_visit(
|
||||
await _ensure_subject(db, study_id, subject_id)
|
||||
visit = await visit_crud.get_visit(db, visit_id)
|
||||
if not visit or visit.subject_id != subject_id or visit.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Visit not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="访视不存在")
|
||||
if visit_in.status == "DONE" and not visit_in.actual_date:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="actual_date required when DONE")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="状态为已完成时必须填写实际日期")
|
||||
updated = await visit_crud.update_visit(db, visit, visit_in)
|
||||
detail = None
|
||||
if visit_in.status:
|
||||
detail = f"visit {visit.visit_code} {visit.status} -> {visit_in.status}"
|
||||
detail = f"访视 {visit.visit_code} {visit.status} -> {visit_in.status}"
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
study_id=study_id,
|
||||
entity_type="visit",
|
||||
entity_id=visit_id,
|
||||
action="VISIT_STATUS_CHANGE" if detail else "UPDATE_VISIT",
|
||||
detail=detail or "Visit updated",
|
||||
detail=detail or "访视已更新",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
@@ -123,7 +123,7 @@ async def delete_visit(
|
||||
await _ensure_subject(db, study_id, subject_id)
|
||||
visit = await visit_crud.get_visit(db, visit_id)
|
||||
if not visit or visit.subject_id != subject_id or visit.study_id != study_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Visit not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="访视不存在")
|
||||
await visit_crud.delete_visit(db, visit)
|
||||
await audit_crud.log_action(
|
||||
db,
|
||||
@@ -131,7 +131,7 @@ async def delete_visit(
|
||||
entity_type="visit",
|
||||
entity_id=visit_id,
|
||||
action="DELETE_VISIT",
|
||||
detail=f"Visit {visit_id} deleted",
|
||||
detail=f"访视 {visit_id} 已删除",
|
||||
operator_id=current_user.id,
|
||||
operator_role=current_user.role,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user