Separate normal task review from AQMM review

This commit is contained in:
A R R R Associates
2026-08-11 09:05:09 +05:30
parent 20fd30ff3d
commit c3684b7afa
17 changed files with 435 additions and 98 deletions
+87
View File
@@ -31,6 +31,29 @@ TASK_STATUSES = [
OPEN_TASK_STATUSES = {"pending", "in_progress", "blocked"}
CLOSED_TASK_STATUSES = {"completed", "not_applicable", "cancelled"}
NORMAL_REVIEW_ROLES = {"manager", "partner", "manager_or_partner"}
def normal_review_role_for_template(template) -> str | None:
if not getattr(template, "requires_review", False):
return None
explicit = (getattr(template, "normal_review_role", None) or "").strip().lower()
if explicit in NORMAL_REVIEW_ROLES:
return explicit
default_role = (getattr(template, "default_role_name", None) or "").strip().lower()
eligible = {
part.strip().lower()
for part in (getattr(template, "eligible_role_names", None) or "").split(",")
if part.strip()
}
if default_role in {"manager", "branch manager"} and "partner" in eligible:
return "partner"
if default_role not in {"manager", "branch manager", "partner"} and ({"manager", "branch manager"} & eligible):
return "manager"
if "partner" in eligible and not ({"manager", "branch manager"} & eligible):
return "partner"
return "manager"
TASK_COMMENT_TYPES = [
("internal_note", "Internal Note"),
@@ -307,6 +330,9 @@ def generate_tasks_for_subscription(db: Session, *, subscription: ClientServiceS
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),
normal_review_required=bool(getattr(template, "requires_review", False)),
normal_review_role=normal_review_role_for_template(template),
normal_review_status="not_ready" if getattr(template, "requires_review", False) else "not_required",
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),
@@ -372,6 +398,12 @@ def _copy_template_snapshot_to_task(
task.response_type = (getattr(template, "response_type", "NONE") or "NONE").upper()
task.evidence_required = getattr(template, "evidence_required", False)
task.remarks_required_if_no = getattr(template, "remarks_required_if_no", False)
task.normal_review_required = bool(getattr(template, "requires_review", False))
task.normal_review_role = normal_review_role_for_template(template)
if not task.normal_review_required:
task.normal_review_status = "not_required"
elif (getattr(task, "normal_review_status", None) or "not_required") == "not_required":
task.normal_review_status = "not_ready"
task.is_aqmm_task = getattr(template, "is_aqmm_task", False)
task.aqmm_mandatory = getattr(template, "aqmm_mandatory", False)
task.aqmm_evidence_required = getattr(template, "aqmm_evidence_required", False)
@@ -465,6 +497,9 @@ def sync_open_engagement_tasks_for_service(
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),
normal_review_required=bool(getattr(template, "requires_review", False)),
normal_review_role=normal_review_role_for_template(template),
normal_review_status="not_ready" if getattr(template, "requires_review", False) else "not_required",
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),
@@ -1323,6 +1358,11 @@ def submit_task_for_review(db: Session, *, task: ClientServiceTaskInstance, note
now = datetime.now(timezone.utc)
task.submitted_for_review_by_user_id = user_id
task.submitted_for_review_at_utc = now
if getattr(task, "normal_review_required", False):
task.normal_review_status = "pending"
task.normal_review_note = None
task.normal_reviewed_by_user_id = None
task.normal_reviewed_at_utc = None
if getattr(task, "aqmm_manager_review_required", False) and task.manager_review_status != "reviewed":
task.manager_review_status = "pending"
if getattr(task, "aqmm_partner_review_required", False) and task.partner_review_status != "reviewed":
@@ -1339,6 +1379,53 @@ def submit_task_for_review(db: Session, *, task: ClientServiceTaskInstance, note
recalculate_task_aqmm_status(db, task)
def apply_normal_task_review(
db: Session,
*,
task: ClientServiceTaskInstance,
decision: str,
note: str,
user_id: int,
) -> None:
if getattr(task, "is_locked", False) or getattr(getattr(task, "subscription", None), "is_locked", False):
return
if not getattr(task, "normal_review_required", False):
raise ValueError("Normal review is not required for this task.")
clean_decision = (decision or "reviewed").strip().lower()
if clean_decision not in {"reviewed", "rework_required"}:
clean_decision = "reviewed"
clean_note = (note or "").strip()
if clean_decision == "rework_required" and not clean_note:
raise ValueError("Rework reason is required.")
now = datetime.now(timezone.utc)
task.normal_review_status = clean_decision
task.normal_review_note = clean_note or task.normal_review_note
task.normal_reviewed_by_user_id = user_id
task.normal_reviewed_at_utc = now
if clean_decision == "rework_required":
task.rework_status = "open"
task.rework_reason = clean_note
task.rework_requested_by_user_id = user_id
task.rework_requested_at_utc = now
task.rework_resolved_at_utc = None
task.status = "blocked"
comment_type = "rework_note"
else:
if getattr(task, "rework_status", "none") == "open":
task.rework_status = "resolved"
task.rework_resolved_at_utc = now
comment_type = "manager_review_note" if (getattr(task, "normal_review_role", "") or "").startswith("manager") else "partner_review_note"
task.updated_by_user_id = user_id
_append_system_task_comment(
db,
task=task,
comment_type=comment_type,
message=clean_note or ("Normal review approved." if clean_decision == "reviewed" else clean_note),
user_id=user_id,
)
def apply_task_review(
db: Session,
*,
+10
View File
@@ -114,6 +114,7 @@ class ServiceDefaultTaskTemplate(CommonBase):
eligible_role_names: Mapped[str | None] = mapped_column(String(200), nullable=True)
is_mandatory: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
requires_review: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
normal_review_role: Mapped[str | None] = mapped_column(String(30), nullable=True)
# AQMM task tags. These flags allow the existing service checklist to become
# the engagement quality checklist for assurance engagements, without creating
@@ -183,6 +184,7 @@ class FirmServiceTaskTemplate(CommonBase):
eligible_role_names: Mapped[str | None] = mapped_column(String(200), nullable=True)
is_mandatory: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
requires_review: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
normal_review_role: Mapped[str | None] = mapped_column(String(30), nullable=True)
# AQMM task tags copied into generated ClientServiceTaskInstance rows.
is_aqmm_task: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, index=True)
@@ -783,6 +785,14 @@ class ClientServiceTaskInstance(CommonBase):
submitted_for_review_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True, index=True)
submitted_for_review_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
# Normal operational review is intentionally separate from AQMM review.
normal_review_required: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, index=True)
normal_review_role: Mapped[str | None] = mapped_column(String(30), nullable=True, index=True)
normal_review_status: Mapped[str] = mapped_column(String(30), nullable=False, default="not_required", index=True)
normal_review_note: Mapped[str | None] = mapped_column(Text, nullable=True)
normal_reviewed_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
normal_reviewed_at_utc: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
manager_review_status: Mapped[str] = mapped_column(String(30), nullable=False, default="not_required", index=True)
manager_review_note: Mapped[str | None] = mapped_column(Text, nullable=True)
manager_reviewed_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True, index=True)
@@ -56,6 +56,14 @@
<input type="checkbox" name="requires_review" {% if task.requires_review %}checked{% endif %}>
Review
</label>
<label class="inline-flex items-center gap-2">
<span>Normal reviewer</span>
<select name="normal_review_role" class="rounded-lg border border-slate-300 px-2 py-1 text-xs">
<option value="manager" {% if (task.normal_review_role or 'manager') == 'manager' %}selected{% endif %}>Manager</option>
<option value="partner" {% if task.normal_review_role == 'partner' %}selected{% endif %}>Partner</option>
<option value="manager_or_partner" {% if task.normal_review_role == 'manager_or_partner' %}selected{% endif %}>Manager / Partner</option>
</select>
</label>
<label class="inline-flex items-center gap-2">
<input type="checkbox" name="is_active" {% if task.is_active %}checked{% endif %}>
Active
@@ -19,7 +19,7 @@
<div class="mt-1 text-xs text-slate-500">
Role: {{ task.default_role_name or '-' }} ·
Mandatory: {{ 'Yes' if task.is_mandatory else 'No' }} ·
Review: {{ 'Yes' if task.requires_review else 'No' }} · AQMM: {{ 'Yes' if task.is_aqmm_task else 'No' }}
Review: {{ ((task.normal_review_role or 'manager').replace('_',' ').title()) if task.requires_review else 'No' }} · AQMM: {{ 'Yes' if task.is_aqmm_task else 'No' }}
</div>
</div>
<div class="flex items-center gap-3">
@@ -44,7 +44,7 @@
<div><label class="mb-2 block text-sm font-medium text-slate-700">Task Name</label><input type="text" name="task_name" required class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div>
<div><label class="mb-2 block text-sm font-medium text-slate-700">Default Role</label><input type="text" name="default_role_name" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div>
<div><label class="mb-2 block text-sm font-medium text-slate-700">Sequence No</label><input type="number" min="1" name="sequence_no" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></div>
<div class="flex items-center gap-6 pt-7 text-sm text-slate-700"><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_mandatory" checked> Mandatory</label><label class="inline-flex items-center gap-2"><input type="checkbox" name="requires_review"> Review</label><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_active" checked> Active</label></div>
<div class="flex items-center gap-6 pt-7 text-sm text-slate-700"><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_mandatory" checked> Mandatory</label><label class="inline-flex items-center gap-2"><input type="checkbox" name="requires_review"> Review</label><label class="inline-flex items-center gap-2">Normal reviewer <select name="normal_review_role" class="rounded border border-slate-300 px-2 py-1 text-xs"><option value="manager">Manager</option><option value="partner">Partner</option><option value="manager_or_partner">Manager / Partner</option></select></label><label class="inline-flex items-center gap-2"><input type="checkbox" name="is_active" checked> Active</label></div>
<div class="md:col-span-2"><label class="mb-2 block text-sm font-medium text-slate-700">Description</label><textarea name="description" rows="4" class="w-full rounded-xl border border-slate-300 px-4 py-2 text-sm"></textarea></div>
<div class="md:col-span-2 rounded-2xl border border-indigo-200 bg-indigo-50 p-4">
<div class="text-sm font-semibold text-indigo-950">AQMM / Quality Control Tagging</div>
@@ -56,6 +56,14 @@
<input type="checkbox" name="requires_review" {% if task.requires_review %}checked{% endif %}>
Review
</label>
<label class="inline-flex items-center gap-2">
<span>Normal reviewer</span>
<select name="normal_review_role" class="rounded-lg border border-slate-300 px-2 py-1 text-xs">
<option value="manager" {% if (task.normal_review_role or 'manager') == 'manager' %}selected{% endif %}>Manager</option>
<option value="partner" {% if task.normal_review_role == 'partner' %}selected{% endif %}>Partner</option>
<option value="manager_or_partner" {% if task.normal_review_role == 'manager_or_partner' %}selected{% endif %}>Manager / Partner</option>
</select>
</label>
<label class="inline-flex items-center gap-2">
<input type="checkbox" name="is_active" {% if task.is_active %}checked{% endif %}>
Active
@@ -81,7 +81,7 @@
<label class="mb-1 block text-xs font-semibold uppercase tracking-wide text-slate-500">Options</label>
<div class="flex flex-wrap gap-3 rounded-xl border border-slate-200 px-3 py-2 text-xs text-slate-700">
<label class="inline-flex items-center gap-1"><input type="checkbox" name="is_mandatory" checked> Mandatory</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="requires_review"> Review</label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="requires_review"> Review</label><label class="inline-flex items-center gap-1">Normal reviewer <select name="normal_review_role" class="rounded border border-slate-300 px-1 py-0.5"><option value="manager">Manager</option><option value="partner">Partner</option><option value="manager_or_partner">Manager / Partner</option></select></label>
<label class="inline-flex items-center gap-1"><input type="checkbox" name="is_active" checked> Active</label>
</div>
</div>
@@ -165,7 +165,7 @@
<td class="px-4 py-3 text-xs text-slate-600">
<div class="flex flex-wrap gap-1">
{% if task.is_mandatory %}<span class="rounded-full bg-slate-100 px-2 py-1">Mandatory</span>{% endif %}
{% if task.requires_review %}<span class="rounded-full bg-amber-100 px-2 py-1 text-amber-800">Review</span>{% endif %}
{% if task.requires_review %}<span class="rounded-full bg-amber-100 px-2 py-1 text-amber-800">Review: {{ (task.normal_review_role or "manager").replace("_"," ").title() }}</span>{% endif %}
{% if task.is_aqmm_task %}<span class="rounded-full bg-indigo-100 px-2 py-1 text-indigo-800">AQMM</span>{% endif %}
{% if task.aqmm_mandatory %}<span class="rounded-full bg-rose-100 px-2 py-1 text-rose-800">AQMM Mandatory</span>{% endif %}
{% if task.aqmm_evidence_required %}<span class="rounded-full bg-sky-100 px-2 py-1 text-sky-800">Evidence</span>{% endif %}
+19 -3
View File
@@ -138,6 +138,15 @@ def _validated_registration_type_code(db, value: str, *, existing_value: str | N
raise ValueError("registration_type")
def _normal_review_role_value(value: str, *, requires_review: bool) -> str | None:
if not requires_review:
return None
clean = (value or "").strip().lower()
if clean in {"manager", "partner", "manager_or_partner"}:
return clean
return "manager"
def _redirect_denied():
from app.core.http_responses import ui_access_denied
return ui_access_denied()
@@ -948,6 +957,7 @@ def _copy_system_defaults_if_firm_tasks_empty(
eligible_role_names=getattr(default, "eligible_role_names", None) or default.default_role_name,
is_mandatory=default.is_mandatory,
requires_review=default.requires_review,
normal_review_role=getattr(default, "normal_review_role", None),
is_aqmm_task=getattr(default, "is_aqmm_task", False),
aqmm_mandatory=getattr(default, "aqmm_mandatory", False),
aqmm_evidence_required=getattr(default, "aqmm_evidence_required", False),
@@ -1241,7 +1251,7 @@ def task_templates_detail(request: Request, catalogue_id: int):
@router.post('/templates/{catalogue_id}/tasks/new')
def task_template_create_submit(request: Request, catalogue_id: int, task_name: str = Form(...), description: str = Form(''), default_role_name: str = Form(''), eligible_roles: list[str] = Form([]), sequence_no: int | None = Form(None), is_mandatory: str | None = Form(None), requires_review: str | None = Form(None), task_category: str = Form(''), response_required: str | None = Form(None), response_type: str = Form('NONE'), evidence_required: str | None = Form(None), remarks_required_if_no: str | None = Form(None), is_aqmm_task: str | None = Form(None), aqmm_mandatory: str | None = Form(None), aqmm_evidence_required: str | None = Form(None), aqmm_manager_review_required: str | None = Form(None), aqmm_partner_review_required: str | None = Form(None), aqmm_review_partner_required: str | None = Form(None), aqmm_blocks_final_release: str | None = Form(None), aqmm_reference: str = Form(''), is_active: str | None = Form(None), csrf_token: str = Form(...)):
def task_template_create_submit(request: Request, catalogue_id: int, task_name: str = Form(...), description: str = Form(''), default_role_name: str = Form(''), eligible_roles: list[str] = Form([]), sequence_no: int | None = Form(None), is_mandatory: str | None = Form(None), requires_review: str | None = Form(None), normal_review_role: str = Form('manager'), task_category: str = Form(''), response_required: str | None = Form(None), response_type: str = Form('NONE'), evidence_required: str | None = Form(None), remarks_required_if_no: str | None = Form(None), is_aqmm_task: str | None = Form(None), aqmm_mandatory: str | None = Form(None), aqmm_evidence_required: str | None = Form(None), aqmm_manager_review_required: str | None = Form(None), aqmm_partner_review_required: str | None = Form(None), aqmm_review_partner_required: str | None = Form(None), aqmm_blocks_final_release: str | None = Form(None), aqmm_reference: str = Form(''), is_active: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token)
db = CommonSessionLocal()
try:
@@ -1267,6 +1277,7 @@ def task_template_create_submit(request: Request, catalogue_id: int, task_name:
sequence_no=sequence_no or next_task_sequence(db, tenant_id=tenant_id, catalogue_id=catalogue_id),
is_mandatory=is_mandatory is not None,
requires_review=requires_review is not None,
normal_review_role=_normal_review_role_value(normal_review_role, requires_review=requires_review is not None),
task_category=task_category.strip() or None,
response_required=response_required is not None,
response_type=(response_type.strip().upper() or "NONE"),
@@ -1351,7 +1362,7 @@ def default_templates_detail(request: Request, catalogue_id: int):
@router.post('/catalogue/{catalogue_id}/defaults/new')
def default_template_create_submit(request: Request, catalogue_id: int, task_name: str = Form(...), description: str = Form(''), default_role_name: str = Form(''), sequence_no: int | None = Form(None), is_mandatory: str | None = Form(None), requires_review: str | None = Form(None), task_category: str = Form(''), response_required: str | None = Form(None), response_type: str = Form('NONE'), evidence_required: str | None = Form(None), remarks_required_if_no: str | None = Form(None), is_aqmm_task: str | None = Form(None), aqmm_mandatory: str | None = Form(None), aqmm_evidence_required: str | None = Form(None), aqmm_manager_review_required: str | None = Form(None), aqmm_partner_review_required: str | None = Form(None), aqmm_review_partner_required: str | None = Form(None), aqmm_blocks_final_release: str | None = Form(None), aqmm_reference: str = Form(''), is_active: str | None = Form(None), csrf_token: str = Form(...)):
def default_template_create_submit(request: Request, catalogue_id: int, task_name: str = Form(...), description: str = Form(''), default_role_name: str = Form(''), sequence_no: int | None = Form(None), is_mandatory: str | None = Form(None), requires_review: str | None = Form(None), normal_review_role: str = Form('manager'), task_category: str = Form(''), response_required: str | None = Form(None), response_type: str = Form('NONE'), evidence_required: str | None = Form(None), remarks_required_if_no: str | None = Form(None), is_aqmm_task: str | None = Form(None), aqmm_mandatory: str | None = Form(None), aqmm_evidence_required: str | None = Form(None), aqmm_manager_review_required: str | None = Form(None), aqmm_partner_review_required: str | None = Form(None), aqmm_review_partner_required: str | None = Form(None), aqmm_blocks_final_release: str | None = Form(None), aqmm_reference: str = Form(''), is_active: str | None = Form(None), csrf_token: str = Form(...)):
validate_csrf(request, csrf_token)
db = CommonSessionLocal()
try:
@@ -1364,7 +1375,7 @@ def default_template_create_submit(request: Request, catalogue_id: int, task_nam
catalogue = get_catalogue(db, catalogue_id)
if not catalogue:
return RedirectResponse(url='/services/defaults', status_code=303)
row = ServiceDefaultTaskTemplate(service_catalogue_id=catalogue_id, task_name=task_name.strip(), description=description.strip() or None, default_role_name=default_role_name.strip() or None, sequence_no=sequence_no or next_default_task_sequence(db, catalogue_id=catalogue_id), is_mandatory=is_mandatory is not None, requires_review=requires_review is not None, task_category=task_category.strip() or None, response_required=response_required is not None, response_type=(response_type.strip().upper() or "NONE"), evidence_required=evidence_required is not None, remarks_required_if_no=remarks_required_if_no is not None, is_aqmm_task=is_aqmm_task is not None, aqmm_mandatory=aqmm_mandatory is not None, aqmm_evidence_required=aqmm_evidence_required is not None, aqmm_manager_review_required=aqmm_manager_review_required is not None, aqmm_partner_review_required=aqmm_partner_review_required is not None, aqmm_review_partner_required=aqmm_review_partner_required is not None, aqmm_blocks_final_release=aqmm_blocks_final_release is not None, aqmm_reference=aqmm_reference.strip() or None, is_active=is_active is not None)
row = ServiceDefaultTaskTemplate(service_catalogue_id=catalogue_id, task_name=task_name.strip(), description=description.strip() or None, default_role_name=default_role_name.strip() or None, sequence_no=sequence_no or next_default_task_sequence(db, catalogue_id=catalogue_id), is_mandatory=is_mandatory is not None, requires_review=requires_review is not None, normal_review_role=_normal_review_role_value(normal_review_role, requires_review=requires_review is not None), task_category=task_category.strip() or None, response_required=response_required is not None, response_type=(response_type.strip().upper() or "NONE"), evidence_required=evidence_required is not None, remarks_required_if_no=remarks_required_if_no is not None, is_aqmm_task=is_aqmm_task is not None, aqmm_mandatory=aqmm_mandatory is not None, aqmm_evidence_required=aqmm_evidence_required is not None, aqmm_manager_review_required=aqmm_manager_review_required is not None, aqmm_partner_review_required=aqmm_partner_review_required is not None, aqmm_review_partner_required=aqmm_review_partner_required is not None, aqmm_blocks_final_release=aqmm_blocks_final_release is not None, aqmm_reference=aqmm_reference.strip() or None, is_active=is_active is not None)
db.add(row); db.commit()
return RedirectResponse(url=f'/services/catalogue/{catalogue_id}/defaults', status_code=303)
finally:
@@ -1403,6 +1414,7 @@ def copy_defaults_to_firm(request: Request, catalogue_id: int, csrf_token: str =
default_role_name=d.default_role_name,
is_mandatory=d.is_mandatory,
requires_review=d.requires_review,
normal_review_role=getattr(d, "normal_review_role", None),
task_category=getattr(d, "task_category", None),
response_required=getattr(d, "response_required", False),
response_type=getattr(d, "response_type", "NONE") or "NONE",
@@ -1707,6 +1719,7 @@ def firm_task_template_edit_submit(
sequence_no: int = Form(1),
is_mandatory: str | None = Form(None),
requires_review: str | None = Form(None),
normal_review_role: str = Form('manager'),
task_category: str = Form(''),
response_required: str | None = Form(None),
response_type: str = Form('NONE'),
@@ -1753,6 +1766,7 @@ def firm_task_template_edit_submit(
task.sequence_no = sequence_no
task.is_mandatory = is_mandatory is not None
task.requires_review = requires_review is not None
task.normal_review_role = _normal_review_role_value(normal_review_role, requires_review=task.requires_review)
task.task_category = task_category.strip() or None
task.response_required = response_required is not None
task.response_type = response_type.strip().upper() or "NONE"
@@ -1849,6 +1863,7 @@ def default_task_template_edit_submit(
sequence_no: int = Form(1),
is_mandatory: str | None = Form(None),
requires_review: str | None = Form(None),
normal_review_role: str = Form('manager'),
task_category: str = Form(''),
response_required: str | None = Form(None),
response_type: str = Form('NONE'),
@@ -1895,6 +1910,7 @@ def default_task_template_edit_submit(
task.sequence_no = sequence_no
task.is_mandatory = is_mandatory is not None
task.requires_review = requires_review is not None
task.normal_review_role = _normal_review_role_value(normal_review_role, requires_review=task.requires_review)
task.task_category = task_category.strip() or None
task.response_required = response_required is not None
task.response_type = response_type.strip().upper() or "NONE"