CTR/STR report and Risk Category auto mark on bases of person occupation and anual salary enter
This commit is contained in:
+4
-4
@@ -136,7 +136,7 @@
|
||||
| translate
|
||||
}}
|
||||
</mat-label>
|
||||
<input matInput required formControlName="fullname" />
|
||||
<input matInput required formControlName="fullname" autocomplete="off" />
|
||||
@if (createClientForm.controls.fullname.hasError('required')) {
|
||||
<mat-error>Client name is <strong>required</strong></mat-error>
|
||||
} @else if (createClientForm.controls.fullname.hasError('pattern')) {
|
||||
@@ -214,7 +214,7 @@
|
||||
<mat-form-field appearance="outline" class="name-field first-name">
|
||||
<mat-label>{{ 'labels.inputs.First Name' | translate }}</mat-label>
|
||||
<span matPrefix class="field-prefix-icon"><mat-icon>badge</mat-icon></span>
|
||||
<input matInput required formControlName="firstname" />
|
||||
<input matInput required formControlName="firstname" autocomplete="off" />
|
||||
@if (createClientForm.controls.firstname.hasError('required')) {
|
||||
<mat-error>First name is <strong>required</strong></mat-error>
|
||||
} @else if (createClientForm.controls.firstname.hasError('pattern')) {
|
||||
@@ -228,7 +228,7 @@
|
||||
<mat-form-field appearance="outline" class="name-field middle-name">
|
||||
<mat-label>{{ 'labels.inputs.Middle Name' | translate }}</mat-label>
|
||||
<span matPrefix class="field-prefix-icon"><mat-icon>person_outline</mat-icon></span>
|
||||
<input matInput formControlName="middlename" />
|
||||
<input matInput formControlName="middlename" autocomplete="off" />
|
||||
@if (
|
||||
createClientForm.controls.middlename?.hasError('pattern') &&
|
||||
createClientForm.controls.middlename?.touched
|
||||
@@ -241,7 +241,7 @@
|
||||
<mat-form-field appearance="outline" class="name-field last-name">
|
||||
<mat-label>{{ 'labels.inputs.Last Name' | translate }}</mat-label>
|
||||
<span matPrefix class="field-prefix-icon"><mat-icon>person</mat-icon></span>
|
||||
<input matInput required formControlName="lastname" />
|
||||
<input matInput required formControlName="lastname" autocomplete="off" />
|
||||
@if (createClientForm.controls.lastname.hasError('required')) {
|
||||
<mat-error>Last name is <strong>required</strong></mat-error>
|
||||
} @else if (createClientForm.controls.lastname.hasError('pattern')) {
|
||||
|
||||
@@ -108,6 +108,7 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
* m_code_value (code "PwdCategory") via the client template, same as occupationCategoryOptions.
|
||||
* Admins can add/reorder/deactivate entries from System -> Manage Codes without a code change. */
|
||||
pwdCategoryOptions: any[] = [];
|
||||
riskRules: any[] = [];
|
||||
|
||||
/** Set true after a save attempt so the UI can highlight a missing required document */
|
||||
seniorPwdDocsTouched = false;
|
||||
@@ -546,6 +547,10 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
ngOnInit() {
|
||||
this.maxDate = this.settingsService.businessDate;
|
||||
this.setOptions();
|
||||
|
||||
this.clientService.getClientRiskRules().subscribe((rules) => {
|
||||
this.riskRules = rules || [];
|
||||
});
|
||||
// Fetch marital status options from code values
|
||||
this.clientService.getCodeValuesByName('MaritalStatus').subscribe((options) => {
|
||||
this.maritalStatusOptions = options || [];
|
||||
@@ -2120,6 +2125,74 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
|
||||
return generalDetails;
|
||||
}
|
||||
|
||||
get calculatedRisk(): { category: number; reason: string } {
|
||||
const legalFormId = this.createClientForm.get('legalFormId')?.value || 1; // 1 = Person, 2 = Entity
|
||||
const occCatId = this.createClientForm.get('occupationCategoryCvId')?.value;
|
||||
const occId = this.createClientForm.get('occupationCvId')?.value;
|
||||
|
||||
const monthlyIncome = parseFloat(this.createClientForm.get('monthlyIncome')?.value) || 0;
|
||||
const annualIncome = parseFloat(this.createClientForm.get('annualIncome')?.value) || 0;
|
||||
const monthlyTurnover = parseFloat(this.createClientForm.get('monthlyTurnover')?.value) || 0;
|
||||
const annualTurnover = parseFloat(this.createClientForm.get('annualTurnover')?.value) || 0;
|
||||
const agricultureIncome = parseFloat(this.createClientForm.get('agricultureIncome')?.value) || 0;
|
||||
const nonAgricultureIncome = parseFloat(this.createClientForm.get('nonAgricultureIncome')?.value) || 0;
|
||||
|
||||
const totalAnnualIncome = annualIncome + agricultureIncome + nonAgricultureIncome;
|
||||
|
||||
// Evaluate rules from DB (ordered by orderPosition ASC)
|
||||
if (this.riskRules && this.riskRules.length > 0) {
|
||||
for (const rule of this.riskRules) {
|
||||
// Match legal form
|
||||
if (rule.legalFormId != null && rule.legalFormId !== legalFormId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Match occupation category
|
||||
if (rule.occupationCategoryCvId != null && rule.occupationCategoryCvId !== occCatId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Match occupation
|
||||
if (rule.occupationCvId != null && rule.occupationCvId !== occId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Match monthly income ranges
|
||||
if (rule.minMonthlyIncome != null && monthlyIncome < rule.minMonthlyIncome) {
|
||||
continue;
|
||||
}
|
||||
if (rule.maxMonthlyIncome != null && monthlyIncome > rule.maxMonthlyIncome) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Match annual income ranges (evaluated against totalAnnualIncome)
|
||||
if (rule.minAnnualIncome != null && totalAnnualIncome < rule.minAnnualIncome) {
|
||||
continue;
|
||||
}
|
||||
if (rule.maxAnnualIncome != null && totalAnnualIncome > rule.maxAnnualIncome) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Match turnover ranges
|
||||
if (rule.minAnnualTurnover != null && annualTurnover < rule.minAnnualTurnover) {
|
||||
continue;
|
||||
}
|
||||
if (rule.maxAnnualTurnover != null && annualTurnover > rule.maxAnnualTurnover) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// All matched!
|
||||
return { category: rule.riskCategory, reason: rule.reason };
|
||||
}
|
||||
}
|
||||
|
||||
// Default Fallbacks
|
||||
if (legalFormId !== LegalFormId.PERSON) {
|
||||
return { category: 2, reason: 'Non-Individual / Entity profile (Default Medium Risk)' };
|
||||
}
|
||||
return { category: 1, reason: 'Default Low Risk profile (Salary / Service / Agriculture / Others)' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+34
-2
@@ -49,6 +49,10 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
/** Minor clients can't hold utility bills, bank statements or an adult-only OVD in their own
|
||||
* name — restricts the address-proof picker to Aadhaar/Passport when true. */
|
||||
@Input() isMinor = false;
|
||||
/** Non-individual (entity) clients — hides personal OVDs (Aadhaar/Passport/DL/Voter ID/NREGA/
|
||||
* NPR/Birth Certificate/School ID) from the proof-of-address picker; a company's registered
|
||||
* office address is evidenced by utility bills, GST/rent documents etc., not a person's ID. */
|
||||
@Input() isNonIndividual = false;
|
||||
|
||||
activeTab = 0;
|
||||
sameAsPermanent = false;
|
||||
@@ -282,8 +286,8 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
this.clientsService.getKycProofTypes().subscribe({
|
||||
next: (res: any) => {
|
||||
const activeFilter = (p: any) => p?.active !== false;
|
||||
this.addressProofs = this.filterAddressProofsForMinor(
|
||||
(res?.addressProofs || []).filter(activeFilter).map(normalizeProofType)
|
||||
this.addressProofs = this.filterAddressProofsForNonIndividual(
|
||||
this.filterAddressProofsForMinor((res?.addressProofs || []).filter(activeFilter).map(normalizeProofType))
|
||||
);
|
||||
this.addressGroups = groupProofsByCategory(this.addressProofs);
|
||||
this.loadingProofs = false;
|
||||
@@ -309,6 +313,26 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
);
|
||||
}
|
||||
|
||||
/** Personal OVDs — not valid proof of a registered office/entity address. */
|
||||
private static readonly NON_INDIVIDUAL_EXCLUDED_ADDRESS_PROOF_CODES = new Set([
|
||||
'AADHAAR',
|
||||
'PASSPORT',
|
||||
'VOTER_ID',
|
||||
'DRIVING_LICENSE',
|
||||
'NREGA_JOB_CARD',
|
||||
'NPR_LETTER',
|
||||
'BIRTH_CERTIFICATE',
|
||||
'SCHOOL_ID_CARD'
|
||||
]);
|
||||
|
||||
private filterAddressProofsForNonIndividual(proofs: ProofType[]): ProofType[] {
|
||||
if (!this.isNonIndividual) return proofs;
|
||||
return proofs.filter(
|
||||
(p) =>
|
||||
!ClientIndiaAddressStepComponent.NON_INDIVIDUAL_EXCLUDED_ADDRESS_PROOF_CODES.has((p.code || '').toUpperCase())
|
||||
);
|
||||
}
|
||||
|
||||
selectAddrProof(proof: ProofType): void {
|
||||
this.selectedAddrProof = proof;
|
||||
this.permanentForm.get('proofId')?.setValue(proof.id);
|
||||
@@ -425,6 +449,14 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Legal form toggled on an already-initialized instance — re-fetch rather than filter
|
||||
// `addressProofs` in place, since the non-individual filter only ever removes entries and
|
||||
// toggling back to individual could never recover what a prior pass had stripped out.
|
||||
if (changes['isNonIndividual'] && !changes['isNonIndividual'].firstChange) {
|
||||
this.selectedAddrProof = null;
|
||||
this.permanentForm.get('proofId')?.setValue('');
|
||||
this.loadProofTypes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1049,6 +1049,12 @@
|
||||
</h4>
|
||||
|
||||
<div class="form-grid">
|
||||
<div class="checkbox-field override-checkbox-wrapper full-width">
|
||||
<mat-checkbox formControlName="allowRiskOverride" [disabled]="readOnly">
|
||||
Override Auto-calculated Risk Category
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>Risk Category</mat-label>
|
||||
<mat-select formControlName="riskCategory">
|
||||
@@ -1086,6 +1092,64 @@
|
||||
<mat-hint>Auto: Low 10yr, Medium 8yr, High 2yr</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
@if (riskCalculationReason && !kycForm.get('allowRiskOverride')?.value && !readOnly) {
|
||||
<div
|
||||
class="risk-auto-calc-info full-width"
|
||||
[class.risk-auto-calc-info--low]="kycForm.get('riskCategory')?.value === 1"
|
||||
[class.risk-auto-calc-info--medium]="kycForm.get('riskCategory')?.value === 2"
|
||||
[class.risk-auto-calc-info--high]="kycForm.get('riskCategory')?.value === 3"
|
||||
>
|
||||
<mat-icon class="info-icon">info</mat-icon>
|
||||
<span class="info-text"
|
||||
>Auto-determined: <strong>{{ riskCalculationReason }}</strong></span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="risk-rules-toggle-wrapper full-width">
|
||||
<button mat-button type="button" class="rules-toggle-btn" (click)="showRiskRulesTable = !showRiskRulesTable">
|
||||
<mat-icon class="rules-toggle-icon">{{ showRiskRulesTable ? 'expand_less' : 'expand_more' }}</mat-icon>
|
||||
{{
|
||||
showRiskRulesTable ? 'Hide Risk Classification Criteria Table' : 'View Risk Classification Criteria Table'
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (showRiskRulesTable) {
|
||||
<div class="risk-rules-card mat-elevation-z1 full-width">
|
||||
<table class="risk-rules-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Risk Category</th>
|
||||
<th>Primary Criteria / Basis</th>
|
||||
<th>Examples / Thresholds</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="rule-row rule-row--high">
|
||||
<td><span class="risk-badge risk-badge--high">High Risk</span></td>
|
||||
<td>PEPs, High-Risk Industries, High Income / Turnovers</td>
|
||||
<td>
|
||||
Politicians; Real Estate, Import/Export; Commission Agents; Income > ₹20L/yr; Turnover > ₹1 Crore
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="rule-row rule-row--medium">
|
||||
<td><span class="risk-badge risk-badge--medium">Medium Risk</span></td>
|
||||
<td>Self-Employed, Professionals, Cross-Border, Moderate Income</td>
|
||||
<td>
|
||||
Business Owners, Traders; Doctors, Lawyers, CAs; NRI, PIO, OCI; Income > ₹6L/yr; Turnover > ₹20L
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="rule-row rule-row--low">
|
||||
<td><span class="risk-badge risk-badge--low">Low Risk</span></td>
|
||||
<td>Non-Earning, Government Employment, Agriculture / Manual</td>
|
||||
<td>Students, Minors, Homemakers; Govt/PSU Employees; Farmers, Daily Wage; Income <= ₹6L/yr</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
<mat-form-field appearance="outline" class="form-field full-width">
|
||||
<mat-label>{{ isKycVerified ? 'Reason for Re-edit *' : 'Remarks' }}</mat-label>
|
||||
<textarea
|
||||
|
||||
@@ -2257,3 +2257,161 @@
|
||||
width: 95px;
|
||||
}
|
||||
}
|
||||
|
||||
.override-checkbox-wrapper {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.risk-auto-calc-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin-top: -8px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid transparent;
|
||||
|
||||
.info-icon {
|
||||
font-size: 18px !important;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-text strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&--low {
|
||||
background: #ecfdf5;
|
||||
color: #065f46;
|
||||
border-color: #a7f3d0;
|
||||
|
||||
.info-icon {
|
||||
color: #10b981;
|
||||
}
|
||||
}
|
||||
|
||||
&--medium {
|
||||
background: #fffbeb;
|
||||
color: #92400e;
|
||||
border-color: #fde68a;
|
||||
|
||||
.info-icon {
|
||||
color: #f59e0b;
|
||||
}
|
||||
}
|
||||
|
||||
&--high {
|
||||
background: #fef2f2;
|
||||
color: #991b1b;
|
||||
border-color: #fecaca;
|
||||
|
||||
.info-icon {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.risk-rules-toggle-wrapper {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.rules-toggle-btn {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1e6bb8;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
background: #f1f5f9;
|
||||
|
||||
&:hover {
|
||||
background: #e2e8f0;
|
||||
color: #0f172a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.risk-rules-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
|
||||
}
|
||||
|
||||
.risk-rules-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
|
||||
th {
|
||||
padding: 10px 12px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
border-bottom: 2px solid #cbd5e1;
|
||||
background: #f8fafc;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
color: #334155;
|
||||
vertical-align: top;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.rule-row {
|
||||
transition: background 0.15s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
}
|
||||
|
||||
.risk-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
text-align: center;
|
||||
width: 80px;
|
||||
|
||||
&--high {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
&--medium {
|
||||
background: #fffbeb;
|
||||
color: #d97706;
|
||||
border: 1px solid #fde68a;
|
||||
}
|
||||
|
||||
&--low {
|
||||
background: #ecfdf5;
|
||||
color: #16a34a;
|
||||
border: 1px solid #a7f3d0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user