Add AQMM task evidence and review notes workflow
This commit is contained in:
@@ -18,6 +18,7 @@ from app.modules.services.execution import (
|
||||
TASK_STATUSES,
|
||||
add_task_comment,
|
||||
apply_bulk_task_update,
|
||||
apply_task_review,
|
||||
apply_task_update,
|
||||
dashboard_stats,
|
||||
generate_tasks_for_subscription,
|
||||
@@ -29,6 +30,8 @@ from app.modules.services.execution import (
|
||||
list_subscription_execution_payload,
|
||||
list_tasks_payload,
|
||||
parse_date_value,
|
||||
recalculate_task_aqmm_status,
|
||||
submit_task_for_review,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/services/work-tracker", tags=["services-work-tracker-ui"])
|
||||
@@ -119,10 +122,10 @@ def _can_update_task_status(db, user, task) -> bool:
|
||||
|
||||
def _allowed_comment_type_codes(db, user) -> set[str]:
|
||||
if _can_bulk_manage_tasks(db, user):
|
||||
return {"internal_note", "client_clarification", "consultant_clarification", "partner_review_note"}
|
||||
return {"internal_note", "client_clarification", "consultant_clarification", "manager_review_note", "partner_review_note", "review_partner_review_note", "rework_note"}
|
||||
if _is_staff(db, user):
|
||||
# Staff may communicate through consultants for clients where consultant is the communication channel.
|
||||
return {"internal_note", "client_clarification", "consultant_clarification"}
|
||||
return {"internal_note", "staff_work_note", "client_clarification", "consultant_clarification"}
|
||||
return set()
|
||||
|
||||
|
||||
@@ -132,6 +135,32 @@ def _can_add_task_comment(db, user, task) -> bool:
|
||||
return _can_update_task_status(db, user, task)
|
||||
|
||||
|
||||
def _is_manager_role(db, user) -> bool:
|
||||
roles = _role_names(db, user)
|
||||
return bool({"Manager", "Branch Manager", "Firm Admin"}.intersection(roles))
|
||||
|
||||
|
||||
def _can_submit_task_for_review(db, user, task) -> bool:
|
||||
return _can_update_task_status(db, user, task)
|
||||
|
||||
|
||||
def _can_manager_review_task(db, user, task) -> bool:
|
||||
return _can_bulk_manage_tasks(db, user) or (_is_manager_role(db, user) and _has_perm(db, user, "service_tasks.edit"))
|
||||
|
||||
|
||||
def _can_partner_review_task(db, user, task) -> bool:
|
||||
return _can_bulk_manage_tasks(db, user)
|
||||
|
||||
|
||||
def _can_review_partner_review_task(db, user, task) -> bool:
|
||||
subscription = getattr(task, "subscription", None)
|
||||
if not subscription:
|
||||
return False
|
||||
if _is_firm_admin(db, user):
|
||||
return _has_perm(db, user, "service_tasks.edit")
|
||||
return _is_partner(db, user) and int(getattr(subscription, "review_partner_user_id", 0) or 0) == int(user.id)
|
||||
|
||||
|
||||
def _comment_type_options_for_user(db, user):
|
||||
allowed = _allowed_comment_type_codes(db, user)
|
||||
return [(code, label) for code, label in TASK_COMMENT_TYPES if code in allowed]
|
||||
@@ -406,6 +435,10 @@ def task_edit_page(request: Request, task_id: int):
|
||||
comment_type_options=_comment_type_options_for_user(db, user),
|
||||
can_add_comment=_can_add_task_comment(db, user, task),
|
||||
can_edit=_can_update_task_status(db, user, task),
|
||||
can_submit_review=_can_submit_task_for_review(db, user, task),
|
||||
can_manager_review=_can_manager_review_task(db, user, task),
|
||||
can_partner_review=_can_partner_review_task(db, user, task),
|
||||
can_review_partner_review=_can_review_partner_review_task(db, user, task),
|
||||
can_manage_fields=_can_bulk_manage_tasks(db, user),
|
||||
can_reassign=_can_assign_staff(db, user),
|
||||
)
|
||||
@@ -481,12 +514,100 @@ def task_edit_submit(
|
||||
is_active=resolved_is_active,
|
||||
user_id=user.id,
|
||||
)
|
||||
recalculate_task_aqmm_status(db, task)
|
||||
db.commit()
|
||||
return RedirectResponse(url="/services/work-tracker", status_code=303)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/submit-review")
|
||||
def task_submit_review(
|
||||
request: Request,
|
||||
task_id: int,
|
||||
note: 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)
|
||||
tenant_id = _active_tenant_id(request, user)
|
||||
branch_id = _active_branch_id(request, user, db)
|
||||
task = get_task(
|
||||
db,
|
||||
tenant_id=tenant_id,
|
||||
task_id=task_id,
|
||||
branch_id=branch_id,
|
||||
assigned_to_user_id=_assigned_user_filter(db, user),
|
||||
partner_user_id=_partner_visibility_user_id(db, user),
|
||||
financial_year=_active_financial_year(request),
|
||||
)
|
||||
if not task:
|
||||
return RedirectResponse(url="/services/work-tracker", status_code=303)
|
||||
if not _can_submit_task_for_review(db, user, task):
|
||||
return _redirect_denied()
|
||||
try:
|
||||
submit_task_for_review(db, task=task, note=note, user_id=user.id)
|
||||
db.commit()
|
||||
return RedirectResponse(url=f"/services/work-tracker/tasks/{task_id}/edit?submitted=1", status_code=303)
|
||||
except ValueError:
|
||||
db.rollback()
|
||||
return RedirectResponse(url=f"/services/work-tracker/tasks/{task_id}/edit?review_error=1", status_code=303)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/review/{review_level}")
|
||||
def task_review_submit(
|
||||
request: Request,
|
||||
task_id: int,
|
||||
review_level: str,
|
||||
decision: str = Form("reviewed"),
|
||||
note: 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)
|
||||
tenant_id = _active_tenant_id(request, user)
|
||||
branch_id = _active_branch_id(request, user, db)
|
||||
task = get_task(
|
||||
db,
|
||||
tenant_id=tenant_id,
|
||||
task_id=task_id,
|
||||
branch_id=branch_id,
|
||||
assigned_to_user_id=None,
|
||||
partner_user_id=_partner_visibility_user_id(db, user),
|
||||
financial_year=_active_financial_year(request),
|
||||
)
|
||||
if not task:
|
||||
return RedirectResponse(url="/services/work-tracker", status_code=303)
|
||||
allowed = False
|
||||
if review_level == "manager":
|
||||
allowed = _can_manager_review_task(db, user, task)
|
||||
elif review_level == "partner":
|
||||
allowed = _can_partner_review_task(db, user, task)
|
||||
elif review_level == "review_partner":
|
||||
allowed = _can_review_partner_review_task(db, user, task)
|
||||
if not allowed:
|
||||
return _redirect_denied()
|
||||
try:
|
||||
apply_task_review(db, task=task, review_level=review_level, decision=decision, note=note, user_id=user.id)
|
||||
db.commit()
|
||||
return RedirectResponse(url=f"/services/work-tracker/tasks/{task_id}/edit?reviewed=1", status_code=303)
|
||||
except ValueError:
|
||||
db.rollback()
|
||||
return RedirectResponse(url=f"/services/work-tracker/tasks/{task_id}/edit?review_error=1", status_code=303)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/tasks/{task_id}/comments")
|
||||
def task_comment_submit(
|
||||
request: Request,
|
||||
|
||||
Reference in New Issue
Block a user