CTR/STR report and Risk Category auto mark on bases of person occupation and anual salary enter

This commit is contained in:
2026-07-29 10:21:02 +05:30
parent 79fedb66e5
commit f682cb17f6
16 changed files with 1808 additions and 29 deletions
@@ -70,6 +70,7 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
@Input() entityName: string = '';
/** Read-only mode — disables form editing until user clicks edit icon */
@Input() readOnly = false;
@Input() calculatedRisk: { category: number; reason: string } | null = null;
today = new Date();
kycId: string = '';
@@ -170,6 +171,17 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
{ name: 'Birth Certificate', code: 'BIRTH_CERTIFICATE' },
{ name: 'School ID Card', code: 'SCHOOL_ID_CARD' }
];
/** Generic RBI-prescribed KYC documents for legal entities (Master Direction on KYC,
* Annexure I) — shown for non-individual clients before a specific constitution type is
* picked. Individual OVDs (Aadhaar/Passport/DL/Voter ID/NREGA/NPR) don't apply to entities. */
private nonIndividualDefaultDocTypes = [
{ name: 'Certificate of Incorporation / Registration Certificate', code: 'CERT_INCORPORATION' },
{ name: 'PAN Card of Entity', code: 'ENTITY_PAN' },
{ name: 'Memorandum & Articles of Association / Partnership Deed / Trust Deed', code: 'CONSTITUTIVE_DOC' },
{ name: 'Board Resolution / Authorization Letter', code: 'BOARD_RESOLUTION' },
{ name: 'KYC of Authorized Signatory / Power of Attorney Holder', code: 'AUTH_SIGNATORY_KYC' },
{ name: 'GST Registration Certificate', code: 'GST_CERTIFICATE' }
];
/** Constitution-based KYC document types for non-individual clients */
private constitutionDocTypes: Record<string, { name: string; mandatory: boolean }[]> = {
HUF: [
@@ -250,6 +262,9 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
{ value: 3, label: 'High Risk', years: 2, color: 'red' }
];
riskCalculationReason = '';
showRiskRulesTable = false;
/** PAN verification */
panVerifyStatus: 'NONE' | 'VERIFIED' | 'FAILED' = 'NONE';
panVerifying = false;
@@ -441,6 +456,7 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
kycDocIssueDate: [''],
kycDocExpiryDate: [''],
riskCategory: [1],
allowRiskOverride: [false],
remarks: [''],
amlCheckStatus: [
'CLEAR',
@@ -488,6 +504,14 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
return docs.filter((d) => !ClientKycStepComponent.MINOR_ONLY_DOC_CODES.has((d.code || '').toUpperCase()));
}
/** Individual-only OVDs vs. entity-only RBI docs — never both, and minor filtering only
* ever applies to individuals. */
private getDefaultDocTypes(): { name: string; code: string }[] {
return this.isNonIndividual
? this.nonIndividualDefaultDocTypes
: this.filterDocTypesForMinor(this.defaultDocumentTypes);
}
ngOnInit(): void {
this.seedDefaultDocTypes();
this.loadExistingKycIfNeeded();
@@ -495,20 +519,36 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
if (this.isNonIndividual && this.constitutionMeta) {
this.updateDocTypesForConstitution();
}
this.kycForm.get('allowRiskOverride')?.valueChanges.subscribe((allowed: boolean) => {
const riskControl = this.kycForm.get('riskCategory');
if (allowed) {
if (!this.readOnly) {
riskControl?.enable();
}
} else {
riskControl?.disable();
if (this.calculatedRisk) {
riskControl?.setValue(this.calculatedRisk.category);
}
}
});
if (!this.kycForm.get('allowRiskOverride')?.value) {
this.kycForm.get('riskCategory')?.disable();
}
}
/** Seed the document-type picker from defaults, filtered by the current isMinor input.
* Only seeds when the list is empty — the template API (loadKycTemplate) takes over once
* a clientId is available. */
/** Seed the document-type picker from defaults (entity RBI docs vs. individual OVDs,
* filtered by the current isMinor input for individuals). Only seeds when the list is
* empty — the template API (loadKycTemplate) takes over once a clientId is available. */
private seedDefaultDocTypes(): void {
if (this.kycDocumentTypes.length) return;
this.kycDocumentTypes = this.filterDocTypesForMinor(
this.defaultDocumentTypes.map((d) => ({
name: d.name,
code: d.code,
mandatory: false
}))
);
this.kycDocumentTypes = this.getDefaultDocTypes().map((d) => ({
name: d.name,
code: d.code,
mandatory: false
}));
this.rebuildKycDocPicker();
}
@@ -526,6 +566,7 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
this.kycForm.reset({
hasPan: true,
riskCategory: 1,
allowRiskOverride: false,
amlCheckStatus: 'CLEAR',
bannedEntityMatch: 'NO',
willfulDefaulter: 'NO',
@@ -546,6 +587,8 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
this.kycDocMergedFile = null;
this.kycDocMergedPreview = null;
this.existingKycIdentifierId = null;
this.riskCalculationReason = '';
this.showRiskRulesTable = false;
this.existingKycDocuments = [];
this.photoSigTouched = false;
this.panVerifyStatus = 'NONE';
@@ -598,12 +641,41 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
this.kycForm.disable({ emitEvent: false });
} else {
this.kycForm.enable({ emitEvent: false });
if (!this.kycForm.get('allowRiskOverride')?.value) {
this.kycForm.get('riskCategory')?.disable({ emitEvent: false });
}
}
}
if (changes['calculatedRisk']) {
const newRisk = changes['calculatedRisk'].currentValue;
if (newRisk) {
this.riskCalculationReason = newRisk.reason;
const allowOverride = this.kycForm.get('allowRiskOverride')?.value;
const riskControl = this.kycForm.get('riskCategory');
if (!allowOverride && riskControl && riskControl.value !== newRisk.category) {
riskControl.setValue(newRisk.category);
riskControl.markAsDirty();
}
} else {
this.riskCalculationReason = '';
}
}
if (changes['constitutionMeta'] && this.isNonIndividual) {
this.updateDocTypesForConstitution();
}
if (changes['isMinor']) {
// Legal form toggled on an already-initialized instance (route-reuse keeps this
// component alive across create sessions) — the individual OVD list and the entity RBI
// doc list are mutually exclusive, so re-seed from scratch rather than filtering in place.
if (changes['isNonIndividual'] && !changes['isNonIndividual'].firstChange && !this.kycTemplateLoaded) {
this.kycDocumentTypes = [];
this.selectedKycDoc = null;
this.kycForm.get('kycDocumentName')?.setValue('');
this.seedDefaultDocTypes();
if (this.isNonIndividual && this.constitutionMeta) {
this.updateDocTypesForConstitution();
}
}
if (changes['isMinor'] && !this.isNonIndividual) {
if (this.isMinor) {
this.kycForm.get('hasPan')?.setValue(false);
this.kycForm.get('panNumber')?.setValue('');
@@ -797,6 +869,7 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
kycDocExpiryDate: parseDate(data.kycDocExpiryDate),
// Risk & Remarks
riskCategory: data.riskCategory || 1,
allowRiskOverride: this.calculatedRisk ? this.calculatedRisk.category !== (data.riskCategory || 1) : false,
remarks: data.remarks || '',
hasPan: !!data.panNumber,
// AML & Compliance
@@ -1015,13 +1088,11 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
error: () => {
// Fallback to defaults if template API unavailable
this.kycTemplateLoaded = true;
this.kycDocumentTypes = this.filterDocTypesForMinor(
this.defaultDocumentTypes.map((d) => ({
name: d.name,
code: d.code,
mandatory: false
}))
);
this.kycDocumentTypes = this.getDefaultDocTypes().map((d) => ({
name: d.name,
code: d.code,
mandatory: false
}));
this.rebuildKycDocPicker();
}
});
@@ -1664,7 +1735,7 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
}
get kycData(): any {
const formVal = this.readOnly ? this.kycForm.getRawValue() : this.kycForm.value;
const formVal = this.kycForm.getRawValue();
const aadhaarRaw = formVal.aadhaarNumber || '';
const aadhaarMasked = aadhaarRaw.length === 12 ? 'XXXXXXXX' + aadhaarRaw.slice(-4) : null;
const dateFormat = this.settingsService.dateFormat;