Files
arrr-erp/app/modules/alerts/workflow_escalations.py
T
2026-07-20 15:27:58 +05:30

44 lines
4.0 KiB
Python

from __future__ import annotations
from datetime import date, datetime, timezone
from typing import Any
from sqlalchemy import select
from sqlalchemy.orm import Session, selectinload
from app.modules.alerts.models import WorkflowEscalation
from app.modules.services.models import ClientServiceSubscription
OPEN_ESCALATION_STATUSES = {"open", "acknowledged"}
def create_workflow_escalation(db: Session, *, subscription: ClientServiceSubscription, raised_by_user_id: int, assigned_to_user_id: int, escalation_level: str, message: str, task_id: int | None = None, category: str = "workflow_dependency", priority: str = "high", follow_up_date: date | None = None) -> WorkflowEscalation:
row = WorkflowEscalation(tenant_id=subscription.tenant_id, branch_id=subscription.branch_id, subscription_id=subscription.id, task_id=task_id, raised_by_user_id=raised_by_user_id, assigned_to_user_id=assigned_to_user_id, escalation_level=escalation_level, category=category, priority=priority, status="open", message=message.strip()[:4000], follow_up_date=follow_up_date)
db.add(row)
db.flush()
return row
def list_workflow_escalations(db: Session, *, tenant_id: int | None, branch_id: int | None, assigned_to_user_id: int | None = None, include_resolved: bool = False, limit: int = 200) -> list[dict[str, Any]]:
stmt = select(WorkflowEscalation).options(selectinload(WorkflowEscalation.subscription).selectinload(ClientServiceSubscription.client), selectinload(WorkflowEscalation.subscription).selectinload(ClientServiceSubscription.catalogue), selectinload(WorkflowEscalation.raised_by), selectinload(WorkflowEscalation.assigned_to))
if tenant_id: stmt = stmt.where(WorkflowEscalation.tenant_id == tenant_id)
if branch_id is not None: stmt = stmt.where(WorkflowEscalation.branch_id == branch_id)
if assigned_to_user_id: stmt = stmt.where(WorkflowEscalation.assigned_to_user_id == assigned_to_user_id)
if not include_resolved: stmt = stmt.where(WorkflowEscalation.status.in_(OPEN_ESCALATION_STATUSES))
rows = db.execute(stmt.order_by(WorkflowEscalation.created_at_utc.asc()).limit(limit)).scalars().all()
now = datetime.now(timezone.utc)
out=[]
for row in rows:
created=row.created_at_utc
if created and created.tzinfo is None: created=created.replace(tzinfo=timezone.utc)
sub=row.subscription
out.append({"id":row.id,"subscription_id":row.subscription_id,"task_id":row.task_id,"client_name":getattr(getattr(sub,"client",None),"client_name",None) or "Unlinked Client","service_name":getattr(getattr(sub,"catalogue",None),"service_name",None) or "Service","financial_year":getattr(sub,"financial_year",None) or "-","level":row.escalation_level,"category":row.category,"priority":row.priority,"status":row.status,"message":row.message,"follow_up_date":row.follow_up_date,"raised_by":getattr(row.raised_by,"full_name",None) or getattr(row.raised_by,"email",None) or "User","assigned_to":getattr(row.assigned_to,"full_name",None) or getattr(row.assigned_to,"email",None) or "User","created_at":row.created_at_utc,"age_days":max(0,(now-created).days) if created else 0,"href":f"/manager/reviews/engagements/{row.subscription_id}" if row.escalation_level=="manager" else f"/partner/reviews/engagements/{row.subscription_id}"})
return out
def update_workflow_escalation(db: Session, *, escalation_id: int, actor_user_id: int, action: str, resolution_note: str = "") -> WorkflowEscalation:
row=db.get(WorkflowEscalation, escalation_id)
if not row or int(row.assigned_to_user_id)!=int(actor_user_id): raise ValueError("Escalation not found or not assigned to you.")
now=datetime.now(timezone.utc)
if action=="acknowledge": row.status="acknowledged"; row.acknowledged_at_utc=now; row.acknowledged_by_user_id=actor_user_id
elif action=="resolve": row.status="resolved"; row.resolved_at_utc=now; row.resolved_by_user_id=actor_user_id; row.resolution_note=(resolution_note or "").strip()[:4000] or None
else: raise ValueError("Invalid escalation action.")
db.flush(); return row