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
« 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."""
3from decimal import Decimal
5from django.core.management import call_command
6from django.core.management.base import BaseCommand
7from django.db import transaction
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
28class Command(BaseCommand):
29 help = "Seed dev demo users (timothy, dev.admin) with CA/FL licenses and initial test loan."
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()
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")
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)
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 )
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 )
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()
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"])
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 )
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 )
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 )
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 )
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 )
236 # Milestone trackers
237 seed_trackers(loan)
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)
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 )