Add engagement closure checklist workflow

This commit is contained in:
A R R R Associates
2026-07-07 14:18:30 +05:30
parent 91b1811eb8
commit c3b11fb9fc
5 changed files with 545 additions and 1 deletions
+126 -1
View File
@@ -10,7 +10,13 @@ from app.core.security.session_auth import get_current_user
from app.core.templating import templates
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.execution import aqmm_task_summary_for_subscription
from app.modules.services.execution import (
aqmm_task_summary_for_subscription,
update_engagement_closure_from_sources,
save_engagement_closure_confirmations,
approve_engagement_closure,
reopen_engagement_closure,
)
from app.modules.clients.models import Client
from app.modules.services.client_services import (
SUBSCRIPTION_STATUSES,
@@ -400,6 +406,7 @@ def subscription_detail(request: Request, subscription_id: int):
kyc_verification = get_latest_engagement_kyc_verification(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)
closure_checklist, closure_summary = update_engagement_closure_from_sources(db, subscription=row, actor_user_id=user.id)
tasks = db.execute(
select(ClientServiceTaskInstance)
@@ -420,6 +427,8 @@ def subscription_detail(request: Request, subscription_id: int):
kyc_verification=kyc_verification,
engagement_letter=engagement_letter,
aqmm_task_summary=aqmm_task_summary,
closure_checklist=closure_checklist,
closure_summary=closure_summary,
can_manage=_can_manage_client_services(db, user),
)
finally:
@@ -578,6 +587,122 @@ def subscription_aqmm_approve(request: Request, subscription_id: int, csrf_token
db.close()
@router.post("/{subscription_id}/closure/sync")
def subscription_closure_sync(request: Request, subscription_id: int, 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)
try:
require_permission(db, user, "clients.view")
except Exception:
return _redirect_denied()
tenant_id = _active_tenant_id(request, user)
row = get_subscription(db, subscription_id=subscription_id, tenant_id=tenant_id)
if row:
update_engagement_closure_from_sources(db, subscription=row, actor_user_id=user.id)
db.commit()
return RedirectResponse(url=f"/services/engagements/{subscription_id}", status_code=303)
finally:
db.close()
@router.post("/{subscription_id}/closure/update")
def subscription_closure_update(
request: Request,
subscription_id: int,
deliverables_sent_to_client: str | None = Form(None),
billing_reviewed: str | None = Form(None),
open_points_closed: str | None = Form(None),
client_communication_completed: str | None = Form(None),
closure_note: str = Form(""),
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)
try:
require_permission(db, user, "clients.edit")
except Exception:
return _redirect_denied()
tenant_id = _active_tenant_id(request, user)
row = get_subscription(db, subscription_id=subscription_id, tenant_id=tenant_id)
if row:
try:
save_engagement_closure_confirmations(
db,
subscription=row,
deliverables_sent_to_client=deliverables_sent_to_client is not None,
billing_reviewed=billing_reviewed is not None,
open_points_closed=open_points_closed is not None,
client_communication_completed=client_communication_completed is not None,
closure_note=closure_note,
actor_user_id=user.id,
)
db.commit()
except ValueError:
db.rollback()
return RedirectResponse(url=f"/services/engagements/{subscription_id}", status_code=303)
finally:
db.close()
@router.post("/{subscription_id}/closure/approve")
def subscription_closure_approve(request: Request, subscription_id: int, 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_lock_engagements(db, user):
return _redirect_denied()
tenant_id = _active_tenant_id(request, user)
row = get_subscription(db, subscription_id=subscription_id, tenant_id=tenant_id)
if row:
try:
approve_engagement_closure(db, subscription=row, actor_user_id=user.id)
db.commit()
except ValueError:
db.rollback()
return RedirectResponse(url=f"/services/engagements/{subscription_id}", status_code=303)
finally:
db.close()
@router.post("/{subscription_id}/closure/reopen")
def subscription_closure_reopen(
request: Request,
subscription_id: int,
reason: str = Form(""),
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_lock_engagements(db, user):
return _redirect_denied()
tenant_id = _active_tenant_id(request, user)
row = get_subscription(db, subscription_id=subscription_id, tenant_id=tenant_id)
if row:
try:
reopen_engagement_closure(db, subscription=row, reason=reason, actor_user_id=user.id)
db.commit()
except ValueError:
db.rollback()
return RedirectResponse(url=f"/services/engagements/{subscription_id}", status_code=303)
finally:
db.close()
@router.get("/{subscription_id}/edit")
def subscription_edit_page(request: Request, subscription_id: int):
db = CommonSessionLocal()