Synchronise approved AQMM across staff and review boards

This commit is contained in:
A R R R Associates
2026-09-22 17:33:06 +05:30
parent ecdb93d6b5
commit 581f5faf80
4 changed files with 89 additions and 2 deletions
+26
View File
@@ -34,6 +34,7 @@ from app.modules.services.execution import (
apply_task_checklist_response, apply_task_checklist_response,
recalculate_task_aqmm_status, recalculate_task_aqmm_status,
submit_task_for_review, submit_task_for_review,
synchronise_approved_engagement_acceptance_tasks,
) )
EMPLOYEE_STATUS = ["active", "inactive", "relieved"] EMPLOYEE_STATUS = ["active", "inactive", "relieved"]
@@ -4077,6 +4078,31 @@ def get_employee_engagement_work_board(
document, checklist, AQMM and lock controls remain the source of truth. document, checklist, AQMM and lock controls remain the source of truth.
""" """
today = date.today() today = date.today()
# Cross-view AQMM self-heal: an engagement that has already passed the
# engagement-level AQMM acceptance must never continue to show the four
# duplicated setup rows as pending in the Staff work board. Reconcile the
# persisted execution rows before the board queries them. The synchroniser
# is deliberately limited to untouched acceptance/team/independence/KYC
# setup rows and does not modify normal execution history, evidence or rework.
subscription_stmt = select(ClientServiceSubscription).where(
ClientServiceSubscription.id == int(engagement_id),
ClientServiceSubscription.tenant_id == int(scope.tenant_id),
)
if scope.branch_id is not None:
subscription_stmt = subscription_stmt.where(
ClientServiceSubscription.branch_id == int(scope.branch_id)
)
subscription_for_sync = db.execute(subscription_stmt).scalar_one_or_none()
if subscription_for_sync is not None:
repaired = synchronise_approved_engagement_acceptance_tasks(
db,
subscription=subscription_for_sync,
actor_user_id=int(scope.own_user_id),
)
if repaired:
db.commit()
assigned_stmt = _employee_work_task_query( assigned_stmt = _employee_work_task_query(
db, scope, assigned_only=True, financial_year=financial_year db, scope, assigned_only=True, financial_year=financial_year
).where(ClientServiceTaskInstance.subscription_id == engagement_id) ).where(ClientServiceTaskInstance.subscription_id == engagement_id)
+14 -1
View File
@@ -17,7 +17,7 @@ from app.modules.employees.service import (
_engagement_team, _engagement_team,
_weighted_progress, _weighted_progress,
) )
from app.modules.services.execution import CLOSED_TASK_STATUSES from app.modules.services.execution import CLOSED_TASK_STATUSES, synchronise_approved_engagement_acceptance_tasks
from app.modules.services.models import ClientServiceSubscription, ClientServiceTaskInstance, ServiceCatalogue, ServiceTaskComment from app.modules.services.models import ClientServiceSubscription, ClientServiceTaskInstance, ServiceCatalogue, ServiceTaskComment
MANAGER_ROLES = {"Manager", "Branch Manager", "System Admin", "Firm Admin", "Partner"} MANAGER_ROLES = {"Manager", "Branch Manager", "System Admin", "Firm Admin", "Partner"}
@@ -599,6 +599,19 @@ def get_manager_review_workspace(
if not tasks: if not tasks:
return None return None
subscription_for_sync = getattr(tasks[0], "subscription", None)
if subscription_for_sync is not None:
repaired_setup_rows = synchronise_approved_engagement_acceptance_tasks(
db,
subscription=subscription_for_sync,
actor_user_id=int(current_user.id),
)
if repaired_setup_rows:
db.commit()
tasks = list(db.execute(
stmt.order_by(ClientServiceTaskInstance.sequence_no.asc(), ClientServiceTaskInstance.id.asc())
).scalars().all())
task_rows = [_manager_review_task_row(task) for task in tasks] task_rows = [_manager_review_task_row(task) for task in tasks]
reviewable = [row for row in task_rows if row["review_state"] in {"pending_review", "reviewed", "rework"}] reviewable = [row for row in task_rows if row["review_state"] in {"pending_review", "reviewed", "rework"}]
pending = [row for row in reviewable if row["review_state"] == "pending_review"] pending = [row for row in reviewable if row["review_state"] == "pending_review"]
+20
View File
@@ -18,6 +18,7 @@ from app.modules.services.execution import (
CLOSED_TASK_STATUSES, CLOSED_TASK_STATUSES,
aqmm_task_summary_for_subscription, aqmm_task_summary_for_subscription,
closure_readiness_for_subscription, closure_readiness_for_subscription,
synchronise_approved_engagement_acceptance_tasks,
) )
from app.modules.services.models import ( from app.modules.services.models import (
ClientServiceSubscription, ClientServiceSubscription,
@@ -571,6 +572,17 @@ def _partner_review_queue_rows(db: Session, request, current_user, *, limit: int
ClientServiceSubscription.review_partner_user_id == current_user.id, ClientServiceSubscription.review_partner_user_id == current_user.id,
)) ))
subscriptions = list(db.execute(stmt.order_by(ClientServiceSubscription.current_due_date.is_(None), ClientServiceSubscription.current_due_date.asc(), ClientServiceSubscription.id.desc()).limit(limit)).scalars().all()) subscriptions = list(db.execute(stmt.order_by(ClientServiceSubscription.current_due_date.is_(None), ClientServiceSubscription.current_due_date.asc(), ClientServiceSubscription.id.desc()).limit(limit)).scalars().all())
repaired_setup_rows = 0
for sub in subscriptions:
repaired_setup_rows += synchronise_approved_engagement_acceptance_tasks(
db,
subscription=sub,
actor_user_id=int(current_user.id),
)
if repaired_setup_rows:
db.commit()
rows: list[dict[str, Any]] = [] rows: list[dict[str, Any]] = []
for sub in subscriptions: for sub in subscriptions:
task_stmt = select(ClientServiceTaskInstance).where( task_stmt = select(ClientServiceTaskInstance).where(
@@ -638,6 +650,14 @@ def get_partner_review_workspace(db: Session, request, current_user, *, subscrip
}: }:
return None return None
repaired_setup_rows = synchronise_approved_engagement_acceptance_tasks(
db,
subscription=subscription,
actor_user_id=int(current_user.id),
)
if repaired_setup_rows:
db.commit()
task_stmt = ( task_stmt = (
select(ClientServiceTaskInstance) select(ClientServiceTaskInstance)
.options( .options(
+29 -1
View File
@@ -18,7 +18,12 @@ from app.modules.core.rbac.deps import get_user_permissions, get_user_roles
from app.modules.documents.models import EngagementDocument from app.modules.documents.models import EngagementDocument
from app.modules.clients.models import Client, ClientBranch, ClientBusinessUnit from app.modules.clients.models import Client, ClientBranch, ClientBusinessUnit
from app.modules.registrations.models import ClientRegistration, RegistrationType from app.modules.registrations.models import ClientRegistration, RegistrationType
from app.modules.services.execution import CLOSED_TASK_STATUSES, TASK_PRIORITIES, TASK_STATUSES from app.modules.services.execution import (
CLOSED_TASK_STATUSES,
TASK_PRIORITIES,
TASK_STATUSES,
synchronise_approved_engagement_acceptance_tasks,
)
from app.modules.services.engagement_resources import build_engagement_resource_context from app.modules.services.engagement_resources import build_engagement_resource_context
from app.modules.services.models import ( from app.modules.services.models import (
ClientServiceSubscription, ClientServiceSubscription,
@@ -408,6 +413,21 @@ def build_partner_payload(
) )
option_stmt = _subscription_scope_filter(option_stmt, tenant_id, branch_id, current_user, roles, financial_year=None) option_stmt = _subscription_scope_filter(option_stmt, tenant_id, branch_id, current_user, roles, financial_year=None)
option_engagements = db.execute(option_stmt).scalars().all() option_engagements = db.execute(option_stmt).scalars().all()
# Keep the Review Board in step with the engagement-level AQMM workflow.
# Older generated task rows can retain their original AQMM gate flags even
# after acceptance is approved; reconcile only those duplicated setup rows
# before task buckets/counts are built.
repaired_setup_rows = 0
for subscription in option_engagements:
repaired_setup_rows += synchronise_approved_engagement_acceptance_tasks(
db,
subscription=subscription,
actor_user_id=int(current_user.id),
)
if repaired_setup_rows:
db.commit()
financial_year_options = sorted({(row.financial_year or "").strip() for row in option_engagements if (row.financial_year or "").strip()}, reverse=True) financial_year_options = sorted({(row.financial_year or "").strip() for row in option_engagements if (row.financial_year or "").strip()}, reverse=True)
period_options = sorted({(row.period_label or "").strip() for row in option_engagements if (row.period_label or "").strip()}) period_options = sorted({(row.period_label or "").strip() for row in option_engagements if (row.period_label or "").strip()})
service_map = {} service_map = {}
@@ -697,6 +717,14 @@ def partner_engagement_detail(request: Request, engagement_id: int):
if not engagement: if not engagement:
return _redirect_denied() return _redirect_denied()
repaired_setup_rows = synchronise_approved_engagement_acceptance_tasks(
db,
subscription=engagement,
actor_user_id=int(current_user.id),
)
if repaired_setup_rows:
db.commit()
task_stmt = select(ClientServiceTaskInstance).options( task_stmt = select(ClientServiceTaskInstance).options(
selectinload(ClientServiceTaskInstance.client), selectinload(ClientServiceTaskInstance.client),
selectinload(ClientServiceTaskInstance.catalogue), selectinload(ClientServiceTaskInstance.catalogue),