Files
selfrelease 94be6189e9 feat: T1.5 风险工作台 — 后端 CRUD + 前端风险列表
- 后端:risks 路由(列表/详情/创建/更新/删除)+ 状态流转
- 前端:风险工作台页(卡片列表、状态筛选、内联状态切换、删除)
- 测试:6 个风险 CRUD 测试(总计 43 tests passed)
- 前端构建 13 路由成功
2026-07-18 22:06:30 +08:00

161 lines
5.2 KiB
Python

"""风险事件路由:CRUD + 状态流转。"""
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.dependencies import get_current_user
from app.models.company import Company
from app.models.risk import RiskEvent
from app.models.user import User
from app.schemas.common import ApiResponse, success
from app.schemas.risk import (
RiskEventCreate,
RiskEventListResponse,
RiskEventResponse,
RiskEventUpdate,
)
router = APIRouter(prefix="/risks", tags=["risks"])
@router.get("", response_model=ApiResponse[RiskEventListResponse])
async def list_risks(
company_id: str | None = Query(default=None),
status_filter: str | None = Query(default=None, alias="status"),
severity: str | None = Query(default=None),
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=100),
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取风险事件列表。"""
query = (
select(RiskEvent)
.join(Company, RiskEvent.company_id == Company.id)
.where(Company.tenant_id == user.tenant_id)
)
if company_id:
query = query.where(RiskEvent.company_id == company_id)
if status_filter:
query = query.where(RiskEvent.status == status_filter)
if severity:
query = query.where(RiskEvent.severity == severity)
count_query = select(func.count()).select_from(query.subquery())
total_result = await db.execute(count_query)
total = total_result.scalar_one()
offset = (page - 1) * page_size
query = query.order_by(RiskEvent.identified_at.desc()).offset(offset).limit(page_size)
result = await db.execute(query)
risks = result.scalars().all()
return success(
data=RiskEventListResponse(
items=[RiskEventResponse.model_validate(r, from_attributes=True) for r in risks],
total=total,
page=page,
page_size=page_size,
)
)
@router.get("/{risk_id}", response_model=ApiResponse[RiskEventResponse])
async def get_risk(
risk_id: str,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""获取风险事件详情。"""
result = await db.execute(
select(RiskEvent)
.join(Company, RiskEvent.company_id == Company.id)
.where(RiskEvent.id == risk_id, Company.tenant_id == user.tenant_id)
)
risk = result.scalar_one_or_none()
if not risk:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="风险事件不存在")
return success(data=RiskEventResponse.model_validate(risk, from_attributes=True))
@router.post("", response_model=ApiResponse[RiskEventResponse], status_code=status.HTTP_201_CREATED)
async def create_risk(
req: RiskEventCreate,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""创建风险事件。"""
# 验证企业属于租户
company_result = await db.execute(
select(Company).where(Company.id == req.company_id, Company.tenant_id == user.tenant_id)
)
if not company_result.scalar_one_or_none():
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在")
risk = RiskEvent(
company_id=req.company_id,
type=req.type,
severity=req.severity,
title=req.title,
description=req.description,
suggested_action=req.suggested_action,
status="open",
)
db.add(risk)
await db.flush()
return success(
data=RiskEventResponse.model_validate(risk, from_attributes=True),
message="创建成功",
)
@router.put("/{risk_id}", response_model=ApiResponse[RiskEventResponse])
async def update_risk(
risk_id: str,
req: RiskEventUpdate,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""更新风险事件(状态流转等)。"""
result = await db.execute(
select(RiskEvent)
.join(Company, RiskEvent.company_id == Company.id)
.where(RiskEvent.id == risk_id, Company.tenant_id == user.tenant_id)
)
risk = result.scalar_one_or_none()
if not risk:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="风险事件不存在")
update_data = req.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(risk, key, value)
await db.flush()
return success(
data=RiskEventResponse.model_validate(risk, from_attributes=True),
message="更新成功",
)
@router.delete("/{risk_id}", response_model=ApiResponse[None])
async def delete_risk(
risk_id: str,
db: AsyncSession = Depends(get_db),
user: User = Depends(get_current_user),
):
"""删除风险事件。"""
result = await db.execute(
select(RiskEvent)
.join(Company, RiskEvent.company_id == Company.id)
.where(RiskEvent.id == risk_id, Company.tenant_id == user.tenant_id)
)
risk = result.scalar_one_or_none()
if not risk:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="风险事件不存在")
await db.delete(risk)
return success(message="删除成功")