Add AQMM quality tags to existing service task checklist

This commit is contained in:
A R R R Associates
2026-07-07 11:30:59 +05:30
parent 05c882304b
commit 05690a220c
11 changed files with 448 additions and 5 deletions
@@ -0,0 +1,125 @@
"""phase 3.1 aqmm quality tags on existing service tasks
Revision ID: 20260625_phase_3_1_aqmm_task_quality_tags
Revises: 20260624_phase_3_aqmm_engagement_level_rework
Create Date: 2026-06-25
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
revision = "20260625_phase_3_1_aqmm_task_quality_tags"
down_revision = "20260624_phase_3_aqmm_engagement_level_rework"
branch_labels = None
depends_on = None
AQMM_TEMPLATE_COLUMNS = [
sa.Column("is_aqmm_task", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("aqmm_mandatory", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("aqmm_evidence_required", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("aqmm_manager_review_required", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("aqmm_partner_review_required", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("aqmm_review_partner_required", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("aqmm_blocks_final_release", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("aqmm_reference", sa.String(length=120), nullable=True),
]
AQMM_INSTANCE_COLUMNS = AQMM_TEMPLATE_COLUMNS + [
sa.Column("aqmm_status", sa.String(length=40), nullable=False, server_default="not_required"),
sa.Column("aqmm_review_status", sa.String(length=40), nullable=False, server_default="not_required"),
sa.Column("aqmm_completed_at_utc", sa.DateTime(timezone=True), nullable=True),
]
def _has_table(bind, table_name: str) -> bool:
return sa.inspect(bind).has_table(table_name)
def _has_column(bind, table_name: str, column_name: str) -> bool:
if not _has_table(bind, table_name):
return False
return column_name in {c["name"] for c in sa.inspect(bind).get_columns(table_name)}
def _add_column_once(bind, table: str, column: sa.Column) -> None:
if not _has_column(bind, table, column.name):
with op.batch_alter_table(table) as batch:
batch.add_column(column)
def _drop_column_once(bind, table: str, column_name: str) -> None:
if _has_column(bind, table, column_name):
with op.batch_alter_table(table) as batch:
batch.drop_column(column_name)
def upgrade() -> None:
bind = op.get_bind()
for table in ["service_default_task_templates", "firm_service_task_templates"]:
if _has_table(bind, table):
for column in AQMM_TEMPLATE_COLUMNS:
_add_column_once(bind, table, column.copy())
if _has_table(bind, "client_service_task_instances"):
for column in AQMM_INSTANCE_COLUMNS:
_add_column_once(bind, "client_service_task_instances", column.copy())
# Backfill existing generated tasks from their firm task templates.
op.execute("""
UPDATE client_service_task_instances csti
SET is_aqmm_task = COALESCE(fst.is_aqmm_task, FALSE),
aqmm_mandatory = COALESCE(fst.aqmm_mandatory, FALSE),
aqmm_evidence_required = COALESCE(fst.aqmm_evidence_required, FALSE),
aqmm_manager_review_required = COALESCE(fst.aqmm_manager_review_required, FALSE),
aqmm_partner_review_required = COALESCE(fst.aqmm_partner_review_required, FALSE),
aqmm_review_partner_required = COALESCE(fst.aqmm_review_partner_required, FALSE),
aqmm_blocks_final_release = COALESCE(fst.aqmm_blocks_final_release, FALSE),
aqmm_reference = fst.aqmm_reference,
aqmm_status = CASE WHEN COALESCE(fst.is_aqmm_task, FALSE) THEN 'pending' ELSE 'not_required' END,
aqmm_review_status = CASE
WHEN COALESCE(fst.aqmm_manager_review_required, FALSE)
OR COALESCE(fst.aqmm_partner_review_required, FALSE)
OR COALESCE(fst.aqmm_review_partner_required, FALSE)
THEN 'pending_review'
ELSE 'not_required'
END
FROM firm_service_task_templates fst
WHERE csti.firm_task_template_id = fst.id
""")
def downgrade() -> None:
bind = op.get_bind()
for table in ["client_service_task_instances"]:
if _has_table(bind, table):
for col in [
"aqmm_completed_at_utc",
"aqmm_review_status",
"aqmm_status",
"aqmm_reference",
"aqmm_blocks_final_release",
"aqmm_review_partner_required",
"aqmm_partner_review_required",
"aqmm_manager_review_required",
"aqmm_evidence_required",
"aqmm_mandatory",
"is_aqmm_task",
]:
_drop_column_once(bind, table, col)
for table in ["firm_service_task_templates", "service_default_task_templates"]:
if _has_table(bind, table):
for col in [
"aqmm_reference",
"aqmm_blocks_final_release",
"aqmm_review_partner_required",
"aqmm_partner_review_required",
"aqmm_manager_review_required",
"aqmm_evidence_required",
"aqmm_mandatory",
"is_aqmm_task",
]:
_drop_column_once(bind, table, col)
+32
View File
@@ -73,6 +73,14 @@ DEFAULT_TASK_COLUMNS = [
"default_role_name", "default_role_name",
"is_mandatory", "is_mandatory",
"requires_review", "requires_review",
"is_aqmm_task",
"aqmm_mandatory",
"aqmm_evidence_required",
"aqmm_manager_review_required",
"aqmm_partner_review_required",
"aqmm_review_partner_required",
"aqmm_blocks_final_release",
"aqmm_reference",
"is_active", "is_active",
"description", "description",
] ]
@@ -84,6 +92,14 @@ FIRM_TASK_COLUMNS = [
"default_role_name", "default_role_name",
"is_mandatory", "is_mandatory",
"requires_review", "requires_review",
"is_aqmm_task",
"aqmm_mandatory",
"aqmm_evidence_required",
"aqmm_manager_review_required",
"aqmm_partner_review_required",
"aqmm_review_partner_required",
"aqmm_blocks_final_release",
"aqmm_reference",
"is_active", "is_active",
"description", "description",
] ]
@@ -1010,6 +1026,14 @@ def import_system_default_tasks(db: Session, *, current_user, file_bytes: bytes,
task.default_role_name = _clean(_cell(row, headers, "default_role_name")) or None task.default_role_name = _clean(_cell(row, headers, "default_role_name")) or None
task.is_mandatory = _bool(_cell(row, headers, "is_mandatory"), True) task.is_mandatory = _bool(_cell(row, headers, "is_mandatory"), True)
task.requires_review = _bool(_cell(row, headers, "requires_review"), False) task.requires_review = _bool(_cell(row, headers, "requires_review"), False)
task.is_aqmm_task = _bool(_cell(row, headers, "is_aqmm_task"), False)
task.aqmm_mandatory = _bool(_cell(row, headers, "aqmm_mandatory"), False)
task.aqmm_evidence_required = _bool(_cell(row, headers, "aqmm_evidence_required"), False)
task.aqmm_manager_review_required = _bool(_cell(row, headers, "aqmm_manager_review_required"), False)
task.aqmm_partner_review_required = _bool(_cell(row, headers, "aqmm_partner_review_required"), False)
task.aqmm_review_partner_required = _bool(_cell(row, headers, "aqmm_review_partner_required"), False)
task.aqmm_blocks_final_release = _bool(_cell(row, headers, "aqmm_blocks_final_release"), False)
task.aqmm_reference = _clean(_cell(row, headers, "aqmm_reference")) or None
task.is_active = _bool(_cell(row, headers, "is_active"), True) task.is_active = _bool(_cell(row, headers, "is_active"), True)
except Exception as exc: except Exception as exc:
errors.append({"row": row_no, "message": str(exc)}) errors.append({"row": row_no, "message": str(exc)})
@@ -1067,6 +1091,14 @@ def import_firm_task_templates(db: Session, *, current_user, tenant_id: int, fil
task.default_role_name = _clean(_cell(row, headers, "default_role_name")) or None task.default_role_name = _clean(_cell(row, headers, "default_role_name")) or None
task.is_mandatory = _bool(_cell(row, headers, "is_mandatory"), True) task.is_mandatory = _bool(_cell(row, headers, "is_mandatory"), True)
task.requires_review = _bool(_cell(row, headers, "requires_review"), False) task.requires_review = _bool(_cell(row, headers, "requires_review"), False)
task.is_aqmm_task = _bool(_cell(row, headers, "is_aqmm_task"), False)
task.aqmm_mandatory = _bool(_cell(row, headers, "aqmm_mandatory"), False)
task.aqmm_evidence_required = _bool(_cell(row, headers, "aqmm_evidence_required"), False)
task.aqmm_manager_review_required = _bool(_cell(row, headers, "aqmm_manager_review_required"), False)
task.aqmm_partner_review_required = _bool(_cell(row, headers, "aqmm_partner_review_required"), False)
task.aqmm_review_partner_required = _bool(_cell(row, headers, "aqmm_review_partner_required"), False)
task.aqmm_blocks_final_release = _bool(_cell(row, headers, "aqmm_blocks_final_release"), False)
task.aqmm_reference = _clean(_cell(row, headers, "aqmm_reference")) or None
task.is_active = _bool(_cell(row, headers, "is_active"), True) task.is_active = _bool(_cell(row, headers, "is_active"), True)
task.updated_by_user_id = current_user.id task.updated_by_user_id = current_user.id
except Exception as exc: except Exception as exc:
+3
View File
@@ -10,6 +10,7 @@ from app.core.security.session_auth import get_current_user
from app.core.templating import templates from app.core.templating import templates
from app.modules.services.models import ClientServiceSubscription, ClientServiceTaskInstance from app.modules.services.models import ClientServiceSubscription, ClientServiceTaskInstance
from app.modules.services.due_dates import apply_due_date_rule_to_subscription from app.modules.services.due_dates import apply_due_date_rule_to_subscription
from app.modules.services.execution import aqmm_task_summary_for_subscription
from app.modules.clients.models import Client from app.modules.clients.models import Client
from app.modules.services.client_services import ( from app.modules.services.client_services import (
SUBSCRIPTION_STATUSES, SUBSCRIPTION_STATUSES,
@@ -398,6 +399,7 @@ def subscription_detail(request: Request, subscription_id: int):
my_pending_declarations = [d for d in declarations if d.requested_user_id == user.id and d.status == "pending"] my_pending_declarations = [d for d in declarations if d.requested_user_id == user.id and d.status == "pending"]
kyc_verification = get_latest_engagement_kyc_verification(db, subscription_id=row.id) kyc_verification = get_latest_engagement_kyc_verification(db, subscription_id=row.id)
engagement_letter = get_current_engagement_letter(db, subscription_id=row.id) engagement_letter = get_current_engagement_letter(db, subscription_id=row.id)
aqmm_task_summary = aqmm_task_summary_for_subscription(db, subscription_id=row.id)
tasks = db.execute( tasks = db.execute(
select(ClientServiceTaskInstance) select(ClientServiceTaskInstance)
@@ -417,6 +419,7 @@ def subscription_detail(request: Request, subscription_id: int):
my_pending_declarations=my_pending_declarations, my_pending_declarations=my_pending_declarations,
kyc_verification=kyc_verification, kyc_verification=kyc_verification,
engagement_letter=engagement_letter, engagement_letter=engagement_letter,
aqmm_task_summary=aqmm_task_summary,
can_manage=_can_manage_client_services(db, user), can_manage=_can_manage_client_services(db, user),
) )
finally: finally:
+94
View File
@@ -6,6 +6,7 @@ from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session, selectinload from sqlalchemy.orm import Session, selectinload
from app.modules.clients.models import Client from app.modules.clients.models import Client
from app.modules.documents.models import EngagementDocument
from app.modules.core.iam.models import User from app.modules.core.iam.models import User
from app.modules.services.client_services import quality_required_for_engagement, QUALITY_APPROVED from app.modules.services.client_services import quality_required_for_engagement, QUALITY_APPROVED
from app.modules.services.models import ( from app.modules.services.models import (
@@ -222,6 +223,16 @@ def generate_tasks_for_subscription(db: Session, *, subscription: ClientServiceS
internal_target_date=_default_internal_target_date(subscription, template), internal_target_date=_default_internal_target_date(subscription, template),
status="pending", status="pending",
priority="normal", priority="normal",
is_aqmm_task=getattr(template, "is_aqmm_task", False),
aqmm_mandatory=getattr(template, "aqmm_mandatory", False),
aqmm_evidence_required=getattr(template, "aqmm_evidence_required", False),
aqmm_manager_review_required=getattr(template, "aqmm_manager_review_required", False),
aqmm_partner_review_required=getattr(template, "aqmm_partner_review_required", False),
aqmm_review_partner_required=getattr(template, "aqmm_review_partner_required", False),
aqmm_blocks_final_release=getattr(template, "aqmm_blocks_final_release", False),
aqmm_reference=getattr(template, "aqmm_reference", None),
aqmm_status="pending" if getattr(template, "is_aqmm_task", False) else "not_required",
aqmm_review_status="pending_review" if (getattr(template, "aqmm_manager_review_required", False) or getattr(template, "aqmm_partner_review_required", False) or getattr(template, "aqmm_review_partner_required", False)) else "not_required",
is_active=True, is_active=True,
created_by_user_id=user_id, created_by_user_id=user_id,
updated_by_user_id=user_id, updated_by_user_id=user_id,
@@ -231,6 +242,89 @@ def generate_tasks_for_subscription(db: Session, *, subscription: ClientServiceS
return created return created
def _task_has_evidence(db: Session, task_id: int) -> bool:
return bool(
db.execute(
select(EngagementDocument.id).where(
EngagementDocument.task_instance_id == int(task_id),
EngagementDocument.is_deleted.is_(False),
)
).first()
)
def _aqmm_task_issue(db: Session, task: ClientServiceTaskInstance) -> str | None:
"""Return blocking reason for one AQMM-tagged task, or None if it passes.
This deliberately reuses the existing task status and task document upload
workflow. Review flags are captured for reporting and future review-specific
workflow; until dedicated manager/partner review statuses are added, the
task must at least be completed and evidence uploaded wherever marked.
"""
if not getattr(task, "is_aqmm_task", False):
return None
if getattr(task, "aqmm_mandatory", False) and task.status != "completed":
return "Task not completed"
if getattr(task, "aqmm_evidence_required", False) and not _task_has_evidence(db, task.id):
return "Evidence not uploaded"
return None
def list_aqmm_quality_tasks(db: Session, *, subscription_id: int) -> list[dict]:
tasks = db.execute(
select(ClientServiceTaskInstance)
.where(
ClientServiceTaskInstance.subscription_id == int(subscription_id),
ClientServiceTaskInstance.is_active.is_(True),
ClientServiceTaskInstance.is_aqmm_task.is_(True),
)
.order_by(ClientServiceTaskInstance.sequence_no.asc(), ClientServiceTaskInstance.id.asc())
).scalars().all()
rows: list[dict] = []
for task in tasks:
has_evidence = _task_has_evidence(db, task.id)
issue = _aqmm_task_issue(db, task)
rows.append({"task": task, "has_evidence": has_evidence, "issue": issue, "passes": issue is None})
return rows
def aqmm_task_summary_for_subscription(db: Session, *, subscription_id: int) -> dict:
rows = list_aqmm_quality_tasks(db, subscription_id=subscription_id)
total = len(rows)
completed = sum(1 for r in rows if r["task"].status == "completed")
mandatory = sum(1 for r in rows if getattr(r["task"], "aqmm_mandatory", False))
mandatory_done = sum(1 for r in rows if getattr(r["task"], "aqmm_mandatory", False) and r["passes"])
evidence_required = sum(1 for r in rows if getattr(r["task"], "aqmm_evidence_required", False))
evidence_missing = sum(1 for r in rows if getattr(r["task"], "aqmm_evidence_required", False) and not r["has_evidence"])
blockers = [r for r in rows if getattr(r["task"], "aqmm_blocks_final_release", False) and not r["passes"]]
status = "not_required"
if total:
status = "completed" if mandatory_done == mandatory and evidence_missing == 0 else "in_progress"
return {
"rows": rows,
"total": total,
"completed": completed,
"mandatory": mandatory,
"mandatory_done": mandatory_done,
"evidence_required": evidence_required,
"evidence_missing": evidence_missing,
"blockers": blockers,
"status": status,
}
def assert_aqmm_quality_tasks_complete(db: Session, *, subscription_id: int) -> None:
summary = aqmm_task_summary_for_subscription(db, subscription_id=subscription_id)
if summary["blockers"]:
first = summary["blockers"][0]
task = first["task"]
raise ValueError(f"AQMM final release blocked: {task.task_name} - {first['issue']}")
if summary["mandatory"] and summary["mandatory_done"] < summary["mandatory"]:
raise ValueError("AQMM mandatory quality checklist tasks are pending.")
if summary["evidence_missing"]:
raise ValueError("AQMM evidence upload is pending for one or more quality checklist tasks.")
def _decorate_task_for_tracker(task: ClientServiceTaskInstance, *, today: date) -> ClientServiceTaskInstance: def _decorate_task_for_tracker(task: ClientServiceTaskInstance, *, today: date) -> ClientServiceTaskInstance:
target_date = getattr(task, "internal_target_date", None) target_date = getattr(task, "internal_target_date", None)
subscription = getattr(task, "subscription", None) subscription = getattr(task, "subscription", None)
+39
View File
@@ -111,6 +111,19 @@ class ServiceDefaultTaskTemplate(CommonBase):
default_role_name: Mapped[str | None] = mapped_column(String(100), nullable=True) default_role_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
is_mandatory: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) is_mandatory: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
requires_review: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) requires_review: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
# AQMM task tags. These flags allow the existing service checklist to become
# the engagement quality checklist for assurance engagements, without creating
# a separate duplicate AQMM checklist module.
is_aqmm_task: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, index=True)
aqmm_mandatory: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_evidence_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_manager_review_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_partner_review_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_review_partner_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_blocks_final_release: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_reference: Mapped[str | None] = mapped_column(String(120), nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
catalogue = relationship("ServiceCatalogue", back_populates="default_task_templates") catalogue = relationship("ServiceCatalogue", back_populates="default_task_templates")
@@ -166,6 +179,17 @@ class FirmServiceTaskTemplate(CommonBase):
default_role_name: Mapped[str | None] = mapped_column(String(100), nullable=True) default_role_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
is_mandatory: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) is_mandatory: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
requires_review: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) requires_review: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
# AQMM task tags copied into generated ClientServiceTaskInstance rows.
is_aqmm_task: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, index=True)
aqmm_mandatory: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_evidence_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_manager_review_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_partner_review_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_review_partner_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_blocks_final_release: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_reference: Mapped[str | None] = mapped_column(String(120), nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True) created_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
@@ -572,6 +596,21 @@ class ClientServiceTaskInstance(CommonBase):
internal_target_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True) internal_target_date: Mapped[date | None] = mapped_column(Date, nullable=True, index=True)
status: Mapped[str] = mapped_column(String(30), nullable=False, default="pending", index=True) status: Mapped[str] = mapped_column(String(30), nullable=False, default="pending", index=True)
priority: Mapped[str] = mapped_column(String(20), nullable=False, default="normal") priority: Mapped[str] = mapped_column(String(20), nullable=False, default="normal")
# Copied from FirmServiceTaskTemplate at generation time. Existing task
# completion/evidence upload flow is reused to calculate AQMM quality status.
is_aqmm_task: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, index=True)
aqmm_mandatory: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_evidence_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_manager_review_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_partner_review_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_review_partner_required: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_blocks_final_release: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aqmm_reference: Mapped[str | None] = mapped_column(String(120), nullable=True)
aqmm_status: Mapped[str] = mapped_column(String(40), default="not_required", nullable=False, index=True)
aqmm_review_status: Mapped[str] = mapped_column(String(40), default="not_required", nullable=False, index=True)
aqmm_completed_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
remarks: Mapped[str | None] = mapped_column(Text, nullable=True) remarks: Mapped[str | None] = mapped_column(Text, nullable=True)
started_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) started_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
@@ -47,6 +47,24 @@
<textarea name="description" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ task.description or '' }}</textarea> <textarea name="description" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ task.description or '' }}</textarea>
</div> </div>
<div class="md:col-span-2 rounded-2xl border border-indigo-200 bg-indigo-50 p-4">
<div class="text-sm font-semibold text-indigo-950">AQMM / Quality Control Tagging</div>
<p class="mt-1 text-xs text-indigo-800">Use these only for tasks that should become part of the assurance engagement quality checklist. Non-assurance engagements will continue using the normal task flow.</p>
<div class="mt-3 grid gap-3 md:grid-cols-2 text-sm text-slate-700">
<label class="inline-flex items-center gap-2"><input type="checkbox" name="is_aqmm_task" {% if task.is_aqmm_task %}checked{% endif %}> Part of AQMM / quality checklist</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_mandatory" {% if task.aqmm_mandatory %}checked{% endif %}> Mandatory for AQMM completion</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_evidence_required" {% if task.aqmm_evidence_required %}checked{% endif %}> Evidence upload required</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_manager_review_required" {% if task.aqmm_manager_review_required %}checked{% endif %}> Manager review required</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_partner_review_required" {% if task.aqmm_partner_review_required %}checked{% endif %}> Partner review required</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_review_partner_required" {% if task.aqmm_review_partner_required %}checked{% endif %}> Review partner review required</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_blocks_final_release" {% if task.aqmm_blocks_final_release %}checked{% endif %}> Block final release until completed</label>
<div>
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-indigo-800">AQMM Reference / Clause</label>
<input type="text" name="aqmm_reference" value="{{ task.aqmm_reference or '' }}" class="w-full rounded-xl border border-indigo-200 px-3 py-2 text-sm" placeholder="e.g. Acceptance / Planning / Review">
</div>
</div>
</div>
<div class="md:col-span-2 rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800"> <div class="md:col-span-2 rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">
To deactivate this system default task, untick <strong>Active</strong> and save. Existing firm-copied tasks will not be automatically changed. To deactivate this system default task, untick <strong>Active</strong> and save. Existing firm-copied tasks will not be automatically changed.
</div> </div>
@@ -19,7 +19,7 @@
<div class="mt-1 text-xs text-slate-500"> <div class="mt-1 text-xs text-slate-500">
Role: {{ task.default_role_name or '-' }} · Role: {{ task.default_role_name or '-' }} ·
Mandatory: {{ 'Yes' if task.is_mandatory else 'No' }} · Mandatory: {{ 'Yes' if task.is_mandatory else 'No' }} ·
Review: {{ 'Yes' if task.requires_review else 'No' }} Review: {{ 'Yes' if task.requires_review else 'No' }} · AQMM: {{ 'Yes' if task.is_aqmm_task else 'No' }}
</div> </div>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
@@ -46,6 +46,19 @@
<div><label class="mb-2 block text-sm font-medium text-slate-700">Sequence No</label><input type="number" min="1" name="sequence_no" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div> <div><label class="mb-2 block text-sm font-medium text-slate-700">Sequence No</label><input type="number" min="1" name="sequence_no" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div>
<div class="flex items-center gap-6 pt-7 text-sm text-slate-700"><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_mandatory" checked> Mandatory</label><label class="inline-flex items-center gap-2"><input type="checkbox" name="requires_review"> Review</label><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_active" checked> Active</label></div> <div class="flex items-center gap-6 pt-7 text-sm text-slate-700"><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_mandatory" checked> Mandatory</label><label class="inline-flex items-center gap-2"><input type="checkbox" name="requires_review"> Review</label><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_active" checked> Active</label></div>
<div class="md:col-span-2"><label class="mb-2 block text-sm font-medium text-slate-700">Description</label><textarea name="description" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></textarea></div> <div class="md:col-span-2"><label class="mb-2 block text-sm font-medium text-slate-700">Description</label><textarea name="description" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></textarea></div>
<div class="md:col-span-2 rounded-2xl border border-indigo-200 bg-indigo-50 p-4">
<div class="text-sm font-semibold text-indigo-950">AQMM / Quality Control Tagging</div>
<div class="mt-3 grid gap-2 md:grid-cols-4 text-xs text-slate-700">
<label class="inline-flex items-center gap-1"><input type="checkbox" name="is_aqmm_task"> Part of AQMM</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_mandatory"> Mandatory</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_evidence_required"> Evidence required</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_manager_review_required"> Manager review</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_partner_review_required"> Partner review</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_review_partner_required"> Review partner review</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_blocks_final_release"> Blocks final release</label>
<input name="aqmm_reference" placeholder="AQMM reference / clause" class="rounded-lg border border-indigo-200 px-2 py-1 text-xs">
</div>
</div>
<div class="md:col-span-2 flex justify-end"><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Add Default Task</button></div> <div class="md:col-span-2 flex justify-end"><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Add Default Task</button></div>
</form> </form>
</section> </section>
@@ -45,6 +45,41 @@
</div> </div>
{% endif %} {% endif %}
<div class="mt-5 rounded-2xl border border-white bg-white p-4">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<h4 class="text-sm font-semibold text-slate-900">AQMM Quality Checklist Tasks</h4>
<p class="mt-1 text-xs text-slate-500">These are your existing service tasks tagged as part of AQMM. No separate checklist is created.</p>
</div>
<div class="grid grid-cols-2 gap-2 text-xs md:grid-cols-4">
<span class="rounded-xl bg-slate-100 px-3 py-2">Total: <strong>{{ aqmm_task_summary.total if aqmm_task_summary else 0 }}</strong></span>
<span class="rounded-xl bg-emerald-100 px-3 py-2 text-emerald-800">Completed: <strong>{{ aqmm_task_summary.completed if aqmm_task_summary else 0 }}</strong></span>
<span class="rounded-xl bg-amber-100 px-3 py-2 text-amber-800">Evidence Missing: <strong>{{ aqmm_task_summary.evidence_missing if aqmm_task_summary else 0 }}</strong></span>
<span class="rounded-xl bg-purple-100 px-3 py-2 text-purple-800">Final Blockers: <strong>{{ aqmm_task_summary.blockers|length if aqmm_task_summary else 0 }}</strong></span>
</div>
</div>
<div class="mt-4 overflow-hidden rounded-xl border border-slate-200">
<table class="min-w-full divide-y divide-slate-200 text-sm">
<thead class="bg-slate-50"><tr><th class="px-3 py-2 text-left">Task</th><th class="px-3 py-2 text-left">AQMM Flags</th><th class="px-3 py-2 text-left">Evidence</th><th class="px-3 py-2 text-left">Status</th><th class="px-3 py-2 text-right">Action</th></tr></thead>
<tbody class="divide-y divide-slate-100">
{% for item in aqmm_task_summary.rows if aqmm_task_summary %}
{% set task = item.task %}
<tr>
<td class="px-3 py-2"><div class="font-medium text-slate-900">{{ task.sequence_no }}. {{ task.task_name }}</div>{% if task.aqmm_reference %}<div class="text-xs text-slate-500">{{ task.aqmm_reference }}</div>{% endif %}</td>
<td class="px-3 py-2 text-xs"><div class="flex flex-wrap gap-1">{% if task.aqmm_mandatory %}<span class="rounded-full bg-rose-100 px-2 py-1 text-rose-800">Mandatory</span>{% endif %}{% if task.aqmm_evidence_required %}<span class="rounded-full bg-sky-100 px-2 py-1 text-sky-800">Evidence</span>{% endif %}{% if task.aqmm_manager_review_required %}<span class="rounded-full bg-amber-100 px-2 py-1 text-amber-800">Manager</span>{% endif %}{% if task.aqmm_partner_review_required %}<span class="rounded-full bg-indigo-100 px-2 py-1 text-indigo-800">Partner</span>{% endif %}{% if task.aqmm_review_partner_required %}<span class="rounded-full bg-purple-100 px-2 py-1 text-purple-800">Review Partner</span>{% endif %}{% if task.aqmm_blocks_final_release %}<span class="rounded-full bg-slate-900 px-2 py-1 text-white">Final Gate</span>{% endif %}</div></td>
<td class="px-3 py-2">{{ 'Uploaded' if item.has_evidence else ('Required' if task.aqmm_evidence_required else '-') }}</td>
<td class="px-3 py-2"><span class="rounded-full px-2 py-1 text-xs font-medium {{ 'bg-emerald-100 text-emerald-800' if item.passes else 'bg-amber-100 text-amber-800' }}">{{ 'OK' if item.passes else item.issue }}</span><div class="mt-1 text-xs text-slate-500">Task: {{ task.status|replace('_',' ')|title }}</div></td>
<td class="px-3 py-2 text-right"><a href="/documents/tasks/{{ task.id }}" class="text-xs font-medium text-brand-700 hover:underline">Open Evidence</a></td>
</tr>
{% else %}
<tr><td colspan="5" class="px-3 py-6 text-center text-sm text-slate-500">No AQMM-tagged task generated yet. Mark selected service tasks as AQMM and generate work tracker tasks.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if my_pending_declarations %} {% if my_pending_declarations %}
<div class="mt-5 rounded-2xl border border-white bg-white p-4"> <div class="mt-5 rounded-2xl border border-white bg-white p-4">
<h4 class="text-sm font-semibold text-slate-900">My Pending Declarations</h4> <h4 class="text-sm font-semibold text-slate-900">My Pending Declarations</h4>
@@ -83,7 +118,7 @@
{% for task in tasks or [] %} {% for task in tasks or [] %}
<tr> <tr>
<td class="px-4 py-3 text-sm font-medium text-slate-900">{{ task.sequence_no }}</td> <td class="px-4 py-3 text-sm font-medium text-slate-900">{{ task.sequence_no }}</td>
<td class="px-4 py-3 text-sm"><div class="font-medium text-slate-900">{{ task.task_name }}</div>{% if task.description %}<div class="text-xs text-slate-500">{{ task.description }}</div>{% endif %}</td> <td class="px-4 py-3 text-sm"><div class="font-medium text-slate-900">{{ task.task_name }}</div>{% if task.description %}<div class="text-xs text-slate-500">{{ task.description }}</div>{% endif %}{% if task.is_aqmm_task %}<div class="mt-1 flex flex-wrap gap-1 text-[11px]"><span class="rounded-full bg-indigo-100 px-2 py-1 text-indigo-800">AQMM</span>{% if task.aqmm_mandatory %}<span class="rounded-full bg-rose-100 px-2 py-1 text-rose-800">Mandatory</span>{% endif %}{% if task.aqmm_evidence_required %}<span class="rounded-full bg-sky-100 px-2 py-1 text-sky-800">Evidence</span>{% endif %}</div>{% endif %}</td>
<td class="px-4 py-3 text-sm text-slate-700">{{ task.status.replace('_',' ').title() }}</td> <td class="px-4 py-3 text-sm text-slate-700">{{ task.status.replace('_',' ').title() }}</td>
<td class="px-4 py-3 text-right"><a href="/documents/tasks/{{ task.id }}" class="text-sm font-medium text-brand-700 hover:underline">Open Task Documents</a></td> <td class="px-4 py-3 text-right"><a href="/documents/tasks/{{ task.id }}" class="text-sm font-medium text-brand-700 hover:underline">Open Task Documents</a></td>
</tr> </tr>
@@ -47,6 +47,24 @@
<textarea name="description" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ task.description or '' }}</textarea> <textarea name="description" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm">{{ task.description or '' }}</textarea>
</div> </div>
<div class="md:col-span-2 rounded-2xl border border-indigo-200 bg-indigo-50 p-4">
<div class="text-sm font-semibold text-indigo-950">AQMM / Quality Control Tagging</div>
<p class="mt-1 text-xs text-indigo-800">Use these only for tasks that should become part of the assurance engagement quality checklist. Non-assurance engagements will continue using the normal task flow.</p>
<div class="mt-3 grid gap-3 md:grid-cols-2 text-sm text-slate-700">
<label class="inline-flex items-center gap-2"><input type="checkbox" name="is_aqmm_task" {% if task.is_aqmm_task %}checked{% endif %}> Part of AQMM / quality checklist</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_mandatory" {% if task.aqmm_mandatory %}checked{% endif %}> Mandatory for AQMM completion</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_evidence_required" {% if task.aqmm_evidence_required %}checked{% endif %}> Evidence upload required</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_manager_review_required" {% if task.aqmm_manager_review_required %}checked{% endif %}> Manager review required</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_partner_review_required" {% if task.aqmm_partner_review_required %}checked{% endif %}> Partner review required</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_review_partner_required" {% if task.aqmm_review_partner_required %}checked{% endif %}> Review partner review required</label>
<label class="inline-flex items-center gap-2"><input type="checkbox" name="aqmm_blocks_final_release" {% if task.aqmm_blocks_final_release %}checked{% endif %}> Block final release until completed</label>
<div>
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-indigo-800">AQMM Reference / Clause</label>
<input type="text" name="aqmm_reference" value="{{ task.aqmm_reference or '' }}" class="w-full rounded-xl border border-indigo-200 px-3 py-2 text-sm" placeholder="e.g. Acceptance / Planning / Review">
</div>
</div>
</div>
<div class="md:col-span-2 rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800"> <div class="md:col-span-2 rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800">
To deactivate this task, untick <strong>Active</strong> and save. No hard delete is used. To deactivate this task, untick <strong>Active</strong> and save. No hard delete is used.
</div> </div>
@@ -74,6 +74,20 @@
<label class="inline-flex items-center gap-1"><input type="checkbox" name="is_active" checked> Active</label> <label class="inline-flex items-center gap-1"><input type="checkbox" name="is_active" checked> Active</label>
</div> </div>
</div> </div>
<div class="lg:col-span-12 rounded-2xl border border-indigo-200 bg-indigo-50 p-4">
<div class="text-sm font-semibold text-indigo-950">AQMM / Quality Control Tagging</div>
<p class="mt-1 text-xs text-indigo-800">Tick only the service checklist tasks that should form part of assurance engagement quality evidence.</p>
<div class="mt-3 grid gap-2 md:grid-cols-4 text-xs text-slate-700">
<label class="inline-flex items-center gap-1"><input type="checkbox" name="is_aqmm_task"> Part of AQMM</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_mandatory"> Mandatory</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_evidence_required"> Evidence required</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_manager_review_required"> Manager review</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_partner_review_required"> Partner review</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_review_partner_required"> Review partner review</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="aqmm_blocks_final_release"> Blocks final release</label>
<input name="aqmm_reference" placeholder="AQMM reference / clause" class="rounded-lg border border-indigo-200 px-2 py-1 text-xs">
</div>
</div>
<div class="lg:col-span-10"> <div class="lg:col-span-10">
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Description</label> <label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Description</label>
<textarea name="description" rows="2" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="Optional instructions for staff or reviewer"></textarea> <textarea name="description" rows="2" class="w-full rounded-xl border border-slate-300 px-3 py-2 text-sm" placeholder="Optional instructions for staff or reviewer"></textarea>
@@ -115,6 +129,10 @@
<div class="flex flex-wrap gap-1"> <div class="flex flex-wrap gap-1">
{% if task.is_mandatory %}<span class="rounded-full bg-slate-100 px-2 py-1">Mandatory</span>{% endif %} {% if task.is_mandatory %}<span class="rounded-full bg-slate-100 px-2 py-1">Mandatory</span>{% endif %}
{% if task.requires_review %}<span class="rounded-full bg-amber-100 px-2 py-1 text-amber-800">Review</span>{% endif %} {% if task.requires_review %}<span class="rounded-full bg-amber-100 px-2 py-1 text-amber-800">Review</span>{% endif %}
{% if task.is_aqmm_task %}<span class="rounded-full bg-indigo-100 px-2 py-1 text-indigo-800">AQMM</span>{% endif %}
{% if task.aqmm_mandatory %}<span class="rounded-full bg-rose-100 px-2 py-1 text-rose-800">AQMM Mandatory</span>{% endif %}
{% if task.aqmm_evidence_required %}<span class="rounded-full bg-sky-100 px-2 py-1 text-sky-800">Evidence</span>{% endif %}
{% if task.aqmm_blocks_final_release %}<span class="rounded-full bg-purple-100 px-2 py-1 text-purple-800">Final Gate</span>{% endif %}
<span class="rounded-full px-2 py-1 {{ 'bg-emerald-100 text-emerald-800' if task.is_active else 'bg-slate-100 text-slate-500' }}">{{ 'Active' if task.is_active else 'Inactive' }}</span> <span class="rounded-full px-2 py-1 {{ 'bg-emerald-100 text-emerald-800' if task.is_active else 'bg-slate-100 text-slate-500' }}">{{ 'Active' if task.is_active else 'Inactive' }}</span>
</div> </div>
</td> </td>
+51 -3
View File
@@ -814,7 +814,7 @@ def task_templates_detail(request: Request, catalogue_id: int):
@router.post('/templates/{catalogue_id}/tasks/new') @router.post('/templates/{catalogue_id}/tasks/new')
def task_template_create_submit(request: Request, catalogue_id: int, task_name: str = Form(...), description: str = Form(''), default_role_name: str = Form(''), sequence_no: int | None = Form(None), is_mandatory: str | None = Form(None), requires_review: str | None = Form(None), is_active: str | None = Form(None), csrf_token: str = Form(...)): def task_template_create_submit(request: Request, catalogue_id: int, task_name: str = Form(...), description: str = Form(''), default_role_name: str = Form(''), sequence_no: int | None = Form(None), is_mandatory: str | None = Form(None), requires_review: str | None = Form(None), is_aqmm_task: str | None = Form(None), aqmm_mandatory: str | None = Form(None), aqmm_evidence_required: str | None = Form(None), aqmm_manager_review_required: str | None = Form(None), aqmm_partner_review_required: str | None = Form(None), aqmm_review_partner_required: str | None = Form(None), aqmm_blocks_final_release: str | None = Form(None), aqmm_reference: str = Form(''), is_active: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) validate_csrf(request, csrf_token)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
@@ -839,6 +839,14 @@ def task_template_create_submit(request: Request, catalogue_id: int, task_name:
sequence_no=sequence_no or next_task_sequence(db, tenant_id=tenant_id, catalogue_id=catalogue_id), sequence_no=sequence_no or next_task_sequence(db, tenant_id=tenant_id, catalogue_id=catalogue_id),
is_mandatory=is_mandatory is not None, is_mandatory=is_mandatory is not None,
requires_review=requires_review is not None, requires_review=requires_review is not None,
is_aqmm_task=is_aqmm_task is not None,
aqmm_mandatory=aqmm_mandatory is not None,
aqmm_evidence_required=aqmm_evidence_required is not None,
aqmm_manager_review_required=aqmm_manager_review_required is not None,
aqmm_partner_review_required=aqmm_partner_review_required is not None,
aqmm_review_partner_required=aqmm_review_partner_required is not None,
aqmm_blocks_final_release=aqmm_blocks_final_release is not None,
aqmm_reference=aqmm_reference.strip() or None,
is_active=is_active is not None, is_active=is_active is not None,
created_by_user_id=user.id, created_by_user_id=user.id,
updated_by_user_id=user.id, updated_by_user_id=user.id,
@@ -888,7 +896,7 @@ def default_templates_detail(request: Request, catalogue_id: int):
@router.post('/catalogue/{catalogue_id}/defaults/new') @router.post('/catalogue/{catalogue_id}/defaults/new')
def default_template_create_submit(request: Request, catalogue_id: int, task_name: str = Form(...), description: str = Form(''), default_role_name: str = Form(''), sequence_no: int | None = Form(None), is_mandatory: str | None = Form(None), requires_review: str | None = Form(None), is_active: str | None = Form(None), csrf_token: str = Form(...)): def default_template_create_submit(request: Request, catalogue_id: int, task_name: str = Form(...), description: str = Form(''), default_role_name: str = Form(''), sequence_no: int | None = Form(None), is_mandatory: str | None = Form(None), requires_review: str | None = Form(None), is_aqmm_task: str | None = Form(None), aqmm_mandatory: str | None = Form(None), aqmm_evidence_required: str | None = Form(None), aqmm_manager_review_required: str | None = Form(None), aqmm_partner_review_required: str | None = Form(None), aqmm_review_partner_required: str | None = Form(None), aqmm_blocks_final_release: str | None = Form(None), aqmm_reference: str = Form(''), is_active: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token) validate_csrf(request, csrf_token)
db = CommonSessionLocal() db = CommonSessionLocal()
try: try:
@@ -901,7 +909,7 @@ def default_template_create_submit(request: Request, catalogue_id: int, task_nam
catalogue = get_catalogue(db, catalogue_id) catalogue = get_catalogue(db, catalogue_id)
if not catalogue: if not catalogue:
return RedirectResponse(url='/services/defaults', status_code=303) return RedirectResponse(url='/services/defaults', status_code=303)
row = ServiceDefaultTaskTemplate(service_catalogue_id=catalogue_id, task_name=task_name.strip(), description=description.strip() or None, default_role_name=default_role_name.strip() or None, sequence_no=sequence_no or next_default_task_sequence(db, catalogue_id=catalogue_id), is_mandatory=is_mandatory is not None, requires_review=requires_review is not None, is_active=is_active is not None) row = ServiceDefaultTaskTemplate(service_catalogue_id=catalogue_id, task_name=task_name.strip(), description=description.strip() or None, default_role_name=default_role_name.strip() or None, sequence_no=sequence_no or next_default_task_sequence(db, catalogue_id=catalogue_id), is_mandatory=is_mandatory is not None, requires_review=requires_review is not None, is_aqmm_task=is_aqmm_task is not None, aqmm_mandatory=aqmm_mandatory is not None, aqmm_evidence_required=aqmm_evidence_required is not None, aqmm_manager_review_required=aqmm_manager_review_required is not None, aqmm_partner_review_required=aqmm_partner_review_required is not None, aqmm_review_partner_required=aqmm_review_partner_required is not None, aqmm_blocks_final_release=aqmm_blocks_final_release is not None, aqmm_reference=aqmm_reference.strip() or None, is_active=is_active is not None)
db.add(row); db.commit() db.add(row); db.commit()
return RedirectResponse(url=f'/services/catalogue/{catalogue_id}/defaults', status_code=303) return RedirectResponse(url=f'/services/catalogue/{catalogue_id}/defaults', status_code=303)
finally: finally:
@@ -940,6 +948,14 @@ def copy_defaults_to_firm(request: Request, catalogue_id: int, csrf_token: str =
default_role_name=d.default_role_name, default_role_name=d.default_role_name,
is_mandatory=d.is_mandatory, is_mandatory=d.is_mandatory,
requires_review=d.requires_review, requires_review=d.requires_review,
is_aqmm_task=getattr(d, "is_aqmm_task", False),
aqmm_mandatory=getattr(d, "aqmm_mandatory", False),
aqmm_evidence_required=getattr(d, "aqmm_evidence_required", False),
aqmm_manager_review_required=getattr(d, "aqmm_manager_review_required", False),
aqmm_partner_review_required=getattr(d, "aqmm_partner_review_required", False),
aqmm_review_partner_required=getattr(d, "aqmm_review_partner_required", False),
aqmm_blocks_final_release=getattr(d, "aqmm_blocks_final_release", False),
aqmm_reference=getattr(d, "aqmm_reference", None),
is_active=d.is_active, is_active=d.is_active,
created_by_user_id=user.id, created_by_user_id=user.id,
updated_by_user_id=user.id, updated_by_user_id=user.id,
@@ -1136,6 +1152,14 @@ def firm_task_template_edit_submit(
sequence_no: int = Form(1), sequence_no: int = Form(1),
is_mandatory: str | None = Form(None), is_mandatory: str | None = Form(None),
requires_review: str | None = Form(None), requires_review: str | None = Form(None),
is_aqmm_task: str | None = Form(None),
aqmm_mandatory: str | None = Form(None),
aqmm_evidence_required: str | None = Form(None),
aqmm_manager_review_required: str | None = Form(None),
aqmm_partner_review_required: str | None = Form(None),
aqmm_review_partner_required: str | None = Form(None),
aqmm_blocks_final_release: str | None = Form(None),
aqmm_reference: str = Form(''),
is_active: str | None = Form(None), is_active: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
@@ -1166,6 +1190,14 @@ def firm_task_template_edit_submit(
task.sequence_no = sequence_no task.sequence_no = sequence_no
task.is_mandatory = is_mandatory is not None task.is_mandatory = is_mandatory is not None
task.requires_review = requires_review is not None task.requires_review = requires_review is not None
task.is_aqmm_task = is_aqmm_task is not None
task.aqmm_mandatory = aqmm_mandatory is not None
task.aqmm_evidence_required = aqmm_evidence_required is not None
task.aqmm_manager_review_required = aqmm_manager_review_required is not None
task.aqmm_partner_review_required = aqmm_partner_review_required is not None
task.aqmm_review_partner_required = aqmm_review_partner_required is not None
task.aqmm_blocks_final_release = aqmm_blocks_final_release is not None
task.aqmm_reference = aqmm_reference.strip() or None
task.is_active = is_active is not None task.is_active = is_active is not None
task.updated_by_user_id = user.id task.updated_by_user_id = user.id
@@ -1227,6 +1259,14 @@ def default_task_template_edit_submit(
sequence_no: int = Form(1), sequence_no: int = Form(1),
is_mandatory: str | None = Form(None), is_mandatory: str | None = Form(None),
requires_review: str | None = Form(None), requires_review: str | None = Form(None),
is_aqmm_task: str | None = Form(None),
aqmm_mandatory: str | None = Form(None),
aqmm_evidence_required: str | None = Form(None),
aqmm_manager_review_required: str | None = Form(None),
aqmm_partner_review_required: str | None = Form(None),
aqmm_review_partner_required: str | None = Form(None),
aqmm_blocks_final_release: str | None = Form(None),
aqmm_reference: str = Form(''),
is_active: str | None = Form(None), is_active: str | None = Form(None),
csrf_token: str = Form(...), csrf_token: str = Form(...),
): ):
@@ -1259,6 +1299,14 @@ def default_task_template_edit_submit(
task.sequence_no = sequence_no task.sequence_no = sequence_no
task.is_mandatory = is_mandatory is not None task.is_mandatory = is_mandatory is not None
task.requires_review = requires_review is not None task.requires_review = requires_review is not None
task.is_aqmm_task = is_aqmm_task is not None
task.aqmm_mandatory = aqmm_mandatory is not None
task.aqmm_evidence_required = aqmm_evidence_required is not None
task.aqmm_manager_review_required = aqmm_manager_review_required is not None
task.aqmm_partner_review_required = aqmm_partner_review_required is not None
task.aqmm_review_partner_required = aqmm_review_partner_required is not None
task.aqmm_blocks_final_release = aqmm_blocks_final_release is not None
task.aqmm_reference = aqmm_reference.strip() or None
task.is_active = is_active is not None task.is_active = is_active is not None
db.commit() db.commit()