Add partner engagement review and release workspace phase 5B

This commit is contained in:
A R R R Associates
2026-07-20 13:11:11 +05:30
parent 4c81659156
commit bca72ab2d9
4 changed files with 523 additions and 9 deletions
@@ -1,6 +1,50 @@
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
<div class="flex items-center justify-between"><div><h2 class="text-lg font-semibold text-slate-900">Partner Review Queue</h2><p class="text-sm text-slate-500">Completed work awaiting partner approval, rework decision or final sign-off.</p></div><a href="/partner/reviews" class="rounded-2xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Open Review Board</a></div>
<div class="mt-5 grid gap-4 lg:grid-cols-2">
{% for item in review_queue %}<a href="{{ item.href }}" class="rounded-3xl border border-slate-200 p-5 hover:bg-slate-50"><div class="flex items-start justify-between gap-3"><div><h3 class="font-semibold text-slate-900">{{ item.task_name }}</h3><p class="mt-1 text-xs text-slate-500">{{ item.client_name }} · {{ item.service_name }}</p></div><span class="rounded-full bg-brand-100 px-2.5 py-1 text-xs font-semibold text-brand-700">Review</span></div><div class="mt-3 text-xs text-slate-500">Prepared by: {{ item.assigned_to }} · Due: {{ item.due_date or '-' }}</div></a>{% else %}<div class="rounded-3xl border border-dashed border-slate-300 p-8 text-center text-sm text-slate-500 lg:col-span-2">No completed tasks waiting for partner review.</div>{% endfor %}
<div class="space-y-6">
<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>
<h2 class="text-lg font-semibold text-slate-900">Partner Engagement Review Queue</h2>
<p class="text-sm text-slate-500">Engagement-level Partner and Review Partner review, significant exceptions and final-release blockers.</p>
</div>
<a href="/partner/reviews" class="rounded-2xl border border-slate-300 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Open Existing Review Board</a>
</div>
<div class="mt-5 space-y-4">
{% for item in partner_engagement_review_queue %}
<article class="rounded-3xl border border-slate-200 p-5">
<div class="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2">
<h3 class="font-semibold text-slate-900">{{ item.client_name }}</h3>
<span class="rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold text-slate-700">{{ item.financial_year }}</span>
{% if item.release_blockers %}<span class="rounded-full bg-rose-100 px-2.5 py-1 text-xs font-semibold text-rose-700">{{ item.release_blockers }} release blocker{{ '' if item.release_blockers == 1 else 's' }}</span>{% endif %}
{% if item.exception_count %}<span class="rounded-full bg-amber-100 px-2.5 py-1 text-xs font-semibold text-amber-700">{{ item.exception_count }} exception{{ '' if item.exception_count == 1 else 's' }}</span>{% endif %}
</div>
<p class="mt-1 text-sm text-slate-500">{{ item.service_name }}</p>
<div class="mt-3 flex flex-wrap gap-2 text-xs">
<span class="rounded-full bg-indigo-50 px-2.5 py-1 font-semibold text-indigo-700">Partner pending: {{ item.pending_partner }}</span>
<span class="rounded-full bg-purple-50 px-2.5 py-1 font-semibold text-purple-700">Review Partner pending: {{ item.pending_review_partner }}</span>
<span class="rounded-full bg-rose-50 px-2.5 py-1 font-semibold text-rose-700">Rework: {{ item.rework_count }}</span>
<span class="rounded-full bg-slate-100 px-2.5 py-1 font-semibold text-slate-700">{{ item.sla.label }}</span>
</div>
</div>
<div class="w-full xl:w-72">
<div class="flex items-center justify-between text-xs text-slate-500"><span>Weighted progress</span><span>{{ item.weighted_progress.progress_percent }}%</span></div>
<div class="mt-2 h-2 rounded-full bg-slate-100"><div class="h-2 rounded-full bg-brand-600" style="width: {{ item.weighted_progress.progress_percent }}%"></div></div>
<a href="{{ item.href }}" class="mt-4 inline-flex w-full justify-center rounded-2xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Start / Continue Partner Review</a>
</div>
</div>
</article>
{% else %}
<div class="rounded-3xl border border-dashed border-slate-300 p-8 text-center text-sm text-slate-500">No engagements are awaiting Partner or Review Partner action.</div>
{% endfor %}
</div>
</div>
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
<h2 class="text-lg font-semibold text-slate-900">Existing Task-Level Review Queue</h2>
<p class="text-sm text-slate-500">Retained for compatibility with the existing Partner workflow.</p>
<div class="mt-5 grid gap-4 lg:grid-cols-2">
{% for item in review_queue %}<a href="{{ item.href }}" class="rounded-3xl border border-slate-200 p-5 hover:bg-slate-50"><div class="flex items-start justify-between gap-3"><div><h3 class="font-semibold text-slate-900">{{ item.task_name }}</h3><p class="mt-1 text-xs text-slate-500">{{ item.client_name }} · {{ item.service_name }}</p></div><span class="rounded-full bg-brand-100 px-2.5 py-1 text-xs font-semibold text-brand-700">Review</span></div><div class="mt-3 text-xs text-slate-500">Prepared by: {{ item.assigned_to }} · Due: {{ item.due_date or '-' }}</div></a>{% else %}<div class="rounded-3xl border border-dashed border-slate-300 p-8 text-center text-sm text-slate-500 lg:col-span-2">No completed tasks waiting in the legacy task queue.</div>{% endfor %}
</div>
</div>
</div>
@@ -0,0 +1,93 @@
{% extends "ui/templates/base/layout.html" %}
{% block content %}
{% set active = workspace.active_task %}
<div class="space-y-6">
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div>
<div class="flex flex-wrap items-center gap-2">
<h1 class="text-2xl font-semibold text-slate-900">Partner Engagement Review &amp; Release</h1>
{% if workspace.is_locked %}<span class="rounded-full bg-slate-900 px-3 py-1 text-xs font-semibold text-white">Locked</span>{% endif %}
</div>
<p class="mt-1 text-sm text-slate-500">{{ workspace.client_name }} · {{ workspace.service_name }} · {{ workspace.financial_year }}</p>
<div class="mt-4 flex flex-wrap gap-2 text-xs">
<span class="rounded-full bg-indigo-50 px-3 py-1.5 font-semibold text-indigo-700">Partner pending: {{ workspace.pending_partner_count }}</span>
<span class="rounded-full bg-purple-50 px-3 py-1.5 font-semibold text-purple-700">Review Partner pending: {{ workspace.pending_review_partner_count }}</span>
<span class="rounded-full bg-amber-50 px-3 py-1.5 font-semibold text-amber-700">Exceptions: {{ workspace.exception_count }}</span>
<span class="rounded-full bg-rose-50 px-3 py-1.5 font-semibold text-rose-700">Release blockers: {{ workspace.release_blocker_count }}</span>
<span class="rounded-full bg-slate-100 px-3 py-1.5 font-semibold text-slate-700">{{ workspace.sla.label }}</span>
</div>
</div>
<div class="flex flex-wrap gap-2">
<a href="{{ workspace.documents_href }}" class="rounded-2xl bg-emerald-600 px-4 py-2 text-sm font-semibold text-white">Final Documents / UDIN</a>
<a href="/partner/dashboard?tab=review" class="rounded-2xl border border-slate-300 px-4 py-2 text-sm font-semibold text-slate-700">Close Workspace</a>
</div>
</div>
<div class="mt-5 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-2xl bg-slate-50 p-4"><div class="text-xs uppercase tracking-wide text-slate-500">Weighted Progress</div><div class="mt-2 text-2xl font-semibold">{{ workspace.weighted_progress.progress_percent }}%</div><div class="mt-2 h-2 rounded-full bg-white"><div class="h-2 rounded-full bg-brand-600" style="width: {{ workspace.weighted_progress.progress_percent }}%"></div></div></div>
<div class="rounded-2xl bg-slate-50 p-4"><div class="text-xs uppercase tracking-wide text-slate-500">AQMM Status</div><div class="mt-2 text-lg font-semibold">{{ workspace.aqmm.status.replace('_',' ').title() }}</div><div class="mt-1 text-xs text-slate-500">{{ workspace.aqmm.mandatory_done }}/{{ workspace.aqmm.mandatory }} mandatory controls passed</div></div>
<div class="rounded-2xl bg-slate-50 p-4"><div class="text-xs uppercase tracking-wide text-slate-500">Release Readiness</div><div class="mt-2 text-lg font-semibold {{ 'text-emerald-700' if workspace.release_ready else 'text-amber-700' }}">{{ 'Ready for final document release' if workspace.release_ready else 'Blocked / pending controls' }}</div></div>
<div class="rounded-2xl bg-slate-50 p-4"><div class="text-xs uppercase tracking-wide text-slate-500">SLA &amp; Ageing</div><div class="mt-2 text-lg font-semibold">{{ workspace.sla.label }}</div><div class="mt-1 text-xs text-slate-500">Age: {{ workspace.sla.age_days }} days{% if workspace.sla.days_overdue %} · {{ workspace.sla.days_overdue }} overdue{% endif %}</div></div>
</div>
<div class="mt-5 grid gap-3 md:grid-cols-2 xl:grid-cols-4">
{% for member in workspace.team %}<div class="rounded-2xl border border-slate-200 p-3"><div class="text-xs text-slate-500">{{ member.role }}</div><div class="mt-1 text-sm font-semibold text-slate-900">{{ member.name }}</div></div>{% endfor %}
</div>
</div>
{% if request.query_params.get('reviewed') %}<div class="rounded-2xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-800">Review decision saved.</div>{% endif %}
{% if request.query_params.get('review_error') %}<div class="rounded-2xl border border-red-200 bg-red-50 p-4 text-sm text-red-800">The review could not be saved. Check readiness, review note, assignment and lock status.</div>{% endif %}
<div class="grid gap-6 xl:grid-cols-[380px_minmax(0,1fr)]">
<aside class="space-y-4">
{% for category in workspace.categories %}
<section class="rounded-3xl border border-slate-200 bg-white p-4 shadow-soft">
<div class="flex items-start justify-between gap-3"><div><h2 class="font-semibold text-slate-900">{{ category.name }}</h2><p class="mt-1 text-xs text-slate-500">{{ category.reviewed }} reviewed · {{ category.pending }} pending · {{ category.rework }} rework</p></div><span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold">{{ category.progress_percent }}%</span></div>
<div class="mt-3 h-1.5 rounded-full bg-slate-100"><div class="h-1.5 rounded-full bg-brand-600" style="width: {{ category.progress_percent }}%"></div></div>
<div class="mt-4 space-y-2">
{% for task in category.tasks %}
<a href="/partner/reviews/engagements/{{ workspace.subscription.id }}?task_id={{ task.id }}" class="block rounded-2xl border p-3 {{ 'border-brand-400 bg-brand-50' if active and active.id == task.id else 'border-slate-200 hover:bg-slate-50' }}">
<div class="flex items-start justify-between gap-2"><div class="text-sm font-semibold text-slate-900">{{ task.task_name }}</div>{% if task.blocks_final_release %}<span class="rounded-full bg-slate-900 px-2 py-0.5 text-[10px] font-semibold text-white">Final Gate</span>{% endif %}</div>
<div class="mt-2 flex flex-wrap gap-1 text-[10px]">
<span class="rounded-full bg-slate-100 px-2 py-1">{{ task.status_label }}</span>
<span class="rounded-full px-2 py-1 {{ 'bg-indigo-100 text-indigo-700' if task.review_state == 'pending_review' else ('bg-emerald-100 text-emerald-700' if task.review_state == 'reviewed' else ('bg-rose-100 text-rose-700' if task.review_state == 'rework' else 'bg-slate-100 text-slate-600')) }}">{{ task.review_state.replace('_',' ').title() }}</span>
{% if task.is_exception %}<span class="rounded-full bg-amber-100 px-2 py-1 text-amber-700">Exception</span>{% endif %}
</div>
</a>
{% endfor %}
</div>
</section>
{% endfor %}
</aside>
<main class="rounded-3xl border border-slate-200 bg-white p-5 shadow-soft">
{% if active %}
<div class="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between"><div><div class="text-xs font-semibold uppercase tracking-wide text-brand-700">{{ active.category }}</div><h2 class="mt-1 text-xl font-semibold text-slate-900">{{ active.task_name }}</h2><p class="mt-2 text-sm text-slate-500">{{ active.description or 'No additional procedure description.' }}</p></div><div class="flex flex-wrap gap-2 text-xs"><span class="rounded-full bg-slate-100 px-3 py-1.5">Assigned: {{ active.assigned_to }}</span>{% if active.review_level %}<span class="rounded-full bg-indigo-100 px-3 py-1.5 font-semibold text-indigo-700">{{ active.review_level.replace('_',' ').title() }}</span>{% endif %}</div></div>
<div class="mt-6 grid gap-4 md:grid-cols-2">
<section class="rounded-2xl border border-slate-200 p-4"><h3 class="font-semibold text-slate-900">Staff Response &amp; Conclusion</h3><dl class="mt-3 space-y-2 text-sm"><div><dt class="text-slate-500">Response</dt><dd class="font-semibold">{{ active.checklist_response or active.checklist_text_response or active.checklist_number_response or active.checklist_date_response or '-' }}</dd></div><div><dt class="text-slate-500">Checklist remarks</dt><dd class="whitespace-pre-wrap">{{ active.checklist_remarks or '-' }}</dd></div><div><dt class="text-slate-500">Work remarks</dt><dd class="whitespace-pre-wrap">{{ active.work_remarks or '-' }}</dd></div></dl></section>
<section class="rounded-2xl border border-slate-200 p-4"><h3 class="font-semibold text-slate-900">Manager Conclusion</h3><dl class="mt-3 space-y-2 text-sm"><div><dt class="text-slate-500">Status</dt><dd class="font-semibold">{{ active.manager_review_status.replace('_',' ').title() }}</dd></div><div><dt class="text-slate-500">Note</dt><dd class="whitespace-pre-wrap">{{ active.manager_review_note or '-' }}</dd></div><div><dt class="text-slate-500">Evidence</dt><dd>{{ active.evidence_count }} file{{ '' if active.evidence_count == 1 else 's' }} · <a class="font-semibold text-brand-700" href="/documents/tasks/{{ active.id }}">Open Evidence</a></dd></div></dl></section>
</div>
{% if active.rework_reason %}<div class="mt-4 rounded-2xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800"><strong>Open/previous rework:</strong> {{ active.rework_reason }}</div>{% endif %}
{% if active.checklist_response == 'NO' %}<div class="mt-4 rounded-2xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800"><strong>Exception response:</strong> The staff response is NO. Review the remarks, evidence and reporting consequence before approval.</div>{% endif %}
<section class="mt-6 rounded-2xl border border-slate-200 p-4"><h3 class="font-semibold text-slate-900">Communication Timeline</h3><div class="mt-3 space-y-3">{% for comment in active.comments %}<div class="rounded-xl bg-slate-50 p-3 text-sm"><div class="text-xs text-slate-500">{{ comment.comment_type.replace('_',' ').title() }} · {{ comment.created_at_utc }}</div><div class="mt-1 whitespace-pre-wrap">{{ comment.message }}</div></div>{% else %}<div class="text-sm text-slate-500">No communication recorded.</div>{% endfor %}</div></section>
{% if active.can_review and not active.is_locked and not workspace.is_locked %}
<form method="post" action="/partner/reviews/engagements/{{ workspace.subscription.id }}/tasks/{{ active.id }}" class="mt-6 rounded-2xl border border-slate-200 p-4">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label class="text-sm font-semibold text-slate-800">Partner conclusion</label>
<div class="mt-3 grid gap-3 sm:grid-cols-2"><label class="rounded-2xl border border-slate-200 p-3"><input type="radio" name="decision" value="reviewed" checked> <span class="ml-2 font-semibold">Approve / Reviewed</span></label><label class="rounded-2xl border border-slate-200 p-3"><input type="radio" name="decision" value="rework_required"> <span class="ml-2 font-semibold">Return for Rework</span></label></div>
<textarea name="review_note" rows="4" placeholder="Partner conclusion, clarification or mandatory rework reason" class="mt-3 w-full rounded-2xl border border-slate-300 px-3 py-2 text-sm">{{ active.partner_review_note if active.review_level == 'partner' else active.review_partner_review_note or '' }}</textarea>
<div class="mt-4 flex flex-wrap gap-2"><button name="workflow_action" value="save" class="rounded-2xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Save Review</button><button name="workflow_action" value="save_next" class="rounded-2xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white">Save &amp; Next Review</button><a href="/partner/dashboard?tab=review" class="rounded-2xl border border-slate-300 px-4 py-2 text-sm font-semibold text-slate-700">Close Workspace</a></div>
</form>
{% else %}
<div class="mt-6 rounded-2xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-600">{% if workspace.is_locked or active.is_locked %}This engagement or task is locked and cannot be reviewed.{% elif active.review_state == 'not_ready' %}This task is visible for context but is not ready for your review. Required execution or prior review is pending.{% else %}This task is read-only for your current engagement role.{% endif %}</div>
{% endif %}
{% else %}<div class="py-16 text-center text-sm text-slate-500">No tasks are available in this engagement.</div>{% endif %}
</main>
</div>
</div>
{% endblock %}