Files
wecom_it_smart_desk/backend/app/services/automation/approval.py
T

93 lines
3.1 KiB
Python
Raw Normal View History

# =============================================================================
# 企微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