Integrate audit checklist responses into service tasks

This commit is contained in:
A R R R Associates
2026-07-19 21:59:24 +05:30
parent 05b736b6c9
commit df52845194
10 changed files with 383 additions and 10 deletions
+109 -2
View File
@@ -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: