215 lines
7.3 KiB
Python
215 lines
7.3 KiB
Python
from typing import Optional
|
|
from urllib.parse import quote
|
|
|
|
from fastapi import APIRouter, Depends, File, Form, Header, HTTPException, Request, Response, UploadFile
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.response import success_response
|
|
from app.db import get_db_session
|
|
from app.services.audit_log_service import AuditLogService
|
|
from app.services.auth_service import AuthService
|
|
from app.services.review_service import ReviewService
|
|
|
|
router = APIRouter(prefix="/reviews", tags=["reviews"])
|
|
ROUTE_PERMISSION_SCOPE = "review_workflow"
|
|
ROUTE_PERMISSION_ACTIONS = {
|
|
"create": "reviews:create",
|
|
"list": "reviews:list",
|
|
"detail": "reviews:detail",
|
|
"result": "reviews:result",
|
|
"report": "reviews:report:get",
|
|
"download_report": "reviews:report:download",
|
|
"retry": "reviews:retry",
|
|
}
|
|
|
|
|
|
def get_review_service(db: Session = Depends(get_db_session)) -> ReviewService:
|
|
return ReviewService(db)
|
|
|
|
|
|
def get_audit_log_service(db: Session = Depends(get_db_session)) -> AuditLogService:
|
|
return AuditLogService(db)
|
|
|
|
|
|
def require_current_user(
|
|
authorization: Optional[str] = Header(default=None),
|
|
db: Session = Depends(get_db_session),
|
|
):
|
|
return AuthService(db).require_current_user(authorization)
|
|
|
|
|
|
@router.post("")
|
|
async def create_review(
|
|
request: Request,
|
|
file: Optional[UploadFile] = File(default=None),
|
|
text: Optional[str] = Form(default=None),
|
|
contract_type: Optional[str] = Form(default=None),
|
|
review_as: str = Form(default="party_a"),
|
|
review_service: ReviewService = Depends(get_review_service),
|
|
audit_log_service: AuditLogService = Depends(get_audit_log_service),
|
|
current_user: dict = Depends(require_current_user),
|
|
):
|
|
normalized_text = text.strip() if text else None
|
|
file_name = file.filename if file else None
|
|
file_text: Optional[str] = None
|
|
|
|
if file is not None:
|
|
review_service.validate_review_upload(file_name=file_name, mime_type=file.content_type)
|
|
file_bytes = await file.read()
|
|
file_text = review_service.extract_text_from_file(file_bytes=file_bytes, file_name=file_name)
|
|
|
|
combined_text = review_service.merge_review_text(text=normalized_text, file_text=file_text)
|
|
|
|
if file is None and not combined_text:
|
|
return {"code": 40001, "message": "file 和 text 至少提供一个", "data": None}
|
|
|
|
if file is not None and not file_text and not normalized_text:
|
|
return {
|
|
"code": 40002,
|
|
"message": "无法从上传文件中提取可审查文本,请改为可复制文本 PDF / DOCX / TXT,或直接粘贴合同正文。",
|
|
"data": None,
|
|
}
|
|
|
|
result = review_service.create_review_task(
|
|
tenant_id=current_user["tenant_id"],
|
|
user_id=current_user["id"],
|
|
review_as=review_as,
|
|
contract_type=contract_type,
|
|
text=combined_text,
|
|
file_name=file_name,
|
|
)
|
|
audit_log_service.record_event(
|
|
tenant_id=current_user.get("tenant_id"),
|
|
user_id=current_user.get("id"),
|
|
action="create_review",
|
|
resource_type="review",
|
|
resource_id=result["id"],
|
|
detail_json={
|
|
"contract_type": contract_type,
|
|
"review_as": review_as,
|
|
"source_type": "file" if file is not None else "text",
|
|
"file_name": file_name,
|
|
"text_length": len(combined_text or ""),
|
|
"status": result.get("status"),
|
|
},
|
|
ip_address=request.client.host if request.client else None,
|
|
user_agent=request.headers.get("user-agent"),
|
|
)
|
|
return success_response(result)
|
|
|
|
|
|
@router.get("")
|
|
def list_reviews(
|
|
review_service: ReviewService = Depends(get_review_service),
|
|
_current_user: dict = Depends(require_current_user),
|
|
):
|
|
return success_response(review_service.list_reviews(_current_user))
|
|
|
|
|
|
@router.get("/{review_id}")
|
|
def get_review_detail(
|
|
review_id: str,
|
|
review_service: ReviewService = Depends(get_review_service),
|
|
_current_user: dict = Depends(require_current_user),
|
|
):
|
|
detail = review_service.get_review_detail(_current_user, review_id)
|
|
|
|
if detail is None:
|
|
raise HTTPException(status_code=404, detail="review not found")
|
|
|
|
return success_response(detail)
|
|
|
|
|
|
@router.post("/{review_id}/retry")
|
|
def retry_review(
|
|
review_id: str,
|
|
request: Request,
|
|
review_service: ReviewService = Depends(get_review_service),
|
|
audit_log_service: AuditLogService = Depends(get_audit_log_service),
|
|
current_user: dict = Depends(require_current_user),
|
|
):
|
|
result = review_service.retry_review_task(current_user, review_id)
|
|
|
|
if result is None:
|
|
raise HTTPException(status_code=404, detail="review not found")
|
|
|
|
audit_log_service.record_event(
|
|
tenant_id=current_user.get("tenant_id"),
|
|
user_id=current_user.get("id"),
|
|
action="retry_review",
|
|
resource_type="review",
|
|
resource_id=result["id"],
|
|
detail_json={
|
|
"original_review_id": review_id,
|
|
"new_review_id": result["id"],
|
|
"status": result.get("status"),
|
|
"current_stage": result.get("current_stage"),
|
|
"next_action": result.get("next_action"),
|
|
},
|
|
ip_address=request.client.host if request.client else None,
|
|
user_agent=request.headers.get("user-agent"),
|
|
)
|
|
return success_response(result, message="审查任务已重新发起")
|
|
|
|
|
|
@router.get("/{review_id}/result")
|
|
def get_review_result(
|
|
review_id: str,
|
|
review_service: ReviewService = Depends(get_review_service),
|
|
_current_user: dict = Depends(require_current_user),
|
|
):
|
|
result = review_service.get_review_result(_current_user, review_id)
|
|
|
|
if result is None:
|
|
raise HTTPException(status_code=404, detail="review not found")
|
|
|
|
return success_response(result)
|
|
|
|
|
|
@router.get("/{review_id}/report")
|
|
def get_review_report(
|
|
review_id: str,
|
|
review_service: ReviewService = Depends(get_review_service),
|
|
_current_user: dict = Depends(require_current_user),
|
|
):
|
|
report = review_service.get_review_report(_current_user, review_id)
|
|
|
|
if report is None:
|
|
raise HTTPException(status_code=404, detail="review not found")
|
|
|
|
return success_response(report)
|
|
|
|
|
|
@router.get("/{review_id}/report/download")
|
|
def download_review_report(
|
|
review_id: str,
|
|
request: Request,
|
|
review_service: ReviewService = Depends(get_review_service),
|
|
audit_log_service: AuditLogService = Depends(get_audit_log_service),
|
|
current_user: dict = Depends(require_current_user),
|
|
):
|
|
report = review_service.get_review_report(current_user, review_id)
|
|
|
|
if report is None:
|
|
raise HTTPException(status_code=404, detail="review not found")
|
|
|
|
content = review_service.build_report_pdf(current_user, review_id)
|
|
if content is None:
|
|
raise HTTPException(status_code=404, detail="review not found")
|
|
|
|
audit_log_service.record_event(
|
|
tenant_id=current_user.get("tenant_id"),
|
|
user_id=current_user.get("id"),
|
|
action="download_report",
|
|
resource_type="review",
|
|
resource_id=review_id,
|
|
detail_json={
|
|
"file_name": report["file_name"],
|
|
"download_url": report["download_url"],
|
|
},
|
|
ip_address=request.client.host if request.client else None,
|
|
user_agent=request.headers.get("user-agent"),
|
|
)
|
|
headers = {"Content-Disposition": f"attachment; filename*=UTF-8''{quote(report['file_name'])}"}
|
|
return Response(content=content, media_type="application/pdf", headers=headers)
|