全局中文化

This commit is contained in:
Cheng Zhou
2026-01-12 11:14:04 +08:00
parent 9021c7fe2b
commit 1338fa7e2b
103 changed files with 2443 additions and 1533 deletions
+1 -1
View File
@@ -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
View File
@@ -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,
)
+15 -15
View File
@@ -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,
)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+7 -7
View File
@@ -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,
)
+14 -14
View File
@@ -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
View File
@@ -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,
)
+7 -7
View File
@@ -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,
)
+7 -7
View File
@@ -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,
)
+7 -7
View File
@@ -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,
)
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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
+25 -25
View File
@@ -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,
)
+3 -3
View File
@@ -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
+8 -8
View File
@@ -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,
)
+8 -8
View File
@@ -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,
)
+3 -3
View File
@@ -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":
+9 -9
View File
@@ -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,
)