Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
392 lines
10 KiB
TypeScript
392 lines
10 KiB
TypeScript
/**
|
|
* 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, Input, OnInit, OnChanges, SimpleChanges, inject } from '@angular/core';
|
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
import { MatCheckbox } from '@angular/material/checkbox';
|
|
import { MatIcon } from '@angular/material/icon';
|
|
import { MatTooltip } from '@angular/material/tooltip';
|
|
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
|
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
|
import { ClientsService } from '../../clients.service';
|
|
import { SettingsService } from 'app/settings/settings.service';
|
|
import { Dates } from 'app/core/utils/dates';
|
|
|
|
export interface CategoryMeta {
|
|
id: string;
|
|
label: string;
|
|
tag: string;
|
|
tagColor: string;
|
|
tagBg: string;
|
|
desc: string;
|
|
icon: string;
|
|
}
|
|
|
|
@Component({
|
|
selector: 'mifosx-client-entity-kyc-step',
|
|
templateUrl: './client-entity-kyc-step.component.html',
|
|
styleUrls: ['./client-entity-kyc-step.component.scss'],
|
|
imports: [
|
|
...STANDALONE_SHARED_IMPORTS,
|
|
FaIconComponent,
|
|
MatCheckbox,
|
|
MatIcon,
|
|
MatTooltip,
|
|
MatProgressSpinner,
|
|
MatSlideToggle
|
|
]
|
|
})
|
|
export class ClientEntityKycStepComponent implements OnInit, OnChanges {
|
|
private fb = inject(UntypedFormBuilder);
|
|
private clientService = inject(ClientsService);
|
|
private settingsService = inject(SettingsService);
|
|
private dateUtils = inject(Dates);
|
|
|
|
@Input() clientId: string = '';
|
|
@Input() constitutionType: string = '';
|
|
/** Read-only mode — disables form editing until user clicks edit icon */
|
|
@Input() readOnly = false;
|
|
|
|
today = new Date();
|
|
|
|
/** Category metadata */
|
|
categories: CategoryMeta[] = [
|
|
{
|
|
id: 'msme',
|
|
label: 'MSME',
|
|
tag: 'Udyam / MSME',
|
|
tagColor: '#0A5C6B',
|
|
tagBg: '#E6F4F6',
|
|
desc: 'Micro, Small & Medium Enterprise — Ministry of MSME / Udyam Registration',
|
|
icon: 'cogs'
|
|
},
|
|
{
|
|
id: 'startup',
|
|
label: 'Startup India Registered',
|
|
tag: 'DPIIT',
|
|
tagColor: '#1A5FAB',
|
|
tagBg: '#EAF2FB',
|
|
desc: 'DPIIT recognition under Startup India initiative',
|
|
icon: 'rocket'
|
|
},
|
|
{
|
|
id: 'mudra',
|
|
label: 'Mudra Borrower',
|
|
tag: 'PMMY',
|
|
tagColor: '#8A5A00',
|
|
tagBg: '#FEF3DC',
|
|
desc: 'Pradhan Mantri Mudra Yojana — SIDBI / Mudra scheme',
|
|
icon: 'hand-holding-usd'
|
|
},
|
|
{
|
|
id: 'eou',
|
|
label: 'Export Oriented Unit',
|
|
tag: 'EOU',
|
|
tagColor: '#1B7A4A',
|
|
tagBg: '#E8F5EE',
|
|
desc: 'Customs-registered EOU under DGFT / Ministry of Commerce',
|
|
icon: 'ship'
|
|
},
|
|
{
|
|
id: 'importexport',
|
|
label: 'Import Export Business',
|
|
tag: 'DGFT / IEC',
|
|
tagColor: '#5B3A8A',
|
|
tagBg: '#F0EBF8',
|
|
desc: 'Entity with valid DGFT Importer Exporter Code',
|
|
icon: 'exchange-alt'
|
|
},
|
|
{
|
|
id: 'psl',
|
|
label: 'Priority Sector Borrower',
|
|
tag: 'RBI PSL',
|
|
tagColor: '#7B1F1F',
|
|
tagBg: '#FCEAEA',
|
|
desc: 'Eligible under RBI Priority Sector Lending guidelines',
|
|
icon: 'bullseye'
|
|
},
|
|
{
|
|
id: 'nps',
|
|
label: 'Non-Priority Sector',
|
|
tag: 'General',
|
|
tagColor: '#444441',
|
|
tagBg: '#F1EFE8',
|
|
desc: 'Standard corporate — no special priority sector classification',
|
|
icon: 'building'
|
|
}
|
|
];
|
|
|
|
/** Toggle & expand state per category */
|
|
enabled: Record<string, boolean> = {};
|
|
expanded: Record<string, boolean> = {};
|
|
|
|
/** Forms per category */
|
|
msmeForm!: UntypedFormGroup;
|
|
startupForm!: UntypedFormGroup;
|
|
mudraForm!: UntypedFormGroup;
|
|
eouForm!: UntypedFormGroup;
|
|
importexportForm!: UntypedFormGroup;
|
|
pslForm!: UntypedFormGroup;
|
|
npsForm!: UntypedFormGroup;
|
|
|
|
/** Saving state */
|
|
saving: Record<string, boolean> = {};
|
|
saved: Record<string, boolean> = {};
|
|
|
|
ngOnInit() {
|
|
this.initForms();
|
|
if (this.clientId) {
|
|
this.loadExisting();
|
|
}
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
if (changes['readOnly']) {
|
|
const forms = [
|
|
this.msmeForm,
|
|
this.startupForm,
|
|
this.mudraForm,
|
|
this.eouForm,
|
|
this.importexportForm,
|
|
this.pslForm,
|
|
this.npsForm
|
|
];
|
|
for (const form of forms) {
|
|
if (!form) continue;
|
|
if (this.readOnly) {
|
|
form.disable({ emitEvent: false });
|
|
} else {
|
|
form.enable({ emitEvent: false });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private initForms() {
|
|
this.msmeForm = this.fb.group({
|
|
udyamRegistrationNumber: [''],
|
|
registrationDate: [''],
|
|
registrationStatus: [''],
|
|
msmeClassification: [''],
|
|
enterpriseType: [''],
|
|
nicActivityCode: [''],
|
|
annualTurnover: [''],
|
|
investmentPlantMachinery: [''],
|
|
financialYearAssessment: [''],
|
|
socialCategoryOwner: [''],
|
|
genderPrimaryOwner: [''],
|
|
noOfEmployees: [''],
|
|
womenLedEnterprise: [false],
|
|
divyangPwdOwned: [false]
|
|
});
|
|
|
|
this.startupForm = this.fb.group({
|
|
dpiitRecognitionNumber: [''],
|
|
recognitionDate: [''],
|
|
certificateExpiryDate: [''],
|
|
entityStage: [''],
|
|
sectorAsDpiit: [''],
|
|
nicIndustryCode: [''],
|
|
taxExemption80iac: [false],
|
|
sidbiFund: [false],
|
|
selfCertification: [false],
|
|
fastTrackPatent: [false],
|
|
govtTenderExemption: [false],
|
|
creditGuaranteeCgss: [false],
|
|
pslLoanLimit: [''],
|
|
pslSubCategory: ['']
|
|
});
|
|
|
|
this.mudraForm = this.fb.group({
|
|
mudraCategory: [''],
|
|
mudraLoanAccountNo: [''],
|
|
lendingInstitutionName: [''],
|
|
sanctionedAmount: [''],
|
|
disbursementDate: [''],
|
|
loanPurpose: [''],
|
|
mudraCardNumber: [''],
|
|
currentOutstanding: [''],
|
|
repaymentStatus: [''],
|
|
npaFlag: [false]
|
|
});
|
|
|
|
this.eouForm = this.fb.group({
|
|
eouRegistrationNumber: [''],
|
|
registeredDcZone: [''],
|
|
bondLutNumber: [''],
|
|
validityFrom: [''],
|
|
validityTo: [''],
|
|
gstinEou: [''],
|
|
annualExportObligation: [''],
|
|
nfeTarget: [''],
|
|
softexFilingStatus: [''],
|
|
brcSubmissionStatus: [''],
|
|
adCategoryBankCode: [''],
|
|
femaDeclarationDate: ['']
|
|
});
|
|
|
|
this.importexportForm = this.fb.group({
|
|
iecCode: [''],
|
|
iecIssueDate: [''],
|
|
iecStatus: [''],
|
|
tradeType: [''],
|
|
primaryHsCode: [''],
|
|
keyTradingCountries: [''],
|
|
adCategoryBankCode: [''],
|
|
ecgcPolicyNumber: [''],
|
|
ecgcPolicyExpiry: [''],
|
|
eefcAccountRequired: [false],
|
|
pcfcFacilityRequired: [false],
|
|
femaDeclarationObtained: [false]
|
|
});
|
|
|
|
this.pslForm = this.fb.group({
|
|
pslCategory: [''],
|
|
pslSubCategory: [''],
|
|
pslLoanPurposeCode: [''],
|
|
pslEligibilityBasis: [''],
|
|
fiddReportingCode: [''],
|
|
pslSanctionedLimit: [''],
|
|
msmeClassification: [''],
|
|
udyamRegistrationNumber: [''],
|
|
udyamRegistrationDate: [''],
|
|
annualTurnover: [''],
|
|
investmentPlantMachinery: [''],
|
|
weakerSectionFlag: [false],
|
|
scStBorrowerFlag: [false],
|
|
womenEntrepreneurFlag: [false],
|
|
minorityCommunityFlag: [false]
|
|
});
|
|
|
|
this.npsForm = this.fb.group({
|
|
industrySector: [''],
|
|
nicCode: [''],
|
|
industryDescription: [''],
|
|
annualRevenueBand: [''],
|
|
riskCategory: [''],
|
|
customerRiskScore: [''],
|
|
amlRiskClassification: [''],
|
|
pepFlag: [false],
|
|
amlWatchlistMatch: [false],
|
|
adverseMediaFlag: [false]
|
|
});
|
|
}
|
|
|
|
getForm(categoryId: string): UntypedFormGroup {
|
|
switch (categoryId) {
|
|
case 'msme':
|
|
return this.msmeForm;
|
|
case 'startup':
|
|
return this.startupForm;
|
|
case 'mudra':
|
|
return this.mudraForm;
|
|
case 'eou':
|
|
return this.eouForm;
|
|
case 'importexport':
|
|
return this.importexportForm;
|
|
case 'psl':
|
|
return this.pslForm;
|
|
case 'nps':
|
|
return this.npsForm;
|
|
default:
|
|
return this.fb.group({});
|
|
}
|
|
}
|
|
|
|
toggleCategory(id: string) {
|
|
this.enabled[id] = !this.enabled[id];
|
|
if (this.enabled[id]) {
|
|
this.expanded[id] = true;
|
|
}
|
|
}
|
|
|
|
toggleExpand(id: string) {
|
|
this.expanded[id] = !this.expanded[id];
|
|
}
|
|
|
|
get enabledCategories(): CategoryMeta[] {
|
|
return this.categories.filter((c) => this.enabled[c.id]);
|
|
}
|
|
|
|
get enabledCount(): number {
|
|
return this.enabledCategories.length;
|
|
}
|
|
|
|
removeCategory(id: string) {
|
|
this.enabled[id] = false;
|
|
this.expanded[id] = false;
|
|
}
|
|
|
|
/** Save a single category */
|
|
saveCategory(categoryId: string) {
|
|
if (!this.clientId) return;
|
|
this.saving[categoryId] = true;
|
|
const form = this.getForm(categoryId);
|
|
const payload: any = { ...form.value, enabled: true, locale: 'en', dateFormat: this.settingsService.dateFormat };
|
|
|
|
// Format date fields
|
|
for (const key of Object.keys(payload)) {
|
|
if (payload[key] instanceof Date) {
|
|
payload[key] = this.dateUtils.formatDate(payload[key], this.settingsService.dateFormat);
|
|
}
|
|
}
|
|
|
|
this.clientService.saveEntityClassification(this.clientId, categoryId, payload).subscribe({
|
|
next: () => {
|
|
this.saving[categoryId] = false;
|
|
this.saved[categoryId] = true;
|
|
setTimeout(() => (this.saved[categoryId] = false), 3000);
|
|
},
|
|
error: () => {
|
|
this.saving[categoryId] = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
/** Load existing classifications */
|
|
private loadExisting() {
|
|
this.clientService.getEntityClassifications(this.clientId).subscribe({
|
|
next: (data: any) => {
|
|
if (!data) return;
|
|
for (const cat of this.categories) {
|
|
const catData = data[cat.id];
|
|
if (catData && catData.enabled) {
|
|
this.enabled[cat.id] = true;
|
|
this.getForm(cat.id).patchValue(catData);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/** Get all classification data for parent */
|
|
get classificationData(): any {
|
|
const result: any = {};
|
|
for (const cat of this.categories) {
|
|
if (this.enabled[cat.id]) {
|
|
result[cat.id] = { ...this.getForm(cat.id).value, enabled: true };
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
get hasClassificationData(): boolean {
|
|
return this.enabledCount > 0;
|
|
}
|
|
|
|
// Keep old getter for backward compatibility
|
|
get entityKycData(): any {
|
|
return this.classificationData;
|
|
}
|
|
get hasEntityKycData(): boolean {
|
|
return this.hasClassificationData;
|
|
}
|
|
}
|