Files
freedakgmail 33b4c734aa feat: 完善前后端核心功能模块
后端:
- 新增认证(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 数据库迁移配置
- 添加初始化示例数据脚本
- 更新项目文档
2026-07-07 09:04:47 +08:00

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}