Coverage for apps/workflow/schemas.py: 87%
131 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-04 17:24 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-04 17:24 +0000
1"""Ninja schemas for the workflow API (trackers, key dates, tasks, templates, rules)."""
3from __future__ import annotations
5import datetime
6from datetime import date
8from ninja import ModelSchema, Schema
9from pydantic import field_validator
11from apps.workflow.models import AutomationRule, KeyDates, LoanTask, LoanTracker, TaskTemplate
14# --- Trackers ------------------------------------------------------------------
15class TrackerOut(ModelSchema):
16 class Meta:
17 model = LoanTracker
18 exclude = ["loan"]
21class TrackerPatch(Schema):
22 status: str | None = None
23 target_date: date | None = None
26# --- Key dates -----------------------------------------------------------------
27class KeyDatesOut(ModelSchema):
28 class Meta:
29 model = KeyDates
30 exclude = ["loan"]
33class KeyDatesPatch(Schema):
34 key: str | None = None
35 date: datetime.date | None = None
36 application_date: datetime.date | None = None
37 le_disclosed_date: datetime.date | None = None
38 le_signed_date: datetime.date | None = None
39 intent_to_proceed_date: datetime.date | None = None
40 appraisal_ordered_date: datetime.date | None = None
41 appraisal_received_date: datetime.date | None = None
42 lock_date: datetime.date | None = None
43 lock_expiration_date: datetime.date | None = None
44 submitted_to_uw_date: datetime.date | None = None
45 approved_date: datetime.date | None = None
46 clear_to_close_date: datetime.date | None = None
47 cd_disclosed_date: datetime.date | None = None
48 cd_signed_date: datetime.date | None = None
49 closing_date: datetime.date | None = None
50 funding_date: datetime.date | None = None
51 first_payment_date: datetime.date | None = None
54# --- Tasks ---------------------------------------------------------------------
55class TaskOut(ModelSchema):
56 class Meta:
57 model = LoanTask
58 exclude = ["loan"]
61class TaskCreate(Schema):
62 title: str
63 description: str = ""
64 assignee_id: int | None = None
65 due_date: date | None = None
66 checklists: list = []
67 follow_up_on_done: bool = False
68 follow_up_offset_days: int = 7
70 @field_validator("title")
71 @classmethod
72 def validate_title(cls, v: str) -> str:
73 s = v.strip() if v else ""
74 if not s: 74 ↛ 75line 74 didn't jump to line 75 because the condition on line 74 was never true
75 raise ValueError("Task title cannot be blank.")
76 if len(s) > 255: 76 ↛ 77line 76 didn't jump to line 77 because the condition on line 76 was never true
77 raise ValueError("Task title cannot exceed 255 characters.")
78 return s
81class TaskPatch(Schema):
82 title: str | None = None
83 description: str | None = None
84 assignee_id: int | None = None
85 due_date: date | None = None
86 completed: bool | None = None
87 checklists: list | None = None
89 @field_validator("title")
90 @classmethod
91 def validate_title(cls, v: str | None) -> str | None:
92 if v is None:
93 return None
94 s = v.strip()
95 if not s:
96 raise ValueError("Task title cannot be blank.")
97 if len(s) > 255:
98 raise ValueError("Task title cannot exceed 255 characters.")
99 return s
102class TaskFromTemplateIn(Schema):
103 template_id: int
104 assignee_id: int | None = None
107# --- Tasks hub (cross-loan) ------------------------------------------------------
108class TaskHubRowOut(Schema):
109 """A LoanTask row flattened across loans, for GET /api/tasks (Tasks Hub)."""
111 id: int
112 title: str
113 description: str
114 assignee_id: int | None
115 due_date: date | None
116 status: str
117 checklists: list
118 loan_guid: str
119 loan_number: str
120 borrower_name: str
123class TaskHubListOut(Schema):
124 count: int
125 rows: list[TaskHubRowOut]
128# --- Templates + rules (config) ------------------------------------------------
129class TaskTemplateOut(ModelSchema):
130 class Meta:
131 model = TaskTemplate
132 fields = "__all__"
135class TaskTemplateCreate(Schema):
136 title: str
137 description: str = ""
138 due_offset_days: int = 3
139 checklists: list = []
140 follow_up: dict = {}
143class AutomationRuleOut(ModelSchema):
144 class Meta:
145 model = AutomationRule
146 fields = "__all__"
149class AutomationRuleCreate(Schema):
150 rule_id: str
151 name: str
152 trigger: str
153 conditions: list = []
154 actions: list = []
155 is_active: bool = True
158class RuleEvaluateIn(Schema):
159 trigger: str
160 target_stage: str | None = None
161 date_key: str | None = None
164class RuleEffectOut(Schema):
165 rule_id: str
166 action_type: str
167 message: str = ""
168 task_title: str = ""
171class RuleErrorOut(Schema):
172 code: str
173 message: str
174 field: str | None = None
177class RuleOutcomeOut(Schema):
178 ok: bool
179 errors: list[RuleErrorOut] = []
180 effects: list[RuleEffectOut] = []
181 created_task_ids: list[int] = []