Integrate audit checklist responses into service tasks
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime, timezone
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
from sqlalchemy import func, or_, select
|
||||
from sqlalchemy.orm import Session, selectinload
|
||||
@@ -243,6 +244,11 @@ def generate_tasks_for_subscription(db: Session, *, subscription: ClientServiceS
|
||||
internal_target_date=_default_internal_target_date(subscription, template),
|
||||
status="pending",
|
||||
priority="normal",
|
||||
task_category=getattr(template, "task_category", None),
|
||||
response_required=getattr(template, "response_required", False),
|
||||
response_type=(getattr(template, "response_type", "NONE") or "NONE").upper(),
|
||||
evidence_required=getattr(template, "evidence_required", False),
|
||||
remarks_required_if_no=getattr(template, "remarks_required_if_no", False),
|
||||
is_aqmm_task=getattr(template, "is_aqmm_task", False),
|
||||
aqmm_mandatory=getattr(template, "aqmm_mandatory", False),
|
||||
aqmm_evidence_required=getattr(template, "aqmm_evidence_required", False),
|
||||
@@ -834,6 +840,84 @@ def list_assignees_for_execution(db: Session, *, tenant_id: int, branch_id: int
|
||||
return db.execute(query.order_by(User.full_name.asc(), User.email.asc())).scalars().all()
|
||||
|
||||
|
||||
|
||||
_CHECKLIST_RESPONSE_TYPES = {"NONE", "YES_NO_NA", "YES_NO", "TEXT", "NUMBER", "DATE"}
|
||||
_CHECKLIST_YES_NO_NA_VALUES = {"YES", "NO", "NA"}
|
||||
_CHECKLIST_YES_NO_VALUES = {"YES", "NO"}
|
||||
|
||||
|
||||
def _normalise_checklist_response_type(value: str | None) -> str:
|
||||
response_type = (value or "NONE").strip().upper()
|
||||
return response_type if response_type in _CHECKLIST_RESPONSE_TYPES else "NONE"
|
||||
|
||||
|
||||
def apply_task_checklist_response(
|
||||
db: Session,
|
||||
task: ClientServiceTaskInstance,
|
||||
*,
|
||||
checklist_response: str,
|
||||
checklist_text_response: str,
|
||||
checklist_number_response: str,
|
||||
checklist_date_response: str,
|
||||
checklist_remarks: str,
|
||||
requested_status: str,
|
||||
) -> None:
|
||||
"""Validate and persist the structured response for an execution task.
|
||||
|
||||
Validation is enforced when a task is marked completed. Draft/in-progress
|
||||
tasks may save partial responses so existing work-tracker behaviour remains
|
||||
unchanged.
|
||||
"""
|
||||
response_type = _normalise_checklist_response_type(getattr(task, "response_type", "NONE"))
|
||||
response_value = (checklist_response or "").strip().upper()
|
||||
text_value = (checklist_text_response or "").strip()
|
||||
number_text = (checklist_number_response or "").strip().replace(",", "")
|
||||
date_text = (checklist_date_response or "").strip()
|
||||
remarks_value = (checklist_remarks or "").strip()
|
||||
|
||||
if response_type == "YES_NO_NA" and response_value and response_value not in _CHECKLIST_YES_NO_NA_VALUES:
|
||||
raise ValueError("Checklist response must be Yes, No or NA.")
|
||||
if response_type == "YES_NO" and response_value and response_value not in _CHECKLIST_YES_NO_VALUES:
|
||||
raise ValueError("Checklist response must be Yes or No.")
|
||||
|
||||
number_value = None
|
||||
if number_text:
|
||||
try:
|
||||
number_value = Decimal(number_text)
|
||||
except (InvalidOperation, ValueError):
|
||||
raise ValueError("Checklist number response is invalid.")
|
||||
|
||||
date_value = None
|
||||
if date_text:
|
||||
try:
|
||||
date_value = date.fromisoformat(date_text)
|
||||
except ValueError:
|
||||
raise ValueError("Checklist date response is invalid.")
|
||||
|
||||
completing = _normalise_status(requested_status) == "completed"
|
||||
if completing and getattr(task, "response_required", False):
|
||||
missing = (
|
||||
(response_type in {"YES_NO_NA", "YES_NO"} and not response_value)
|
||||
or (response_type == "TEXT" and not text_value)
|
||||
or (response_type == "NUMBER" and number_value is None)
|
||||
or (response_type == "DATE" and date_value is None)
|
||||
or (response_type == "NONE")
|
||||
)
|
||||
if missing:
|
||||
raise ValueError("Complete the required checklist response before marking the task completed.")
|
||||
|
||||
if completing and getattr(task, "remarks_required_if_no", False) and response_value == "NO" and not remarks_value:
|
||||
raise ValueError("Checklist remarks are required when the response is No.")
|
||||
|
||||
if completing and getattr(task, "evidence_required", False) and not _task_has_evidence(db, task.id):
|
||||
raise ValueError("Upload task evidence before marking the task completed.")
|
||||
|
||||
task.checklist_response = response_value or None
|
||||
task.checklist_text_response = text_value or None
|
||||
task.checklist_number_response = number_value
|
||||
task.checklist_date_response = date_value
|
||||
task.checklist_remarks = remarks_value or None
|
||||
|
||||
def apply_task_update(
|
||||
task: ClientServiceTaskInstance,
|
||||
*,
|
||||
@@ -866,6 +950,7 @@ def apply_task_update(
|
||||
|
||||
|
||||
def apply_bulk_task_update(
|
||||
db: Session,
|
||||
tasks: list[ClientServiceTaskInstance],
|
||||
*,
|
||||
status: str | None,
|
||||
@@ -882,8 +967,30 @@ def apply_bulk_task_update(
|
||||
skipped += 1
|
||||
continue
|
||||
previous_status = task.status
|
||||
if status:
|
||||
task.status = _normalise_status(status)
|
||||
requested_status = _normalise_status(status) if status else None
|
||||
if requested_status == "completed":
|
||||
response_type = _normalise_checklist_response_type(getattr(task, "response_type", "NONE"))
|
||||
missing_response = (
|
||||
getattr(task, "response_required", False)
|
||||
and (
|
||||
(response_type in {"YES_NO_NA", "YES_NO"} and not getattr(task, "checklist_response", None))
|
||||
or (response_type == "TEXT" and not getattr(task, "checklist_text_response", None))
|
||||
or (response_type == "NUMBER" and getattr(task, "checklist_number_response", None) is None)
|
||||
or (response_type == "DATE" and getattr(task, "checklist_date_response", None) is None)
|
||||
or response_type == "NONE"
|
||||
)
|
||||
)
|
||||
missing_remarks = (
|
||||
getattr(task, "remarks_required_if_no", False)
|
||||
and getattr(task, "checklist_response", None) == "NO"
|
||||
and not getattr(task, "checklist_remarks", None)
|
||||
)
|
||||
missing_evidence = getattr(task, "evidence_required", False) and not _task_has_evidence(db, task.id)
|
||||
if missing_response or missing_remarks or missing_evidence:
|
||||
skipped += 1
|
||||
continue
|
||||
if requested_status:
|
||||
task.status = requested_status
|
||||
if update_assignee:
|
||||
task.assigned_to_user_id = assigned_to_user_id
|
||||
if update_internal_target_date:
|
||||
|
||||
Reference in New Issue
Block a user