33b4c734aa
后端: - 新增认证(auth)、任务(tasks)、映射(mappings)、对账(reconciliation)、异常(exceptions)、导出(exports) API - 新增核心模块: database, security, permissions, tenant, exceptions, error_handlers - 新增数据模型: user, company, reconciliation_task, field_mapping, uploaded_file 等 - 新增服务层: ai_recognizer, file_parser, file_storage, mapping, reconciliation 等 - 添加数据库迁移脚本 前端: - 新增登录页面和仪表盘页面 - 新增任务列表、任务详情、字段映射页面 - 新增异常处理页面和规则设置页面 - 新增 API 代理路由 /api/[...path] - 新增 UI 组件库 (button, card, dialog, input, table 等) - 新增 auth 组件 (ProtectedRoute, PermissionGate) - 新增 layout 组件 (Header, Sidebar) - 新增 mapping 组件 (FieldMappingTable, AISuggestionPanel) - 新增 API 客户端和 hooks (useAsync, useToast, usePermission 等) - 新增状态管理 (auth-store, company-store, ui-store) - 集成 Tailwind CSS 和 shadcn/ui 组件库 其他: - 添加 Alembic 数据库迁移配置 - 添加初始化示例数据脚本 - 更新项目文档
219 lines
7.6 KiB
Python
219 lines
7.6 KiB
Python
"""
|
|
异常处理 API
|
|
|
|
提供异常的查询、更新、批量操作等接口
|
|
"""
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, Body
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.database import get_db
|
|
from app.core.tenant import get_current_company_id
|
|
from app.models.exception_item import ExceptionItem
|
|
from app.services.exception_service import (
|
|
get_exceptions,
|
|
update_exception,
|
|
batch_resolve_exceptions,
|
|
ExceptionService,
|
|
)
|
|
|
|
|
|
router = APIRouter(prefix="/api/exceptions", tags=["异常处理"])
|
|
|
|
|
|
@router.get("/")
|
|
async def list_exceptions(
|
|
task_id: Optional[int] = Query(None, description="任务ID"),
|
|
status: Optional[str] = Query(None, description="状态"),
|
|
severity: Optional[str] = Query(None, description="严重程度"),
|
|
exception_type: Optional[str] = Query(None, description="异常类型"),
|
|
page: int = Query(1, ge=1, description="页码"),
|
|
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
|
db: AsyncSession = Depends(get_db),
|
|
company_id: int = Depends(get_current_company_id),
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
查询异常列表
|
|
|
|
支持按任务、状态、严重程度、类型筛选
|
|
"""
|
|
exceptions, total = await get_exceptions(
|
|
db=db,
|
|
task_id=task_id,
|
|
company_id=company_id,
|
|
status=status,
|
|
severity=severity,
|
|
exception_type=exception_type,
|
|
page=page,
|
|
page_size=page_size,
|
|
)
|
|
|
|
return {
|
|
"items": [
|
|
{
|
|
"id": e.id,
|
|
"task_id": e.task_id,
|
|
"exception_type": e.exception_type,
|
|
"severity": e.severity.lower() if e.severity else None,
|
|
"status": e.status.lower() if e.status else None,
|
|
"employee_id": e.employee_id,
|
|
"employee_name": e.employee_name,
|
|
"description": e.description,
|
|
"detail": e.detail,
|
|
"salary_amount": e.salary_amount,
|
|
"social_security_amount": e.social_security_amount,
|
|
"tax_amount": e.tax_amount,
|
|
"bank_amount": e.bank_amount,
|
|
"difference_amount": e.difference_amount,
|
|
"suggested_action": e.suggested_action,
|
|
"ai_suggestion": e.ai_suggestion,
|
|
"handling_note": e.handling_note,
|
|
"handled_by": e.handled_by,
|
|
"handler_name": e.handler.full_name if e.handler else None,
|
|
"handled_at": e.handled_at.isoformat() if e.handled_at else None,
|
|
"created_at": e.created_at.isoformat() if e.created_at else None,
|
|
"updated_at": e.updated_at.isoformat() if e.updated_at else None,
|
|
}
|
|
for e in exceptions
|
|
],
|
|
"total": total,
|
|
"page": page,
|
|
"page_size": page_size,
|
|
}
|
|
|
|
|
|
@router.get("/summary")
|
|
async def get_exception_summary(
|
|
task_id: Optional[int] = Query(None, description="任务ID"),
|
|
db: AsyncSession = Depends(get_db),
|
|
company_id: int = Depends(get_current_company_id),
|
|
) -> Dict[str, Any]:
|
|
"""获取异常汇总统计"""
|
|
service = ExceptionService(db)
|
|
summary = await service.get_exception_summary(
|
|
task_id=task_id,
|
|
company_id=company_id,
|
|
)
|
|
return summary
|
|
|
|
|
|
@router.get("/{exception_id}")
|
|
async def get_exception_detail(
|
|
exception_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
company_id: int = Depends(get_current_company_id),
|
|
) -> Dict[str, Any]:
|
|
"""获取异常详情"""
|
|
service = ExceptionService(db)
|
|
exception = await service.get_exception(exception_id)
|
|
|
|
if not exception:
|
|
raise HTTPException(status_code=404, detail="异常不存在")
|
|
|
|
if exception.company_id != company_id:
|
|
raise HTTPException(status_code=403, detail="无权访问该异常")
|
|
|
|
return {
|
|
"id": exception.id,
|
|
"task_id": exception.task_id,
|
|
"exception_type": exception.exception_type,
|
|
"severity": exception.severity,
|
|
"status": exception.status,
|
|
"employee_id": exception.employee_id,
|
|
"employee_name": exception.employee_name,
|
|
"description": exception.description,
|
|
"detail": exception.detail,
|
|
"salary_amount": exception.salary_amount,
|
|
"social_security_amount": exception.social_security_amount,
|
|
"tax_amount": exception.tax_amount,
|
|
"bank_amount": exception.bank_amount,
|
|
"difference_amount": exception.difference_amount,
|
|
"suggested_action": exception.suggested_action,
|
|
"ai_suggestion": exception.ai_suggestion,
|
|
"handling_note": exception.handling_note,
|
|
"handled_by": exception.handled_by,
|
|
"handler_name": exception.handler.full_name if exception.handler else None,
|
|
"handled_at": exception.handled_at.isoformat() if exception.handled_at else None,
|
|
"created_at": exception.created_at.isoformat() if exception.created_at else None,
|
|
"updated_at": exception.updated_at.isoformat() if exception.updated_at else None,
|
|
}
|
|
|
|
|
|
@router.patch("/{exception_id}")
|
|
async def update_exception_status(
|
|
exception_id: int,
|
|
status: Optional[str] = Body(None),
|
|
handling_note: Optional[str] = Body(None),
|
|
db: AsyncSession = Depends(get_db),
|
|
company_id: int = Depends(get_current_company_id),
|
|
):
|
|
"""更新异常状态"""
|
|
service = ExceptionService(db)
|
|
|
|
# 检查权限
|
|
exception = await service.get_exception(exception_id)
|
|
if not exception:
|
|
raise HTTPException(status_code=404, detail="异常不存在")
|
|
if exception.company_id != company_id:
|
|
raise HTTPException(status_code=403, detail="无权访问该异常")
|
|
|
|
updated = await service.update_exception(
|
|
exception_id=exception_id,
|
|
status=status,
|
|
handling_note=handling_note,
|
|
)
|
|
|
|
return {"success": True, "data": updated}
|
|
|
|
|
|
@router.post("/batch-update")
|
|
async def batch_update_exceptions(
|
|
exception_ids: List[int] = Body(..., description="异常ID列表"),
|
|
status: str = Body(..., description="新状态"),
|
|
db: AsyncSession = Depends(get_db),
|
|
company_id: int = Depends(get_current_company_id),
|
|
) -> Dict[str, Any]:
|
|
"""批量更新异常状态"""
|
|
service = ExceptionService(db)
|
|
|
|
# 检查权限
|
|
query = select(ExceptionItem).where(ExceptionItem.id.in_(exception_ids))
|
|
result = await db.execute(query)
|
|
exceptions = result.scalars().all()
|
|
|
|
# 验证权限
|
|
for e in exceptions:
|
|
if e.company_id != company_id:
|
|
raise HTTPException(status_code=403, detail="存在无权访问的异常")
|
|
|
|
count = await service.batch_update_status(exception_ids, status)
|
|
|
|
return {"success": True, "updated_count": count}
|
|
|
|
|
|
@router.post("/batch-resolve")
|
|
async def batch_resolve(
|
|
exception_ids: List[int] = Body(..., description="异常ID列表"),
|
|
handling_note: str = Body(..., description="处理备注"),
|
|
db: AsyncSession = Depends(get_db),
|
|
company_id: int = Depends(get_current_company_id),
|
|
) -> Dict[str, Any]:
|
|
"""批量处理异常(标记为已解决)"""
|
|
service = ExceptionService(db)
|
|
|
|
# 检查权限
|
|
query = select(ExceptionItem).where(ExceptionItem.id.in_(exception_ids))
|
|
result = await db.execute(query)
|
|
exceptions = result.scalars().all()
|
|
|
|
# 验证权限
|
|
for e in exceptions:
|
|
if e.company_id != company_id:
|
|
raise HTTPException(status_code=403, detail="存在无权访问的异常")
|
|
|
|
count = await service.batch_resolve(exception_ids, handling_note)
|
|
|
|
return {"success": True, "resolved_count": count} |