Add Excel import export and code linking for task categories
This commit is contained in:
@@ -19,6 +19,7 @@ from app.modules.services.models import (
|
||||
ServiceCatalogue,
|
||||
ServiceCategory,
|
||||
ServiceDefaultTaskTemplate,
|
||||
ServiceTaskCategory,
|
||||
ServiceDueDateExtension,
|
||||
ServiceDueDateRule,
|
||||
)
|
||||
@@ -84,6 +85,13 @@ DEFAULT_TASK_COLUMNS = [
|
||||
"default_role_name",
|
||||
"is_mandatory",
|
||||
"requires_review",
|
||||
"task_category_code",
|
||||
"task_category",
|
||||
"response_required",
|
||||
"response_type",
|
||||
"evidence_required",
|
||||
"remarks_required_if_no",
|
||||
"task_tool_code",
|
||||
"is_aqmm_task",
|
||||
"aqmm_mandatory",
|
||||
"aqmm_evidence_required",
|
||||
@@ -103,6 +111,13 @@ FIRM_TASK_COLUMNS = [
|
||||
"default_role_name",
|
||||
"is_mandatory",
|
||||
"requires_review",
|
||||
"task_category_code",
|
||||
"task_category",
|
||||
"response_required",
|
||||
"response_type",
|
||||
"evidence_required",
|
||||
"remarks_required_if_no",
|
||||
"task_tool_code",
|
||||
"is_aqmm_task",
|
||||
"aqmm_mandatory",
|
||||
"aqmm_evidence_required",
|
||||
@@ -1015,6 +1030,35 @@ def import_due_date_extensions(
|
||||
return {"created": created if not errors else 0, "updated": updated if not errors else 0, "skipped": skipped, "errors": errors}
|
||||
|
||||
|
||||
def _resolve_import_task_category(db: Session, *, catalogue_id: int, tenant_id: int | None, category_code: str, category_name: str, user_id: int | None):
|
||||
"""Resolve category by stable code first; preserve legacy name-based imports.
|
||||
|
||||
When task_category_code is supplied it must already exist in the category
|
||||
master for the same service/scope. This prevents spelling mistakes from
|
||||
silently creating unintended category records. Older workbooks that only
|
||||
contain task_category continue to work exactly as before.
|
||||
"""
|
||||
code = normalize_code(category_code) if category_code else ""
|
||||
name = _clean(category_name)
|
||||
scope_filter = ServiceTaskCategory.tenant_id.is_(None) if tenant_id is None else ServiceTaskCategory.tenant_id == tenant_id
|
||||
if code:
|
||||
category = db.execute(
|
||||
select(ServiceTaskCategory).where(
|
||||
ServiceTaskCategory.service_catalogue_id == catalogue_id,
|
||||
scope_filter,
|
||||
ServiceTaskCategory.code == code,
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if category is None:
|
||||
raise ValueError(f"Task category code {code} was not found for this service. Import/create the Task Category master first.")
|
||||
if name and category.name.strip().lower() != name.strip().lower():
|
||||
# Code is the stable key. The master name wins so renamed categories
|
||||
# do not break task imports.
|
||||
pass
|
||||
return category
|
||||
return ensure_task_category(db, catalogue_id=catalogue_id, tenant_id=tenant_id, name=name or None, user_id=user_id)
|
||||
|
||||
|
||||
def import_system_default_tasks(db: Session, *, current_user, file_bytes: bytes, update_existing: bool = True, expected_service_code: str | None = None) -> dict:
|
||||
ws, headers = _load_sheet(file_bytes, "system_default_tasks")
|
||||
missing = _validate_headers(headers, ["service_code", "sequence_no", "task_name"])
|
||||
@@ -1056,8 +1100,14 @@ def import_system_default_tasks(db: Session, *, current_user, file_bytes: bytes,
|
||||
task.default_role_name = _clean(_cell(row, headers, "default_role_name")) or None
|
||||
task.is_mandatory = _bool(_cell(row, headers, "is_mandatory"), True)
|
||||
task.requires_review = _bool(_cell(row, headers, "requires_review"), False)
|
||||
task.task_category = _clean(_cell(row, headers, "task_category")) or None
|
||||
category = ensure_task_category(db, catalogue_id=catalogue.id, tenant_id=None, name=task.task_category, user_id=current_user.id)
|
||||
category = _resolve_import_task_category(
|
||||
db,
|
||||
catalogue_id=catalogue.id,
|
||||
tenant_id=None,
|
||||
category_code=_clean(_cell(row, headers, "task_category_code")),
|
||||
category_name=_clean(_cell(row, headers, "task_category")),
|
||||
user_id=current_user.id,
|
||||
)
|
||||
task.task_category_id = category.id if category else None
|
||||
task.task_category = category.name if category else None
|
||||
task.response_required = _bool(_cell(row, headers, "response_required"), False)
|
||||
@@ -1134,8 +1184,14 @@ def import_firm_task_templates(db: Session, *, current_user, tenant_id: int, fil
|
||||
task.default_role_name = _clean(_cell(row, headers, "default_role_name")) or None
|
||||
task.is_mandatory = _bool(_cell(row, headers, "is_mandatory"), True)
|
||||
task.requires_review = _bool(_cell(row, headers, "requires_review"), False)
|
||||
task.task_category = _clean(_cell(row, headers, "task_category")) or None
|
||||
category = ensure_task_category(db, catalogue_id=catalogue.id, tenant_id=tenant_id, name=task.task_category, user_id=current_user.id)
|
||||
category = _resolve_import_task_category(
|
||||
db,
|
||||
catalogue_id=catalogue.id,
|
||||
tenant_id=tenant_id,
|
||||
category_code=_clean(_cell(row, headers, "task_category_code")),
|
||||
category_name=_clean(_cell(row, headers, "task_category")),
|
||||
user_id=current_user.id,
|
||||
)
|
||||
task.task_category_id = category.id if category else None
|
||||
task.task_category = category.name if category else None
|
||||
task.response_required = _bool(_cell(row, headers, "response_required"), False)
|
||||
|
||||
Reference in New Issue
Block a user