Add task role eligibility and safe open engagement sync

This commit is contained in:
A R R R Associates
2026-08-08 15:38:05 +05:30
parent 1a02af213d
commit 01a1c1abc1
7 changed files with 452 additions and 13 deletions
+78 -1
View File
@@ -36,6 +36,11 @@ from app.modules.services.due_dates import (
parse_optional_date,
)
from app.modules.services.catalogue_merge import build_merge_preview, merge_service_catalogues
from app.modules.services.execution import (
TASK_EXECUTION_ROLES,
serialise_eligible_task_roles,
sync_open_engagement_tasks_for_service,
)
from app.modules.services.task_documents import (
create_task_document_requirement,
get_task_document_requirement,
@@ -79,6 +84,7 @@ def _base_ctx(request: Request, user, db, **ctx):
"engagement_type_choices": ENGAGEMENT_TYPE_CHOICES,
"due_period_types": DUE_PERIOD_TYPES,
"due_year_basis_choices": DUE_YEAR_BASIS_CHOICES,
"task_execution_roles": TASK_EXECUTION_ROLES,
}
base.update(ctx)
return base
@@ -939,6 +945,7 @@ def _copy_system_defaults_if_firm_tasks_empty(
description=default.description,
sequence_no=default.sequence_no,
default_role_name=default.default_role_name,
eligible_role_names=getattr(default, "eligible_role_names", None) or default.default_role_name,
is_mandatory=default.is_mandatory,
requires_review=default.requires_review,
is_aqmm_task=getattr(default, "is_aqmm_task", False),
@@ -1234,7 +1241,7 @@ def task_templates_detail(request: Request, catalogue_id: int):
@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), task_category: str = Form(''), response_required: str | None = Form(None), response_type: str = Form('NONE'), evidence_required: str | None = Form(None), remarks_required_if_no: 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(...)):
def task_template_create_submit(request: Request, catalogue_id: int, task_name: str = Form(...), description: str = Form(''), default_role_name: str = Form(''), eligible_roles: list[str] = Form([]), sequence_no: int | None = Form(None), is_mandatory: str | None = Form(None), requires_review: str | None = Form(None), task_category: str = Form(''), response_required: str | None = Form(None), response_type: str = Form('NONE'), evidence_required: str | None = Form(None), remarks_required_if_no: 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)
db = CommonSessionLocal()
try:
@@ -1256,6 +1263,7 @@ def task_template_create_submit(request: Request, catalogue_id: int, task_name:
task_name=task_name.strip(),
description=description.strip() or None,
default_role_name=default_role_name.strip() or None,
eligible_role_names=serialise_eligible_task_roles(eligible_roles, default_role_name),
sequence_no=sequence_no or next_task_sequence(db, tenant_id=tenant_id, catalogue_id=catalogue_id),
is_mandatory=is_mandatory is not None,
requires_review=requires_review is not None,
@@ -1602,6 +1610,48 @@ def firm_task_template_toggle_active(
db.close()
@router.post('/templates/{catalogue_id}/sync-open-engagements')
def sync_firm_tasks_to_open_engagements(
request: Request,
catalogue_id: int,
include_started_open_tasks: str | None = Form(None),
csrf_token: str = Form(...),
):
validate_csrf(request, csrf_token)
db = CommonSessionLocal()
try:
user = get_current_user(request, db=db)
if not user:
return RedirectResponse(url='/login', status_code=303)
if not _can_manage_firm_tasks(db, user):
return _redirect_denied()
tenant_id = _active_tenant_id(request, user)
selection = get_firm_selection(db, tenant_id=tenant_id, catalogue_id=catalogue_id)
if not selection or not selection.is_enabled:
return RedirectResponse(url='/services', status_code=303)
result = sync_open_engagement_tasks_for_service(
db,
tenant_id=tenant_id,
catalogue_id=catalogue_id,
user_id=user.id,
include_started_open_tasks=include_started_open_tasks is not None,
)
db.commit()
return RedirectResponse(
url=(
f'/services/templates/{catalogue_id}'
f'?synced=1&created={result["created"]}'
f'&updated_pending={result["updated_pending"]}'
f'&updated_started={result["updated_started"]}'
f'&preserved={result["preserved_history"]}'
),
status_code=303,
)
finally:
db.close()
@router.get('/templates/{catalogue_id}/tasks/{task_id}/edit')
def firm_task_template_edit_page(request: Request, catalogue_id: int, task_id: int):
db = CommonSessionLocal()
@@ -1653,6 +1703,7 @@ def firm_task_template_edit_submit(
task_name: str = Form(...),
description: str = Form(''),
default_role_name: str = Form(''),
eligible_roles: list[str] = Form([]),
sequence_no: int = Form(1),
is_mandatory: str | None = Form(None),
requires_review: str | None = Form(None),
@@ -1670,6 +1721,8 @@ def firm_task_template_edit_submit(
aqmm_blocks_final_release: str | None = Form(None),
aqmm_reference: str = Form(''),
is_active: str | None = Form(None),
sync_open_engagements: str | None = Form(None),
sync_started_open_tasks: str | None = Form(None),
csrf_token: str = Form(...),
):
validate_csrf(request, csrf_token)
@@ -1696,6 +1749,7 @@ def firm_task_template_edit_submit(
task.task_name = task_name.strip()
task.description = description.strip() or None
task.default_role_name = default_role_name.strip() or None
task.eligible_role_names = serialise_eligible_task_roles(eligible_roles, default_role_name)
task.sequence_no = sequence_no
task.is_mandatory = is_mandatory is not None
task.requires_review = requires_review is not None
@@ -1715,7 +1769,28 @@ def firm_task_template_edit_submit(
task.is_active = is_active is not None
task.updated_by_user_id = user.id
sync_result = None
if sync_open_engagements is not None:
sync_result = sync_open_engagement_tasks_for_service(
db,
tenant_id=tenant_id,
catalogue_id=catalogue_id,
user_id=user.id,
include_started_open_tasks=sync_started_open_tasks is not None,
)
db.commit()
if sync_result is not None:
return RedirectResponse(
url=(
f'/services/templates/{catalogue_id}'
f'?synced=1&created={sync_result["created"]}'
f'&updated_pending={sync_result["updated_pending"]}'
f'&updated_started={sync_result["updated_started"]}'
f'&preserved={sync_result["preserved_history"]}'
),
status_code=303,
)
return RedirectResponse(url=f'/services/templates/{catalogue_id}', status_code=303)
finally:
db.close()
@@ -1770,6 +1845,7 @@ def default_task_template_edit_submit(
task_name: str = Form(...),
description: str = Form(''),
default_role_name: str = Form(''),
eligible_roles: list[str] = Form([]),
sequence_no: int = Form(1),
is_mandatory: str | None = Form(None),
requires_review: str | None = Form(None),
@@ -1815,6 +1891,7 @@ def default_task_template_edit_submit(
task.task_name = task_name.strip()
task.description = description.strip() or None
task.default_role_name = default_role_name.strip() or None
task.eligible_role_names = serialise_eligible_task_roles(eligible_roles, default_role_name)
task.sequence_no = sequence_no
task.is_mandatory = is_mandatory is not None
task.requires_review = requires_review is not None