Coverage for apps/fees_trid/services.py: 90%
17 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"""Shared fee-replace logic used by both ``set_fees`` and the Change-of-Circumstance
2endpoint (design.md D4, add-coc-gate)."""
4from __future__ import annotations
6from django.db import transaction as db_transaction
8from apps.core_los.models import Loan
9from apps.fees_trid.models import Fee
10from apps.fees_trid.schemas import FeeIn
11from domain.enums import FeeSection
12from domain.result import AppError, DomainError
14_VALID_SECTIONS = {s.value for s in FeeSection}
17def replace_fees(loan: Loan, fees: list[FeeIn]) -> list[Fee]:
18 """Delete the loan's current fees and recreate them from ``fees``, validating
19 each fee's TRID section first. Raises ``DomainError`` and changes nothing if any
20 section is invalid."""
21 bad = [f.section for f in fees if f.section not in _VALID_SECTIONS]
22 if bad: 22 ↛ 23line 22 didn't jump to line 23 because the condition on line 22 was never true
23 raise DomainError([AppError("VALIDATION", f"Invalid fee section(s): {', '.join(bad)}")])
24 with db_transaction.atomic():
25 loan.fees.all().delete()
26 for f in fees:
27 Fee.objects.create(
28 loan=loan,
29 section=f.section,
30 name=f.name,
31 total_amt=f.total_amt,
32 tolerance_bucket=f.tolerance_bucket or "", # Fee.save() fills from section
33 paid_by=f.paid_by,
34 paid_to=f.paid_to,
35 poc_ind=f.poc_ind,
36 )
37 return list(loan.fees.all())