"""业务工单链:认领 → 提交审核 → 复核(软校验职责分离,流转写审计)。 状态机:待处理 ─认领─► 处理中 ─提交审核─► 待审核 ─复核─► 已完成 / 已驳回(终态)。 流转均用条件更新防并发跳步,每次流转写 audit_log。 """ from __future__ import annotations from datetime import datetime from sqlalchemy.ext.asyncio import AsyncSession from model.audit_log import AuditLog from model.biz_work_order import BizWorkOrder from model.sys_user import SysUser from repositories.biz_work_order import BizWorkOrderRepo from schemas.work_order import WorkOrderResp from utils.exceptions import NotFoundError, ParamError from utils.pagination import normalize_pagination, pagination_result def _audit(db: AsyncSession, user: SysUser, module: str, action: str, target, detail: str) -> None: db.add( AuditLog( user_id=user.id, username=user.real_name or user.username, module=module, action=action, target=str(target), detail=detail, status="成功", ) ) def _resp(wo: BizWorkOrder) -> WorkOrderResp: return WorkOrderResp( id=wo.id, work_order_no=wo.work_order_no, order_type=wo.order_type, sub_type=wo.sub_type, customer_id=wo.customer_id, submitter_id=wo.submitter_id, handler_id=wo.handler_id, current_node=wo.current_node, priority=wo.priority, status=wo.status, biz_content=wo.biz_content, create_time=wo.create_time, update_time=wo.update_time, ) async def list_work_orders( db: AsyncSession, status: str | None = None, *, page: int = 1, page_size: int = 10, ) -> dict: """分页查询公共工单,默认每页 10 条。""" page, page_size, offset = normalize_pagination(page, page_size) repo = BizWorkOrderRepo(db) orders = await repo.list_with_filter( status=status, limit=page_size, offset=offset, ) return pagination_result( [_resp(o).model_dump(mode="json") for o in orders], await repo.count_with_filter(status=status), page=page, page_size=page_size, ) async def get_work_order(db: AsyncSession, work_order_id: int) -> WorkOrderResp: wo = await BizWorkOrderRepo(db).get(work_order_id) if wo is None: raise NotFoundError("工单不存在") return _resp(wo) async def claim(db: AsyncSession, user: SysUser, work_order_id: int) -> dict: """认领:待处理 → 处理中,handler_id 记为当前专员。""" wo_repo = BizWorkOrderRepo(db) wo = await wo_repo.get(work_order_id) if wo is None: raise NotFoundError("工单不存在") try: if not await wo_repo.conditional_transition( work_order_id, from_status="待处理", to_status="处理中", handler_id=user.id, update_time=datetime.now(), ): raise ParamError("工单已被认领或状态已变更") _audit(db, user, "work_order", "claim", work_order_id, f"认领工单 {wo.work_order_no}:待处理 → 处理中") await db.commit() return {"work_order_id": work_order_id, "status": "处理中", "work_order_no": wo.work_order_no} except Exception: await db.rollback() raise async def submit_review(db: AsyncSession, user: SysUser, work_order_id: int) -> dict: """提交审核:处理中 → 待审核。""" wo_repo = BizWorkOrderRepo(db) wo = await wo_repo.get(work_order_id) if wo is None: raise NotFoundError("工单不存在") try: if not await wo_repo.conditional_transition( work_order_id, from_status="处理中", to_status="待审核", current_node="复核", update_time=datetime.now(), ): raise ParamError("工单状态已变更,无法提交审核") _audit(db, user, "work_order", "submit_review", work_order_id, f"提交审核工单 {wo.work_order_no}:处理中 → 待审核") await db.commit() return {"work_order_id": work_order_id, "status": "待审核", "work_order_no": wo.work_order_no} except Exception: await db.rollback() raise async def review( db: AsyncSession, user: SysUser, work_order_id: int, approve: bool, comment: str | None = None ) -> dict: """复核:待审核 → 已完成 / 已驳回(终态)。复核人与处理人为同一人时软校验仅提示。""" wo_repo = BizWorkOrderRepo(db) wo = await wo_repo.get(work_order_id) if wo is None: raise NotFoundError("工单不存在") to_status = "已完成" if approve else "已驳回" warning = "复核人与处理人为同一人" if wo.handler_id == user.id else None try: if not await wo_repo.conditional_transition( work_order_id, from_status="待审核", to_status=to_status, current_node="完成", update_time=datetime.now(), ): raise ParamError("工单状态已变更,无法复核") detail = f"复核工单 {wo.work_order_no}:待审核 → {to_status}" if comment: detail += f"({comment})" _audit(db, user, "work_order", "review", work_order_id, detail) await db.commit() return { "work_order_id": work_order_id, "status": to_status, "work_order_no": wo.work_order_no, "warning": warning, } except Exception: await db.rollback() raise