新增风险项撤回接口和审计记录

This commit is contained in:
kx_guo 2026-09-18 17:31:03 +08:00
parent e965aad724
commit 6559386871
3 changed files with 70 additions and 0 deletions

View File

@ -49,6 +49,16 @@ async def update_risk_item(
return ApiResponse(data=await RiskItemService(session).update(item_id, payload, current_user))
@router.post("/{item_id}/withdraw", response_model=ApiResponse[None])
async def withdraw_risk_item(
item_id: int,
session: DatabaseSession,
current_user: Annotated[UserAccount, Depends(require_permission("risk_item.review", operate=True))],
) -> ApiResponse[None]:
await RiskItemService(session).withdraw(item_id, current_user)
return ApiResponse(data=None, message="风险项已撤回")
@router.post("/{item_id}/review", response_model=ApiResponse[ReviewRecordData])
async def review_risk_item(
item_id: int,

View File

@ -68,6 +68,8 @@ class RiskItemService:
filters = []
if status != "all":
filters.append(RiskItem.status == status)
else:
filters.append(RiskItem.status != "已撤回")
if source_type != "all":
filters.append(RiskItem.source_type == source_type)
statement = select(RiskItem).where(*filters).order_by(RiskItem.created_at.desc()).limit(limit)
@ -105,6 +107,26 @@ class RiskItemService:
await self.session.flush()
return self.to_data(item)
async def withdraw(self, item_id: int, operator: UserAccount) -> None:
item = await self.session.get(RiskItem, item_id)
if item is None:
raise AppException("风险项不存在", code=404, status_code=404)
if item.status not in {"待审核", "待预审"}:
raise AppException("只有待处理风险项可以撤回", code=409, status_code=409)
previous_status = item.status
item.status = "已撤回"
self.session.add(
OperationAudit(
actor_user_id=operator.id,
actor_emp_no=operator.emp_no,
action="risk_item.withdraw",
resource_type="risk_item",
resource_id=str(item.id),
audit_metadata={"previous_status": previous_status},
)
)
await self.session.flush()
async def review(self, item_id: int, action: str, note: str, reviewer: UserAccount) -> ReviewRecordData:
item = await self.session.get(RiskItem, item_id)
if item is None:

View File

@ -0,0 +1,38 @@
from unittest.mock import AsyncMock, MagicMock
import pytest
from app.core.exceptions import AppException
from app.models.entities import OperationAudit, RiskItem, UserAccount
from app.services.risk_items import RiskItemService
@pytest.mark.asyncio
async def test_pending_risk_item_can_be_withdrawn_with_audit() -> None:
item = RiskItem(id=12, source_type="表单", status="待审核", photos=[], ocr_payload={})
operator = UserAccount(id=3, emp_no="admin-1", name="管理员", role="admin", status="启用")
session = MagicMock()
session.get = AsyncMock(return_value=item)
session.flush = AsyncMock()
await RiskItemService(session).withdraw(item.id, operator)
assert item.status == "已撤回"
audit = next(call.args[0] for call in session.add.call_args_list if isinstance(call.args[0], OperationAudit))
assert audit.action == "risk_item.withdraw"
assert audit.audit_metadata == {"previous_status": "待审核"}
session.flush.assert_awaited_once()
@pytest.mark.asyncio
async def test_reviewed_risk_item_cannot_be_withdrawn() -> None:
item = RiskItem(id=13, source_type="表单", status="已通过", photos=[], ocr_payload={})
operator = UserAccount(id=3, emp_no="admin-1", name="管理员", role="admin", status="启用")
session = MagicMock()
session.get = AsyncMock(return_value=item)
with pytest.raises(AppException, match="只有待处理风险项可以撤回"):
await RiskItemService(session).withdraw(item.id, operator)
session.add.assert_not_called()