Separate normal task review from AQMM review
This commit is contained in:
@@ -309,7 +309,19 @@ def _advanced_engagement_rows(tasks: list[ClientServiceTaskInstance]) -> list[di
|
||||
team = _engagement_team(subscription)
|
||||
status_values = {(getattr(t, "status", None) or "pending").strip().lower() for t in group}
|
||||
completed = sum(1 for t in group if (getattr(t, "status", None) or "pending").strip().lower() in CLOSED_TASK_STATUSES)
|
||||
review_pending = sum(1 for t in group if (getattr(t, "manager_review_status", None) or "") == "pending" or (getattr(t, "partner_review_status", None) or "") == "pending" or (getattr(t, "review_partner_review_status", None) or "") == "pending")
|
||||
review_pending = sum(
|
||||
1
|
||||
for t in group
|
||||
if (
|
||||
getattr(t, "normal_review_required", False)
|
||||
and (getattr(t, "normal_review_role", None) or "").strip().lower() in {"manager", "manager_or_partner"}
|
||||
and (getattr(t, "normal_review_status", None) or "not_required") in {"pending", "rework_required"}
|
||||
)
|
||||
or (
|
||||
getattr(t, "aqmm_manager_review_required", False)
|
||||
and (getattr(t, "manager_review_status", None) or "not_required") in {"pending", "rework_required"}
|
||||
)
|
||||
)
|
||||
if completed == len(group) and group:
|
||||
status = "completed"
|
||||
elif "blocked" in status_values or getattr(subscription, "workflow_pause_reason", None):
|
||||
@@ -482,19 +494,33 @@ _MANAGER_REVIEW_DONE = {"reviewed"}
|
||||
_MANAGER_REVIEW_REWORK = {"rework_required"}
|
||||
|
||||
|
||||
def _manager_review_task_state(task: ClientServiceTaskInstance) -> str:
|
||||
def _manager_review_task_state(task: ClientServiceTaskInstance) -> tuple[str, str | None]:
|
||||
normal_role = (getattr(task, "normal_review_role", None) or "").strip().lower()
|
||||
normal_status = (getattr(task, "normal_review_status", None) or "not_required").strip().lower()
|
||||
manager_status = (getattr(task, "manager_review_status", None) or "not_required").strip().lower()
|
||||
task_status = (getattr(task, "status", None) or "pending").strip().lower()
|
||||
rework_status = (getattr(task, "rework_status", None) or "none").strip().lower()
|
||||
if manager_status in _MANAGER_REVIEW_REWORK or rework_status == "open":
|
||||
return "rework"
|
||||
if manager_status in _MANAGER_REVIEW_DONE:
|
||||
return "reviewed"
|
||||
if manager_status in _MANAGER_REVIEW_PENDING or task_status in REVIEW_STATUSES:
|
||||
return "pending_review"
|
||||
if getattr(task, "aqmm_manager_review_required", False) and task_status in CLOSED_TASK_STATUSES:
|
||||
return "pending_review"
|
||||
return "not_ready"
|
||||
|
||||
if getattr(task, "normal_review_required", False) and normal_role in {"manager", "manager_or_partner"}:
|
||||
if normal_status == "rework_required" or rework_status == "open":
|
||||
return "rework", "normal"
|
||||
if normal_status == "reviewed":
|
||||
if not getattr(task, "aqmm_manager_review_required", False):
|
||||
return "reviewed", "normal"
|
||||
elif normal_status == "pending":
|
||||
return "pending_review", "normal"
|
||||
|
||||
if getattr(task, "aqmm_manager_review_required", False):
|
||||
if manager_status in _MANAGER_REVIEW_REWORK or rework_status == "open":
|
||||
return "rework", "aqmm_manager"
|
||||
if manager_status in _MANAGER_REVIEW_DONE:
|
||||
return "reviewed", "aqmm_manager"
|
||||
if manager_status in _MANAGER_REVIEW_PENDING:
|
||||
return "pending_review", "aqmm_manager"
|
||||
task_status = (getattr(task, "status", None) or "pending").strip().lower()
|
||||
if task_status in CLOSED_TASK_STATUSES:
|
||||
return "pending_review", "aqmm_manager"
|
||||
|
||||
return "not_ready", None
|
||||
|
||||
|
||||
def _manager_review_category(task: ClientServiceTaskInstance) -> str:
|
||||
@@ -502,7 +528,7 @@ def _manager_review_category(task: ClientServiceTaskInstance) -> str:
|
||||
|
||||
|
||||
def _manager_review_task_row(task: ClientServiceTaskInstance) -> dict[str, Any]:
|
||||
state = _manager_review_task_state(task)
|
||||
state, review_kind = _manager_review_task_state(task)
|
||||
comments = [c for c in (getattr(task, "comments", None) or []) if not getattr(c, "is_deleted", False)]
|
||||
documents = list(getattr(task, "documents", None) or [])
|
||||
assignee = getattr(task, "assigned_to", None)
|
||||
@@ -516,6 +542,10 @@ def _manager_review_task_row(task: ClientServiceTaskInstance) -> dict[str, Any]:
|
||||
"task_status_label": _status_label(getattr(task, "status", None)),
|
||||
"review_state": state,
|
||||
"review_state_label": state.replace("_", " ").title(),
|
||||
"review_kind": review_kind,
|
||||
"review_kind_label": "Normal Review" if review_kind == "normal" else ("AQMM Manager Review" if review_kind == "aqmm_manager" else ""),
|
||||
"normal_review_status": (getattr(task, "normal_review_status", None) or "not_required").strip().lower(),
|
||||
"normal_review_note": getattr(task, "normal_review_note", None) or "",
|
||||
"manager_review_status": (getattr(task, "manager_review_status", None) or "not_required").strip().lower(),
|
||||
"manager_review_note": getattr(task, "manager_review_note", None) or "",
|
||||
"rework_status": (getattr(task, "rework_status", None) or "none").strip().lower(),
|
||||
|
||||
+3
-3
@@ -57,7 +57,7 @@
|
||||
<div><div class="text-xs font-semibold uppercase text-slate-500">{{ task.category }}</div><h2 class="mt-1 text-xl font-semibold text-slate-900">{{ task.task_name }}</h2><p class="mt-2 whitespace-pre-line text-sm text-slate-600">{{ task.description }}</p></div>
|
||||
<div class="flex flex-wrap gap-2"><span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold">{{ task.task_status_label }}</span>{% if task.is_aqmm_task %}<span class="rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700">AQMM</span>{% endif %}{% if task.blocks_final_release %}<span class="rounded-full bg-red-100 px-3 py-1 text-xs font-semibold text-red-700">Final Gate</span>{% endif %}</div>
|
||||
</div>
|
||||
<div class="mt-5 grid gap-3 sm:grid-cols-2 lg:grid-cols-4"><div class="rounded-xl bg-slate-50 p-3"><div class="text-xs text-slate-500">Prepared By</div><div class="mt-1 font-semibold">{{ task.assigned_to }}</div></div><div class="rounded-xl bg-slate-50 p-3"><div class="text-xs text-slate-500">Evidence</div><div class="mt-1 font-semibold">{{ task.evidence_count }} document(s)</div></div><div class="rounded-xl bg-slate-50 p-3"><div class="text-xs text-slate-500">Comments</div><div class="mt-1 font-semibold">{{ task.comment_count }}</div></div><div class="rounded-xl bg-slate-50 p-3"><div class="text-xs text-slate-500">Manager Review</div><div class="mt-1 font-semibold">{{ task.review_state_label }}</div></div></div>
|
||||
<div class="mt-5 grid gap-3 sm:grid-cols-2 lg:grid-cols-4"><div class="rounded-xl bg-slate-50 p-3"><div class="text-xs text-slate-500">Prepared By</div><div class="mt-1 font-semibold">{{ task.assigned_to }}</div></div><div class="rounded-xl bg-slate-50 p-3"><div class="text-xs text-slate-500">Evidence</div><div class="mt-1 font-semibold">{{ task.evidence_count }} document(s)</div></div><div class="rounded-xl bg-slate-50 p-3"><div class="text-xs text-slate-500">Comments</div><div class="mt-1 font-semibold">{{ task.comment_count }}</div></div><div class="rounded-xl bg-slate-50 p-3"><div class="text-xs text-slate-500">Manager Review</div><div class="mt-1 font-semibold">{{ task.review_state_label }}</div><div class="mt-1 text-xs text-slate-500">{{ task.review_kind_label }}</div></div></div>
|
||||
</section>
|
||||
|
||||
<section class="grid gap-4 lg:grid-cols-2">
|
||||
@@ -66,12 +66,12 @@
|
||||
</section>
|
||||
|
||||
<section class="af-card">
|
||||
<h3 class="font-semibold text-slate-900">Manager Review Decision</h3>
|
||||
<h3 class="font-semibold text-slate-900">{{ task.review_kind_label or "Manager Review" }} Decision</h3>
|
||||
{% if task.is_locked or workspace.is_locked %}<div class="mt-3 rounded-xl border border-amber-200 bg-amber-50 p-3 text-sm text-amber-800">This task or engagement is locked. Review is read-only.</div>{% endif %}
|
||||
<form method="post" action="/manager/reviews/engagements/{{ workspace.subscription_id }}/tasks/{{ task.id }}" class="mt-4 space-y-4">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<div class="grid gap-3 md:grid-cols-2"><label class="block"><span class="text-sm font-semibold text-slate-700">Decision</span><select name="decision" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" {% if task.is_locked or workspace.is_locked or task.review_state == 'not_ready' %}disabled{% endif %}><option value="reviewed">Approve / Reviewed</option><option value="rework_required">Return for Rework</option></select></label><label class="block"><span class="text-sm font-semibold text-slate-700">Review state</span><input class="mt-1 w-full rounded-xl border border-slate-200 bg-slate-50 px-3 py-2" value="{{ task.review_state_label }}" readonly></label></div>
|
||||
<label class="block"><span class="text-sm font-semibold text-slate-700">Manager review note</span><textarea name="review_note" rows="5" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" placeholder="Required when returning for rework; recommended for significant conclusions." {% if task.is_locked or workspace.is_locked or task.review_state == 'not_ready' %}disabled{% endif %}>{{ task.manager_review_note }}</textarea></label>
|
||||
<label class="block"><span class="text-sm font-semibold text-slate-700">Manager review note</span><textarea name="review_note" rows="5" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" placeholder="Required when returning for rework; recommended for significant conclusions." {% if task.is_locked or workspace.is_locked or task.review_state == 'not_ready' %}disabled{% endif %}>{{ task.normal_review_note if task.review_kind == 'normal' else task.manager_review_note }}</textarea></label>
|
||||
<div class="flex flex-wrap gap-2"><button type="submit" name="workflow_action" value="save" class="af-btn af-btn-secondary" {% if task.is_locked or workspace.is_locked or task.review_state == 'not_ready' %}disabled{% endif %}>Save Review</button><button type="submit" name="workflow_action" value="save_next" class="af-btn af-btn-primary" {% if task.is_locked or workspace.is_locked or task.review_state == 'not_ready' %}disabled{% endif %}>Save & Next Review</button><a href="/manager/dashboard?tab=review-queue" class="af-btn af-btn-secondary">Close Workspace</a></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -15,7 +15,7 @@ from app.modules.manager_dashboard.service import (
|
||||
get_manager_review_workspace,
|
||||
get_next_manager_review_task_id,
|
||||
)
|
||||
from app.modules.services.execution import apply_task_review
|
||||
from app.modules.services.execution import apply_normal_task_review, apply_task_review
|
||||
from app.modules.alerts.workflow_escalations import update_workflow_escalation
|
||||
|
||||
router = APIRouter(prefix="/manager", tags=["manager-dashboard-v2-ui"])
|
||||
@@ -160,14 +160,23 @@ def manager_review_task_submit(
|
||||
if not task:
|
||||
return RedirectResponse(url="/manager/dashboard?tab=review-queue", status_code=303)
|
||||
try:
|
||||
apply_task_review(
|
||||
db,
|
||||
task=task,
|
||||
review_level="manager",
|
||||
decision=decision,
|
||||
note=review_note,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
if active.get("review_kind") == "normal":
|
||||
apply_normal_task_review(
|
||||
db,
|
||||
task=task,
|
||||
decision=decision,
|
||||
note=review_note,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
else:
|
||||
apply_task_review(
|
||||
db,
|
||||
task=task,
|
||||
review_level="manager",
|
||||
decision=decision,
|
||||
note=review_note,
|
||||
user_id=current_user.id,
|
||||
)
|
||||
db.commit()
|
||||
except ValueError:
|
||||
db.rollback()
|
||||
|
||||
Reference in New Issue
Block a user