Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
@@ -29,6 +29,7 @@ import { ClientsService } from '../../clients.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { ProofType, normalizeProofType, groupProofsByCategory } from '../../shared/kyc-proof-types';
|
||||
import { deriveKycExceptionFields } from '../../kyc-exception.util';
|
||||
import { KycUploadHelperService } from '../../shared/kyc-upload-helper.service';
|
||||
import { CaptureImageDialogComponent } from '../../clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component';
|
||||
import { DrawSignatureDialogComponent } from '../../clients-view/custom-dialogs/draw-signature-dialog/draw-signature-dialog.component';
|
||||
@@ -71,7 +72,15 @@ 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;
|
||||
@Input() calculatedRisk: {
|
||||
category: number;
|
||||
reason: string;
|
||||
amlCheckStatus?: string;
|
||||
bannedEntityMatch?: string;
|
||||
willfulDefaulter?: string;
|
||||
ecgcSalFlag?: string;
|
||||
dormantFlag?: string;
|
||||
} | null = null;
|
||||
|
||||
today = new Date();
|
||||
kycId: string = '';
|
||||
@@ -267,6 +276,14 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
riskCalculationReason = '';
|
||||
showRiskRulesTable = false;
|
||||
|
||||
/**
|
||||
* Exception state as last read back from the server. Needed to tell "this client never had an
|
||||
* exception" from "this client had one and has now cleared it" — only the latter should be
|
||||
* stamped resolved and surface in the Resolved tab.
|
||||
*/
|
||||
persistedIsException = false;
|
||||
persistedExceptionReason: string | null = null;
|
||||
|
||||
/** PAN verification */
|
||||
panVerifyStatus: 'NONE' | 'VERIFIED' | 'FAILED' = 'NONE';
|
||||
panVerifying = false;
|
||||
@@ -459,7 +476,10 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
kycDocExpiryDate: [''],
|
||||
riskCategory: [1],
|
||||
allowRiskOverride: [false],
|
||||
allowAmlOverride: [false],
|
||||
remarks: [''],
|
||||
isException: [false],
|
||||
exceptionReason: [[]],
|
||||
amlCheckStatus: [
|
||||
'CLEAR',
|
||||
Validators.required
|
||||
@@ -536,9 +556,35 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
});
|
||||
|
||||
this.kycForm.get('allowAmlOverride')?.valueChanges.subscribe((allowed: boolean) => {
|
||||
const amlControls = [
|
||||
this.kycForm.get('amlCheckStatus'),
|
||||
this.kycForm.get('bannedEntityMatch'),
|
||||
this.kycForm.get('willfulDefaulter'),
|
||||
this.kycForm.get('ecgcSalFlag'),
|
||||
this.kycForm.get('dormantFlag')
|
||||
];
|
||||
if (allowed) {
|
||||
if (!this.readOnly) {
|
||||
amlControls.forEach((ctrl) => ctrl?.enable());
|
||||
}
|
||||
} else {
|
||||
amlControls.forEach((ctrl) => ctrl?.disable());
|
||||
this.applyAutoCalculatedAmlFlags();
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.kycForm.get('allowRiskOverride')?.value) {
|
||||
this.kycForm.get('riskCategory')?.disable();
|
||||
}
|
||||
|
||||
if (!this.kycForm.get('allowAmlOverride')?.value) {
|
||||
this.kycForm.get('amlCheckStatus')?.disable();
|
||||
this.kycForm.get('bannedEntityMatch')?.disable();
|
||||
this.kycForm.get('willfulDefaulter')?.disable();
|
||||
this.kycForm.get('ecgcSalFlag')?.disable();
|
||||
this.kycForm.get('dormantFlag')?.disable();
|
||||
}
|
||||
}
|
||||
|
||||
/** Seed the document-type picker from defaults (entity RBI docs vs. individual OVDs,
|
||||
@@ -576,8 +622,12 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
dormantFlag: 'ACTIVE',
|
||||
accountStatus: 'ACTIVE',
|
||||
form60Type: 'FORM_60',
|
||||
form15Type: '15G'
|
||||
form15Type: '15G',
|
||||
isException: false,
|
||||
exceptionReason: []
|
||||
});
|
||||
this.persistedIsException = false;
|
||||
this.persistedExceptionReason = null;
|
||||
this.kycId = '';
|
||||
this.kycLoaded = false;
|
||||
this.kycStatus = 0;
|
||||
@@ -646,6 +696,13 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (!this.kycForm.get('allowRiskOverride')?.value) {
|
||||
this.kycForm.get('riskCategory')?.disable({ emitEvent: false });
|
||||
}
|
||||
if (!this.kycForm.get('allowAmlOverride')?.value) {
|
||||
this.kycForm.get('amlCheckStatus')?.disable({ emitEvent: false });
|
||||
this.kycForm.get('bannedEntityMatch')?.disable({ emitEvent: false });
|
||||
this.kycForm.get('willfulDefaulter')?.disable({ emitEvent: false });
|
||||
this.kycForm.get('ecgcSalFlag')?.disable({ emitEvent: false });
|
||||
this.kycForm.get('dormantFlag')?.disable({ emitEvent: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changes['calculatedRisk']) {
|
||||
@@ -658,6 +715,11 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
riskControl.setValue(newRisk.category);
|
||||
riskControl.markAsDirty();
|
||||
}
|
||||
|
||||
const allowAmlOverride = this.kycForm.get('allowAmlOverride')?.value;
|
||||
if (!allowAmlOverride) {
|
||||
this.applyAutoCalculatedAmlFlags(newRisk);
|
||||
}
|
||||
} else {
|
||||
this.riskCalculationReason = '';
|
||||
}
|
||||
@@ -872,6 +934,13 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
// Risk & Remarks
|
||||
riskCategory: data.riskCategory || 1,
|
||||
allowRiskOverride: this.calculatedRisk ? this.calculatedRisk.category !== (data.riskCategory || 1) : false,
|
||||
allowAmlOverride: this.calculatedRisk
|
||||
? this.calculatedRisk.amlCheckStatus !== (data.amlCheckStatus || 'CLEAR') ||
|
||||
this.calculatedRisk.bannedEntityMatch !== (data.bannedEntityMatch || 'NO') ||
|
||||
this.calculatedRisk.willfulDefaulter !== (data.willfulDefaulter || 'NO') ||
|
||||
this.calculatedRisk.ecgcSalFlag !== (data.ecgcSalFlag || 'NO') ||
|
||||
this.calculatedRisk.dormantFlag !== (data.dormantFlag || 'ACTIVE')
|
||||
: false,
|
||||
remarks: data.remarks || '',
|
||||
hasPan: !!data.panNumber,
|
||||
// AML & Compliance
|
||||
@@ -889,16 +958,18 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
form60Date: parseDate(data.form60Date),
|
||||
form15Applicable: data.form15Applicable || false,
|
||||
form15Type: data.form15Type || '15G',
|
||||
form15FY: data.form15FY || ''
|
||||
form15FY: data.form15FY || '',
|
||||
isException: data.isException || false,
|
||||
exceptionReason: data.exceptionReason ? data.exceptionReason.split(', ') : []
|
||||
});
|
||||
|
||||
// Set remarks as required if KYC is verified
|
||||
if (this.isKycVerified) {
|
||||
this.kycForm.get('remarks')?.setValidators(Validators.required);
|
||||
this.kycForm.get('remarks')?.updateValueAndValidity();
|
||||
this.kycForm.get('remarks')?.markAsTouched(); // Force red underline so user knows it's blocking
|
||||
}
|
||||
|
||||
this.persistedIsException = data.isException === true;
|
||||
this.persistedExceptionReason = data.exceptionReason || null;
|
||||
// Sync chip-picker selection with the prefilled document name
|
||||
if (data.kycDocumentName) {
|
||||
this.selectedKycDoc = this.kycDocProofs.find((p) => p.name === data.kycDocumentName) || null;
|
||||
@@ -1798,7 +1869,21 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
form15Applicable: v.form15Applicable || false,
|
||||
form15Type: v.form15Type || null,
|
||||
form15FY: v.form15FY || null,
|
||||
remarks: v.remarks || null
|
||||
remarks: v.remarks || null,
|
||||
// Exception state is DERIVED from verification status, not from a checkbox — an unverified
|
||||
// or blank PAN/Aadhaar is an exception by definition, so the officer cannot forget to flag
|
||||
// it. See deriveKycExceptionFields() for the raise / narrow / close rules.
|
||||
...deriveKycExceptionFields({
|
||||
panVerified: this.isNonIndividual ? this.entPanVerifyStatus === 'VERIFIED' : this.panVerifyStatus === 'VERIFIED',
|
||||
aadhaarVerified: this.aadhaarVerifyStatus === 'VERIFIED',
|
||||
// Non-individual clients submit a dummy masked Aadhaar, so checking it would saddle every
|
||||
// company with a permanent exception.
|
||||
aadhaarApplicable: !this.isNonIndividual,
|
||||
hadException: this.persistedIsException,
|
||||
previousReason: this.persistedExceptionReason,
|
||||
manualReasons: Array.isArray(v.exceptionReason) ? v.exceptionReason : [],
|
||||
todayFormatted: this.dateUtils.formatDate(new Date(), dateFormat)
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1811,9 +1896,25 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
v.enterprisePan ||
|
||||
v.cinNumber ||
|
||||
v.kycDocumentName ||
|
||||
v.form15Applicable
|
||||
v.form15Applicable ||
|
||||
v.isException
|
||||
);
|
||||
}
|
||||
|
||||
private applyAutoCalculatedAmlFlags(risk = this.calculatedRisk): void {
|
||||
if (!risk) return;
|
||||
this.kycForm.get('amlCheckStatus')?.setValue(risk.amlCheckStatus || 'CLEAR');
|
||||
this.kycForm.get('bannedEntityMatch')?.setValue(risk.bannedEntityMatch || 'NO');
|
||||
this.kycForm.get('willfulDefaulter')?.setValue(risk.willfulDefaulter || 'NO');
|
||||
this.kycForm.get('ecgcSalFlag')?.setValue(risk.ecgcSalFlag || 'NO');
|
||||
this.kycForm.get('dormantFlag')?.setValue(risk.dormantFlag || 'ACTIVE');
|
||||
|
||||
this.kycForm.get('amlCheckStatus')?.markAsDirty();
|
||||
this.kycForm.get('bannedEntityMatch')?.markAsDirty();
|
||||
this.kycForm.get('willfulDefaulter')?.markAsDirty();
|
||||
this.kycForm.get('ecgcSalFlag')?.markAsDirty();
|
||||
this.kycForm.get('dormantFlag')?.markAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user