Files
s2f/backend/app/api/tasks.py
T
freedakgmail feebbb10ac refactor: 统一分页组件和UI优化
- 使用公共Pagination组件统一各页面的分页逻辑
- 优化exceptions页面异常列表展示
- 简化settings/rules页面规则管理
- 后端tasks.py增加分页参数支持
2026-07-07 13:53:54 +08:00

202 lines
6.6 KiB
Python

"""
对账任务 API
管理对账任务的生命周期
"""
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import Float
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
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.models.user import User
from app.schemas.user import UserResponse
router = APIRouter(prefix="/api/tasks", tags=["对账任务"])
@router.get("/", response_model=Dict[str, Any])
async def list_tasks(
period: Optional[str] = Query(None, description="筛选月份,格式: 2026-04"),
status: Optional[str] = Query(None, description="筛选状态"),
page: int = Query(1, ge=1, description="页码"),
page_size: int = Query(10, ge=1, le=100, description="每页数量"),
db: AsyncSession = Depends(get_db),
company_id: Optional[int] = Depends(get_current_company_id),
) -> Dict[str, Any]:
"""
获取对账任务列表
支持按月份和状态筛选,返回分页结构
"""
# 构建查询
query = select(ReconciliationTask).where(
ReconciliationTask.company_id == company_id
)
if period:
query = query.where(ReconciliationTask.period == period)
if status:
query = query.where(ReconciliationTask.status == status)
# 计算总数
count_query = select(func.count()).select_from(ReconciliationTask).where(
ReconciliationTask.company_id == company_id
)
if period:
count_query = count_query.where(ReconciliationTask.period == period)
if status:
count_query = count_query.where(ReconciliationTask.status == status)
count_result = await db.execute(count_query)
total = count_result.scalar() or 0
# 按创建时间倒序 + 分页
skip = (page - 1) * page_size
query = query.order_by(ReconciliationTask.created_at.desc())
query = query.offset(skip).limit(page_size)
result = await db.execute(query)
tasks = result.scalars().all()
return {
"items": [
{
"id": task.id,
"name": f"{task.period} 对账任务",
"period": task.period,
"status": task.status,
"total_employees": task.total_employees,
"matched_count": task.matched_count,
"exception_count": task.exception_count,
"created_at": task.created_at.isoformat() if task.created_at else None,
"completed_at": task.updated_at.isoformat() if task.status == "COMPLETED" and task.updated_at else None,
}
for task in tasks
],
"total": total,
"page": page,
"page_size": page_size,
"total_pages": (total + page_size - 1) // page_size if page_size > 0 else 0,
}
@router.get("/stats", response_model=Dict[str, Any])
async def get_task_stats(
db: AsyncSession = Depends(get_db),
company_id: int = Depends(get_current_company_id),
) -> Dict[str, Any]:
"""
获取任务统计信息
返回本月任务数、待处理异常、已完成、匹配率等统计
"""
from datetime import datetime
from dateutil.relativedelta import relativedelta
# 计算本月时间范围
now = datetime.now()
first_day_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
next_month = first_day_of_month + relativedelta(months=1)
current_period = now.strftime("%Y-%m")
# 统计本月任务
from sqlalchemy import case as sql_case
query = select(
func.count(ReconciliationTask.id).label("total"),
func.sum(
sql_case(
(ReconciliationTask.status == "COMPLETED", 1),
else_=0
)
).label("completed"),
func.sum(
sql_case(
(ReconciliationTask.exception_count > 0, 1),
else_=0
)
).label("with_exceptions"),
).where(
ReconciliationTask.company_id == company_id,
ReconciliationTask.period == current_period,
)
result = await db.execute(query)
row = result.one()
total_tasks = row.total or 0
completed_tasks = row.completed or 0
# 获取待处理异常数
from app.models.exception_item import ExceptionItem
exception_query = select(
func.count(ExceptionItem.id)
).where(
ExceptionItem.company_id == company_id,
ExceptionItem.status == "PENDING",
)
exception_result = await db.execute(exception_query)
pending_exceptions = exception_result.scalar() or 0
# 计算匹配率
match_rate = 0.0
if total_tasks > 0:
match_query = select(
func.avg(
func.cast(ReconciliationTask.matched_count * 100.0 / func.nullif(ReconciliationTask.total_employees, 0), Float)
)
).where(
ReconciliationTask.company_id == company_id,
ReconciliationTask.period == current_period,
ReconciliationTask.status == "COMPLETED",
)
match_result = await db.execute(match_query)
match_rate = match_result.scalar() or 0.0
return {
"monthly_tasks": total_tasks,
"pending_exceptions": pending_exceptions,
"completed_tasks": completed_tasks,
"match_rate": round(match_rate, 1),
"current_period": current_period,
}
@router.get("/{task_id}", response_model=Dict[str, Any])
async def get_task(
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="无权访问该任务")
return {
"id": task.id,
"name": f"{task.period} 对账任务",
"period": task.period,
"status": task.status,
"total_employees": task.total_employees,
"matched_count": task.matched_count,
"exception_count": task.exception_count,
"file_ids": task.file_ids,
"reconciliation_result": task.reconciliation_result,
"created_at": task.created_at.isoformat() if task.created_at else None,
"completed_at": task.updated_at.isoformat() if task.status == "COMPLETED" and task.updated_at else None,
"error_message": task.error_message,
}