Coverage for apps/workflow/tasks_hub_api.py: 100%

24 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-04 14:06 +0000

1"""Cross-loan Tasks Hub router: GET /api/tasks (mounted at "", not under /loans). 

2 

3The per-loan task endpoints in apps/workflow/api.py are unchanged; this is a sibling 

4read-only view flattening tasks across every loan the principal can see. 

5""" 

6 

7from __future__ import annotations 

8 

9from django.db.models import Prefetch 

10from ninja import Router 

11 

12from apps.accounts.auth import requires, staff_loan_queryset 

13from apps.application_1003.models import Borrower 

14from apps.workflow.models import LoanTask 

15from apps.workflow.schemas import TaskHubListOut 

16 

17router = Router(tags=["tasks-hub"]) 

18 

19 

20def _task_status(task: LoanTask) -> str: 

21 """Done/InProgress/ToDo, derived from `completed` + `checklists` (design.md D1).""" 

22 if task.completed: 

23 return "Done" 

24 if any(item.get("done") for item in task.checklists): 

25 return "InProgress" 

26 return "ToDo" 

27 

28 

29def _task_row(task: LoanTask) -> dict: 

30 loan = task.loan 

31 primary = next(iter(getattr(loan, "_primary_borrowers", [])), None) 

32 return { 

33 "id": task.id, 

34 "title": task.title, 

35 "description": task.description, 

36 "assignee_id": task.assignee_id, 

37 "due_date": task.due_date, 

38 "status": _task_status(task), 

39 "checklists": task.checklists, 

40 "loan_guid": str(loan.guid), 

41 "loan_number": loan.arive_loan_no, 

42 "borrower_name": f"{primary.first_name} {primary.last_name}" if primary else "—", 

43 } 

44 

45 

46@router.get("/tasks", response=TaskHubListOut, auth=requires("LOAN_VIEW")) 

47def list_all_tasks(request): 

48 qs = staff_loan_queryset(request.principal, "all") 

49 tasks = LoanTask.objects.filter(loan__in=qs).select_related("loan").prefetch_related( 

50 Prefetch( 

51 "loan__borrowers", 

52 queryset=Borrower.objects.filter(is_primary=True), 

53 to_attr="_primary_borrowers", 

54 ) 

55 ) 

56 rows = [_task_row(task) for task in tasks] 

57 return {"count": len(rows), "rows": rows}