WIP-CHECKPOINT[auth-refactor]: 固化工程师崩溃前部分成果 + 同树其他未提交WIP(仅源码,不含密钥/二进制)-- 待重激活工程师续作
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# =============================================================================
|
||||
# 企微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
|
||||
Reference in New Issue
Block a user