Coverage for apps/mismo/api.py: 95%
37 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"""MISMO export router: pre-export readiness + the MISMO 3.4 XML download (under /loans)."""
3from uuid import UUID
5from django.http import HttpResponse
6from ninja import Router
8from apps.accounts.auth import ensure_staff_scope, requires
9from apps.application_1003.models import Declaration, Demographics, SubjectProperty
10from apps.core_los.models import Loan
11from apps.mismo.generator import TARGETS, generate_mismo_xml
12from apps.mismo.schemas import MismoReadinessOut
13from domain.mismo_readiness import (
14 MismoBorrowerReadiness,
15 MismoReadinessContext,
16 check_mismo_readiness,
17)
18from domain.result import AppError, DomainError
20router = Router(tags=["mismo"])
23def _scoped_loan(request, guid: UUID) -> Loan:
24 loan = Loan.objects.filter(guid=guid).first()
25 if loan is None: 25 ↛ 26line 25 didn't jump to line 26 because the condition on line 25 was never true
26 raise DomainError([AppError("NOT_FOUND", "Loan not found")])
27 ensure_staff_scope(request.principal, loan)
28 return loan
31def _build_readiness_ctx(loan: Loan) -> MismoReadinessContext:
32 borrowers = [
33 MismoBorrowerReadiness(
34 first_name=b.first_name,
35 last_name=b.last_name,
36 has_declarations=Declaration.objects.filter(borrower=b).exists(),
37 has_demographics=Demographics.objects.filter(borrower=b).exists(),
38 has_current_residence=b.residences.filter(residence_type="Current").exists(),
39 )
40 for b in loan.borrowers.all()
41 ]
42 return MismoReadinessContext(
43 borrowers=borrowers,
44 has_subject_property=SubjectProperty.objects.filter(loan=loan).exists(),
45 has_down_payment_source=loan.down_payment_sources.exists(),
46 has_loan_identifier=loan.loan_identifiers.filter(
47 identifier_type="UniversalLoanIdentifier"
48 ).exists(),
49 )
52def _readiness_payload(readiness) -> dict:
53 return {
54 "findings": [
55 {"bucket": f.bucket, "code": f.code, "message": f.message}
56 for f in readiness.findings
57 ]
58 }
61@router.get("/{guid}/mismo/readiness", response=MismoReadinessOut, auth=requires("LOAN_VIEW"))
62def mismo_readiness(request, guid: UUID):
63 loan = _scoped_loan(request, guid)
64 return _readiness_payload(check_mismo_readiness(_build_readiness_ctx(loan)))
67@router.get("/{guid}/mismo", auth=requires("LOAN_VIEW"))
68def mismo_export(request, guid: UUID, target: str = "GENERIC"):
69 loan = _scoped_loan(request, guid)
70 target = target.upper()
71 if target not in TARGETS:
72 raise DomainError(
73 [AppError("VALIDATION", f"Unknown MISMO export target: {target!r}")],
74 status_code=422,
75 )
76 xml_bytes = generate_mismo_xml(loan, target=target)
77 response = HttpResponse(xml_bytes, content_type="application/xml")
78 filename = f"{loan.arive_loan_no}-{target.lower()}.xml"
79 response["Content-Disposition"] = f'attachment; filename="{filename}"'
80 return response