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:
@@ -11,6 +11,7 @@ import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { AuthenticationService } from '../../core/authentication/authentication.service';
|
||||
import { ClientsService } from '../../clients/clients.service';
|
||||
import { ExportSheetSpec, exportWorkbook } from '../../cashier/cashier-export';
|
||||
import {
|
||||
BRANCHES,
|
||||
@@ -134,12 +135,19 @@ export class LoanPipelineComponent implements OnInit {
|
||||
bcAgentName: '',
|
||||
customerType: 'NEW' as CustomerType,
|
||||
applicantName: '',
|
||||
clientId: null as number | null,
|
||||
branchCode: this.branches[0].code,
|
||||
productCode: this.products[0].code,
|
||||
requestedAmount: null as number | null,
|
||||
tenureMonths: null as number | null
|
||||
};
|
||||
|
||||
/** Existing-customer search (real Fineract clients, unlike the rest of this mock pipeline). */
|
||||
existingClientSearchText = '';
|
||||
existingClientSearchResults: any[] = [];
|
||||
searchingExistingClient = false;
|
||||
private existingClientSearchTimer: any;
|
||||
|
||||
toastMsg = '';
|
||||
toastVisible = false;
|
||||
private toastTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
@@ -147,6 +155,7 @@ export class LoanPipelineComponent implements OnInit {
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly router = inject(Router);
|
||||
private readonly auth = inject(AuthenticationService);
|
||||
private readonly clientsService = inject(ClientsService);
|
||||
|
||||
ngOnInit(): void {
|
||||
const requestedView = this.route.snapshot.queryParamMap.get('view') as ViewId | null;
|
||||
@@ -428,11 +437,59 @@ export class LoanPipelineComponent implements OnInit {
|
||||
return this.products.find((p) => p.code === code) || this.products[0];
|
||||
}
|
||||
|
||||
/** Switching Customer Type away from "Existing" drops any client picked under the old mode --
|
||||
* avoids storing a stale clientId against a now-"New Customer" application. */
|
||||
onCustomerTypeChange(): void {
|
||||
if (this.newApp.customerType === 'NEW') {
|
||||
this.clearExistingClient();
|
||||
}
|
||||
}
|
||||
|
||||
onExistingClientSearch(text: string): void {
|
||||
this.existingClientSearchText = text;
|
||||
clearTimeout(this.existingClientSearchTimer);
|
||||
if (!text || text.length < 2) {
|
||||
this.existingClientSearchResults = [];
|
||||
return;
|
||||
}
|
||||
this.searchingExistingClient = true;
|
||||
this.existingClientSearchTimer = setTimeout(() => {
|
||||
this.clientsService.searchByText(text, 0, 8, '', '', true, 'INDIVIDUAL').subscribe({
|
||||
next: (data: any) => {
|
||||
this.existingClientSearchResults = data?.content || [];
|
||||
this.searchingExistingClient = false;
|
||||
},
|
||||
error: () => {
|
||||
this.existingClientSearchResults = [];
|
||||
this.searchingExistingClient = false;
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
|
||||
selectExistingClient(client: any): void {
|
||||
this.newApp.clientId = client.id;
|
||||
this.newApp.applicantName = client.displayName;
|
||||
this.existingClientSearchResults = [];
|
||||
this.existingClientSearchText = '';
|
||||
}
|
||||
|
||||
clearExistingClient(): void {
|
||||
this.newApp.clientId = null;
|
||||
this.newApp.applicantName = '';
|
||||
this.existingClientSearchText = '';
|
||||
this.existingClientSearchResults = [];
|
||||
}
|
||||
|
||||
saveNewApplication(): void {
|
||||
if (!this.newApp.applicantName.trim() || !this.newApp.requestedAmount || this.newApp.requestedAmount <= 0) {
|
||||
this.toast('Enter the applicant name and a valid requested amount.');
|
||||
return;
|
||||
}
|
||||
if (this.newApp.customerType === 'EXISTING' && !this.newApp.clientId) {
|
||||
this.toast('Search and select the existing client for this application.');
|
||||
return;
|
||||
}
|
||||
if (this.newApp.channel === 'BC' && !this.newApp.bcAgentName.trim()) {
|
||||
this.toast('Enter the BC agent/point name for a Business Correspondent application.');
|
||||
return;
|
||||
@@ -451,6 +508,7 @@ export class LoanPipelineComponent implements OnInit {
|
||||
bcAgentName: this.newApp.bcAgentName || '—',
|
||||
customerType: this.newApp.customerType,
|
||||
applicantName: this.newApp.applicantName.trim(),
|
||||
clientId: this.newApp.clientId,
|
||||
branchCode: branch.code,
|
||||
branchName: branch.name,
|
||||
productCode: product.code,
|
||||
@@ -469,11 +527,14 @@ export class LoanPipelineComponent implements OnInit {
|
||||
bcAgentName: '',
|
||||
customerType: 'NEW',
|
||||
applicantName: '',
|
||||
clientId: null,
|
||||
branchCode: this.isHeadOffice ? this.branches[0].code : this.ownBranchCode || this.branches[0].code,
|
||||
productCode: this.products[0].code,
|
||||
requestedAmount: null,
|
||||
tenureMonths: null
|
||||
};
|
||||
this.existingClientSearchText = '';
|
||||
this.existingClientSearchResults = [];
|
||||
this.router.navigate([
|
||||
'/loan-origination/application',
|
||||
app.applicationNo
|
||||
|
||||
Reference in New Issue
Block a user