From 34c258e26e8ea8aaf3307ffcb3b4fc6a263e4ac9 Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Mon, 3 Aug 2026 17:23:38 +0530 Subject: [PATCH] Add bulk firm selection to service catalogue --- .../templates/services/catalogue_list.html | 89 ++++++++++- app/modules/services/ui.py | 138 ++++++++++++++++++ 2 files changed, 226 insertions(+), 1 deletion(-) diff --git a/app/modules/services/templates/services/catalogue_list.html b/app/modules/services/templates/services/catalogue_list.html index a3b4855..71d6ce9 100644 --- a/app/modules/services/templates/services/catalogue_list.html +++ b/app/modules/services/templates/services/catalogue_list.html @@ -19,6 +19,20 @@ + {% if request.query_params.get('bulk_action') == 'enable' or request.query_params.get('bulk_action') == 'disable' %} +
+ Bulk update completed: {{ request.query_params.get('bulk_updated', '0') }} changed, + {{ request.query_params.get('bulk_unchanged', '0') }} already in the requested state, + and {{ request.query_params.get('bulk_skipped', '0') }} skipped. +
+ {% elif request.query_params.get('bulk_action') == 'no_selection' %} +
Select at least one service before applying a bulk action.
+ {% elif request.query_params.get('bulk_action') == 'invalid_branch' %} +
The selected default branch is not available for the active firm.
+ {% elif request.query_params.get('bulk_action') == 'invalid' %} +
The requested bulk action is invalid.
+ {% endif %} + {% if can_manage_firm_services %}
Select the services your firm provides. Once selected, you can open Firm Task Templates and customise tasks for your firm. @@ -59,10 +73,41 @@
+ {% if can_manage_firm_services %} +
+ + + + + + + +
+
0 services selected
+ {% if branches %} + + {% else %} + + {% endif %} + + + +
+
+ {% endif %} +
+ {% if can_manage_firm_services %} + + {% endif %} @@ -75,6 +120,11 @@ {% for row in rows %} {% set selection = firm_selection_by_catalogue.get(row.id) if firm_selection_by_catalogue else None %} + {% if can_manage_firm_services %} + + {% endif %} {% else %} - + {% endfor %}
+ + Code Name Category
+ + {{ row.service_code }}
{{ row.service_name }}
@@ -118,7 +168,7 @@
No catalogue services found.
No catalogue services found.
@@ -190,4 +240,41 @@
+{% if can_manage_firm_services %} + +{% endif %} {% endblock %} diff --git a/app/modules/services/ui.py b/app/modules/services/ui.py index 311330a..d1352c3 100644 --- a/app/modules/services/ui.py +++ b/app/modules/services/ui.py @@ -849,6 +849,144 @@ def catalogue_edit_submit(request: Request, catalogue_id: int, service_name: str db.close() +@router.post('/catalogue/bulk-selection') +def bulk_update_firm_services( + request: Request, + service_ids: list[int] = Form([]), + bulk_action: str = Form(...), + default_branch_id: str = Form(''), + return_q: str = Form(''), + return_category_id: str = Form(''), + return_recurrence_type: str = Form(''), + return_engagement_type: str = Form(''), + return_page: int = Form(1), + return_per_page: int = Form(20), + 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_services(db, user): + return _redirect_denied() + + tenant_id = _active_tenant_id(request, user) + action = (bulk_action or '').strip().lower() + if action not in {'enable', 'disable'}: + action = 'invalid' + + unique_service_ids = list(dict.fromkeys(service_id for service_id in service_ids if service_id > 0)) + branch_id: int | None = None + branch_value = str(default_branch_id or '').strip() + if branch_value: + try: + parsed_branch_id = int(branch_value) + except (TypeError, ValueError): + parsed_branch_id = 0 + branch = db.execute( + select(Branch).where( + Branch.id == parsed_branch_id, + Branch.tenant_id == tenant_id, + Branch.is_active.is_(True), + ) + ).scalar_one_or_none() + if not branch: + action = 'invalid_branch' + else: + branch_id = branch.id + + updated_count = 0 + unchanged_count = 0 + skipped_count = 0 + + if action in {'enable', 'disable'} and unique_service_ids: + catalogue_rows = db.execute( + select(ServiceCatalogue).where( + ServiceCatalogue.id.in_(unique_service_ids), + ServiceCatalogue.is_active.is_(True), + ) + ).scalars().all() + catalogue_by_id = {row.id: row for row in catalogue_rows} + + selections = db.execute( + select(FirmServiceSelection).where( + FirmServiceSelection.tenant_id == tenant_id, + FirmServiceSelection.service_catalogue_id.in_(unique_service_ids), + ) + ).scalars().all() + selection_by_catalogue = { + selection.service_catalogue_id: selection for selection in selections + } + + for catalogue_id in unique_service_ids: + if catalogue_id not in catalogue_by_id: + skipped_count += 1 + continue + + selection = selection_by_catalogue.get(catalogue_id) + if action == 'enable': + if selection is None: + selection = FirmServiceSelection( + tenant_id=tenant_id, + service_catalogue_id=catalogue_id, + is_enabled=True, + default_branch_id=branch_id, + activated_by_user_id=user.id, + updated_by_user_id=user.id, + ) + db.add(selection) + updated_count += 1 + else: + changed = not bool(selection.is_enabled) + selection.is_enabled = True + if branch_id is not None and selection.default_branch_id != branch_id: + selection.default_branch_id = branch_id + changed = True + selection.updated_by_user_id = user.id + if changed: + updated_count += 1 + else: + unchanged_count += 1 + else: + if selection is not None and selection.is_enabled: + selection.is_enabled = False + selection.updated_by_user_id = user.id + updated_count += 1 + else: + unchanged_count += 1 + + db.commit() + elif not unique_service_ids: + action = 'no_selection' + + query_parts = [ + f'bulk_action={action}', + f'bulk_updated={updated_count}', + f'bulk_unchanged={unchanged_count}', + f'bulk_skipped={skipped_count}', + f'page={max(1, return_page)}', + f'per_page={return_per_page if return_per_page in (20, 50, 100) else 20}', + ] + if return_q: + from urllib.parse import quote_plus + query_parts.append(f'q={quote_plus(return_q)}') + if return_category_id: + query_parts.append(f'category_id={return_category_id}') + if return_recurrence_type: + query_parts.append(f'recurrence_type={return_recurrence_type}') + if return_engagement_type: + query_parts.append(f'engagement_type={return_engagement_type}') + + return RedirectResponse( + url='/services/catalogue?' + '&'.join(query_parts), + status_code=303, + ) + finally: + db.close() + + @router.post('/catalogue/{catalogue_id}/toggle') def toggle_firm_service(request: Request, catalogue_id: int, default_branch_id: str = Form(''), csrf_token: str = Form(...)): validate_csrf(request, csrf_token)