feat(loan-origination): RBI compliance checklist, real client integration, and CIC consent/charge flows
- 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:
@@ -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 & 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 & 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>
|
||||
|
||||
Reference in New Issue
Block a user