Add controlled service catalogue merge workflow
This commit is contained in:
+83
-12
@@ -1,7 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, File, Form, Request, UploadFile
|
||||
from fastapi.responses import FileResponse, RedirectResponse, StreamingResponse
|
||||
from sqlalchemy import select
|
||||
@@ -32,7 +30,7 @@ from app.modules.services.due_dates import (
|
||||
list_due_rules,
|
||||
parse_optional_date,
|
||||
)
|
||||
from app.modules.services.catalogue_export import build_service_catalogue_export
|
||||
from app.modules.services.catalogue_merge import build_merge_preview, merge_service_catalogues
|
||||
from app.modules.services.task_documents import (
|
||||
create_task_document_requirement,
|
||||
get_task_document_requirement,
|
||||
@@ -335,8 +333,8 @@ def catalogue_list(request: Request, q: str = '', category_id: int | None = None
|
||||
db.close()
|
||||
|
||||
|
||||
@router.get('/catalogue/export')
|
||||
def catalogue_export(request: Request):
|
||||
@router.get('/catalogue/merge')
|
||||
def catalogue_merge_page(request: Request, source_id: int | None = None, target_id: int | None = None):
|
||||
db = CommonSessionLocal()
|
||||
try:
|
||||
user = get_current_user(request, db=db)
|
||||
@@ -344,15 +342,88 @@ def catalogue_export(request: Request):
|
||||
return RedirectResponse(url='/login', status_code=303)
|
||||
if not _is_system_admin(db, user):
|
||||
return _redirect_denied()
|
||||
require_permission(db, user, 'services.view')
|
||||
require_permission(db, user, 'services.edit')
|
||||
|
||||
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}"'},
|
||||
services = db.execute(
|
||||
select(ServiceCatalogue).order_by(ServiceCatalogue.is_active.desc(), ServiceCatalogue.service_code.asc())
|
||||
).scalars().all()
|
||||
preview = None
|
||||
error = None
|
||||
if source_id and target_id:
|
||||
try:
|
||||
preview = build_merge_preview(db, source_id=source_id, target_id=target_id)
|
||||
except ValueError as exc:
|
||||
error = str(exc)
|
||||
|
||||
return _render(
|
||||
request,
|
||||
'modules/services/templates/services/catalogue_merge.html',
|
||||
db,
|
||||
user,
|
||||
title='Merge Service Catalogue',
|
||||
services=services,
|
||||
source_id=source_id,
|
||||
target_id=target_id,
|
||||
preview=preview,
|
||||
error=error,
|
||||
)
|
||||
except Exception:
|
||||
return _redirect_denied()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post('/catalogue/merge')
|
||||
def catalogue_merge_submit(
|
||||
request: Request,
|
||||
source_id: int = Form(...),
|
||||
target_id: int = Form(...),
|
||||
confirmation: 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 _is_system_admin(db, user):
|
||||
return _redirect_denied()
|
||||
require_permission(db, user, 'services.edit')
|
||||
|
||||
preview = build_merge_preview(db, source_id=source_id, target_id=target_id)
|
||||
expected = f"MERGE {preview.source.service_code} INTO {preview.target.service_code}"
|
||||
if confirmation.strip().upper() != expected.upper():
|
||||
return RedirectResponse(
|
||||
url=f'/services/catalogue/merge?source_id={source_id}&target_id={target_id}&error=confirmation',
|
||||
status_code=303,
|
||||
)
|
||||
|
||||
result = merge_service_catalogues(
|
||||
db,
|
||||
source_id=source_id,
|
||||
target_id=target_id,
|
||||
actor_user_id=user.id,
|
||||
)
|
||||
db.commit()
|
||||
|
||||
write_audit_log(
|
||||
db,
|
||||
action='services.catalogue.merged',
|
||||
entity_type='service_catalogue',
|
||||
entity_id=target_id,
|
||||
entity_name=result['target_code'],
|
||||
actor=user,
|
||||
request=request,
|
||||
details=result,
|
||||
)
|
||||
return RedirectResponse(url=f'/services/catalogue/{target_id}?merged=1', status_code=303)
|
||||
except ValueError:
|
||||
db.rollback()
|
||||
return RedirectResponse(url='/services/catalogue/merge?error=invalid', status_code=303)
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user