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:
+61
@@ -0,0 +1,61 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<div class="container lec-container">
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<h2 class="lec-title">RBI Loan Eligibility Checklist</h2>
|
||||
<p class="lec-subtitle">
|
||||
Run the bank's RBI-circular-based eligibility checks for any client, independently of the loan application
|
||||
itself.
|
||||
</p>
|
||||
|
||||
@if (!selectedClient) {
|
||||
<div class="lec-search">
|
||||
<mat-form-field class="lec-search-field">
|
||||
<mat-label>Search Client (name, account no., mobile...)</mat-label>
|
||||
<input matInput [(ngModel)]="searchText" (ngModelChange)="onSearch($event)" />
|
||||
</mat-form-field>
|
||||
|
||||
@if (searching) {
|
||||
<p class="lec-hint">Searching…</p>
|
||||
} @else if (searchText.length >= 2 && searchResults.length === 0) {
|
||||
<p class="lec-hint">No clients found.</p>
|
||||
}
|
||||
|
||||
@if (searchResults.length > 0) {
|
||||
<div class="lec-results">
|
||||
@for (client of searchResults; track client.id) {
|
||||
<button type="button" class="lec-result-row" (click)="selectClient(client)">
|
||||
<span class="lec-result-name">{{ client.displayName }}</span>
|
||||
<span class="lec-result-meta">{{ client.accountNo ?? client.officeName ?? '' }}</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="lec-selected-client">
|
||||
<div>
|
||||
<span class="lec-selected-label">Client</span>
|
||||
<span class="lec-selected-name">{{ selectedClient.displayName }}</span>
|
||||
</div>
|
||||
<button type="button" mat-button (click)="changeClient()">Change Client</button>
|
||||
</div>
|
||||
|
||||
<mat-form-field class="lec-amount-field">
|
||||
<mat-label>Proposed Loan Amount (₹)</mat-label>
|
||||
<input matInput type="number" min="0" [(ngModel)]="loanAmount" />
|
||||
</mat-form-field>
|
||||
|
||||
<mifosx-rbi-compliance-step [clientId]="selectedClient.id" [loanAmount]="loanAmount">
|
||||
</mifosx-rbi-compliance-step>
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
.lec-container {
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.lec-title {
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
.lec-subtitle {
|
||||
color: #64748b;
|
||||
font-size: 13.5px;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.lec-search {
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.lec-search-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lec-hint {
|
||||
color: #94a3b8;
|
||||
font-size: 12.5px;
|
||||
margin: -8px 0 0;
|
||||
}
|
||||
|
||||
.lec-results {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.lec-result-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
background: #fff;
|
||||
border: none;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
}
|
||||
|
||||
.lec-result-name {
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.lec-result-meta {
|
||||
color: #94a3b8;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
|
||||
.lec-selected-client {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.lec-selected-label {
|
||||
display: block;
|
||||
font-size: 11.5px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.4px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.lec-selected-name {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.lec-amount-field {
|
||||
max-width: 320px;
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { ClientsService } from 'app/clients/clients.service';
|
||||
import { RbiComplianceStepComponent } from '../rbi-compliance-step/rbi-compliance-step.component';
|
||||
|
||||
/**
|
||||
* Standalone RBI eligibility checklist -- a loan officer picks any client and runs the same
|
||||
* RBI-circular-based checks (age, area of operation, PSL classification, director/related-party,
|
||||
* wilful defaulter, exposure norms, LTV/margin, fair practices code) independently of the loan
|
||||
* account creation wizard, before or while a loan is being considered for that client.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-loan-eligibility-checklist',
|
||||
templateUrl: './loan-eligibility-checklist.component.html',
|
||||
styleUrls: ['./loan-eligibility-checklist.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FormsModule,
|
||||
RbiComplianceStepComponent
|
||||
]
|
||||
})
|
||||
export class LoanEligibilityChecklistComponent {
|
||||
private clientsService = inject(ClientsService);
|
||||
|
||||
searchText = '';
|
||||
searchResults: any[] = [];
|
||||
searching = false;
|
||||
private searchTimer: any;
|
||||
|
||||
selectedClient: { id: number; displayName: string } | null = null;
|
||||
loanAmount: number | null = null;
|
||||
|
||||
onSearch(text: string): void {
|
||||
this.searchText = text;
|
||||
clearTimeout(this.searchTimer);
|
||||
if (!text || text.length < 2) {
|
||||
this.searchResults = [];
|
||||
return;
|
||||
}
|
||||
this.searching = true;
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(text, 0, 8, '', '', true, 'INDIVIDUAL').subscribe({
|
||||
next: (data: any) => {
|
||||
this.searchResults = data?.content || [];
|
||||
this.searching = false;
|
||||
},
|
||||
error: () => {
|
||||
this.searchResults = [];
|
||||
this.searching = false;
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
|
||||
selectClient(client: any): void {
|
||||
this.selectedClient = { id: client.id, displayName: client.displayName };
|
||||
this.searchResults = [];
|
||||
this.searchText = '';
|
||||
this.loanAmount = null;
|
||||
}
|
||||
|
||||
changeClient(): void {
|
||||
this.selectedClient = null;
|
||||
this.loanAmount = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user