# ============================================================================= # 企微IT智能服务台 — 阶段5 自动化 审批单服务 # ============================================================================= # 说明:管理高危动作 / 员工二次确认对应的审批单(ApprovalTicket)。 # 1. ensure_ticket:动作首次需要审批时创建(幂等,避免重复提单) # 2. decide:坐席审批或员工 H5 确认后更新状态 # 3. get_pending_for_action:查询动作当前待决审批单 # ============================================================================= from __future__ import annotations import logging from datetime import datetime, timezone from typing import Any, Optional from sqlalchemy import select from app.models.automation import ApprovalTicket logger = logging.getLogger(__name__) class ApprovalService: """审批单服务。""" def __init__(self, db: Any): self.db = db async def ensure_ticket( self, action: Any, channel: str, reason: Optional[str] = None ) -> ApprovalTicket: """确保动作存在一张待决审批单(幂等)。""" stmt = select(ApprovalTicket).where( ApprovalTicket.action_id == action.id, ApprovalTicket.status == "pending", ) existing = (await self.db.execute(stmt)).scalar_one_or_none() if existing is not None: return existing ticket = ApprovalTicket( action_id=action.id, session_id=action.session_id, channel=channel, status="pending", reason=reason, ) self.db.add(ticket) await self.db.flush() return ticket async def get_pending_for_action(self, action_id: str) -> Optional[ApprovalTicket]: """查询动作当前待决审批单。""" stmt = select(ApprovalTicket).where( ApprovalTicket.action_id == action_id, ApprovalTicket.status == "pending", ) return (await self.db.execute(stmt)).scalar_one_or_none() async def decide( self, ticket_id: str, decision: str, note: Optional[str], approver_id: Optional[str], ) -> ApprovalTicket: """更新审批单决策。 Args: decision: approve / reject note: 审批意见 approver_id: 审批人(坐席或员工) Returns: ApprovalTicket: 更新后的审批单 Raises: ValueError: 审批单不存在或已决 """ stmt = select(ApprovalTicket).where(ApprovalTicket.id == ticket_id) ticket = (await self.db.execute(stmt)).scalar_one_or_none() if ticket is None: raise ValueError(f"审批单不存在: {ticket_id}") if ticket.status != "pending": return ticket ticket.status = "approved" if decision == "approve" else "rejected" ticket.decision_note = note ticket.approver_id = approver_id ticket.decided_at = datetime.now(timezone.utc) await self.db.flush() return ticket