Hide inactive merged services from service catalogue

This commit is contained in:
A R R R Associates
2026-07-17 23:20:08 +05:30
parent 0b58bf182b
commit e219a0800c
4 changed files with 42 additions and 8 deletions
+2
View File
@@ -44,6 +44,8 @@ def build_merge_preview(db: Session, *, source_id: int, target_id: int) -> Merge
target = db.get(ServiceCatalogue, target_id) target = db.get(ServiceCatalogue, target_id)
if not source or not target: if not source or not target:
raise ValueError("The selected source or target service no longer exists.") 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 = { counts = {
"firm_selections": _count(db, FirmServiceSelection, FirmServiceSelection.service_catalogue_id == source_id), "firm_selections": _count(db, FirmServiceSelection, FirmServiceSelection.service_catalogue_id == source_id),
+8 -2
View File
@@ -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): 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.service_category),
selectinload(ServiceCatalogue.default_task_templates), selectinload(ServiceCatalogue.default_task_templates),
selectinload(ServiceCatalogue.due_date_rules), selectinload(ServiceCatalogue.due_date_rules),
@@ -115,6 +117,7 @@ def list_firm_services_payload(db: Session, *, tenant_id: int, q: str = ""):
.where( .where(
FirmServiceSelection.tenant_id == tenant_id, FirmServiceSelection.tenant_id == tenant_id,
FirmServiceSelection.is_enabled.is_(True), FirmServiceSelection.is_enabled.is_(True),
ServiceCatalogue.is_active.is_(True),
) )
) )
if q.strip(): 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.service_category),
selectinload(ServiceCatalogue.default_task_templates), selectinload(ServiceCatalogue.default_task_templates),
selectinload(ServiceCatalogue.due_date_rules), selectinload(ServiceCatalogue.due_date_rules),
@@ -12,6 +12,7 @@
<a href="/services/templates" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Firm Task Templates</a> <a href="/services/templates" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Firm Task Templates</a>
{% if can_create %} {% if can_create %}
<a href="/services/defaults" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">System Default Tasks</a> <a href="/services/defaults" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">System Default Tasks</a>
<a href="/services/catalogue/export" class="rounded-xl border border-emerald-300 bg-emerald-50 px-4 py-2 text-sm font-medium text-emerald-800 hover:bg-emerald-100">Export Excel</a>
<a href="/services/catalogue/merge" class="rounded-xl border border-amber-300 px-4 py-2 text-sm font-medium text-amber-800 hover:bg-amber-50">Merge Services</a> <a href="/services/catalogue/merge" class="rounded-xl border border-amber-300 px-4 py-2 text-sm font-medium text-amber-800 hover:bg-amber-50">Merge Services</a>
<a href="/services/catalogue/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Add Catalogue Service</a> <a href="/services/catalogue/new" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-medium text-white hover:bg-brand-700">Add Catalogue Service</a>
{% endif %} {% endif %}
+31 -6
View File
@@ -21,6 +21,7 @@ from app.modules.services.bulk_imports import (
import_system_default_tasks, import_system_default_tasks,
import_due_date_extensions, import_due_date_extensions,
) )
from app.modules.services.catalogue_export import build_service_catalogue_export
from app.modules.services.due_dates import ( from app.modules.services.due_dates import (
DUE_PERIOD_TYPES, DUE_PERIOD_TYPES,
DUE_YEAR_BASIS_CHOICES, DUE_YEAR_BASIS_CHOICES,
@@ -333,6 +334,28 @@ def catalogue_list(request: Request, q: str = '', category_id: int | None = None
db.close() 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') @router.get('/catalogue/merge')
def catalogue_merge_page(request: Request, source_id: int | None = None, target_id: int | None = None): def catalogue_merge_page(request: Request, source_id: int | None = None, target_id: int | None = None):
db = CommonSessionLocal() db = CommonSessionLocal()
@@ -345,7 +368,9 @@ def catalogue_merge_page(request: Request, source_id: int | None = None, target_
require_permission(db, user, 'services.edit') require_permission(db, user, 'services.edit')
services = db.execute( 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() ).scalars().all()
preview = None preview = None
error = None error = None
@@ -495,7 +520,7 @@ def catalogue_detail(request: Request, catalogue_id: int):
if not _can_view_services(db, user): if not _can_view_services(db, user):
return _redirect_denied() return _redirect_denied()
row = get_catalogue(db, catalogue_id) 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 RedirectResponse(url='/services/catalogue', status_code=303)
tenant_id = _active_tenant_id(request, user) tenant_id = _active_tenant_id(request, user)
can_edit = _is_system_admin(db, user) and _has_perm(db, user, 'services.edit') 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() return _redirect_denied()
require_permission(db, user, 'services.edit') require_permission(db, user, 'services.edit')
row = get_catalogue(db, catalogue_id) 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 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)) 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: except Exception:
@@ -785,7 +810,7 @@ def catalogue_edit_submit(request: Request, catalogue_id: int, service_name: str
return _redirect_denied() return _redirect_denied()
require_permission(db, user, 'services.edit') require_permission(db, user, 'services.edit')
row = get_catalogue(db, catalogue_id) 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 RedirectResponse(url='/services/catalogue', status_code=303)
selected_category = get_category(db, int(category_id)) if str(category_id).strip() else None selected_category = get_category(db, int(category_id)) if str(category_id).strip() else None
row.service_name = service_name.strip() 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() return _redirect_denied()
tenant_id = _active_tenant_id(request, user) tenant_id = _active_tenant_id(request, user)
row = get_catalogue(db, catalogue_id) row = get_catalogue(db, catalogue_id)
if not row: if not row or not row.is_active:
return RedirectResponse(url='/services', status_code=303) return RedirectResponse(url='/services/catalogue', status_code=303)
selection = get_firm_selection(db, tenant_id=tenant_id, catalogue_id=catalogue_id) selection = get_firm_selection(db, tenant_id=tenant_id, catalogue_id=catalogue_id)
if not selection: if not selection:
selection = FirmServiceSelection( selection = FirmServiceSelection(