Coverage for apps/accounts/management/commands/seed_dev_demo.py: 94%

84 statements  

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

1"""Seed complete dev demo environment: staff users, licenses, RBAC, catalogs, and demo loans.""" 

2 

3from decimal import Decimal 

4 

5from django.core.management import call_command 

6from django.core.management.base import BaseCommand 

7from django.db import transaction 

8 

9from apps.accounts.models import License, OrgUnit, Role, User, UserRole 

10from apps.application_1003.models import ( 

11 Asset, 

12 Borrower, 

13 Declaration, 

14 Demographics, 

15 HousingExpense, 

16 Income, 

17 Residence, 

18 SubjectProperty, 

19) 

20from apps.borrower_portal.models import BorrowerAccount, PosApplication 

21from apps.communications.api import _seed_templates_if_empty 

22from apps.core_los.models import Loan, LoanNumberSequence, LoanTransaction 

23from apps.integrations.encryption import encrypt_secret 

24from apps.workflow.models import seed_trackers 

25from apps.workflow.rules_api import _seed_rules_if_empty 

26 

27 

28class Command(BaseCommand): 

29 help = "Seed dev demo users (timothy, dev.admin) with CA/FL licenses and initial test loan." 

30 

31 @transaction.atomic 

32 def handle(self, *args, **options): 

33 # 1. Run upstream seed commands 

34 call_command("seed_rbac") 

35 try: 

36 call_command("seed_document_classifications") 

37 except Exception: 

38 pass 

39 try: 

40 call_command("seed_rate_products") 

41 except Exception: 

42 pass 

43 try: 

44 call_command("seed_reports") 

45 except Exception: 

46 pass 

47 _seed_rules_if_empty() 

48 _seed_templates_if_empty() 

49 

50 # 2. HQ Org Unit 

51 hq, _ = OrgUnit.objects.get_or_create( 

52 name="Lumen Lending HQ", 

53 defaults={"is_headquarters": True}, 

54 ) 

55 owner_role = Role.objects.get(name="Owner") 

56 lo_role = Role.objects.get(name="Loan Officer") 

57 

58 # 3. Timothy Reed (Primary LO demo user) 

59 timothy, created = User.objects.get_or_create( 

60 email="timothy@lumenlending.com", 

61 defaults={ 

62 "first_name": "Timothy", 

63 "last_name": "Reed", 

64 "phone": "(561) 555-0142", 

65 "title": "Senior Loan Officer", 

66 "nmls_id": "1849201", 

67 "org_unit": hq, 

68 "is_staff": True, 

69 }, 

70 ) 

71 timothy.set_password("lumen") 

72 timothy.is_active = True 

73 timothy.save() 

74 UserRole.objects.get_or_create(user=timothy, role=lo_role, org_unit=hq) 

75 UserRole.objects.get_or_create(user=timothy, role=owner_role, org_unit=hq) 

76 

77 # Licenses for Timothy 

78 for st in ("CA", "FL"): 

79 License.objects.update_or_create( 

80 user=timothy, 

81 state_code=st, 

82 defaults={"active": True, "sponsored": True, "nmls_id": "1849201"}, 

83 ) 

84 

85 # 4. Dev Admin 

86 admin, _ = User.objects.get_or_create( 

87 email="dev.admin@lumen.test", 

88 defaults={ 

89 "first_name": "Dev", 

90 "last_name": "Admin", 

91 "org_unit": hq, 

92 "is_staff": True, 

93 }, 

94 ) 

95 admin.set_password("devadmin") 

96 admin.is_active = True 

97 admin.save() 

98 UserRole.objects.get_or_create(user=admin, role=owner_role, org_unit=hq) 

99 for st in ("CA", "FL"): 

100 License.objects.update_or_create( 

101 user=admin, 

102 state_code=st, 

103 defaults={"active": True, "sponsored": True, "nmls_id": "999999"}, 

104 ) 

105 

106 # 5. Demo Loan: Alex Rivera (Application Intake) 

107 loan, _ = Loan.objects.get_or_create( 

108 arive_loan_no="LMN-10001", 

109 defaults={ 

110 "org_unit": hq, 

111 "loan_officer": timothy, 

112 "stage": "APPLICATION_INTAKE", 

113 "purpose": "Purchase", 

114 "occupancy": "PrimaryResidence", 

115 "state_code": "FL", 

116 "county": "Palm Beach", 

117 }, 

118 ) 

119 loan.loan_officer = timothy 

120 loan.org_unit = hq 

121 loan.stage = "APPLICATION_INTAKE" 

122 loan.purpose = "Purchase" 

123 loan.state_code = "FL" 

124 loan.county = "Palm Beach" 

125 loan.save() 

126 

127 # LMN-10001 is created by number above, not through next_loan_number(): move the counter 

128 # past every existing LMN-<n>, or the next new loan would collide with it. 

