Fix duplicate AQMM gating on approved engagements

This commit is contained in:
A R R R Associates
2026-09-22 16:59:30 +05:30
parent de47ceb72d
commit ecdb93d6b5
+131 -1
View File
@@ -279,6 +279,128 @@ def _default_internal_target_date(subscription: ClientServiceSubscription, templ
return None
def _normalise_task_name_for_setup_sync(value: str | None) -> str:
return " ".join((value or "").strip().lower().replace("&", "and").split())
def _is_engagement_acceptance_setup_task_name(value: str | None) -> bool:
"""Return True only for execution rows that duplicate the engagement-level AQMM setup.
These tasks are retained in the work tracker as historical/scope rows, but once the
engagement-level AQMM workflow is approved they must not ask staff to repeat the
same acceptance, independence, KYC or team-setup approvals.
"""
name = _normalise_task_name_for_setup_sync(value)
if not name:
return False
if "acceptance and continuance" in name:
return True
if "independence and conflict" in name:
return True
if "kyc" in name and "engagement letter" in name:
return True
if name.startswith("create ") and "engagement" in name and "assign team" in name:
return True
return False
def _apply_approved_engagement_acceptance_to_task(
task: ClientServiceTaskInstance,
*,
actor_user_id: int,
) -> bool:
"""Synchronise one duplicate setup task from an already-approved engagement AQMM gate.
No task is deleted and no executed/reviewed normal work is rewritten. Only the four
pre-execution setup controls that duplicate the engagement-level acceptance workflow
are satisfied automatically. This removes the circular gate where an active, AQMM-
approved engagement asks staff to obtain AQMM approval again before starting work.
"""
if not _is_engagement_acceptance_setup_task_name(getattr(task, "task_name", None)):
return False
# Never rewrite genuine task execution/rework history. This repair is intentionally
# limited to untouched setup rows, plus rows already auto-completed by this sync.
status = (getattr(task, "status", None) or "pending").strip().lower()
if status not in {"pending", "completed"}:
return False
if getattr(task, "started_at_utc", None) is not None and status != "completed":
return False
if (getattr(task, "rework_status", None) or "none").strip().lower() == "open":
return False
now = datetime.now(timezone.utc)
changed = False
def set_if_different(attr: str, value) -> None:
nonlocal changed
if getattr(task, attr, None) != value:
setattr(task, attr, value)
changed = True
set_if_different("status", "completed")
if getattr(task, "completed_at_utc", None) is None:
task.completed_at_utc = now
changed = True
# The engagement-level AQMM approval has already performed these reviews. Keep the
# task row, but do not make staff repeat Manager/Partner/Review-Partner approvals.
set_if_different("normal_review_required", False)
set_if_different("normal_review_role", None)
set_if_different("normal_review_status", "not_required")
set_if_different("aqmm_manager_review_required", False)
set_if_different("aqmm_partner_review_required", False)
set_if_different("aqmm_review_partner_required", False)
set_if_different("manager_review_status", "not_required")
set_if_different("partner_review_status", "not_required")
set_if_different("review_partner_review_status", "not_required")
# Evidence for these four setup controls lives in the engagement-level AQMM records
# (declarations/KYC/engagement letter). Do not require duplicate task-document evidence.
set_if_different("aqmm_evidence_required", False)
set_if_different("evidence_status", "not_required")
set_if_different("aqmm_review_status", "not_required")
set_if_different("aqmm_status", "completed")
if getattr(task, "aqmm_completed_at_utc", None) is None:
task.aqmm_completed_at_utc = now
changed = True
if changed:
task.updated_by_user_id = actor_user_id
return changed
def synchronise_approved_engagement_acceptance_tasks(
db: Session,
*,
subscription: ClientServiceSubscription,
actor_user_id: int,
) -> int:
"""Self-heal duplicate pre-acceptance execution rows after AQMM approval.
Returns the number of task rows changed. The caller can use this to decide whether
a commit is required. Existing non-setup tasks, task responses, evidence, rework and
reviews are left untouched.
"""
if not quality_required_for_engagement(subscription.engagement_type):
return 0
if getattr(subscription, "quality_acceptance_status", None) != QUALITY_APPROVED:
return 0
tasks = db.execute(
select(ClientServiceTaskInstance).where(
ClientServiceTaskInstance.subscription_id == subscription.id,
ClientServiceTaskInstance.is_active.is_(True),
)
).scalars().all()
changed = 0
for task in tasks:
if _apply_approved_engagement_acceptance_to_task(task, actor_user_id=actor_user_id):
changed += 1
return changed
def generate_tasks_for_subscription(db: Session, *, subscription: ClientServiceSubscription, user_id: int) -> int:
if quality_required_for_engagement(subscription.engagement_type) and getattr(subscription, "quality_acceptance_status", None) != QUALITY_APPROVED:
raise ValueError(getattr(subscription, "quality_block_reason", None) or "AQMM acceptance is pending for this assurance engagement. Complete AQMM before generating tasks.")
@@ -372,11 +494,19 @@ def generate_tasks_for_subscription_if_ready(
and getattr(subscription, "quality_acceptance_status", None) != QUALITY_APPROVED
):
return 0
return generate_tasks_for_subscription(
created = generate_tasks_for_subscription(
db,
subscription=subscription,
user_id=user_id,
)
repaired = synchronise_approved_engagement_acceptance_tasks(
db,
subscription=subscription,
actor_user_id=user_id,
)
# Preserve the historical meaning (positive = database changed) while allowing
# existing already-generated engagements to self-heal and be committed by callers.
return int(created) + int(repaired)
def _sync_normal_review_snapshot_preserving_history(