Add multi-client bulk engagement setup

This commit is contained in:
A R R R Associates
2026-08-04 15:01:44 +05:30
parent 0f6e4126e8
commit efc73e1f83
3 changed files with 443 additions and 1 deletions
+234 -1
View File
@@ -18,6 +18,7 @@ from app.modules.services.execution import (
reopen_engagement_closure,
)
from app.modules.clients.models import Client
from app.modules.core.iam.models import User
from app.modules.services.client_services import (
SUBSCRIPTION_STATUSES,
assessment_year_from_financial_year,
@@ -146,7 +147,17 @@ def _lock_subscription_row(row: ClientServiceSubscription, user) -> bool:
@router.get("")
def subscription_list(request: Request, q: str = "", financial_year: str = "", include_inactive: bool = True, locked: int = 0, skipped: int = 0):
def subscription_list(
request: Request,
q: str = "",
financial_year: str = "",
include_inactive: bool = True,
locked: int = 0,
skipped: int = 0,
bulk_created: int = 0,
bulk_existing: int = 0,
bulk_skipped: int = 0,
):
db = CommonSessionLocal()
try:
user = get_current_user(request, db=db)
@@ -180,6 +191,9 @@ def subscription_list(request: Request, q: str = "", financial_year: str = "", i
include_inactive=include_inactive,
locked_count=locked,
skipped_count=skipped,
bulk_created_count=bulk_created,
bulk_existing_count=bulk_existing,
bulk_skipped_count=bulk_skipped,
can_manage=_can_manage_client_services(db, user),
can_lock_engagements=_can_lock_engagements(db, user),
)
@@ -327,6 +341,225 @@ def subscription_create_submit(
db.close()
@router.get("/bulk-new")
def subscription_bulk_create_page(request: Request, error: str = ""):
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)
branch_id = _active_branch_id(request, user, db)
clients = list_clients_for_assignment(
db,
tenant_id=tenant_id,
branch_id=branch_id,
partner_id=_locked_partner_id(db, user),
)
enabled_services = list_enabled_services_for_assignment(db, tenant_id=tenant_id)
partners = list_assignable_users(db, tenant_id=tenant_id, role_names=("Partner",))
managers = list_assignable_users(db, tenant_id=tenant_id, role_names=("Branch Manager",))
staff_users = list_assignable_users(db, tenant_id=tenant_id, role_names=("Staff",))
review_partners = list_review_partners(db, tenant_id=tenant_id)
return _render(
request,
"modules/services/templates/services/engagements/bulk_form.html",
db,
user,
title="Bulk Engagement Setup",
clients=clients,
enabled_services=enabled_services,
partners=partners,
client_partner_names={row.id: (row.full_name or row.email) for row in partners},
managers=managers,
staff_users=staff_users,
review_partners=review_partners,
financial_year=_active_financial_year(request),
error_message={
"service": "Select a valid enabled firm service.",
"partner": "Select a valid active Partner.",
"manager": "The selected Manager is not available for this firm.",
"staff": "The selected Staff member is not available for this firm.",
"review_partner": "The selected Review Partner is not available for this firm.",
"clients": "Select at least one permitted client.",
}.get(error),
)
finally:
db.close()
@router.post("/bulk-new")
def subscription_bulk_create_submit(
request: Request,
client_ids: list[int] = Form([]),
service_catalogue_id: int = Form(...),
assigned_partner_user_id: int = Form(...),
assigned_manager_user_id: str = Form(""),
assigned_staff_user_id: str = Form(""),
review_partner_user_id: str = Form(""),
financial_year: str = Form(""),
remarks: str = Form(""),
csrf_token: str = Form(...),
):
validate_csrf(request, csrf_token)
db = CommonSessionLocal()
created_count = 0
existing_count = 0
skipped_count = 0
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)
selected_financial_year = normalize_financial_year(financial_year or _active_financial_year(request))
locked_response = redirect_if_financial_year_locked(
db,
tenant_id=tenant_id,
year_code=selected_financial_year,
redirect_url=f"/services/engagements?financial_year={selected_financial_year}",
)
if locked_response:
return locked_response
firm_selection = get_enabled_firm_service(
db,
tenant_id=tenant_id,
service_catalogue_id=service_catalogue_id,
)
if not firm_selection:
return RedirectResponse(url="/services/engagements/bulk-new?error=service", status_code=303)
partners = list_assignable_users(db, tenant_id=tenant_id, role_names=("Partner",))
managers = list_assignable_users(db, tenant_id=tenant_id, role_names=("Branch Manager",))
staff_users = list_assignable_users(db, tenant_id=tenant_id, role_names=("Staff",))
review_partners = list_review_partners(db, tenant_id=tenant_id)
partner_ids = {row.id for row in partners}
manager_ids = {row.id for row in managers}
staff_ids = {row.id for row in staff_users}
review_partner_ids = {row.id for row in review_partners}
if assigned_partner_user_id not in partner_ids:
return RedirectResponse(url="/services/engagements/bulk-new?error=partner", status_code=303)
manager_id = int(assigned_manager_user_id) if assigned_manager_user_id.strip() else None
staff_id = int(assigned_staff_user_id) if assigned_staff_user_id.strip() else None
review_partner_id = int(review_partner_user_id) if review_partner_user_id.strip() else None
if manager_id is not None and manager_id not in manager_ids:
return RedirectResponse(url="/services/engagements/bulk-new?error=manager", status_code=303)
if staff_id is not None and staff_id not in staff_ids:
return RedirectResponse(url="/services/engagements/bulk-new?error=staff", status_code=303)
if review_partner_id is not None and review_partner_id not in review_partner_ids:
return RedirectResponse(url="/services/engagements/bulk-new?error=review_partner", status_code=303)
partner = db.get(User, assigned_partner_user_id)
if not partner or partner.tenant_id != tenant_id or not partner.is_active:
return RedirectResponse(url="/services/engagements/bulk-new?error=partner", status_code=303)
engagement_branch_id = partner.branch_id
allowed_clients = list_clients_for_assignment(
db,
tenant_id=tenant_id,
branch_id=_active_branch_id(request, user, db),
partner_id=_locked_partner_id(db, user),
)
allowed_client_ids = {row.id for row in allowed_clients}
selected_client_ids = []
seen_client_ids = set()
for value in client_ids:
client_id = int(value)
if client_id in seen_client_ids:
continue
seen_client_ids.add(client_id)
if client_id in allowed_client_ids:
selected_client_ids.append(client_id)
else:
skipped_count += 1
if not selected_client_ids:
return RedirectResponse(url="/services/engagements/bulk-new?error=clients", status_code=303)
engagement_type = getattr(firm_selection.catalogue, "engagement_type", "non_assurance") or "non_assurance"
requires_review_partner = review_partner_required_for_engagement(
db,
tenant_id=tenant_id,
engagement_type=engagement_type,
)
for client_id in selected_client_ids:
existing = get_existing_subscription(
db,
tenant_id=tenant_id,
client_id=client_id,
service_catalogue_id=service_catalogue_id,
financial_year=selected_financial_year,
)
if existing:
existing_count += 1
continue
client = db.get(Client, client_id)
if not client or client.tenant_id != tenant_id:
skipped_count += 1
continue
row = ClientServiceSubscription(
tenant_id=tenant_id,
branch_id=engagement_branch_id,
client_id=client_id,
service_catalogue_id=service_catalogue_id,
firm_service_selection_id=firm_selection.id,
assigned_partner_user_id=assigned_partner_user_id,
assigned_manager_user_id=manager_id,
assigned_staff_user_id=staff_id,
review_partner_user_id=(
review_partner_id or getattr(client, "default_review_partner_user_id", None)
if requires_review_partner
else None
),
financial_year=selected_financial_year,
assessment_year=assessment_year_from_financial_year(selected_financial_year),
engagement_type=engagement_type,
status="active",
remarks=remarks.strip() or None,
is_active=True,
created_by_user_id=user.id,
updated_by_user_id=user.id,
)
db.add(row)
db.flush()
apply_due_date_rule_to_subscription(db, row)
ensure_engagement_quality_workflow(db, subscription=row, actor_user_id=user.id, create_declarations=False)
enforce_quality_gate_on_subscription(row)
created_count += 1
db.commit()
return RedirectResponse(
url=(
f"/services/engagements?financial_year={selected_financial_year}"
f"&bulk_created={created_count}&bulk_existing={existing_count}&bulk_skipped={skipped_count}"
),
status_code=303,
)
except Exception:
db.rollback()
raise
finally:
db.close()
@router.post("/bulk-lock")
def subscription_bulk_lock(
request: Request,