Files
cbsnexora/src/app/loan-origination/application-detail/application-detail.component.html
T
niteshandClaude Sonnet 5 c18219fce5
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
feat(loan-origination): persist the sanctioning pipeline to the backend
Replaces the in-memory mock (LOAN_APPLICATIONS array + seeded RNG, reset on
every refresh) with real Fineract-backed persistence: a new
LoanOriginationService for CRUD plus one API call per stage transition, a
loan-fineract-bridge that creates a real loan account (create + approve +
disburse) at disbursement for Existing Customers, and file uploads through
Fineract's generic document store keyed by a stable checklist slot so
"On file"/View controls survive a reload.

Also fixes several gaps found while verifying full-cycle persistence:
- CIC charge-account selection never saved (missing persist() call)
- Co-borrower relationship/monthly income lived in local-only component state
  instead of on the application, so they reset on refresh
- Field Investigation "View Full Details" showed local-session photos only;
  stored photos are now downloaded and shown as real thumbnails
- "Assign Field Officer" was free text; now a dropdown of real CBS loan
  officers pulled from /staff
- site photos and cheque/signature images now open in the same zoomable,
  pannable lightgallery viewer used elsewhere in the app

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 17:05:05 +05:30

2526 lines
120 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
Copyright since 2025 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<!-- (input)/(change) are bound once here rather than per field: both bubble, and Angular's own
ngModel listener sits on the input itself, so by the time this fires the model already holds
the new value. Any inline field on this screen — including ones added later — therefore saves
without needing its own handler wired up. -->
<div class="pg" (input)="onInlineEdit()" (change)="onInlineEdit()">
@if (loadingApplication) {
<p class="pg-sub">Loading application…</p>
}
@if (application; as a) {
<header class="pg-head">
<div>
<div class="pg-eyebrow">CREDIT · LOAN ORIGINATION · {{ a.branchName | uppercase }}</div>
<h1 class="pg-title">
{{ a.applicationNo }} — {{ a.applicantName }}
<span [class]="badgeClass(a)">{{ statusLabels[a.overallStatus] }}</span>
</h1>
<p class="pg-sub npa-report-note">
{{ a.productName }} · ₹{{ a.requestedAmount | number: '1.0-0' }} · Applied {{ a.applicationDate }} ·
{{ progress(a) }}% through the sanctioning workflow.
</p>
</div>
<div class="pg-toolbar">
@if (uploadingDocs > 0) {
<span class="loan-badge loan-badge--progress">
Uploading {{ uploadingDocs }} file{{ uploadingDocs > 1 ? 's' : '' }}…
</span>
} @else if (docUploadFailed) {
<span class="loan-badge loan-badge--rejected">File upload failed</span>
}
@if (saving) {
<span class="loan-badge loan-badge--progress">Saving…</span>
} @else if (saveFailed) {
<span class="loan-badge loan-badge--rejected">Not saved — check connection</span>
}
<button type="button" class="pg-btn pg-btn--ghost" (click)="goBack()">‹ Back to Pipeline</button>
</div>
</header>
<div class="pg-card">
<dl class="loan-kv-grid">
<div class="loan-kv-field">
<dt>Branch</dt>
<dd>{{ a.branchName }}</dd>
</div>
<div class="loan-kv-field">
<dt>Channel</dt>
<dd>
{{ a.channel === 'BC' ? a.bcAgentName : a.channel === 'SELF_APPLY' ? 'Self-Apply (Digital)' : 'Branch' }}
</dd>
</div>
<div class="loan-kv-field">
<dt>Customer Type</dt>
<dd>{{ a.customerType === 'EXISTING' ? 'Existing Customer' : 'New Customer' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Tenure / Rate</dt>
<dd>{{ a.tenureMonths }} months @ {{ a.interestRate }}%</dd>
</div>
<div class="loan-kv-field">
<dt>Monthly Income / Eligible Amount</dt>
<dd>₹{{ a.monthlyIncome | number: '1.0-0' }} / ₹{{ a.eligibleLoanAmount | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Sanctioning Authority</dt>
<dd>{{ sanctioningAuthorityOf(a) }}</dd>
</div>
<div class="loan-kv-field">
<dt>Share Member</dt>
<dd>
@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>
<button type="button" class="pg-btn pg-btn--ghost" (click)="allotMembership(a)">Allot Shares</button>
}
</dd>
</div>
@if (a.coBorrowerRequired) {
<div class="loan-kv-field">
<dt>Co-Borrower</dt>
<dd>
{{ a.coBorrowerName }}
@if (a.coBorrowerWanted && a.coBorrowerRelationship) {
({{ a.coBorrowerRelationship }})
}
</dd>
</div>
}
@if (a.guarantorRequired) {
<div class="loan-kv-field">
<dt>Guarantor</dt>
<dd>{{ a.guarantorName }}</dd>
</div>
}
</dl>
</div>
<div class="pg-card">
<div class="pos-section-label">Sanctioning Workflow Progress</div>
<div class="loan-stepper">
@for (s of stageOrder; track s) {
@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>
@if (a.overallStatus === 'REJECTED') {
<div class="pg-card">
<div class="pos-section-label">Application Rejected — at {{ stageLabels[a.currentStage] }}</div>
<p class="pg-sub npa-report-note">See the approval summary below for the recorded reason.</p>
</div>
} @else if (a.overallStatus === 'ON_HOLD') {
<div class="pg-card">
<div class="pos-section-label">Personal Discussion — {{ discrepancyLabels[a.discrepancyType] }}</div>
<div class="loan-action-card">
<p>{{ a.personalDiscussionNotes }}</p>
<label class="inv-form-field">
<span>Resolution Notes</span>
<input type="text" [(ngModel)]="actionRemarks" placeholder="How was this resolved?" />
</label>
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--success" (click)="resolveDiscrepancy(a)">
Resolve &amp; Proceed to Loan Committee
</button>
</div>
</div>
</div>
} @else if (a.overallStatus === 'DISBURSED') {
<div class="pg-card">
<div class="pos-section-label">Disbursed</div>
<dl class="loan-kv-grid">
<div class="loan-kv-field">
<dt>Disbursement Date</dt>
<dd>{{ a.disbursementDate }}</dd>
</div>
<div class="loan-kv-field">
<dt>Mode</dt>
<dd>{{ a.disbursementMode }}</dd>
</div>
<div class="loan-kv-field">
<dt>ACH Confirmation</dt>
<dd>{{ a.achConfirmationRef }}</dd>
</div>
<div class="loan-kv-field">
<dt>EMI Activation Confirmation</dt>
<dd>{{ a.emiActivationConfirmed ? 'Confirmed on ' + a.emiActivationDate : 'Pending' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Core Banking Loan Account</dt>
<dd>
@if (a.fineractLoanAccountNo) {
<span class="loan-badge loan-badge--verified">A/C {{ a.fineractLoanAccountNo }}</span>
} @else {
<span class="pg-sub">Mock disbursement only — no core banking account</span>
}
</dd>
</div>
</dl>
</div>
<div class="pg-card">
<div class="pos-section-label">EMI Repayment Schedule</div>
@if (isRevolving(a)) {
<p class="pg-sub npa-report-note">
{{ a.productName }} is a revolving/renewable limit — repaid on demand and renewed annually, not on a fixed
EMI schedule.
</p>
} @else {
<div class="inv-kpis npa-kpis--ratio">
<div class="inv-kpi">
<span class="inv-kpi__label">Loan Amount</span>
<span class="inv-kpi__value">₹{{ a.requestedAmount | number: '1.0-0' }}</span>
<span class="inv-kpi__sub">{{ a.tenureMonths }} months @ {{ a.interestRate }}%</span>
</div>
<div class="inv-kpi">
<span class="inv-kpi__label">Monthly EMI</span>
<span class="inv-kpi__value">₹{{ emiAmountFor(a) | number: '1.0-0' }}</span>
<span class="inv-kpi__sub">Reducing balance</span>
</div>
<div class="inv-kpi">
<span class="inv-kpi__label">Total Interest Payable</span>
<span class="inv-kpi__value">₹{{ totalInterestFor(a) | number: '1.0-0' }}</span>
<span class="inv-kpi__sub">over the full tenure</span>
</div>
<div class="inv-kpi">
<span class="inv-kpi__label">Total Amount Payable</span>
<span class="inv-kpi__value">₹{{ totalPayableFor(a) | number: '1.0-0' }}</span>
<span class="inv-kpi__sub">principal + interest</span>
</div>
</div>
<div class="pg-table-scroll loan-emi-table-scroll">
<table class="pg-table">
<thead>
<tr>
<th class="num">Inst. No</th>
<th>Due Date</th>
<th class="num">Opening Balance</th>
<th class="num">EMI</th>
<th class="num">Principal</th>
<th class="num col-out">Interest</th>
<th class="num">Closing Balance</th>
</tr>
</thead>
<tbody>
@for (row of emiScheduleFor(a); track row.installmentNo) {
<tr>
<td class="num">{{ row.installmentNo }}</td>
<td>{{ row.dueDate }}</td>
<td class="num">₹{{ row.openingBalance | number: '1.0-0' }}</td>
<td class="num">₹{{ row.emiAmount | number: '1.0-0' }}</td>
<td class="num col-in">₹{{ row.principalComponent | number: '1.0-0' }}</td>
<td class="num col-out">₹{{ row.interestComponent | number: '1.0-0' }}</td>
<td class="num">₹{{ row.closingBalance | number: '1.0-0' }}</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
} @else {
<!-- ===================== APPLICATION ===================== -->
@if (a.currentStage === 'APPLICATION') {
<div class="pg-card">
<div class="pos-section-label">Application Received</div>
<div class="loan-action-card">
<p>
Received via
{{ a.channel === 'BC' ? a.bcAgentName : a.channel === 'SELF_APPLY' ? 'Self-Apply (Digital)' : 'Branch' }}
on {{ a.applicationDate }}.
</p>
<div class="loan-action-row">
<button
type="button"
class="pg-btn pg-btn--primary"
(click)="proceed(a, 'Application logged and forwarded for KYC.')"
>
Proceed to KYC &amp; Document Collection
</button>
</div>
</div>
</div>
}
<!-- ===================== KYC & DOCUMENTS ===================== -->
@if (a.currentStage === 'KYC_DOCUMENTS') {
<div class="pg-card">
<div class="pos-section-label">KYC &amp; Document Collection — OVD (RBI KYC Master Direction)</div>
<div class="loan-action-card">
@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>
@if (newKycCapture.incomeProof.employmentType === 'SALARIED') {
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
{{
newKycCapture.incomeProof.salaried.file
? 'Form 16 / 3 Month Salary Slip ✓'
: 'Upload Form 16 / 3 Month Salary Slip'
}}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewSalariedProofFile(a, $event)"
/>
</label>
@if (newKycCapture.incomeProof.salaried.file || hasStoredDoc('KYC:incomeProof:salaried')) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="
viewKycSlot(a, newKycCapture.incomeProof.salaried.file, 'KYC:incomeProof:salaried')
"
>
@if (newKycCapture.incomeProof.salaried.previewUrl) {
<img [src]="newKycCapture.incomeProof.salaried.previewUrl" alt="Salary proof" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
</div>
} @else {
<p class="pg-sub npa-report-note">Last 3 years' Income Tax Returns:</p>
@for (doc of newKycCapture.incomeProof.itr; track $index) {
<div class="loi-kyc-card-files">
@if (!doc.exception) {
<label class="pg-btn pg-btn--ghost">
{{
doc.file ? 'ITR — Year ' + ($index + 1) + ' ✓' : 'Upload ITR — Year ' + ($index + 1)
}}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewItrFile(a, $index, $event)"
/>
</label>
@if (doc.file || hasStoredDoc('KYC:incomeProof:itr:' + $index)) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewKycSlot(a, doc.file, 'KYC:incomeProof:itr:' + $index)"
>
@if (doc.previewUrl) {
<img [src]="doc.previewUrl" alt="ITR Year {{ $index + 1 }}" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
}
@if (!doc.file) {
<label class="loan-checklist-item">
<input
type="checkbox"
[(ngModel)]="doc.exception"
(ngModelChange)="onItrExceptionChange(a)"
/>
Not Available — Add Exception
</label>
}
</div>
@if (doc.exception) {
<label class="inv-form-field">
<span>Exception Remark — Year {{ $index + 1 }}</span>
<input
type="text"
[(ngModel)]="doc.exceptionRemark"
(ngModelChange)="onItrExceptionChange(a)"
placeholder="e.g. Business started in 2024, ITR not filed for this year"
/>
</label>
}
}
}
@if (a.incomeProofSubmitted) {
@if (itrHasException) {
<span class="loan-badge loan-badge--missing">Submitted — with exception(s)</span>
} @else {
<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,.csv,text/csv"
hidden
(change)="onNewBankStatementFile(a, $event)"
/>
</label>
@if (newKycCapture.bankStatement.file || hasStoredDoc('KYC:bankStatement')) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewKycSlot(a, newKycCapture.bankStatement.file, 'KYC:bankStatement')"
>
@if (newKycCapture.bankStatement.previewUrl) {
<img [src]="newKycCapture.bankStatement.previewUrl" alt="Bank statement" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
@if (canReopenBankStatementAnalysis()) {
<button
type="button"
class="loi-bsa-reopen"
title="Re-open the transaction analysis"
(click)="reopenBankStatementAnalysisFor(a)"
>
📊 Re-check Analysis
</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 || hasStoredDoc('KYC:electricityBill')) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewKycSlot(a, newKycCapture.electricityBill.file, 'KYC:electricityBill')"
>
@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>
</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>
@if (newKycCapture.incomeProof.employmentType === 'SALARIED') {
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
{{
newKycCapture.incomeProof.salaried.file
? 'Form 16 / 3 Month Salary Slip ✓'
: 'Upload Form 16 / 3 Month Salary Slip'
}}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewSalariedProofFile(a, $event)"
/>
</label>
@if (newKycCapture.incomeProof.salaried.file) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewNewSingleDoc(newKycCapture.incomeProof.salaried.file)"
>
@if (newKycCapture.incomeProof.salaried.previewUrl) {
<img [src]="newKycCapture.incomeProof.salaried.previewUrl" alt="Salary proof" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
</div>
} @else {
<p class="pg-sub npa-report-note">Last 3 years' Income Tax Returns:</p>
@for (doc of newKycCapture.incomeProof.itr; track $index) {
<div class="loi-kyc-card-files">
@if (!doc.exception) {
<label class="pg-btn pg-btn--ghost">
{{ doc.file ? 'ITR — Year ' + ($index + 1) + ' ✓' : 'Upload ITR — Year ' + ($index + 1) }}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onNewItrFile(a, $index, $event)"
/>
</label>
@if (doc.file) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewNewSingleDoc(doc.file)"
>
@if (doc.previewUrl) {
<img [src]="doc.previewUrl" alt="ITR Year {{ $index + 1 }}" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
}
@if (!doc.file) {
<label class="loan-checklist-item">
<input
type="checkbox"
[(ngModel)]="doc.exception"
(ngModelChange)="onItrExceptionChange(a)"
/>
Not Available — Add Exception
</label>
}
</div>
@if (doc.exception) {
<label class="inv-form-field">
<span>Exception Remark — Year {{ $index + 1 }}</span>
<input
type="text"
[(ngModel)]="doc.exceptionRemark"
(ngModelChange)="onItrExceptionChange(a)"
placeholder="e.g. Business started in 2024, ITR not filed for this year"
/>
</label>
}
}
}
@if (a.incomeProofSubmitted) {
@if (itrHasException) {
<span class="loan-badge loan-badge--missing">Submitted — with exception(s)</span>
} @else {
<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,.csv,text/csv"
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>
@if (isCsvFile(newKycCapture.bankStatement.file)) {
<button
type="button"
class="loi-bsa-reopen"
title="Re-open the transaction analysis"
(click)="reopenBankStatementAnalysis(newKycCapture.bankStatement.file)"
>
📊 Re-check Analysis
</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>
</div>
}
@if (a.productDocuments.length > 0) {
<div class="loi-cic-subsection">
<div class="loi-cic-subsection-title">Loan-Specific Documents — {{ a.productName }}</div>
<div class="loi-kyc-doc-grid">
@for (doc of a.productDocuments; track doc.key) {
@if (doc.key === 'coOwnerKyc') {
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">{{ doc.label }}</span>
@if (doc.hint) {
<span class="loi-kyc-masked">{{ doc.hint }}</span>
}
<label class="loan-checklist-item">
<input
type="checkbox"
[(ngModel)]="coOwnerCapture.hasCoOwner"
(ngModelChange)="onCoOwnerToggle(a)"
/>
This property has a co-owner
</label>
@if (coOwnerCapture.hasCoOwner) {
<div class="loi-kyc-card-files">
<button
type="button"
class="pg-btn pg-btn--ghost"
(click)="openCoOwnerVerify(a, 'aadhaar')"
>
{{ coOwnerCapture.aadhaar.verified ? 'Aadhaar ✓ Verified' : 'Verify Co-Owner Aadhaar' }}
</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="openCoOwnerVerify(a, 'pan')">
{{ coOwnerCapture.pan.verified ? 'PAN ✓ Verified' : 'Verify Co-Owner PAN' }}
</button>
</div>
}
@if (doc.submitted) {
<span class="loan-badge loan-badge--verified">
{{ coOwnerCapture.hasCoOwner ? 'Verified' : 'Not Applicable' }}
</span>
} @else {
<span class="loan-badge loan-badge--missing">Missing</span>
}
</div>
} @else {
<div class="loi-kyc-card">
<span class="loi-kyc-card-title">
{{ doc.label }}
@if (doc.custom) {
<button
type="button"
class="loi-doc-remove"
title="Remove this document"
(click)="removeCustomProductDoc(a, doc)"
>
✕
</button>
}
</span>
@if (doc.hint) {
<span class="loi-kyc-masked">{{ doc.hint }}</span>
}
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
{{ productDocCapture[doc.key]?.file ? 'Uploaded ✓' : 'Upload' }}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onProductDocFile(doc, $event)"
/>
</label>
@if (productDocCapture[doc.key]; as capture) {
@if (capture.file || hasStoredDoc('PRODUCT_DOC:' + doc.key)) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewKycSlot(a, capture.file, 'PRODUCT_DOC:' + doc.key)"
>
@if (capture.previewUrl) {
<img [src]="capture.previewUrl" alt="{{ doc.label }}" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
}
</div>
@if (doc.submitted) {
<span class="loan-badge loan-badge--verified">Submitted</span>
} @else {
<span class="loan-badge loan-badge--missing">Missing</span>
}
</div>
}
}
</div>
<div class="loan-action-row">
<input
type="text"
[(ngModel)]="newCustomDocLabel"
placeholder="Add More Doc"
class="loi-custom-doc-input"
/>
<button type="button" class="pg-btn pg-btn--ghost" (click)="addCustomProductDoc(a)">
+ Add Custom Document
</button>
</div>
</div>
}
<label class="inv-form-field">
<span>Monthly Income (₹)</span>
<input type="number" [(ngModel)]="monthlyIncomeInput" placeholder="e.g. 45000" />
</label>
@if (!isRevolving(a)) {
<label class="inv-form-field">
<span>Requested Loan Amount (₹)</span>
<input type="number" [(ngModel)]="requestedAmountInput" />
</label>
@if (amountOutOfRange(a)) {
<p class="loi-kyc-error">
{{ a.productName }} allows ₹{{ productLimitsFor(a)?.minAmt | number: '1.0-0' }} – ₹{{
productLimitsFor(a)?.maxAmt | number: '1.0-0'
}}
— this amount won't be saved until it's in range.
</p>
}
<label class="inv-form-field">
<span>Tenure (Months)</span>
<input type="number" [(ngModel)]="tenureMonthsInput" />
</label>
@if (tenureOutOfRange(a)) {
<p class="loi-kyc-error">
{{ a.productName }} allows {{ productLimitsFor(a)?.minTenure }}–{{ productLimitsFor(a)?.maxTenure }}
months — this tenure won't be saved until it's in range.
</p>
}
}
<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>
} @else {
<p class="pg-sub npa-report-note">
Estimated EMI: ₹{{ emiPreview(a) | number: '1.0-0' }} / month ({{
emiToIncomePctPreview(a) | number: '1.0-1'
}}% of monthly income)
</p>
@if (emiToIncomePctPreview(a) > 40) {
@if (a.coBorrowerRequired && coBorrowerDetailsComplete(a) && combinedEligibilityOk(a)) {
<p class="pg-sub npa-report-note">
EMI exceeds 40% of the applicant's own income, but the Co-Borrower added below already covers it —
see the Co-Borrower section.
</p>
} @else {
<p class="loi-kyc-error">
EMI exceeds 40% of monthly income — add a Co-Borrower, reduce the loan amount, or increase the
tenure before proceeding.
</p>
}
}
}
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--ghost" (click)="setIncomeNow(a)">
{{ a.monthlyIncome > 0 ? 'Update Income & Terms' : 'Assess Income & Calculate Eligible Amount' }}
</button>
</div>
@if (a.monthlyIncome > 0) {
<dl class="loan-kv-grid" style="padding: 0 0 14px">
<div class="loan-kv-field">
<dt>Monthly Income (Last Assessed)</dt>
<dd>₹{{ a.monthlyIncome | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<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) {
<div class="loi-cic-subsection">
<div class="loi-cic-subsection-title">Co-Borrower (raises household eligibility)</div>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.coBorrowerWanted" />
Add a Co-Borrower to increase eligibility
</label>
@if (a.coBorrowerWanted) {
<label class="inv-form-field">
<span>Co-Borrower Name</span>
<input type="text" [(ngModel)]="a.coBorrowerName" placeholder="Full name" />
</label>
<label class="inv-form-field">
<span>Relationship with Applicant</span>
<select [(ngModel)]="a.coBorrowerRelationship">
<option value="">Select</option>
<option value="Father">Father</option>
<option value="Mother">Mother</option>
<option value="Wife">Wife</option>
<option value="Husband">Husband</option>
<option value="Son">Son</option>
<option value="Daughter">Daughter</option>
<option value="Other">Other</option>
</select>
</label>
<label class="inv-form-field">
<span>Co-Borrower Monthly Income (₹)</span>
<input type="number" [(ngModel)]="a.coBorrowerMonthlyIncome" placeholder="e.g. 30000" />
</label>
<p class="pg-sub npa-report-note">Upload the co-borrower's last 3 months' salary slips:</p>
<div class="loi-kyc-card-files">
@for (doc of coBorrowerCapture.salarySlips; track $index) {
<label class="pg-btn pg-btn--ghost">
{{
doc.file
? 'Salary Slip — Month ' + ($index + 1) + ' ✓'
: 'Upload Salary Slip — Month ' + ($index + 1)
}}
<input
type="file"
accept="image/*,application/pdf"
hidden
(change)="onCoBorrowerSalarySlipFile($index, $event)"
/>
</label>
@if (doc.file || hasStoredDoc('COBORROWER:salarySlip:' + $index)) {
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewKycSlot(a, doc.file, 'COBORROWER:salarySlip:' + $index)"
>
@if (doc.previewUrl) {
<img [src]="doc.previewUrl" alt="Salary Slip Month {{ $index + 1 }}" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
}
}
</div>
@if (coBorrowerDetailsComplete(a)) {
<div class="loi-emi-breakdown">
<div class="loi-emi-breakdown-title">Combined Household Eligibility Calculation</div>
<dl class="loan-kv-grid">
<div class="loan-kv-field">
<dt>Applicant's Monthly Income</dt>
<dd>₹{{ a.monthlyIncome | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Applicant's EMI Capacity (40%)</dt>
<dd>₹{{ applicantEmiCapacity(a) | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Co-Borrower's Monthly Income</dt>
<dd>₹{{ a.coBorrowerMonthlyIncome | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Co-Borrower's EMI Capacity (40%)</dt>
<dd>₹{{ coBorrowerEmiCapacity(a) | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Combined Household EMI Capacity</dt>
<dd>₹{{ combinedEmiCapacity(a) | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Estimated EMI (this loan)</dt>
<dd>₹{{ emiAmountFor(a) | number: '1.0-0' }}</dd>
</div>
</dl>
<p class="pg-sub npa-report-note loi-emi-formula">
Combined Capacity = (40% × ₹{{ a.monthlyIncome | number: '1.0-0' }} applicant) + (40% × ₹{{
a.coBorrowerMonthlyIncome | number: '1.0-0'
}}
co-borrower) = ₹{{ applicantEmiCapacity(a) | number: '1.0-0' }} + ₹{{
coBorrowerEmiCapacity(a) | number: '1.0-0'
}}
= ₹{{ combinedEmiCapacity(a) | number: '1.0-0' }}. Estimated EMI ₹{{
emiAmountFor(a) | number: '1.0-0'
}}
is {{ combinedEligibilityOk(a) ? 'within' : 'above' }} this combined capacity.
</p>
@if (combinedEligibilityOk(a)) {
<span class="loan-badge loan-badge--verified">Eligible with Co-Borrower</span>
} @else {
<span class="loan-badge loan-badge--missing"
>Still not eligible — increase co-borrower income, reduce the amount, or extend tenure</span
>
}
</div>
}
}
@if (!affordabilityOk(a)) {
<p class="loi-kyc-error">
@if (!coBorrowerDetailsComplete(a)) {
@if (!a.coBorrowerWanted) {
Check "Add a Co-Borrower" and complete their details to proceed.
} @else {
Add the co-borrower's name, relationship, monthly income and all 3 salary slips to proceed.
}
} @else {
Combined household income still doesn't cover the EMI at 40% — adjust the amount, tenure, or
co-borrower before proceeding.
}
</p>
}
</div>
}
<div class="loan-action-row">
<button
type="button"
class="pg-btn pg-btn--primary"
[disabled]="!allKycDone(a)"
(click)="completeKycStage(a)"
>
Proceed to CIC / Credit Bureau Check
</button>
</div>
</div>
</div>
}
<!-- ===================== CIC / CREDIT BUREAU CHECK ===================== -->
@if (a.currentStage === 'CIC_CHECK') {
<div class="pg-card">
<div class="pos-section-label">CIC / Credit Bureau Check — {{ a.cicBureau }}</div>
<div class="loan-action-card">
<!-- Consent and charge stay visible for the whole CIC stage, not just until the score
is checked -- same layout either way, so what was collected (and by whom) is never
a click away from disappearing once the score card below takes over. -->
<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 || hasStoredDoc('CIC:consent')) {
<div class="loi-kyc-thumbs">
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewKycSlot(a, cicConsentCapture.file, 'CIC:consent')"
>
@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>
@if (a.cibilScore === 0) {
<label class="inv-form-field">
<span>Bureau</span>
<select [(ngModel)]="a.cicBureau">
@for (bureau of cicBureaus; track bureau) {
<option [value]="bureau">{{ bureau }}</option>
}
</select>
</label>
<div class="loan-action-row">
<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 {
<div class="loi-cic-hero-card">
<div
class="loi-cic-score-badge"
[class.loi-cic-score-badge--excellent]="a.cibilScore >= 750"
[class.loi-cic-score-badge--good]="a.cibilScore >= 650 && a.cibilScore < 750"
[class.loi-cic-score-badge--fair]="a.cibilScore >= 550 && a.cibilScore < 650"
[class.loi-cic-score-badge--poor]="a.cibilScore < 550"
>
<span class="loi-cic-score-num">{{ a.cibilScore }}</span>
<span class="loi-cic-score-band">{{ cibilBandOf(a.cibilScore) }}</span>
</div>
<div class="loi-cic-details-grid">
<div class="loi-cic-detail-item">
<span class="loi-cic-label">Credit Bureau</span>
<strong class="loi-cic-val">{{ a.cicBureau }}</strong>
</div>
<div class="loi-cic-detail-item">
<span class="loi-cic-label">Uploaded Report File</span>
<strong class="loi-cic-val loi-cic-file-name">{{
a.cicReportUploaded ? a.cicReportFileName : 'Not uploaded yet'
}}</strong>
</div>
<div class="loi-cic-detail-item">
<span class="loi-cic-label">Field Inquiry Status</span>
<strong class="loi-cic-val">
{{
a.fiRequestPushed
? 'Pushed to ' + a.fiAgencyAssigned + ' (' + a.fiRequestDate + ')'
: 'Not pushed yet'
}}
</strong>
</div>
</div>
@if (a.cicReportUploaded) {
<div class="loi-cic-action-box">
<button
type="button"
class="pg-btn pg-btn--primary loi-cic-analysis-btn"
(click)="openCicReportAnalysis(a)"
>
📊 View Analysis
</button>
@if (hasStoredDoc('CIC:report')) {
<button type="button" class="pg-btn pg-btn--ghost" (click)="viewKycSlot(a, null, 'CIC:report')">
View file
</button>
}
</div>
}
</div>
}
@if (a.cibilScore > 0) {
<div class="loan-action-row loi-cic-action-row">
<label
class="pg-btn pg-btn--ghost loi-cic-upload-btn"
title="Upload or change PDF Credit Bureau Report"
>
<span>{{
a.cicReportUploaded ? '📄 Re-upload PDF Report' : 'Upload ' + a.cicBureau + ' Report (PDF)'
}}</span>
<input type="file" hidden accept=".pdf,.jpg,.jpeg,.png" (change)="onCicFileSelected($event, a)" />
</label>
@if (a.cicReportUploaded && !a.fiRequestPushed) {
<button type="button" class="pg-btn pg-btn--ghost" (click)="pushFieldInquiryNow(a)">
Push Field Inquiry to Agency
</button>
} @else if (a.cicReportUploaded && a.fiRequestPushed) {
<button type="button" class="pg-btn pg-btn--primary" (click)="completeCicStage(a)">
Proceed to Field Investigation
</button>
}
</div>
}
</div>
</div>
}
<!-- ===================== FIELD INVESTIGATION ===================== -->
@if (a.currentStage === 'FIELD_INVESTIGATION') {
<div class="pg-card">
<div class="pos-section-label">Field Investigation (FI) — Agent Report</div>
<div class="loan-action-card">
@if (a.clientId) {
<div class="loi-cic-subsection">
<div class="loi-cic-subsection-title">Registered Addresses on File (Fineract)</div>
@if (loadingAddresses) {
<p class="pg-sub npa-report-note">Loading addresses…</p>
} @else if (realAddresses.length === 0) {
<p class="pg-sub npa-report-note">No addresses on file for this client.</p>
} @else {
<div class="loi-fi-grid">
@for (addr of realAddresses; track $index) {
<div class="loi-fi-card">
<div class="loi-fi-card-title">{{ addr.addressType || 'Address' }}</div>
<p class="pg-sub npa-report-note">{{ formatRealAddress(addr) }}</p>
</div>
}
</div>
}
</div>
}
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.hasShop" />
Applicant has a shop / business premises
</label>
<div class="loi-fi-grid">
@for (key of fiRows(a); track key) {
<div class="loi-fi-card">
<div class="loi-fi-card-title">{{ fiCheckLabels[key] }}</div>
@if (a[key].officerAssigned === '—') {
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Assign Field Officer</span>
<select [(ngModel)]="fiOfficerNameInput[key]">
<option value="">Select an officer</option>
@for (staffMember of fiStaffOptions; track staffMember.id) {
<option [value]="staffMember.displayName">{{ staffMember.displayName }}</option>
}
</select>
</label>
</div>
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--ghost" (click)="assignFieldOfficerNow(a, key)">
Assign Officer
</button>
</div>
} @else {
<p class="pg-sub npa-report-note">
Assigned to <strong>{{ a[key].officerAssigned }}</strong> on {{ a[key].assignedDate }}
</p>
@if (a[key].status === 'PENDING') {
@if (fiIsOverdue(a, key)) {
<p class="loi-kyc-error">⚠ {{ fiSlaLabel(a, key) }} — this visit is overdue.</p>
} @else {
<p class="pg-sub npa-report-note">{{ fiSlaLabel(a, key) }}</p>
}
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Latitude</span>
<input type="text" [(ngModel)]="a[key].latitude" placeholder="e.g. 23.259933" />
</label>
<label class="inv-form-field">
<span>Longitude</span>
<input type="text" [(ngModel)]="a[key].longitude" placeholder="e.g. 77.412615" />
</label>
</div>
<p class="pg-sub npa-report-note">Site Photos ({{ fiPhotoCapture[key].length }}):</p>
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost">
Add Photo(s)
<input
type="file"
accept="image/*"
multiple
hidden
(change)="onFiPhotoFile(a, key, $event)"
/>
</label>
@for (photo of fiPhotoCapture[key]; track $index) {
<button
type="button"
class="loi-kyc-thumb"
title="Remove photo"
(click)="removeFiPhoto(a, key, $index)"
>
@if (photo.previewUrl) {
<img [src]="photo.previewUrl" alt="Site photo {{ $index + 1 }}" />
} @else {
<span class="loi-kyc-thumb-label">File</span>
}
</button>
}
</div>
<div class="loan-action-row">
<button
type="button"
class="pg-btn pg-btn--primary"
[disabled]="!fiCanVerify(a, key)"
(click)="verifyFieldVisit(a, key)"
>
Mark Verified — Address Correct
</button>
</div>
@if (!fiCanVerify(a, key)) {
<p class="loi-kyc-error">
Capture latitude, longitude and at least one site photo before marking Verified.
</p>
}
<label class="inv-form-field">
<span>Reject — Remark (if address is incorrect)</span>
<input
type="text"
[(ngModel)]="fiRejectRemarkInput[key]"
placeholder="e.g. House locked, neighbours confirm a different family lives there"
/>
</label>
<div class="loan-action-row">
<button
type="button"
class="pg-btn pg-btn--ghost"
[disabled]="!fiRejectRemarkInput[key].trim()"
(click)="rejectFieldVisit(a, key)"
>
Reject with Remark
</button>
</div>
} @else if (a[key].status === 'VERIFIED') {
<span class="loan-badge loan-badge--verified">Verified</span>
<button type="button" class="loi-fi-view-details" (click)="openFiVisitDetails(a, key)">
📋 View Full Details
</button>
} @else {
<span class="loan-badge loan-badge--missing">Failed</span>
<button type="button" class="loi-fi-view-details" (click)="openFiVisitDetails(a, key)">
📋 View Full Details
</button>
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Re-Assign a New Field Officer</span>
<select [(ngModel)]="fiOfficerNameInput[key]">
<option value="">Select an officer</option>
@for (staffMember of fiStaffOptions; track staffMember.id) {
<option [value]="staffMember.displayName">{{ staffMember.displayName }}</option>
}
</select>
</label>
</div>
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--ghost" (click)="assignFieldOfficerNow(a, key)">
Re-Assign Officer
</button>
</div>
}
}
</div>
}
</div>
@if (a.secured) {
<div class="loi-emi-breakdown">
<div class="loi-emi-breakdown-title">Property Valuation</div>
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Property Type</span>
<select [(ngModel)]="a.propertyType">
@for (t of propertyTypes; track t) {
<option [value]="t">{{ t }}</option>
}
</select>
</label>
<label class="inv-form-field">
<span>Built-Up Area (sq ft)</span>
<input type="number" [(ngModel)]="a.builtUpAreaSqFt" (ngModelChange)="updatePropertyValuation(a)" />
</label>
<label class="inv-form-field">
<span>Market Rate (₹ / sq ft)</span>
<input
type="number"
[(ngModel)]="a.marketRatePerSqFt"
(ngModelChange)="updatePropertyValuation(a)"
/>
</label>
<label class="inv-form-field">
<span>Guideline / Circle Rate (₹ / sq ft)</span>
<input type="number" [(ngModel)]="a.guidelineRatePerSqFt" />
</label>
<label class="inv-form-field">
<span>Valuer Name</span>
<input type="text" [(ngModel)]="a.valuerName" placeholder="Empanelled valuer" />
</label>
<label class="inv-form-field">
<span>Valuation Date</span>
<input type="text" [(ngModel)]="a.valuationDate" placeholder="DD-MM-YYYY" />
</label>
</div>
<dl class="loan-kv-grid">
<div class="loan-kv-field">
<dt>Market Value (Area × Market Rate)</dt>
<dd>₹{{ a.propertyValuation | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Guideline Value (Area × Circle Rate)</dt>
<dd>₹{{ guidelineValue(a) | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Realizable Value (90% of Market)</dt>
<dd>₹{{ realizableValue(a) | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Distress Sale Value (75% of Market)</dt>
<dd>₹{{ distressValue(a) | number: '1.0-0' }}</dd>
</div>
<div class="loan-kv-field">
<dt>Loan-to-Value (vs Realizable)</dt>
<dd>{{ ltvPercent(a) | number: '1.0-1' }}%</dd>
</div>
</dl>
@if (ltvHigh(a)) {
<p class="loi-kyc-error">
LTV exceeds 80% of the realizable value — this loan amount is aggressive against the property's
worth; consider a lower amount or additional security.
</p>
}
</div>
}
<div class="loan-kv-grid" style="padding: 0 0 14px">
<div class="loan-kv-field">
<dt>Agent Report</dt>
<dd>{{ a.agentReportUploaded ? a.agentReportFileName : 'Not uploaded yet' }}</dd>
</div>
@if (a.agentReportUploaded) {
<div class="loan-kv-field">
<dt>Report Remark</dt>
<dd>{{ a.agentReportRemark }}</dd>
</div>
}
</div>
@if (a.agentReportUploaded && hasStoredDoc('FI:agentReport')) {
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--ghost" (click)="viewKycSlot(a, null, 'FI:agentReport')">
View file
</button>
</div>
}
@if (!a.agentReportUploaded) {
<label class="inv-form-field">
<span>Agent Report Reference / Remark</span>
<input
type="text"
[(ngModel)]="agentReportRemarkInput"
placeholder="e.g. All addresses visited and cross-verified — no discrepancies noted"
/>
</label>
<div class="loan-action-row">
<label class="pg-btn pg-btn--ghost" style="cursor: pointer">
Upload Agent Report
<input
type="file"
hidden
accept=".pdf,.jpg,.jpeg,.png"
(change)="onAgentReportFileSelected($event, a)"
/>
</label>
</div>
}
<div class="loan-action-row">
<button
type="button"
class="pg-btn pg-btn--primary"
[disabled]="!fiComplete(a) || !a.agentReportUploaded"
(click)="completeFi(a)"
>
Record FI &amp; Proceed to Personal Discussion
</button>
</div>
</div>
</div>
}
<!-- ===================== PERSONAL DISCUSSION ===================== -->
@if (a.currentStage === 'PERSONAL_DISCUSSION') {
<div class="pg-card">
<div class="pos-section-label">Personal Discussion with Manager</div>
<div class="loan-action-card">
<div class="loi-cic-subsection">
<div class="loi-cic-subsection-title">Briefing Report — For the Meeting</div>
<p class="pg-sub npa-report-note">
Everything captured so far — KYC, CIC score, Field Investigation, property valuation, agent report and
the full stage history — as one PDF for whoever takes this meeting.
</p>
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--ghost" (click)="printApplicationSummary(a)">
🖨️ Print Summary Report
</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="downloadApplicationSummary(a)">
⬇️ Download Summary Report
</button>
</div>
</div>
<div class="loi-cic-subsection">
<div class="loi-cic-subsection-title">Meeting Scheduler</div>
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Meeting Date</span>
<input type="text" [(ngModel)]="a.meetingDate" placeholder="DD-MM-YYYY" />
</label>
<label class="inv-form-field">
<span>Meeting Time</span>
<input type="text" [(ngModel)]="a.meetingTime" placeholder="e.g. 14:30" />
</label>
<label class="inv-form-field">
<span>Mode of Meeting</span>
<select [(ngModel)]="a.meetingMode">
<option value="">Select</option>
<option value="OFFLINE">Offline (In-Branch)</option>
<option value="ONLINE">Online (Video Call)</option>
<option value="CALL">Phone Call</option>
</select>
</label>
<label class="inv-form-field">
<span>Bank Officer Taking the Meeting</span>
<select [(ngModel)]="a.meetingWith">
<option value="">Select</option>
@for (m of meetingManagers; track m) {
<option [value]="m">{{ m }}</option>
}
</select>
</label>
</div>
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--ghost" (click)="scheduleMeetingNow(a)">
{{ a.meetingScheduled ? 'Update Schedule' : 'Schedule Meeting' }}
</button>
</div>
@if (a.meetingScheduled) {
<p class="pg-sub npa-report-note">
Scheduled with <strong>{{ a.meetingWith }}</strong> on {{ a.meetingDate }} at {{ a.meetingTime }} ({{
a.meetingMode === 'ONLINE' ? 'Online' : a.meetingMode === 'CALL' ? 'Phone Call' : 'Offline'
}})
</p>
}
</div>
@if (a.meetingScheduled) {
<label class="inv-form-field">
<span>Discrepancy (if any)</span>
<select [(ngModel)]="discrepancySelection">
<option value="NONE">No Discrepancy</option>
<option value="DOCUMENT_DISCREPANCY">Document Discrepancy</option>
<option value="NAME_MISMATCH">Name Mismatch</option>
<option value="OFFICE_VERIFICATION_FAILED">Office Verification Failed</option>
<option value="PROPERTY_DISCREPANCY">Property Discrepancy</option>
<option value="FI_REPORT_DISCREPANCY">FI Report Discrepancy</option>
<option value="FAKE_DECLARATION">Fake Declaration Suspected</option>
</select>
</label>
<label class="inv-form-field">
<span>Discussion Report</span>
<input
type="text"
[(ngModel)]="actionRemarks"
placeholder="What was discussed and decided on the call/meeting"
/>
</label>
<div class="loan-action-row">
@if (discrepancySelection === 'NONE') {
<button type="button" class="pg-btn pg-btn--success" (click)="clearPersonalDiscussion(a)">
Clear, Take Declaration &amp; Proceed to Loan Committee
</button>
} @else {
<button type="button" class="pg-btn pg-btn--warn" (click)="flagPersonalDiscussion(a)">
Flag Discrepancy — Hold Application
</button>
}
</div>
} @else {
<p class="loi-kyc-error">Schedule the meeting above before recording the discussion report.</p>
}
</div>
</div>
}
<!-- ===================== LOAN COMMITTEE APPROVAL ===================== -->
@if (a.currentStage === 'CREDIT_COMMITTEE') {
<div class="pg-card">
<div class="pos-section-label">Loan Committee Approval — {{ sanctioningAuthorityOf(a) }}</div>
<div class="loan-action-card">
<div class="loi-cic-subsection">
<div class="loi-cic-subsection-title">Full Application Report — For the Committee</div>
<p class="pg-sub npa-report-note">
Everything captured on this application — KYC, CIC score, Field Investigation, property valuation, agent
report, meeting and stage history — as one PDF for every committee member to review before deciding.
</p>
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--ghost" (click)="printApplicationSummary(a)">
🖨️ Print Full Report
</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="downloadApplicationSummary(a)">
⬇️ Download Full Report
</button>
</div>
</div>
<p class="pg-sub npa-report-note">Reports Required — present below reports, then complete.</p>
<div class="loan-checklist">
<label class="loan-checklist-item">
<input type="checkbox" [ngModel]="a.cicReportUploaded" disabled /> CIBIL / Credit Bureau Report
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.backgroundCheckDone" /> Background Check
</label>
@if (a.secured) {
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.cersaiSearchDone" /> CSE — CERSAI Search Enquiry
</label>
}
<label class="loan-checklist-item">
<input type="checkbox" [ngModel]="a.aadhaarVerified && a.panVerified" disabled /> OVD
</label>
<label class="loan-checklist-item">
<input type="checkbox" [ngModel]="a.incomeProofSubmitted" disabled /> Income Proof
</label>
<label class="loan-checklist-item">
<input type="checkbox" [ngModel]="fiComplete(a)" disabled /> FI Report
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.managerMeetingDone" /> Manager Meeting
</label>
<label class="loan-checklist-item">
<input type="checkbox" [ngModel]="a.isShareMember" disabled /> Share Member Approval
</label>
@if (a.secured) {
<label class="loan-checklist-item">
<input type="checkbox" [ngModel]="a.propertyValuation > 0" disabled /> Property Valuation
</label>
}
</div>
<label class="inv-form-field">
<span>Committee Remarks</span>
<input type="text" [(ngModel)]="actionRemarks" placeholder="Remarks" />
</label>
<div class="inv-form-grid">
<label class="inv-form-field">
<span>Resolution No.</span>
<input type="text" [(ngModel)]="a.committeeResolutionNo" placeholder="e.g. LC/2026/0042" />
</label>
<label class="inv-form-field">
<span>Resolution Date</span>
<input type="text" [(ngModel)]="a.committeeResolutionDate" placeholder="DD-MM-YYYY" />
</label>
</div>
@if (committeeReportsReady(a) && !committeeCanApprove(a)) {
<p class="loi-kyc-error">
Enter the Resolution No. and Resolution Date the committee passed this decision under before approving.
</p>
}
<div class="loan-action-row">
<button
type="button"
class="pg-btn pg-btn--success"
[disabled]="!committeeCanApprove(a)"
(click)="approveCommitteeStage(a)"
>
Approve
</button>
<button
type="button"
class="pg-btn pg-btn--danger"
(click)="rejectStage(a, 'creditCommitteeApproval', 'Sanctioning Authority')"
>
Reject
</button>
</div>
</div>
</div>
}
<!-- ===================== RISK APPROVAL ===================== -->
@if (a.currentStage === 'RISK_APPROVAL') {
<div class="pg-card">
<div class="pos-section-label">Risk Approval — Verification Checklist</div>
<div class="loan-action-card">
<div class="loan-checklist">
<label class="loan-checklist-item">
<input type="checkbox" [ngModel]="a.addressVerification.status === 'VERIFIED'" disabled /> ID / Address
Verified
</label>
<label class="loan-checklist-item">
<input type="checkbox" [ngModel]="a.incomeProofSubmitted" disabled /> Income Proof Verified
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.previousLoanDeclarationDone" /> Previous Loan Declaration
</label>
@if (a.secured) {
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.propertySearchReportDone" /> Property Search Report
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.valuationReportConfirmed" /> Valuation Report
</label>
}
<label class="loan-checklist-item">
<input type="checkbox" [ngModel]="fiComplete(a)" disabled /> FI Report
</label>
<label class="loan-checklist-item">
<input type="checkbox" [ngModel]="a.backgroundCheckDone" disabled /> Personal Background Check
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.guarantorRequired" /> Guarantor Required
</label>
</div>
@if (a.guarantorRequired) {
<div class="loi-cic-subsection">
<div class="loi-cic-subsection-title">Guarantor — Must Be an Existing Share Member</div>
@if (!a.guarantorClientId) {
<label class="inv-form-field">
<span>Search Existing Client</span>
<input
type="text"
[(ngModel)]="guarantorSearchText"
(ngModelChange)="onGuarantorSearch($event)"
placeholder="Name, account no., mobile..."
/>
</label>
@if (searchingGuarantor) {
<p class="loi-search-hint">Searching…</p>
} @else if (guarantorSearchText.length >= 2 && guarantorSearchResults.length === 0) {
<p class="loi-search-hint">No clients found.</p>
}
@if (guarantorSearchResults.length > 0) {
<div class="loi-search-results">
@for (client of guarantorSearchResults; track client.id) {
<button type="button" class="loi-search-row" (click)="selectGuarantor(a, client)">
<span class="loi-search-name">{{ client.displayName }}</span>
<span class="loi-search-meta">{{ client.accountNo ?? client.officeName ?? '' }}</span>
</button>
}
</div>
}
<p class="loi-kyc-error">
The guarantor must already be a bank client with an active share account — search and select them
above, a name alone isn't enough.
</p>
} @else {
<label class="inv-form-field">
<span>Guarantor</span>
<div class="loi-selected-client">
<span>{{ a.guarantorName }}</span>
<button type="button" class="pg-btn" (click)="clearGuarantor(a)">Change</button>
</div>
</label>
@if (loadingGuarantorShareCheck) {
<p class="loi-search-hint">Checking share membership…</p>
} @else if (a.guarantorHasShareAccount) {
<span class="loan-badge loan-badge--verified">Share Member — Verified</span>
<div class="loi-share-account-list">
@for (sa of guarantorShareAccounts; 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--missing">No Share Account Found</span>
<p class="loi-kyc-error">
This client has no active share account — a guarantor must already be a bank member. Pick a
different guarantor.
</p>
}
}
</div>
}
<label class="inv-form-field">
<span>Risk Remarks</span>
<input
type="text"
[(ngModel)]="actionRemarks"
placeholder="ID/address, income adequacy, previous-loan declaration"
/>
</label>
<div class="loan-action-row">
<button
type="button"
class="pg-btn pg-btn--success"
[disabled]="!riskChecklistReady(a)"
(click)="approveStage(a, 'riskApproval', 'Risk Approval')"
>
Approve
</button>
<button
type="button"
class="pg-btn pg-btn--danger"
(click)="rejectStage(a, 'riskApproval', 'Risk Approval')"
>
Reject
</button>
</div>
</div>
</div>
}
<!-- ===================== LEGAL APPROVAL ===================== -->
@if (a.currentStage === 'LEGAL_APPROVAL') {
<div class="pg-card">
<div class="pos-section-label">Legal Approval — Fees &amp; A/c Opened By</div>
<div class="loan-action-card">
<div class="loan-checklist">
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.feesAndAccountOpened" /> Fees &amp; A/c Opening Done
</label>
@if (a.secured) {
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.mortgageCreated" /> Mortgage Created
</label>
}
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.legalStampDone" /> Legal Stamp Done
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.fileFilingDone" /> File Filing Done
</label>
</div>
@if (a.legalDocuments.length > 0) {
<div class="loi-cic-subsection">
<div class="loi-legal-doc-header">
<div class="loi-cic-subsection-title">Legal Documents</div>
<span class="loi-legal-doc-progress">
{{ legalDocsSubmittedCount(a) }} / {{ a.legalDocuments.length }} submitted
</span>
</div>
<div class="loi-legal-doc-grid">
@for (doc of a.legalDocuments; track doc.key) {
<div class="loi-legal-doc-card" [class.loi-legal-doc-card--submitted]="doc.submitted">
<div class="loi-legal-doc-title-row">
<span class="loi-legal-doc-icon">{{ legalDocIcon(doc.key) }}</span>
<span class="loi-legal-doc-title">{{ doc.label }}</span>
@if (doc.custom) {
<button
type="button"
class="loi-doc-remove"
title="Remove this document"
(click)="removeCustomLegalDoc(a, doc)"
>
✕
</button>
}
</div>
<div class="loi-kyc-card-files">
<label class="pg-btn pg-btn--ghost loi-legal-doc-upload-btn">
+ Add File(s)
<input
type="file"
accept="image/*,application/pdf"
multiple
hidden
(change)="onLegalDocFile(doc, $event)"
/>
</label>
@for (capture of legalDocCapture[doc.key]; track $index) {
<div class="loi-legal-doc-thumb-wrap">
<button
type="button"
class="loi-kyc-thumb"
title="View document"
(click)="viewNewSingleDoc(capture.file)"
>
@if (capture.previewUrl) {
<img [src]="capture.previewUrl" alt="{{ doc.label }} {{ $index + 1 }}" />
} @else {
<span class="loi-kyc-thumb-label">View file</span>
}
</button>
<button
type="button"
class="loi-legal-doc-thumb-remove"
title="Remove this file"
(click)="removeLegalDocFile(doc, $index)"
>
✕
</button>
</div>
}
<!-- Already in the document store from an earlier session. -->
@for (stored of storedDocsFor('LEGAL_DOC:' + doc.key); track stored.id) {
<div class="loi-legal-doc-thumb-wrap">
<button
type="button"
class="loi-kyc-thumb"
title="{{ stored.fileName }}"
(click)="viewStoredDoc(a, stored)"
>
<span class="loi-kyc-thumb-label">View file</span>
</button>
<button
type="button"
class="loi-legal-doc-thumb-remove"
title="Delete this stored file"
(click)="removeStoredDoc(a, stored)"
>
✕
</button>
</div>
}
</div>
@if (doc.submitted) {
<span class="loan-badge loan-badge--verified">
✓ Submitted
@if (legalDocCapture[doc.key]?.length) {
· {{ legalDocCapture[doc.key].length }} file{{
legalDocCapture[doc.key].length > 1 ? 's' : ''
}}
this session
}
</span>
} @else {
<span class="loan-badge loan-badge--missing">Missing</span>
}
</div>
}
</div>
<div class="loan-action-row">
<input
type="text"
[(ngModel)]="newLegalDocLabel"
placeholder="Add More Doc"
class="loi-custom-doc-input"
/>
<button type="button" class="pg-btn pg-btn--ghost" (click)="addCustomLegalDoc(a)">
+ Add Custom Document
</button>
</div>
</div>
}
<label class="inv-form-field">
<span>Agreement Signing Slot</span>
<input type="text" [(ngModel)]="a.agreementSigningSlot" placeholder="DD-MM-YYYY" />
</label>
<label class="inv-form-field">
<span>Legal Remarks</span>
<input type="text" [(ngModel)]="actionRemarks" placeholder="Remarks" />
</label>
<div class="loan-action-row">
<button
type="button"
class="pg-btn pg-btn--success"
[disabled]="!legalChecklistDone(a)"
(click)="approveStage(a, 'legalApproval', 'Legal Approval')"
>
Approve &amp; Allot Signing Slot
</button>
<button
type="button"
class="pg-btn pg-btn--danger"
(click)="rejectStage(a, 'legalApproval', 'Legal Approval')"
>
Reject
</button>
</div>
</div>
</div>
}
<!-- ===================== CUSTOMER SIGNING SLOT ===================== -->
@if (a.currentStage === 'CUSTOMER_SIGNING') {
<div class="pg-card">
<div class="pos-section-label">Customer Signing Slot — {{ a.agreementSigningSlot }}</div>
<div class="loan-action-card">
<p>Loan Agreement and Sanction Letter ready for the customer's acknowledgement and signature.</p>
<div class="loan-action-row">
<button type="button" class="pg-btn pg-btn--primary" (click)="markSigned(a)">
Mark Signed by Customer
</button>
</div>
</div>
</div>
}
<!-- ===================== CHEQUE RECEIVED / ACH SIGN ===================== -->
@if (a.currentStage === 'CHEQUE_ACH') {
<div class="pg-card">
<div class="pos-section-label">Cheque Received / ACH Sign</div>
<div class="loan-action-card">
<div class="loan-checklist">
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.chequeReceived" /> Cheque Received
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.achSigned" /> ACH Mandate Signed
</label>
</div>
<div class="loan-action-row">
<button
type="button"
class="pg-btn pg-btn--primary"
[disabled]="!a.chequeReceived || !a.achSigned"
(click)="confirmChequeAndProceed(a)"
>
Confirm &amp; Proceed to Post-Disbursement Check
</button>
</div>
</div>
</div>
}
<!-- ===================== POST-DISBURSEMENT CHECK ===================== -->
@if (a.currentStage === 'POST_CHECK') {
<div class="pg-card">
<div class="pos-section-label">Post-Disbursement Check — Final Compliance Gate</div>
<div class="loan-action-card">
<p class="pg-sub npa-report-note">Document Signing by:</p>
<div class="loan-checklist">
<label class="loan-checklist-item"> <input type="checkbox" [(ngModel)]="a.signedByAgent" /> Agent </label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.signedByManager" /> Manager
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.signedByCommittee" /> Committee
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.signedByRiskApproval" /> Risk Approval
</label>
<label class="loan-checklist-item">
<input type="checkbox" [(ngModel)]="a.signedByLegalApproval" /> Legal Approval
</label>
</div>
<label class="inv-form-field">
<span>Remarks</span>
<input type="text" [(ngModel)]="actionRemarks" placeholder="Remarks" />
</label>
<div class="loan-action-row">
<button
type="button"
class="pg-btn pg-btn--success"
[disabled]="!postCheckSignaturesDone(a)"
(click)="approveStage(a, 'postCheckApproval', 'Post-Disbursement Check')"
>
Clear for Disbursement
</button>
<button type="button" class="pg-btn pg-btn--warn" (click)="exceptionApprove(a)">
Approve on Exception
</button>
<button
type="button"
class="pg-btn pg-btn--danger"
(click)="rejectStage(a, 'postCheckApproval', 'Post-Disbursement Check')"
>
Reject
</button>
</div>
</div>
</div>
}
<!-- ===================== DISBURSEMENT ===================== -->
@if (a.currentStage === 'DISBURSED') {
<div class="pg-card">
<div class="pos-section-label">Ready for Disbursement</div>
<div class="loan-action-card">
<label class="inv-form-field">
<span>Disbursement Mode</span>
<select [(ngModel)]="disbursementMode">
<option value="Account Credit">Account Credit</option>
<option value="NEFT">NEFT</option>
<option value="RTGS">RTGS</option>
</select>
</label>
<p class="pg-sub npa-report-note">
Disbursing also confirms EMI Activation for this account in the same step.
@if (a.customerType === 'EXISTING' && a.clientId) {
A real Fineract loan account will be created, approved and disbursed in Core Banking at the same time.
} @else {
This is a mock disbursement only — no Core Banking account exists until this customer is fully onboarded
as a client.
}
</p>
@if (a.fineractLoanError) {
<p class="loi-kyc-error">{{ a.fineractLoanError }}</p>
}
<div class="loan-action-row">
<button
type="button"
class="pg-btn pg-btn--success"
[disabled]="creatingRealLoan"
(click)="disburseNow(a)"
>
@if (creatingRealLoan) {
Creating Real Loan Account in Core Banking…
} @else if (a.fineractLoanError) {
Retry Disburse &amp; Confirm EMI Activation
} @else {
Disburse Now &amp; Confirm EMI Activation
}
</button>
</div>
</div>
</div>
}
}
<div class="pg-card">
<div class="pos-section-label">Approval Summary</div>
<div class="loan-approval-summary">
<div
class="loan-approval-tile"
[class.loan-approval-tile--approved]="
a.creditCommitteeApproval.decision === 'APPROVED' ||
a.creditCommitteeApproval.decision === 'EXCEPTION_APPROVED'
"
[class.loan-approval-tile--rejected]="a.creditCommitteeApproval.decision === 'REJECTED'"
[class.loan-approval-tile--pending]="a.creditCommitteeApproval.decision === 'PENDING'"
>
<span class="loan-approval-tile__role">Loan Committee</span>
<span class="loan-approval-tile__decision">{{ a.creditCommitteeApproval.decision }}</span>
<span>{{ a.creditCommitteeApproval.approverName }} · {{ a.creditCommitteeApproval.date }}</span>
<span>{{ a.creditCommitteeApproval.remarks }}</span>
</div>
<div
class="loan-approval-tile"
[class.loan-approval-tile--approved]="
a.riskApproval.decision === 'APPROVED' || a.riskApproval.decision === 'EXCEPTION_APPROVED'
"
[class.loan-approval-tile--rejected]="a.riskApproval.decision === 'REJECTED'"
[class.loan-approval-tile--pending]="a.riskApproval.decision === 'PENDING'"
>
<span class="loan-approval-tile__role">Risk Approval</span>
<span class="loan-approval-tile__decision">{{ a.riskApproval.decision }}</span>
<span>{{ a.riskApproval.approverName }} · {{ a.riskApproval.date }}</span>
<span>{{ a.riskApproval.remarks }}</span>
</div>
<div
class="loan-approval-tile"
[class.loan-approval-tile--approved]="
a.legalApproval.decision === 'APPROVED' || a.legalApproval.decision === 'EXCEPTION_APPROVED'
"
[class.loan-approval-tile--rejected]="a.legalApproval.decision === 'REJECTED'"
[class.loan-approval-tile--pending]="a.legalApproval.decision === 'PENDING'"
>
<span class="loan-approval-tile__role">Legal Approval</span>
<span class="loan-approval-tile__decision">{{ a.legalApproval.decision }}</span>
<span>{{ a.legalApproval.approverName }} · {{ a.legalApproval.date }}</span>
<span>{{ a.legalApproval.remarks }}</span>
</div>
<div
class="loan-approval-tile"
[class.loan-approval-tile--approved]="
a.postCheckApproval.decision === 'APPROVED' || a.postCheckApproval.decision === 'EXCEPTION_APPROVED'
"
[class.loan-approval-tile--rejected]="a.postCheckApproval.decision === 'REJECTED'"
[class.loan-approval-tile--pending]="a.postCheckApproval.decision === 'PENDING'"
>
<span class="loan-approval-tile__role">Post-Disbursement Check</span>
<span class="loan-approval-tile__decision">{{ a.postCheckApproval.decision }}</span>
<span>{{ a.postCheckApproval.approverName }} · {{ a.postCheckApproval.date }}</span>
<span>{{ a.postCheckApproval.remarks }}</span>
</div>
</div>
</div>
<div class="pg-card">
<div class="pos-section-label">Application Journey — Date-wise Audit Trail</div>
<div class="npa-timeline" style="padding: 4px 18px 18px">
@for (h of a.stageHistory; track h.date + h.stage + h.outcome) {
<div [class]="historyToneClass(h.outcome)">
<span class="npa-timeline-item__icon">{{ stepMarker(a, h.stage) }}</span>
<div class="npa-timeline-item__body">
<div class="npa-timeline-item__row">
<span class="npa-timeline-item__type">{{ stageLabels[h.stage] }} — {{ h.outcome }}</span>
<span class="npa-timeline-item__date">{{ h.date }}</span>
</div>
<p class="npa-timeline-item__remarks">{{ h.remarks }}</p>
<span class="npa-timeline-item__channel">{{ h.actionBy }}</span>
</div>
</div>
} @empty {
<p class="pg-empty">No activity recorded yet.</p>
}
</div>
</div>
} @else {
<div class="pg-card">
<div class="pos-section-label">Application Not Found</div>
<div class="loan-action-card">
<button type="button" class="pg-btn pg-btn--ghost" (click)="goBack()">‹ Back to Pipeline</button>
</div>
</div>
}
</div>
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>