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 数据库迁移配置 - 添加初始化示例数据脚本 - 更新项目文档
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
"""
|
||||
异常处理服务
|
||||
|
||||
处理异常的查询、更新、批量操作等
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy import select, update, func, and_, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.exception_item import ExceptionItem, ExceptionStatus
|
||||
from app.models.reconciliation_task import ReconciliationTask
|
||||
|
||||
|
||||
class ExceptionService:
|
||||
"""异常处理服务"""
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
async def get_exceptions(
|
||||
self,
|
||||
task_id: Optional[int] = None,
|
||||
company_id: Optional[int] = None,
|
||||
status: Optional[str] = None,
|
||||
severity: Optional[str] = None,
|
||||
exception_type: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> Tuple[List[ExceptionItem], int]:
|
||||
"""
|
||||
查询异常列表
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
company_id: 企业ID
|
||||
status: 状态
|
||||
severity: 严重程度
|
||||
exception_type: 异常类型
|
||||
page: 页码
|
||||
page_size: 每页数量
|
||||
|
||||
Returns:
|
||||
(异常列表, 总数)
|
||||
"""
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
query = select(ExceptionItem).options(selectinload(ExceptionItem.handler))
|
||||
count_query = select(func.count(ExceptionItem.id))
|
||||
|
||||
# 条件
|
||||
filters = []
|
||||
if task_id:
|
||||
filters.append(ExceptionItem.task_id == task_id)
|
||||
if company_id:
|
||||
filters.append(ExceptionItem.company_id == company_id)
|
||||
if status:
|
||||
filters.append(ExceptionItem.status == status)
|
||||
if severity:
|
||||
filters.append(ExceptionItem.severity == severity)
|
||||
if exception_type:
|
||||
filters.append(ExceptionItem.exception_type == exception_type)
|
||||
|
||||
if filters:
|
||||
query = query.where(and_(*filters))
|
||||
count_query = count_query.where(and_(*filters))
|
||||
|
||||
# 总数
|
||||
total_result = await self.db.execute(count_query)
|
||||
total = total_result.scalar() or 0
|
||||
|
||||
# 分页
|
||||
query = query.order_by(
|
||||
ExceptionItem.created_at.desc()
|
||||
).offset((page - 1) * page_size).limit(page_size)
|
||||
|
||||
result = await self.db.execute(query)
|
||||
exceptions = result.scalars().all()
|
||||
|
||||
return list(exceptions), total
|
||||
|
||||
async def get_exception(self, exception_id: int) -> Optional[ExceptionItem]:
|
||||
"""获取单个异常"""
|
||||
return await self.db.get(ExceptionItem, exception_id)
|
||||
|
||||
async def update_exception(
|
||||
self,
|
||||
exception_id: int,
|
||||
status: Optional[str] = None,
|
||||
handling_note: Optional[str] = None,
|
||||
handled_by: Optional[int] = None,
|
||||
) -> Optional[ExceptionItem]:
|
||||
"""
|
||||
更新异常
|
||||
|
||||
Args:
|
||||
exception_id: 异常ID
|
||||
status: 新状态
|
||||
handling_note: 处理备注
|
||||
handled_by: 处理人ID
|
||||
|
||||
Returns:
|
||||
更新后的异常
|
||||
"""
|
||||
exception = await self.db.get(ExceptionItem, exception_id)
|
||||
if not exception:
|
||||
return None
|
||||
|
||||
if status:
|
||||
exception.status = status
|
||||
if handling_note:
|
||||
exception.handling_note = handling_note
|
||||
if handled_by:
|
||||
exception.handled_by = handled_by
|
||||
|
||||
exception.updated_at = datetime.utcnow()
|
||||
|
||||
await self.db.commit()
|
||||
await self.db.refresh(exception)
|
||||
|
||||
return exception
|
||||
|
||||
async def batch_update_status(
|
||||
self,
|
||||
exception_ids: List[int],
|
||||
status: str,
|
||||
) -> int:
|
||||
"""
|
||||
批量更新状态
|
||||
|
||||
Args:
|
||||
exception_ids: 异常ID列表
|
||||
status: 新状态
|
||||
|
||||
Returns:
|
||||
更新的数量
|
||||
"""
|
||||
if not exception_ids:
|
||||
return 0
|
||||
|
||||
stmt = (
|
||||
update(ExceptionItem)
|
||||
.where(ExceptionItem.id.in_(exception_ids))
|
||||
.values(
|
||||
status=status,
|
||||
updated_at=datetime.utcnow(),
|
||||
)
|
||||
)
|
||||
|
||||
result = await self.db.execute(stmt)
|
||||
await self.db.commit()
|
||||
|
||||
return result.rowcount or 0
|
||||
|
||||
async def batch_resolve(
|
||||
self,
|
||||
exception_ids: List[int],
|
||||
handling_note: str,
|
||||
handled_by: Optional[int] = None,
|
||||
) -> int:
|
||||
"""
|
||||
批量处理异常
|
||||
|
||||
Args:
|
||||
exception_ids: 异常ID列表
|
||||
handling_note: 处理备注
|
||||
handled_by: 处理人ID
|
||||
|
||||
Returns:
|
||||
处理的数量
|
||||
"""
|
||||
if not exception_ids:
|
||||
return 0
|
||||
|
||||
update_data = {
|
||||
"status": ExceptionStatus.RESOLVED.value,
|
||||
"handling_note": handling_note,
|
||||
"updated_at": datetime.utcnow(),
|
||||
}
|
||||
if handled_by:
|
||||
update_data["handled_by"] = handled_by
|
||||
|
||||
stmt = (
|
||||
update(ExceptionItem)
|
||||
.where(ExceptionItem.id.in_(exception_ids))
|
||||
.values(**update_data)
|
||||
)
|
||||
|
||||
result = await self.db.execute(stmt)
|
||||
await self.db.commit()
|
||||
|
||||
return result.rowcount or 0
|
||||
|
||||
async def get_exception_summary(
|
||||
self,
|
||||
task_id: Optional[int] = None,
|
||||
company_id: Optional[int] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
获取异常汇总统计
|
||||
|
||||
Args:
|
||||
task_id: 任务ID
|
||||
company_id: 企业ID
|
||||
|
||||
Returns:
|
||||
汇总统计
|
||||
"""
|
||||
# 按状态统计
|
||||
status_query = select(
|
||||
ExceptionItem.status,
|
||||
func.count(ExceptionItem.id).label("count")
|
||||
).group_by(ExceptionItem.status)
|
||||
|
||||
# 按严重程度统计
|
||||
severity_query = select(
|
||||
ExceptionItem.severity,
|
||||
func.count(ExceptionItem.id).label("count")
|
||||
).group_by(ExceptionItem.severity)
|
||||
|
||||
# 按类型统计
|
||||
type_query = select(
|
||||
ExceptionItem.exception_type,
|
||||
func.count(ExceptionItem.id).label("count")
|
||||
).group_by(ExceptionItem.exception_type)
|
||||
|
||||
# 条件
|
||||
filters = []
|
||||
if task_id:
|
||||
filters.append(ExceptionItem.task_id == task_id)
|
||||
if company_id:
|
||||
filters.append(ExceptionItem.company_id == company_id)
|
||||
|
||||
if filters:
|
||||
status_query = status_query.where(and_(*filters))
|
||||
severity_query = severity_query.where(and_(*filters))
|
||||
type_query = type_query.where(and_(*filters))
|
||||
|
||||
# 执行查询
|
||||
status_result = await self.db.execute(status_query)
|
||||
severity_result = await self.db.execute(severity_query)
|
||||
type_result = await self.db.execute(type_query)
|
||||
|
||||
return {
|
||||
"by_status": {
|
||||
row.status: row.count for row in status_result.all()
|
||||
},
|
||||
"by_severity": {
|
||||
row.severity: row.count for row in severity_result.all()
|
||||
},
|
||||
"by_type": {
|
||||
row.exception_type: row.count for row in type_result.all()
|
||||
},
|
||||
}
|
||||
|
||||
async def get_pending_exceptions(
|
||||
self,
|
||||
company_id: int,
|
||||
limit: int = 10,
|
||||
) -> List[ExceptionItem]:
|
||||
"""获取待处理的异常"""
|
||||
query = (
|
||||
select(ExceptionItem)
|
||||
.where(
|
||||
and_(
|
||||
ExceptionItem.company_id == company_id,
|
||||
ExceptionItem.status == ExceptionStatus.PENDING.value,
|
||||
)
|
||||
)
|
||||
.order_by(ExceptionItem.created_at.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
|
||||
result = await self.db.execute(query)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
# 便捷函数
|
||||
async def get_exceptions(
|
||||
db: AsyncSession,
|
||||
**kwargs,
|
||||
) -> Tuple[List[ExceptionItem], int]:
|
||||
"""查询异常列表"""
|
||||
service = ExceptionService(db)
|
||||
return await service.get_exceptions(**kwargs)
|
||||
|
||||
|
||||
async def update_exception(
|
||||
db: AsyncSession,
|
||||
exception_id: int,
|
||||
**kwargs,
|
||||
) -> Optional[ExceptionItem]:
|
||||
"""更新异常"""
|
||||
service = ExceptionService(db)
|
||||
return await service.update_exception(exception_id, **kwargs)
|
||||
|
||||
|
||||
async def batch_resolve_exceptions(
|
||||
db: AsyncSession,
|
||||
exception_ids: List[int],
|
||||
handling_note: str,
|
||||
handled_by: Optional[int] = None,
|
||||
) -> int:
|
||||
"""批量处理异常"""
|
||||
service = ExceptionService(db)
|
||||
return await service.batch_resolve(exception_ids, handling_note, handled_by)
|
||||
Reference in New Issue
Block a user