Delete VPS document stage after verified local storage sync

This commit is contained in:
A R R R Associates
2026-07-20 15:27:58 +05:30
parent bca72ab2d9
commit 55c5a7acdf
15 changed files with 361 additions and 7 deletions
+5
View File
@@ -8,6 +8,7 @@ from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session, selectinload
from app.modules.clients.models import Client
from app.modules.alerts.workflow_escalations import list_workflow_escalations
from app.modules.core.iam.models import User
from app.modules.core.rbac.models import Role, UserRole
from app.modules.core.tenancy.models import Branch, Tenant
@@ -297,6 +298,7 @@ def build_partner_dashboard_payload(db: Session, request, current_user) -> dict[
clients = _load_clients(db, tenant_id, branch_id, current_user, roles)
staff = _staff_rows(db, tenant_id, branch_id, tasks)
billing = _billing_rows(db, tenant_id, branch_id, fy)
unified_escalations = list_workflow_escalations(db, tenant_id=tenant_id, branch_id=branch_id, assigned_to_user_id=current_user.id)
subscriptions_count = _count(db, _subscription_scope(select(func.count(ClientServiceSubscription.id)), tenant_id, branch_id, current_user, roles, fy)) if tenant_id else 0
@@ -318,6 +320,8 @@ def build_partner_dashboard_payload(db: Session, request, current_user) -> dict[
"invoice_count": billing["invoice_count"],
"outstanding": billing["outstanding"],
"today": today,
"unified_escalation_count": len(unified_escalations),
"escalations_over_3_days": len([r for r in unified_escalations if r["age_days"] >= 3]),
}
return {
@@ -330,6 +334,7 @@ def build_partner_dashboard_payload(db: Session, request, current_user) -> dict[
"client_pending": client_pending_rows[:25],
"review_queue": review_rows[:50],
"partner_engagement_review_queue": _partner_review_queue_rows(db, request, current_user),
"unified_escalations": unified_escalations,
"clients": clients,
"staff_rows": staff,
"billing": billing,
@@ -0,0 +1 @@
<div class="space-y-6"><section class="grid gap-4 md:grid-cols-3"><div class="af-metric-card border-violet-200 bg-violet-50"><div class="text-xs font-semibold uppercase text-violet-700">Open Escalations</div><div class="mt-2 text-3xl font-semibold text-violet-700">{{ overview.unified_escalation_count }}</div></div><div class="af-metric-card border-red-200 bg-red-50"><div class="text-xs font-semibold uppercase text-red-700">Age 3+ Days</div><div class="mt-2 text-3xl font-semibold text-red-700">{{ overview.escalations_over_3_days }}</div></div><div class="af-metric-card"><div class="text-xs font-semibold uppercase text-slate-500">Partner Reviews</div><div class="mt-2 text-3xl font-semibold">{{ overview.review_pending_count }}</div></div></section><section class="af-card overflow-x-auto"><h3 class="text-lg font-semibold">Unified Partner Escalation & Review Ageing</h3><p class="mb-4 text-sm text-slate-500">Escalations assigned to the Engagement Partner or Review Partner.</p><table class="min-w-full text-sm"><tbody>{% for row in unified_escalations %}<tr class="border-b align-top"><td class="px-3 py-3"><a href="{{ row.href }}" class="font-semibold text-brand-700">{{ row.client_name }} — {{ row.service_name }}</a><div class="text-xs text-slate-500">{{ row.financial_year }} · {{ row.level|replace('_',' ')|title }} · {{ row.age_days }} day(s)</div><div class="mt-2 text-slate-600">{{ row.message }}</div></td><td class="px-3 py-3 text-right"><form method="post" action="/partner/escalations/{{ row.id }}/action" class="inline-block space-y-2 text-left"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><textarea name="resolution_note" rows="2" class="w-56 rounded-lg border px-2 py-1 text-xs" placeholder="Resolution note"></textarea><div class="flex gap-2"><button name="action" value="acknowledge" class="af-btn af-btn-secondary text-xs">Acknowledge</button><button name="action" value="resolve" class="af-btn af-btn-primary text-xs">Resolve</button></div></form></td></tr>{% else %}<tr><td class="px-3 py-8 text-center text-slate-500">No open escalations assigned to you.</td></tr>{% endfor %}</tbody></table></section><section class="af-card"><h3 class="text-lg font-semibold">Partner Review Ageing</h3><div class="mt-3 space-y-3">{% for row in partner_engagement_review_queue %}<a href="{{ row.href }}" class="block rounded-xl border p-3"><div class="font-semibold">{{ row.client_name }} — {{ row.service_name }}</div><div class="text-xs text-slate-500">{{ row.financial_year }} · Partner {{ row.pending_partner }} · Review Partner {{ row.pending_review_partner }} · {{ row.sla.label }}</div></a>{% else %}<div class="text-sm text-slate-500">No pending partner reviews.</div>{% endfor %}</div></section></div>
@@ -1,4 +1,5 @@
<div class="space-y-6">
<div class="flex justify-end"><a href="/partner/dashboard?tab=escalations" class="af-btn af-btn-secondary">Escalations & Review Ageing</a></div>
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
+16
View File
@@ -16,6 +16,7 @@ from app.modules.partner_dashboard.service import (
get_partner_review_workspace,
)
from app.modules.services.execution import apply_task_review
from app.modules.alerts.workflow_escalations import update_workflow_escalation
router = APIRouter(prefix="/partner", tags=["partner-dashboard-v2-ui"])
@@ -25,6 +26,7 @@ VALID_TABS = {
"clients": "modules/partner_dashboard/templates/partner_dashboard/partials/clients.html",
"staff": "modules/partner_dashboard/templates/partner_dashboard/partials/staff.html",
"review": "modules/partner_dashboard/templates/partner_dashboard/partials/review.html",
"escalations": "modules/partner_dashboard/templates/partner_dashboard/partials/escalations.html",
"billing": "modules/partner_dashboard/templates/partner_dashboard/partials/billing.html",
"reports": "modules/partner_dashboard/templates/partner_dashboard/partials/reports.html",
"wizards": "modules/partner_dashboard/templates/partner_dashboard/partials/wizards.html",
@@ -168,3 +170,17 @@ def partner_review_task_submit(
return RedirectResponse(url=f"/partner/reviews/engagements/{subscription_id}?task_id={task_id}&reviewed=1", status_code=303)
finally:
db.close()
@router.post("/escalations/{escalation_id}/action")
def partner_escalation_action(request: Request, escalation_id: int, action: str = Form(...), resolution_note: str = Form(""), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token)
db=CommonSessionLocal()
try:
current_user=get_current_user(request, db=db)
if not current_user: return RedirectResponse(url="/login",status_code=303)
if not can_access_partner_dashboard(db,current_user): return ui_access_denied()
try: update_workflow_escalation(db, escalation_id=escalation_id, actor_user_id=current_user.id, action=action, resolution_note=resolution_note); db.commit()
except ValueError: db.rollback()
return RedirectResponse(url="/partner/dashboard?tab=escalations",status_code=303)
finally: db.close()