Coverage for apps/borrower_portal/schemas.py: 100%

124 statements  

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

1"""Django Ninja schemas for the borrower portal (add-borrower-portal-api). 

2 

3Shapes follow the borrower portal's contracts (``borrower-pos/src/services/{auth,me,pos}/ 

4*.types.ts``): snake_case application fields, camelCase only where the portal already uses it. 

5""" 

6 

7from __future__ import annotations 

8 

9from typing import Any, Literal 

10 

11from ninja import Schema 

12 

13 

14# --- accounts --------------------------------------------------------------------- 

15class NotifyOut(Schema): 

16 email: bool 

17 sms: bool 

18 

19 

20class BorrowerUserOut(Schema): 

21 id: int 

22 name: str 

23 email: str 

24 phone: str 

25 kind: Literal["borrower"] 

26 notify: NotifyOut 

27 

28 

29class BorrowerAuthResultOut(Schema): 

30 token: str 

31 user: BorrowerUserOut 

32 

33 

34class RegisterInput(Schema): 

35 name: str = "" 

36 email: str = "" 

37 password: str = "" 

38 

39 

40class ResetInput(Schema): 

41 token: str = "" 

42 password: str = "" 

43 

44 

45class OtpRequestInput(Schema): 

46 email: str = "" 

47 

48 

49class OtpRequestOut(Schema): 

50 sent: bool 

51 devOtp: str | None = None 

52 

53 

54class OtpVerifyInput(Schema): 

55 email: str = "" 

56 code: str = "" 

57 

58 

59class BorrowerProfilePatch(Schema): 

60 name: str 

61 email: str 

62 phone: str = "" 

63 notify: NotifyOut 

64 

65 

66# --- application ------------------------------------------------------------------ 

67class StartApplicationInput(Schema): 

68 purpose: str = "" 

69 occupancy: str = "" 

70 state_code: str = "" 

71 

72 

73class SaveStepInput(Schema): 

74 section: str 

75 data: dict[str, Any] = {} 

76 # For per-borrower sections, whose record to save; defaults to the signed-in borrower. 

77 borrower_id: int | None = None 

78 

79 

80class DeleteStepItemInput(Schema): 

81 section: str 

82 item_id: int | str 

83 

84 

85class PosMyOut(Schema): 

86 # The nested application is plain JSON built by pos_api._snapshot (PosApplication shape). 

87 application: dict[str, Any] | None 

88 steps: dict[str, bool] 

89 percent_complete: int 

90 

91 

92class PortalConfigOut(Schema): 

93 languages: list[str] 

94 default_language: str 

95 programs: list[str] 

96 sections_by_program: dict[str, list[str]] 

97 features: dict[str, bool] 

98 

99 

100class AddressSuggestionOut(Schema): 

101 label: str 

102 address_line: str 

103 city: str 

104 state_code: str 

105 county: str 

106 postal_code: str 

107 

108 

109# --- invitations ------------------------------------------------------------------ 

110class PosInviteInput(Schema): 

111 name: str = "" 

112 email: str = "" 

113 

114 

115class PosInviteOut(Schema): 

116 user_id: int 

117 # The temporary password goes only to the borrower by email (add-sendgrid-email). 

118 email_sent: bool 

119 # Development (DEBUG) only, for local testing without opening the email. 

120 devTempPassword: str | None = None 

121 

122 

123class BorrowerPosStatusOut(Schema): 

124 borrower_id: int 

125 name: str 

126 email: str 

127 invited: bool 

128 started: bool 

129 has_account: bool 

130 # Status of the latest invite email (Sent / Delivered / Opened / Bounced / Failed …), or null. 

131 invite_email_status: str | None = None 

132 

133 

134# --- documents -------------------------------------------------------------------- 

135class UploadInviteInput(Schema): 

136 email: str = "" 

137 message: str = "" 

138 

139 

140class UploadInviteOut(Schema): 

141 id: int 

142 email: str 

143 message: str 

144 status: str 

145 created_at: str 

146 

147 

148class UploadInviteCreatedOut(Schema): 

149 invite: UploadInviteOut 

150 uploadLink: str 

151 

152 

153class UploadPageOut(Schema): 

154 loanNumber: str 

155 company: str 

156 borrowerName: str 

157 message: str 

158 requested: list[dict[str, Any]] 

159 uploadCount: int 

160 catalog: list[dict[str, Any]] 

161 

162 

163class DocumentUploadInput(Schema): 

164 name: str = "" 

165 classification: str = "" 

166 need_id: int | None = None 

167 

168 

169class UploadCountOut(Schema): 

170 uploadCount: int 

171 

172 

173class PortalNeedOut(Schema): 

174 id: int 

175 name: str 

176 type: str 

177 status: str 

178 description: str 

179 due_date: str | None 

180 

181 

182class PortalDocumentOut(Schema): 

183 id: int 

184 name: str 

185 classification: str 

186 uploaded_via: str 

187 created_at: str 

188 

189 

190class PortalDisclosureOut(Schema): 

191 id: int 

192 package_type: str 

193 status: str 

194 sent_at: str | None 

195 signed_at: str | None