feat(loan-origination): RBI compliance checklist, real client integration, and CIC consent/charge flows
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s

- Add standalone RBI Eligibility Checklist and RBI Rules Reference pages (11 real RBI-circular
  based rules: age-at-maturity, area of operation, PSL, director/related-party, wilful defaulter,
  exposure norms, LTV/margin, fair practices code, prepayment charges, own-shares security,
  insurance bundling), wired into the Loan Origination menu.
- Bridge the mock Loan Origination Pipeline to real Fineract data wherever an application is
  raised against a real Existing Customer: client search in the pipeline intake form, real Share
  Member accounts (with a details popup), and real KYC (Aadhaar/PAN verified status + masked
  numbers + document image view, generic document lookup for Income Proof/Bank Statement/
  Electricity Bill) on the Application Detail page.
- Add local-only capture UI for New Customer / no-account applications: front/back document
  images with crop + simulated Aadhaar OTP / PAN verification, employment-type-aware income proof,
  and an expiry-validated electricity bill upload.
- Add an EMI Calculation Basis breakdown (principal, rate, tenure, total interest/payable, and the
  substituted reducing-balance formula) to the KYC stage's income assessment.
- Add a CIC/Credit Bureau Check consent flow: a generated declaration letter (print for Branch,
  share for BC/Self-Apply), gated on the signed copy being uploaded before the credit score can be
  checked, plus a CIC check charge collected either by debiting a real linked account or via a
  mock payment-link flow for customers with none.
- Let the stage stepper jump directly to any already-reached stage (forward or backward) via a new
  furthestStageIndex, without losing any data already recorded on the application.
