From e219a0800c3fc07284fc4bb451a2b10ea335eceb Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Fri, 17 Jul 2026 23:20:08 +0530 Subject: [PATCH] Hide inactive merged services from service catalogue --- app/modules/services/catalogue_merge.py | 2 + app/modules/services/services.py | 10 ++++- .../templates/services/catalogue_list.html | 1 + app/modules/services/ui.py | 37 ++++++++++++++++--- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/app/modules/services/catalogue_merge.py b/app/modules/services/catalogue_merge.py index 0c5aff5..5a450f0 100644 --- a/app/modules/services/catalogue_merge.py +++ b/app/modules/services/catalogue_merge.py @@ -44,6 +44,8 @@ def build_merge_preview(db: Session, *, source_id: int, target_id: int) -> Merge target = db.get(ServiceCatalogue, target_id) if not source or not target: raise ValueError("The selected source or target service no longer exists.") + if not source.is_active or not target.is_active: + raise ValueError("Only active services can be selected for a merge.") counts = { "firm_selections": _count(db, FirmServiceSelection, FirmServiceSelection.service_catalogue_id == source_id), diff --git a/app/modules/services/services.py b/app/modules/services/services.py index 16437d1..0a13c60 100644 --- a/app/modules/services/services.py +++ b/app/modules/services/services.py @@ -65,7 +65,9 @@ def get_category(db: Session, category_id: int) -> ServiceCategory | None: def list_catalogue_payload(db: Session, *, q: str = "", category_id: int | None = None, recurrence_type: str = "", engagement_type: str = "", page: int = 1, per_page: int = 20): - query = select(ServiceCatalogue).options( + query = select(ServiceCatalogue).where( + ServiceCatalogue.is_active.is_(True) + ).options( selectinload(ServiceCatalogue.service_category), selectinload(ServiceCatalogue.default_task_templates), selectinload(ServiceCatalogue.due_date_rules), @@ -115,6 +117,7 @@ def list_firm_services_payload(db: Session, *, tenant_id: int, q: str = ""): .where( FirmServiceSelection.tenant_id == tenant_id, FirmServiceSelection.is_enabled.is_(True), + ServiceCatalogue.is_active.is_(True), ) ) if q.strip(): @@ -143,7 +146,10 @@ def list_disabled_catalogues(db: Session, *, tenant_id: int, q: str = ""): ) ) - query = select(ServiceCatalogue).where(~ServiceCatalogue.id.in_(enabled_subq)).options( + query = select(ServiceCatalogue).where( + ServiceCatalogue.is_active.is_(True), + ~ServiceCatalogue.id.in_(enabled_subq), + ).options( selectinload(ServiceCatalogue.service_category), selectinload(ServiceCatalogue.default_task_templates), selectinload(ServiceCatalogue.due_date_rules), diff --git a/app/modules/services/templates/services/catalogue_list.html b/app/modules/services/templates/services/catalogue_list.html index 72380bd..829c924 100644 --- a/app/modules/services/templates/services/catalogue_list.html +++ b/app/modules/services/templates/services/catalogue_list.html @@ -12,6 +12,7 @@ Firm Task Templates {% if can_create %} System Default Tasks + Export Excel Merge Services Add Catalogue Service {% endif %} diff --git a/app/modules/services/ui.py b/app/modules/services/ui.py index 686800c..29085f8 100644 --- a/app/modules/services/ui.py +++ b/app/modules/services/ui.py @@ -21,6 +21,7 @@ from app.modules.services.bulk_imports import ( import_system_default_tasks, import_due_date_extensions, ) +from app.modules.services.catalogue_export import build_service_catalogue_export from app.modules.services.due_dates import ( DUE_PERIOD_TYPES, DUE_YEAR_BASIS_CHOICES, @@ -333,6 +334,28 @@ def catalogue_list(request: Request, q: str = '', category_id: int | None = None db.close() +@router.get('/catalogue/export') +def catalogue_export(request: Request): + db = CommonSessionLocal() + try: + user = get_current_user(request, db=db) + if not user: + return RedirectResponse(url='/login', status_code=303) + if not _is_system_admin(db, user): + return _redirect_denied() + require_permission(db, user, 'services.view') + + workbook_bytes = build_service_catalogue_export(db) + filename = f"service_catalogue_export_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.xlsx" + return StreamingResponse( + iter([workbook_bytes]), + media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + headers={'Content-Disposition': f'attachment; filename="{filename}"'}, + ) + finally: + db.close() + + @router.get('/catalogue/merge') def catalogue_merge_page(request: Request, source_id: int | None = None, target_id: int | None = None): db = CommonSessionLocal() @@ -345,7 +368,9 @@ def catalogue_merge_page(request: Request, source_id: int | None = None, target_ require_permission(db, user, 'services.edit') services = db.execute( - select(ServiceCatalogue).order_by(ServiceCatalogue.is_active.desc(), ServiceCatalogue.service_code.asc()) + select(ServiceCatalogue) + .where(ServiceCatalogue.is_active.is_(True)) + .order_by(ServiceCatalogue.service_code.asc()) ).scalars().all() preview = None error = None @@ -495,7 +520,7 @@ def catalogue_detail(request: Request, catalogue_id: int): if not _can_view_services(db, user): return _redirect_denied() row = get_catalogue(db, catalogue_id) - if not row: + if not row or not row.is_active: return RedirectResponse(url='/services/catalogue', status_code=303) tenant_id = _active_tenant_id(request, user) can_edit = _is_system_admin(db, user) and _has_perm(db, user, 'services.edit') @@ -764,7 +789,7 @@ def catalogue_edit_page(request: Request, catalogue_id: int): return _redirect_denied() require_permission(db, user, 'services.edit') row = get_catalogue(db, catalogue_id) - if not row: + if not row or not row.is_active: return RedirectResponse(url='/services/catalogue', status_code=303) return _render(request, 'modules/services/templates/services/catalogue_form.html', db, user, title='Edit Service Catalogue', mode='edit', catalogue=row, categories=list_categories(db)) except Exception: @@ -785,7 +810,7 @@ def catalogue_edit_submit(request: Request, catalogue_id: int, service_name: str return _redirect_denied() require_permission(db, user, 'services.edit') row = get_catalogue(db, catalogue_id) - if not row: + if not row or not row.is_active: return RedirectResponse(url='/services/catalogue', status_code=303) selected_category = get_category(db, int(category_id)) if str(category_id).strip() else None row.service_name = service_name.strip() @@ -823,8 +848,8 @@ def toggle_firm_service(request: Request, catalogue_id: int, default_branch_id: return _redirect_denied() tenant_id = _active_tenant_id(request, user) row = get_catalogue(db, catalogue_id) - if not row: - return RedirectResponse(url='/services', status_code=303) + if not row or not row.is_active: + return RedirectResponse(url='/services/catalogue', status_code=303) selection = get_firm_selection(db, tenant_id=tenant_id, catalogue_id=catalogue_id) if not selection: selection = FirmServiceSelection(