129 highest = max( 

130 ( 

131 int(no.split("-", 1)[1]) 

132 for no in Loan.objects.filter(arive_loan_no__startswith="LMN-").values_list( 

133 "arive_loan_no", flat=True 

134 ) 

135 if no.split("-", 1)[1].isdigit() 

136 ), 

137 default=0, 

138 ) 

139 seq, _ = LoanNumberSequence.objects.get_or_create(pk=1) 

140 if seq.next_value <= highest: 

141 seq.next_value = highest + 1 

142 seq.save(update_fields=["next_value"]) 

143 

144 # Transaction 

145 LoanTransaction.objects.update_or_create( 

146 loan=loan, 

147 defaults={ 

148 "base_loan_amt": Decimal("400000.00"), 

149 "note_amt": Decimal("400000.00"), 

150 "purchase_price_amt": Decimal("500000.00"), 

151 "appraised_value_amt": Decimal("500000.00"), 

152 "note_rate_percent": Decimal("6.625"), 

153 "ltv_ratio_percent": Decimal("80.00"), 

154 "cltv_ratio_percent": Decimal("80.00"), 

155 "down_payment_amt": Decimal("100000.00"), 

156 "term_months": 360, 

157 }, 

158 ) 

159 

160 # Primary Borrower 

161 borrower, _ = Borrower.objects.update_or_create( 

162 loan=loan, 

163 is_primary=True, 

164 defaults={ 

165 "first_name": "Alex", 

166 "last_name": "Rivera", 

167 "email": "alex.rivera@example.com", 

168 "cell_phone": "(561) 555-0199", 

169 "birth_date": "1988-06-15", 

170 "credit_auth_ind": True, 

171 "citizen_residency_type": "USCitizen", 

172 "ssn_encrypted": encrypt_secret("123-45-6789"), 

173 }, 

174 ) 

175 

176 # Primary Borrower Current Residence 

177 Residence.objects.update_or_create( 

178 borrower=borrower, 

179 residence_type="Current", 

180 defaults={ 

181 "address_line": "742 Evergreen Terrace", 

182 "city": "West Palm Beach", 

183 "state_code": "FL", 

184 "postal_code": "33401", 

185 "country_code": "US", 

186 }, 

187 ) 

188 

189 # Subject property 

190 SubjectProperty.objects.get_or_create( 

191 loan=loan, 

192 defaults={ 

193 "address_line": "742 Evergreen Terrace", 

194 "city": "West Palm Beach", 

195 "state_code": "FL", 

196 "postal_code": "33401", 

197 "property_type": "SingleFamily", 

198 "financed_unit_count": 1, 

199 "estimated_value_amt": Decimal("500000.00"), 

200 }, 

201 ) 

202 

203 # Financials 

204 if not Income.objects.filter(loan=loan, borrower=borrower, income_type="Base").exists(): 

205 Income.objects.create( 

206 loan=loan, 

207 borrower=borrower, 

208 income_type="Base", 

209 monthly_amt=Decimal("9500.00"), 

210 current_ind=True, 

211 ) 

212 if not Asset.objects.filter(loan=loan, asset_type="CheckingAccount").exists(): 

213 Asset.objects.create( 

214 loan=loan, 

215 asset_type="CheckingAccount", 

216 cash_or_market_value_amt=Decimal("85000.00"), 

217 ) 

218 if not HousingExpense.objects.filter(loan=loan, expense_type="FirstMortgagePI").exists(): 

219 HousingExpense.objects.create( 

220 loan=loan, 

221 expense_type="FirstMortgagePI", 

222 payment_amt=Decimal("2560.00"), 

223 timing_type="Proposed", 

224 ) 

225 if not Declaration.objects.filter(borrower=borrower).exists(): 

226 Declaration.objects.create( 

227 borrower=borrower, 

228 intent_to_occupy_type="Yes", 

229 ) 

230 if not Demographics.objects.filter(borrower=borrower).exists(): 

231 Demographics.objects.create( 

232 borrower=borrower, 

233 sex="Male", 

234 ) 

235 

236 # Milestone trackers 

237 seed_trackers(loan) 

238 

239 # Borrower portal login for the demo borrower (add-borrower-portal-api). 

240 account, _ = BorrowerAccount.objects.get_or_create( 

241 email="alex.rivera@example.com", defaults={"name": "Alex Rivera"} 

242 ) 

243 account.name = "Alex Rivera" 

244 account.is_active = True 

245 account.set_password("lumen") 

246 account.save() 

247 PosApplication.objects.get_or_create(loan=loan) 

248 

249 self.stdout.write( 

250 self.style.SUCCESS( 

251 "Successfully seeded dev demo environment:\n" 

252 " - timothy@lumenlending.com / lumen (LO, CA+FL licensed)\n" 

253 " - dev.admin@lumen.test / devadmin (Owner, CA+FL licensed)\n" 

254 f" - Demo Loan: {loan.arive_loan_no} ({loan.stage}, Alex Rivera)\n" 

255 " - alex.rivera@example.com / lumen (borrower portal)" 

256 ) 

257 )