This commit is contained in:
2026-08-13 10:15:28 +05:30
parent 5a6fb1f9b5
commit 75df8865dd
22 changed files with 3688 additions and 48 deletions
@@ -56,7 +56,24 @@
<div class="loan-kv-field">
<dt>Share Member</dt>
<dd>
@if (a.isShareMember) {
@if (a.clientId) {
<!-- Real Existing Customer — checked against actual Fineract share accounts, not
the mock isShareMember fields every other (dummy-data) application uses. -->
@if (loadingShareAccounts) {
Checking membership…
} @else if (realShareAccounts.length > 0) {
<div class="loi-share-account-list">
@for (sa of realShareAccounts; track sa.id) {
<button type="button" class="loi-share-account-chip" (click)="viewShareAccountDetails(sa.id)">
{{ sa.accountNo }} — {{ sa.productName }}
</button>
}
</div>
} @else {
<span class="loan-badge loan-badge--hold">Not a Member</span>
<button type="button" class="pg-btn pg-btn--ghost" (click)="allotMembership(a)">Allot Shares</button>
}
} @else if (a.isShareMember) {
Yes — {{ a.shareCertNo }} (since {{ a.shareMemberSince }})
} @else {
<span class="loan-badge loan-badge--hold">Not a Member</span>
@@ -83,11 +100,24 @@
<div class="pos-section-label">Sanctioning Workflow Progress</div>
<div class="loan-stepper">
@for (s of stageOrder; track s) {
<div [class]="stepStatusClass(a, s)">
<span class="loan-step__connector"></span>
<span class="loan-step__dot">{{ stepMarker(a, s) }}</span>
<span class="loan-step__label">{{ stageLabels[s] }}</span>
</div>
@if (canJumpToStage(a, s)) {
<button
type="button"
[class]="stepStatusClass(a, s) + ' loan-step--clickable'"
title="Jump to {{ stageLabels[s] }} — nothing already entered is cleared"
(click)="goToStage(a, s)"
>
<span class="loan-step__connector"></span>
<span class="loan-step__dot">{{ stepMarker(a, s) }}</span>
<span class="loan-step__label">{{ stageLabels[s] }}</span>
</button>
} @else {
<div [class]="stepStatusClass(a, s)">
<span class="loan-step__connector"></span>
<span class="loan-step__dot">{{ stepMarker(a, s) }}</span>
<span class="loan-step__label">{{ stageLabels[s] }}</span>
</div>
}
}
</div>
</div>
@@ -225,36 +255,521 @@
<div class="pg-card">
<div class="pos-section-label">KYC &amp; Document Collection — OVD (RBI KYC Master Direction)</div>
<div class="loan-action-card">
<div class="loan-checklist">
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.aadhaarVerified" /> Aadhaar Verified (OVD)
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.panVerified" /> PAN Verified
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.incomeProofSubmitted" /> Income Proof Submitted
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.bankStatementSubmitted" /> Bank Statement Submitted
</label>
@if (a.secured) {
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.propertyDetailsSubmitted" /> Property Details Submitted
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.taxReceiptSubmitted" /> Property Tax Receipt Submitted
</label>
@if (a.clientId) {
<!-- Real Existing Customer -- Aadhaar/PAN read straight from Fineract's KYC record
(verified status + masked number + click-to-view image); the other three
proofs are checked against the client's real uploaded documents where
available, falling back to the same local-only capture UI as a New Customer
application when nothing is on file yet. -->
@if (loadingKyc) {
<p class="pg-sub npa-report-note">Loading KYC record…</p>
} @else {
<div class="loi-kyc-doc-grid">
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">Aadhaar</span>
<div class="loi-kyc-row">
<span
[class]="'loan-badge ' + (a.aadhaarVerified ? 'loan-badge--verified' : 'loan-badge--missing')"
>
{{ a.aadhaarVerified ? 'Verified' : 'Not Verified' }}
</span>
@if (realKyc?.aadhaarNumberMasked) {
<span class="loi-kyc-masked">{{ realKyc.aadhaarNumberMasked }}</span>
}
</div>
<button type="button" class="pg-btn pg-btn--ghost" (click)="viewKycIdentifierDoc('aadhaar')">
View Document
</button>
</div>
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">PAN</span>
<div class="loi-kyc-row">
<span [class]="'loan-badge ' + (a.panVerified ? 'loan-badge--verified' : 'loan-badge--missing')">
{{ a.panVerified ? 'Verified' : 'Not Verified' }}
</span>
@if (realKyc?.panNumber) {
<span class="loi-kyc-masked">{{ maskPan(realKyc.panNumber) }}</span>
}
</div>
<button type="button" class="pg-btn pg-btn--ghost" (click)="viewKycIdentifierDoc('pan')">
View Document
</button>
</div>
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">Income Proof</span>
@if (findDocument('income'); as incomeDoc) {
<span class="loan-badge loan-badge--verified">Submitted</span>
<span class="loi-kyc-masked">{{ incomeDoc.name || incomeDoc.fileName }}</span>
<button type="button" class="pg-btn pg-btn--ghost" (click)="viewGenericDocument(incomeDoc)">
View
</button>
} @else {
<!-- Nothing on file in Fineract for this client -- let the officer capture it
here instead of just ticking a box with nothing behind it. -->
<label class="inv-form-field">
<span>Employment Type</span>
<select
[(ngModel)]="newKycCapture.incomeProof.employmentType"
(ngModelChange)="onIncomeProofEmploymentTypeChange(a)"
>
<option value="SALARIED">Salaried</option>
<option value="BUSINESS">Business / Self-Employed</option>
</select>
</label>
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
{{
newKycCapture.incomeProof.file
? incomeProofDocLabel + ' ✓'
: 'Upload ' + incomeProofDocLabel
}}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewIncomeProofFile(a, $event)"
/>
</label>
@if (newKycCapture.incomeProof.file) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewNewSingleDoc(newKycCapture.incomeProof.file)"
>
@if (newKycCapture.incomeProof.previewUrl) {
<img [src]="newKycCapture.incomeProof.previewUrl" alt="Income proof" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
</div>
@if (a.incomeProofSubmitted) {
<span class="loan-badge loan-badge--verified">Submitted</span>
} @else {
<span class="loan-badge loan-badge--missing">Missing</span>
}
}
</div>
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">Bank Statement</span>
@if (findDocument('bank statement'); as bankDoc) {
<span class="loan-badge loan-badge--verified">Submitted</span>
<span class="loi-kyc-masked">{{ bankDoc.name || bankDoc.fileName }}</span>
<button type="button" class="pg-btn pg-btn--ghost" (click)="viewGenericDocument(bankDoc)">
View
</button>
} @else {
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
{{ newKycCapture.bankStatement.file ? 'Statement ✓' : 'Upload Bank Statement' }}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewBankStatementFile(a, $event)"
/>
</label>
@if (newKycCapture.bankStatement.file) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewNewSingleDoc(newKycCapture.bankStatement.file)"
>
@if (newKycCapture.bankStatement.previewUrl) {
<img [src]="newKycCapture.bankStatement.previewUrl" alt="Bank statement" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
</div>
@if (a.bankStatementSubmitted) {
<span class="loan-badge loan-badge--verified">Submitted</span>
} @else {
<span class="loan-badge loan-badge--missing">Missing</span>
}
}
</div>
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">Electricity Bill (Address Proof)</span>
@if (findDocument('electricity') || findDocument('address proof'); as billDoc) {
<span class="loan-badge loan-badge--verified">Submitted</span>
<span class="loi-kyc-masked">{{ billDoc.name || billDoc.fileName }}</span>
<button type="button" class="pg-btn pg-btn--ghost" (click)="viewGenericDocument(billDoc)">
View
</button>
} @else {
<label class="inv-form-field">
<span>Bill Date</span>
<input
type="date"
[(ngModel)]="newKycCapture.electricityBill.billDate"
(ngModelChange)="onElectricityBillDateChange(a)"
/>
</label>
@if (electricityBillTooOld) {
<p class="loi-kyc-error">Must not be older than 3 months.</p>
}
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
{{ newKycCapture.electricityBill.file ? 'Bill ✓' : 'Upload Bill' }}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewElectricityBillFile(a, $event)"
/>
</label>
@if (newKycCapture.electricityBill.file) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewNewSingleDoc(newKycCapture.electricityBill.file)"
>
@if (newKycCapture.electricityBill.previewUrl) {
<img [src]="newKycCapture.electricityBill.previewUrl" alt="Electricity bill" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
</div>
@if (a.electricityBillSubmitted) {
<span class="loan-badge loan-badge--verified">Submitted</span>
} @else {
<span class="loan-badge loan-badge--missing">Missing</span>
}
}
</div>
@if (a.secured) {
<div class="loi-kyc-card loi-kyc-card--checkbox">
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.propertyDetailsSubmitted" /> Property Details Submitted
</label>
</div>
<div class="loi-kyc-card loi-kyc-card--checkbox">
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.taxReceiptSubmitted" /> Property Tax Receipt Submitted
</label>
</div>
}
<div class="loi-kyc-card loi-kyc-card--checkbox">
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.aadhaarFingerprintCaptured" /> Aadhaar Fingerprint Captured
(e-KYC)
</label>
</div>
</div>
}
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.electricityBillSubmitted" /> Electricity Bill (Address Proof)
Submitted
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.aadhaarFingerprintCaptured" /> Aadhaar Fingerprint Captured
(e-KYC)
</label>
</div>
} @else {
<!-- No real Fineract client linked to this application (whether it's a "New
Customer" intake or an "Existing Customer" mock application raised without a
real client pick) -- there's nowhere to read real KYC/documents from, so this
mirrors the client-creation KYC step's capture UX (front/back image + OTP) and
generic document upload, kept local-only/simulated. -->
<p class="loi-kyc-note">
Captured for this application — final Aadhaar/PAN verification happens when the customer's CBS client
profile is created.
</p>
<div class="loi-kyc-doc-grid">
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">Aadhaar</span>
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
{{ newKycCapture.aadhaar.front ? 'Front ✓' : 'Upload Front' }}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewKycFile('aadhaar', 'front', $event)"
/>
</label>
<label class="pg-btn pg-btn--ghost">
{{ newKycCapture.aadhaar.back ? 'Back ✓' : 'Upload Back' }}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewKycFile('aadhaar', 'back', $event)"
/>
</label>
</div>
@if (newKycCapture.aadhaar.front || newKycCapture.aadhaar.back) {
<div class="loi-kyc-thumbs">
@if (newKycCapture.aadhaar.front) {
<button
type="button"
class="loi-kyc-thumb"
title="View front"
(click)="viewNewSingleDoc(newKycCapture.aadhaar.front)"
>
@if (newKycCapture.aadhaar.frontPreviewUrl) {
<img [src]="newKycCapture.aadhaar.frontPreviewUrl" alt="Aadhaar front" />
} @else {
<span class="loi-kyc-thumb-label">Front file</span>
}
</button>
}
@if (newKycCapture.aadhaar.back) {
<button
type="button"
class="loi-kyc-thumb"
title="View back"
(click)="viewNewSingleDoc(newKycCapture.aadhaar.back)"
>
@if (newKycCapture.aadhaar.backPreviewUrl) {
<img [src]="newKycCapture.aadhaar.backPreviewUrl" alt="Aadhaar back" />
} @else {
<span class="loi-kyc-thumb-label">Back file</span>
}
</button>
}
</div>
}
@if (newKycCapture.aadhaar.verified) {
<span class="loan-badge loan-badge--verified">Aadhaar Verified</span>
} @else {
<div class="loi-kyc-card-verify">
<input
type="text"
maxlength="12"
placeholder="Aadhaar Number"
[(ngModel)]="newKycCapture.aadhaar.number"
/>
<button type="button" class="pg-btn pg-btn--ghost" (click)="sendNewAadhaarOtp()">Send OTP</button>
@if (newKycCapture.aadhaar.otpSent) {
<div class="loi-otp-digits">
@for (d of newKycCapture.aadhaar.otpDigits; track $index) {
<input
type="text"
maxlength="1"
[value]="d"
(input)="onNewAadhaarOtpDigit($index, $event)"
(keydown)="onNewAadhaarOtpKeydown($index, $event)"
/>
}
</div>
<button type="button" class="pg-btn pg-btn--primary" (click)="verifyNewAadhaarOtp(a)">
Verify
</button>
}
</div>
}
</div>
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">PAN</span>
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
{{ newKycCapture.pan.front ? 'Front ✓' : 'Upload Front' }}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewKycFile('pan', 'front', $event)"
/>
</label>
<label class="pg-btn pg-btn--ghost">
{{ newKycCapture.pan.back ? 'Back ✓' : 'Upload Back' }}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewKycFile('pan', 'back', $event)"
/>
</label>
</div>
@if (newKycCapture.pan.front || newKycCapture.pan.back) {
<div class="loi-kyc-thumbs">
@if (newKycCapture.pan.front) {
<button
type="button"
class="loi-kyc-thumb"
title="View front"
(click)="viewNewSingleDoc(newKycCapture.pan.front)"
>
@if (newKycCapture.pan.frontPreviewUrl) {
<img [src]="newKycCapture.pan.frontPreviewUrl" alt="PAN front" />
} @else {
<span class="loi-kyc-thumb-label">Front file</span>
}
</button>
}
@if (newKycCapture.pan.back) {
<button
type="button"
class="loi-kyc-thumb"
title="View back"
(click)="viewNewSingleDoc(newKycCapture.pan.back)"
>
@if (newKycCapture.pan.backPreviewUrl) {
<img [src]="newKycCapture.pan.backPreviewUrl" alt="PAN back" />
} @else {
<span class="loi-kyc-thumb-label">Back file</span>
}
</button>
}
</div>
}
@if (newKycCapture.pan.verified) {
<span class="loan-badge loan-badge--verified">PAN Verified</span>
} @else {
<div class="loi-kyc-card-verify">
<input
type="text"
maxlength="10"
placeholder="PAN Number"
[(ngModel)]="newKycCapture.pan.number"
/>
<button type="button" class="pg-btn pg-btn--primary" (click)="verifyNewPan(a)">Verify PAN</button>
</div>
}
</div>
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">Income Proof</span>
<label class="inv-form-field">
<span>Employment Type</span>
<select
[(ngModel)]="newKycCapture.incomeProof.employmentType"
(ngModelChange)="onIncomeProofEmploymentTypeChange(a)"
>
<option value="SALARIED">Salaried</option>
<option value="BUSINESS">Business / Self-Employed</option>
</select>
</label>
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
{{
newKycCapture.incomeProof.file ? incomeProofDocLabel + ' ✓' : 'Upload ' + incomeProofDocLabel
}}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewIncomeProofFile(a, $event)"
/>
</label>
@if (newKycCapture.incomeProof.file) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewNewSingleDoc(newKycCapture.incomeProof.file)"
>
@if (newKycCapture.incomeProof.previewUrl) {
<img [src]="newKycCapture.incomeProof.previewUrl" alt="Income proof" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
</div>
@if (a.incomeProofSubmitted) {
<span class="loan-badge loan-badge--verified">Submitted</span>
} @else {
<span class="loan-badge loan-badge--missing">Missing</span>
}
</div>
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">Bank Statement</span>
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
{{ newKycCapture.bankStatement.file ? 'Statement ✓' : 'Upload Bank Statement' }}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewBankStatementFile(a, $event)"
/>
</label>
@if (newKycCapture.bankStatement.file) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewNewSingleDoc(newKycCapture.bankStatement.file)"
>
@if (newKycCapture.bankStatement.previewUrl) {
<img [src]="newKycCapture.bankStatement.previewUrl" alt="Bank statement" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
</div>
@if (a.bankStatementSubmitted) {
<span class="loan-badge loan-badge--verified">Submitted</span>
} @else {
<span class="loan-badge loan-badge--missing">Missing</span>
}
</div>
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">Electricity Bill (Address Proof)</span>
<label class="inv-form-field">
<span>Bill Date</span>
<input
type="date"
[(ngModel)]="newKycCapture.electricityBill.billDate"
(ngModelChange)="onElectricityBillDateChange(a)"
/>
</label>
@if (electricityBillTooOld) {
<p class="loi-kyc-error">Must not be older than 3 months.</p>
}
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
{{ newKycCapture.electricityBill.file ? 'Bill ✓' : 'Upload Bill' }}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewElectricityBillFile(a, $event)"
/>
</label>
@if (newKycCapture.electricityBill.file) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewNewSingleDoc(newKycCapture.electricityBill.file)"
>
@if (newKycCapture.electricityBill.previewUrl) {
<img [src]="newKycCapture.electricityBill.previewUrl" alt="Electricity bill" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
</div>
@if (a.electricityBillSubmitted) {
<span class="loan-badge loan-badge--verified">Submitted</span>
} @else {
<span class="loan-badge loan-badge--missing">Missing</span>
}
</div>
@if (a.secured) {
<div class="loi-kyc-card loi-kyc-card--checkbox">
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.propertyDetailsSubmitted" /> Property Details Submitted
</label>
</div>
<div class="loi-kyc-card loi-kyc-card--checkbox">
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.taxReceiptSubmitted" /> Property Tax Receipt Submitted
</label>
</div>
}
<div class="loi-kyc-card loi-kyc-card--checkbox">
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.aadhaarFingerprintCaptured" /> Aadhaar Fingerprint Captured
(e-KYC)
</label>
</div>
</div>
}
@if (a.monthlyIncome === 0) {
<label class="inv-form-field">
<span>Monthly Income (₹)</span>
@@ -263,6 +778,11 @@
<p class="pg-sub npa-report-note">
Eligible Loan Amount preview: ₹{{ eligiblePreview(a) | number: '1.0-0' }}
</p>
@if (isRevolving(a)) {
<p class="pg-sub npa-report-note">
{{ a.productName }} is a revolving/renewable limit — repaid on demand, no fixed EMI.
</p>
}
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--ghost" (click)="setIncomeNow(a)">
Assess Income &amp; Calculate Eligible Amount
@@ -278,8 +798,54 @@
<dt>Eligible Loan Amount</dt>
<dd>₹{{ a.eligibleLoanAmount | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Estimated EMI</dt>
<dd>
@if (isRevolving(a)) {
No fixed EMI (revolving limit)
} @else {
₹{{ emiAmountFor(a) | number: '1.0-0' }} / month
}
</dd>
</div>
</dl>
}
@if (!isRevolving(a)) {
<div class="loi-emi-breakdown">
<div class="loi-emi-breakdown-title">EMI Calculation Basis</div>
<dl class="loan-kv-grid">
<div class="loan-kv-field">
<dt>Principal (Loan Amount)</dt>
<dd>₹{{ a.requestedAmount | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Interest Rate</dt>
<dd>{{ a.interestRate }}% p.a. ({{ a.interestRate / 12 | number: '1.2-2' }}% / month)</dd>
</div>
<div class="loan-kv-field">
<dt>Tenure</dt>
<dd>{{ a.tenureMonths }} months</dd>
</div>
<div class="loan-kv-field">
<dt>Total Interest Payable</dt>
<dd>₹{{ totalInterestFor(a) | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Total Amount Payable</dt>
<dd>₹{{ totalPayableFor(a) | number: '1.0-0' }}</dd>
</div>
</dl>
<p class="pg-sub npa-report-note loi-emi-formula">
Reducing-balance EMI formula: EMI = [P × r × (1+r)<sup>n</sup>] / [(1+r)<sup>n</sup> − 1] — where P =
Principal, r = Monthly Interest Rate, n = Tenure in months. On P = ₹{{
a.requestedAmount | number: '1.0-0'
}}, r = {{ a.interestRate / 12 / 100 | number: '1.4-4' }}, n = {{ a.tenureMonths }} → EMI = ₹{{
emiAmountFor(a) | number: '1.0-0'
}}
/ month.
</p>
</div>
}
@if (a.coBorrowerRequired) {
<p class="pg-sub npa-report-note">Co-Borrower Required — {{ a.coBorrowerName }}</p>
}
@@ -303,6 +869,137 @@
<div class="pos-section-label">CIC / Credit Bureau Check — {{ a.cicBureau }}</div>
<div class="loan-action-card">
@if (a.cibilScore === 0) {
<div class="loi-cic-subsection">
<div class="loi-cic-subsection-title">Customer Consent — Credit Bureau Check</div>
<p class="pg-sub npa-report-note">
RBI's Fair Practices Code requires the customer's signed consent before a credit bureau pull. Generate
the declaration letter below, get it signed by the customer, then upload the signed copy.
</p>
<div class="loan-action-row">
@if (a.channel === 'BRANCH') {
<button type="button" class="pg-btn pg-btn--ghost" (click)="printCicConsentLetter(a)">
Print Declaration Letter
</button>
} @else {
<button type="button" class="pg-btn pg-btn--ghost" (click)="downloadCicConsentLetter(a)">
Download Declaration Letter
</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="shareCicConsentLetter(a)">
Share (Email / WhatsApp / Drive…)
</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="emailCicConsentLetter(a)">Email</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="whatsappCicConsentLetter(a)">
WhatsApp
</button>
}
<label class="pg-btn pg-btn--ghost">
{{ cicConsentCapture.file ? 'Signed Declaration ✓' : 'Upload Signed Declaration' }}
<input type="file" accept="image/*,application/pdf" hidden (change)="onCicConsentFile(a, $event)" />
</label>
</div>
@if (cicConsentCapture.file) {
<div class="loi-kyc-thumbs">
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewNewSingleDoc(cicConsentCapture.file)"
>
@if (cicConsentCapture.previewUrl) {
<img [src]="cicConsentCapture.previewUrl" alt="Signed consent declaration" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
</div>
}
@if (a.cicConsentSigned) {
<span class="loan-badge loan-badge--verified">Signed Declaration Uploaded</span>
} @else {
<span class="loan-badge loan-badge--missing">Awaiting Signed Declaration</span>
}
</div>
<div class="loi-cic-subsection">
<div class="loi-cic-subsection-title">CIC Check Charge — ₹{{ cicCheckCharge }}</div>
@if (a.clientId && realDebitableAccounts.length > 0) {
<!-- Existing Customer with a real account -- pick which one to debit. This
pipeline never posts an actual Fineract transaction, so the "debit" is
recorded as a label for audit only. -->
<p class="pg-sub npa-report-note">
Select which account to debit the ₹{{ cicCheckCharge }} charge from:
</p>
<div class="loi-kyc-doc-grid">
@for (acc of realDebitableAccounts; track acc.id) {
<button
type="button"
class="loi-kyc-card loi-charge-account"
[class.loi-charge-account--selected]="a.cicChargeAccountLabel.startsWith(acc.accountNo)"
(click)="selectCicChargeAccount(a, acc)"
>
<span class="loi-kyc-card-title">{{ acc.kind }}</span>
<span class="loi-kyc-masked">{{ acc.accountNo }} — {{ acc.productName }}</span>
<span class="loi-kyc-masked">Balance: ₹{{ acc.accountBalance | number: '1.0-0' }}</span>
</button>
}
</div>
} @else {
<!-- New Customer, or an Existing Customer with no open account on file --
collect the charge the way a real-world merchant would: a payment link the
customer pays externally, with the reference they hand back recorded as
proof. -->
@if (a.clientId && loadingShareAccounts) {
<p class="pg-sub npa-report-note">Loading accounts…</p>
} @else {
@if (a.clientId) {
<p class="pg-sub npa-report-note">
No open savings/current account on file for this customer — collect the charge via payment link
instead.
</p>
}
@if (!a.cicPaymentLinkUrl || a.cicPaymentLinkUrl === '—') {
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--ghost" (click)="generateCicPaymentLink(a)">
Generate Payment Link
</button>
</div>
} @else {
<p class="loi-kyc-masked">{{ a.cicPaymentLinkUrl }}</p>
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--ghost" (click)="copyCicPaymentLink(a)">
Copy Link
</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="emailCicPaymentLink(a)">
Email
</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="whatsappCicPaymentLink(a)">
WhatsApp
</button>
</div>
@if (!a.cicChargeCollected) {
<label class="inv-form-field">
<span>Payment Reference No. (from customer)</span>
<input type="text" [(ngModel)]="cicPaymentRefInput" placeholder="e.g. UTR / Txn ID" />
</label>
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--ghost" (click)="confirmCicPaymentReceived(a)">
Confirm Payment Received
</button>
</div>
}
}
}
}
@if (a.cicChargeCollected) {
<span class="loan-badge loan-badge--verified">
Charge Collected —
{{ a.cicChargeMode === 'ACCOUNT_DEBIT' ? a.cicChargeAccountLabel : 'Ref ' + a.cicPaymentReference }}
</span>
} @else {
<span class="loan-badge loan-badge--missing">Charge Not Collected</span>
}
</div>
<label class="inv-form-field">
<span>Bureau</span>
<select [(ngModel)]="a.cicBureau">
@@ -312,10 +1009,21 @@
</select>
</label>
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--primary" (click)="checkCreditScoreNow(a)">
<button
type="button"
class="pg-btn pg-btn--primary"
[disabled]="!a.cicConsentSigned || !a.cicChargeCollected"
(click)="checkCreditScoreNow(a)"
>
Check Credit Score
</button>
</div>
@if (!a.cicConsentSigned || !a.cicChargeCollected) {
<p class="pg-sub npa-report-note">
Print/share the declaration and get it signed, and collect the ₹{{ cicCheckCharge }} check charge,
before checking the credit score.
</p>
}
} @else {
<dl class="loan-kv-grid" style="padding: 0 0 14px">
<div class="loan-kv-field">
@@ -876,3 +1584,5 @@
</div>
}
</div>
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>