Delete VPS document stage after verified local storage sync
This commit is contained in:
@@ -7,6 +7,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 Permission, Role, RolePermission, UserRole
|
||||
from app.modules.employees.service import (
|
||||
@@ -385,6 +386,7 @@ def build_manager_dashboard_payload(db: Session, request, current_user) -> dict[
|
||||
staff_rows = _staff_rows(db, scope, task_rows)
|
||||
client_rows = _client_rows(db, scope, task_rows)
|
||||
advanced_engagements = _advanced_engagement_rows(tasks)
|
||||
unified_escalations = list_workflow_escalations(db, tenant_id=getattr(scope, "tenant_id", None), branch_id=getattr(scope, "branch_id", None), assigned_to_user_id=current_user.id)
|
||||
capacity_rows = _capacity_rows(tasks)
|
||||
sla_breached = [row for row in advanced_engagements if row["sla"]["status"] == "breached"]
|
||||
sla_warning = [row for row in advanced_engagements if row["sla"]["status"] in {"critical", "warning"}]
|
||||
@@ -430,6 +432,8 @@ def build_manager_dashboard_payload(db: Session, request, current_user) -> dict[
|
||||
"staff_count": len(staff_rows),
|
||||
"client_count": len(client_rows),
|
||||
"escalation_count": len(escalation_rows),
|
||||
"unified_escalation_count": len(unified_escalations),
|
||||
"escalations_over_3_days": len([r for r in unified_escalations if r["age_days"] >= 3]),
|
||||
"engagement_count": len(advanced_engagements),
|
||||
"sla_breached_count": len(sla_breached),
|
||||
"sla_warning_count": len(sla_warning),
|
||||
@@ -451,6 +455,7 @@ def build_manager_dashboard_payload(db: Session, request, current_user) -> dict[
|
||||
"client_pending": client_pending_rows[:60],
|
||||
"documents_pending": client_pending_rows[:40],
|
||||
"escalations": escalation_rows[:80],
|
||||
"unified_escalations": unified_escalations,
|
||||
"age_buckets": age_buckets,
|
||||
"status_summary": sorted([{"label": k, "count": v} for k, v in status_summary.items()], key=lambda x: x["count"], reverse=True)[:10],
|
||||
"service_summary": sorted([{"label": k, "count": v} for k, v in service_summary.items()], key=lambda x: x["count"], reverse=True)[:10],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="space-y-6">
|
||||
<section class="grid gap-4 md:grid-cols-2 xl:grid-cols-4"><div class="af-metric-card border-red-200 bg-red-50"><div class="text-xs font-semibold uppercase text-red-700">SLA Breached</div><div class="mt-2 text-3xl font-semibold text-red-700">{{ overview.sla_breached_count }}</div></div><div class="af-metric-card border-amber-200 bg-amber-50"><div class="text-xs font-semibold uppercase text-amber-700">SLA Warning</div><div class="mt-2 text-3xl font-semibold text-amber-700">{{ overview.sla_warning_count }}</div></div><div class="af-metric-card"><div class="text-xs font-semibold uppercase text-slate-500">Task Escalations</div><div class="mt-2 text-3xl font-semibold">{{ overview.escalation_count }}</div></div><div class="af-metric-card border-violet-200 bg-violet-50"><div class="text-xs font-semibold uppercase text-violet-700">Review Dependencies</div><div class="mt-2 text-3xl font-semibold text-violet-700">{{ overview.engagement_review_count }}</div></div></section>
|
||||
<section class="grid gap-6 xl:grid-cols-2"><div class="af-card overflow-x-auto"><h3 class="text-lg font-semibold">SLA Breaches</h3><p class="mb-4 text-sm text-slate-500">Engagement-level statutory/internal due-date breaches.</p><table class="min-w-full text-sm"><tbody>{% for row in sla_breached %}<tr class="border-b"><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.progress_percent }}% complete</div></td><td class="px-3 py-3 text-right"><div class="font-semibold text-red-700">{{ row.sla.label }}</div><div class="text-xs text-slate-500">Age {{ row.sla.age_days }} day(s)</div></td></tr>{% else %}<tr><td class="px-3 py-8 text-center text-slate-500">No SLA breaches.</td></tr>{% endfor %}</tbody></table></div><div class="af-card overflow-x-auto"><h3 class="text-lg font-semibold">SLA Warning Window</h3><p class="mb-4 text-sm text-slate-500">Due within seven days.</p><table class="min-w-full text-sm"><tbody>{% for row in sla_warning %}<tr class="border-b"><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.progress_percent }}% complete</div></td><td class="px-3 py-3 text-right font-semibold text-amber-700">{{ row.sla.label }}</td></tr>{% else %}<tr><td class="px-3 py-8 text-center text-slate-500">No engagements in warning window.</td></tr>{% endfor %}</tbody></table></div></section>
|
||||
<div class="af-card"><div class="mb-4 flex items-center justify-between"><div><h3 class="text-lg font-semibold">Existing Task Escalation Register</h3><p class="text-sm text-slate-500">Unassigned, overdue and client-pending tasks remain visible.</p></div><a href="/alerts" class="af-btn af-btn-primary">Open Alerts</a></div>{% set rows = escalations %}{% include 'modules/manager_dashboard/templates/manager_dashboard/partials/_task_table.html' %}</div>
|
||||
</div>
|
||||
<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">SLA Breached</div><div class="mt-2 text-3xl font-semibold">{{ overview.sla_breached_count }}</div></div></section>
|
||||
<section class="af-card overflow-x-auto"><h3 class="text-lg font-semibold">Unified Escalation Register</h3><p class="mb-4 text-sm text-slate-500">Durable staff-to-manager escalations with acknowledgement, resolution and ageing.</p><table class="min-w-full text-sm"><thead><tr class="border-b text-left text-xs uppercase text-slate-500"><th class="px-3 py-2">Engagement</th><th class="px-3 py-2">Escalation</th><th class="px-3 py-2">Age</th><th class="px-3 py-2">Action</th></tr></thead><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 }} · Raised by {{ row.raised_by }}</div></td><td class="px-3 py-3"><div class="font-semibold">{{ row.category|replace('_',' ')|title }}</div><div class="mt-1 max-w-xl text-slate-600">{{ row.message }}</div><div class="mt-1 text-xs text-slate-500">Status: {{ row.status|title }}{% if row.follow_up_date %} · Follow-up {{ row.follow_up_date }}{% endif %}</div></td><td class="px-3 py-3"><span class="rounded-full px-2 py-1 text-xs font-semibold {% if row.age_days >= 3 %}bg-red-100 text-red-700{% else %}bg-slate-100 text-slate-700{% endif %}">{{ row.age_days }} day(s)</span></td><td class="px-3 py-3"><form method="post" action="/manager/escalations/{{ row.id }}/action" class="space-y-2"><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 colspan="4" class="px-3 py-8 text-center text-slate-500">No open escalations assigned to you.</td></tr>{% endfor %}</tbody></table></section>
|
||||
<section class="grid gap-6 xl:grid-cols-2"><div class="af-card"><h3 class="text-lg font-semibold">Review Ageing — SLA Breaches</h3><div class="mt-3 space-y-3">{% for row in sla_breached %}<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-red-700">{{ row.sla.label }} · Age {{ row.sla.age_days }} day(s)</div></a>{% else %}<div class="text-sm text-slate-500">No breached engagements.</div>{% endfor %}</div></div><div class="af-card"><h3 class="text-lg font-semibold">Review Ageing — Warning</h3><div class="mt-3 space-y-3">{% for row in sla_warning %}<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-amber-700">{{ row.sla.label }}</div></a>{% else %}<div class="text-sm text-slate-500">No warning-window engagements.</div>{% endfor %}</div></div></section>
|
||||
</div>
|
||||
@@ -16,6 +16,7 @@ from app.modules.manager_dashboard.service import (
|
||||
get_next_manager_review_task_id,
|
||||
)
|
||||
from app.modules.services.execution import apply_task_review
|
||||
from app.modules.alerts.workflow_escalations import update_workflow_escalation
|
||||
|
||||
router = APIRouter(prefix="/manager", tags=["manager-dashboard-v2-ui"])
|
||||
|
||||
@@ -180,3 +181,17 @@ def manager_review_task_submit(
|
||||
return RedirectResponse(url=f"/manager/reviews/engagements/{subscription_id}?task_id={task_id}&reviewed=1", status_code=303)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/escalations/{escalation_id}/action")
|
||||
def manager_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_manager_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="/manager/dashboard?tab=escalations",status_code=303)
|
||||
finally: db.close()
|
||||
|
||||
Reference in New Issue
Block a user