Files
s2f/backend/app/api/reconciliation.py
T
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

141 lines
4.5 KiB
Python

"""
对账执行 API
执行对账任务
"""
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Depends, HTTPException
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.reconciliation_task import ReconciliationTask
from app.services.reconciliation.engine import run_reconciliation
router = APIRouter(prefix="/api/reconciliation", tags=["对账执行"])
@router.post("/execute/{task_id}")
async def execute_reconciliation(
task_id: int,
db: AsyncSession = Depends(get_db),
company_id: int = Depends(get_current_company_id),
) -> Dict[str, Any]:
"""
执行对账
触发对账引擎,处理上传的数据并返回结果
"""
# 获取任务
task = await db.get(ReconciliationTask, task_id)
if not task:
raise HTTPException(status_code=404, detail="任务不存在")
if task.company_id != company_id:
raise HTTPException(status_code=403, detail="无权访问该任务")
if task.status not in ["MAPPING_COMPLETED", "DATA_UPLOADED"]:
raise HTTPException(status_code=400, detail="任务状态不允许执行对账")
try:
# 获取上传的数据
salary_data = task.salary_data or []
social_security_data = task.social_security_data or []
tax_data = task.tax_data or []
bank_data = task.bank_data if hasattr(task, 'bank_data') else None
# 执行对账
result = await run_reconciliation(
db=db,
company_id=company_id,
task_id=task_id,
period=task.period,
salary_records=salary_data,
social_security_records=social_security_data,
tax_records=tax_data,
bank_records=bank_data,
enable_bank_rules=bool(bank_data),
)
return {
"success": True,
"task_id": task_id,
"result": {
"total_employees": result.total_employees,
"matched_employees": result.matched_employees,
"exception_count": result.exception_count,
"exceptions_by_type": result.exceptions_by_type,
"exceptions_by_severity": result.exceptions_by_severity,
"execution_time_ms": result.execution_time_ms,
"completed_at": result.completed_at,
},
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"对账执行失败: {str(e)}")
@router.get("/result/{task_id}")
async def get_reconciliation_result(
task_id: int,
db: AsyncSession = Depends(get_db),
company_id: int = Depends(get_current_company_id),
) -> Dict[str, Any]:
"""
获取对账结果
返回已执行的对账结果汇总
"""
task = await db.get(ReconciliationTask, task_id)
if not task:
raise HTTPException(status_code=404, detail="任务不存在")
if task.company_id != company_id:
raise HTTPException(status_code=403, detail="无权访问该任务")
# 获取异常统计
from app.services.exception_service import ExceptionService
service = ExceptionService(db)
summary = await service.get_exception_summary(task_id=task_id, company_id=company_id)
return {
"task_id": task_id,
"period": task.period,
"total_employees": task.total_employees,
"matched_employees": task.matched_count,
"exception_count": task.exception_count,
"exceptions_by_type": summary.get("by_type", {}),
"exceptions_by_severity": summary.get("by_severity", {}),
"exceptions_by_status": summary.get("by_status", {}),
"completed_at": task.updated_at.isoformat() if task.updated_at else None,
}
@router.post("/retry/{task_id}")
async def retry_reconciliation(
task_id: int,
db: AsyncSession = Depends(get_db),
company_id: int = Depends(get_current_company_id),
) -> Dict[str, Any]:
"""
重试对账
重新执行已完成的或失败的对账任务
"""
task = await db.get(ReconciliationTask, task_id)
if not task:
raise HTTPException(status_code=404, detail="任务不存在")
if task.company_id != company_id:
raise HTTPException(status_code=403, detail="无权访问该任务")
# 重置任务状态
task.status = "PROCESSING"
task.matched_count = 0
task.exception_count = 0
await db.commit()
# 执行对账
return await execute_reconciliation(task_id, db, company_id)