Files
cbsnexora/src/app/clients/clients-view/kyc-tab/kyc-tab.component.ts
T
keshav 569f17ef46
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s
kyc exception at client creation time and some tds work
2026-08-05 17:23:56 +05:30

792 lines
26 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/.
*/
/** Angular Imports */
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { UntypedFormBuilder, UntypedFormGroup, Validators, FormsModule } from '@angular/forms';
import { catchError, of, Subject, takeUntil } from 'rxjs';
/** Custom Services */
import { ClientsService } from '../../clients.service';
import { deriveKycExceptionFields } from '../../kyc-exception.util';
import { Dates } from 'app/core/utils/dates';
import { jsPDF } from 'jspdf';
import { MatIcon } from '@angular/material/icon';
import { MatTooltip } from '@angular/material/tooltip';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
* KYC Tab Component.
* Displays and manages KYC (Know Your Customer) details for an existing client.
* API: GET/POST /clients/{clientId}/kyc
*/
@Component({
selector: 'mifosx-kyc-tab',
templateUrl: './kyc-tab.component.html',
styleUrls: ['./kyc-tab.component.scss'],
standalone: true,
imports: [
...STANDALONE_SHARED_IMPORTS,
FormsModule,
MatIcon,
MatProgressSpinner,
MatTooltip,
FaIconComponent
]
})
export class KycTabComponent implements OnInit, OnDestroy {
private route = inject(ActivatedRoute);
private clientsService = inject(ClientsService);
private formBuilder = inject(UntypedFormBuilder);
private dateUtils = inject(Dates);
private destroy$ = new Subject<void>();
clientId: string;
kycId: string | null = null;
kycData: any = null;
clientData: any = null;
clientDateOfBirth: string | null = null;
isNonIndividual = false;
loading = true;
saving = false;
isEditMode = false;
errorMessage: string | null = null;
/** Re-KYC flow state */
reKycUploading = false;
reKycCompleting = false;
reKycSuccess = false;
reKycError: string | null = null;
selectedDeclarationFile: File | null = null;
verifying: { pan: boolean; aadhaar: boolean; gstin: boolean } = {
pan: false,
aadhaar: false,
gstin: false
};
/** Verification status tracking for re-verify */
verificationStatus: { pan: string | null; aadhaar: string | null; gstin: string | null } = {
pan: null,
aadhaar: null,
gstin: null
};
verificationError: { pan: string | null; aadhaar: string | null; gstin: string | null } = {
pan: null,
aadhaar: null,
gstin: null
};
verificationDetails: { pan: any; aadhaar: any; gstin: any } = {
pan: null,
aadhaar: null,
gstin: null
};
/** Aadhaar OTP flow state */
aadhaarOtpSent = false;
aadhaarOtpReferenceId: string | null = null;
aadhaarOtp = '';
aadhaarOtpVerifying = false;
showAadhaarReVerify = false;
aadhaarFullNumber = '';
kycForm: UntypedFormGroup = this.formBuilder.group({
kycType: [
1,
[Validators.required]
],
riskCategory: [
1,
[Validators.required]
],
panNumber: [
'',
[Validators.pattern(/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/)]
],
aadhaarNumber: [
'',
[Validators.pattern(/^\d{12}$/)]
],
gstinNumber: [
'',
[Validators.pattern(/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/)]
],
remarks: [''],
isException: [false],
exceptionReason: [[]]
});
ngOnInit(): void {
this.route.parent.params.subscribe((params: any) => {
this.clientId = params['clientId'];
this.loadKycData();
this.loadClientDateOfBirth();
});
}
loadKycData(): void {
this.loading = true;
this.errorMessage = null;
this.clientsService
.getClientKyc(this.clientId)
.pipe(
catchError(() => {
this.loading = false;
return of(null);
})
)
.subscribe((data: any) => {
this.loading = false;
this.kycData = data;
if (data) {
const rawKycId = data.id ?? data.kycId ?? data.resourceId;
this.kycId = rawKycId != null ? rawKycId.toString() : null;
this.kycForm.patchValue({
kycType: data.kycType || 1,
riskCategory: data.riskCategory || 1,
panNumber: data.panNumber || '',
// Aadhaar is stored masked on the server; user must re-enter
// the full 12-digit number to re-verify, so don't pre-fill.
aadhaarNumber: '',
gstinNumber: data.gstinNumber || '',
remarks: data.remarks || '',
isException: data.isException || false,
exceptionReason: data.exceptionReason ? data.exceptionReason.split(', ') : []
});
}
});
}
/** Load client's date of birth and legal form from parent route resolved data */
loadClientDateOfBirth(): void {
this.route.parent.data.subscribe((data: any) => {
this.clientData = data?.clientViewData || null;
if (data?.clientViewData?.dateOfBirth) {
const dob = data.clientViewData.dateOfBirth;
if (Array.isArray(dob) && dob.length >= 3) {
this.clientDateOfBirth = `${dob[0]}-${String(dob[1]).padStart(2, '0')}-${String(dob[2]).padStart(2, '0')}`;
} else if (typeof dob === 'string') {
this.clientDateOfBirth = dob;
}
}
if (data?.clientViewData?.legalForm?.id === 2) {
this.isNonIndividual = true;
}
});
}
toggleEditMode(): void {
this.isEditMode = !this.isEditMode;
this.resetOtpState();
if (this.isEditMode) {
// Clear Aadhaar so the user types a fresh 12-digit number for re-verify;
// server only returns the masked form which would fail validation.
this.kycForm.get('aadhaarNumber')?.reset('');
this.verificationStatus.aadhaar = null;
this.verificationStatus.pan = null;
this.verificationError.aadhaar = null;
this.verificationError.pan = null;
}
}
/**
* Exception columns derived from verification status — see `deriveKycExceptionFields()`.
*
* A field counts as verified if it was verified in this session OR is already verified on the
* stored record; either way the customer has cleared that check. Verifying one of two
* outstanding items narrows the exception to the remaining one rather than closing it.
*/
private buildExceptionFields() {
const panVerified = this.verificationStatus.pan === 'VERIFIED' || this.kycData?.panVerified === true;
const aadhaarVerified = this.verificationStatus.aadhaar === 'VERIFIED' || this.kycData?.aadhaarVerified === true;
return deriveKycExceptionFields({
panVerified,
aadhaarVerified,
aadhaarApplicable: !this.isNonIndividual,
hadException: this.kycData?.isException === true,
previousReason: this.kycData?.exceptionReason || null,
manualReasons: Array.isArray(this.kycForm.value.exceptionReason) ? this.kycForm.value.exceptionReason : [],
todayFormatted: this.dateUtils.formatDate(new Date(), 'dd MMMM yyyy')
});
}
saveKyc(): void {
if (this.kycForm.invalid) {
return;
}
this.saving = true;
this.errorMessage = null;
const aadhaarRaw = this.kycForm.value.aadhaarNumber || '';
const aadhaarMasked = aadhaarRaw.length === 12 ? 'XXXXXXXX' + aadhaarRaw.slice(-4) : aadhaarRaw || null;
const payload: any = {
locale: 'en',
panNumber: this.kycForm.value.panNumber || null,
aadhaarNumberMasked: aadhaarMasked,
gstinNumber: this.kycForm.value.gstinNumber || null,
remarks: this.kycForm.value.remarks || null,
// dateFormat is required so the backend can parse exceptionResolvedOn when an exception closes.
dateFormat: 'dd MMMM yyyy',
...this.buildExceptionFields()
};
if (!this.kycId) {
payload.kycType = this.kycForm.value.kycType || 1;
payload.riskCategory = this.kycForm.value.riskCategory || 1;
} else {
payload.riskCategory = this.kycForm.value.riskCategory || 1;
}
const save$ = this.kycId
? this.clientsService.updateClientKyc(this.clientId, this.kycId, payload)
: this.clientsService.saveClientKyc(this.clientId, payload);
save$.subscribe({
next: (response: any) => {
this.saving = false;
this.isEditMode = false;
if (!this.kycId && response?.resourceId) {
this.kycId = response.resourceId.toString();
}
this.loadKycData();
},
error: (err: any) => {
this.saving = false;
this.errorMessage =
err?.error?.developerMessage ||
err?.error?.defaultUserMessage ||
err?.message ||
'Failed to save KYC details.';
}
});
}
private saveKycBeforeVerify(callback: () => void, field: 'pan' | 'aadhaar' | 'gstin'): void {
if (this.isEditMode) {
const aadhaarRaw = this.kycForm.value.aadhaarNumber || '';
const aadhaarMasked = aadhaarRaw.length === 12 ? 'XXXXXXXX' + aadhaarRaw.slice(-4) : aadhaarRaw || null;
const payload: any = {
locale: 'en',
panNumber: this.kycForm.value.panNumber || null,
aadhaarNumberMasked: aadhaarMasked,
gstinNumber: this.kycForm.value.gstinNumber || null,
remarks: this.kycForm.value.remarks || null,
riskCategory: this.kycForm.value.riskCategory || 1,
dateFormat: 'dd MMMM yyyy',
...this.buildExceptionFields()
};
this.clientsService.updateClientKyc(this.clientId, this.kycId, payload).subscribe({
next: () => {
this.loadKycData();
callback();
},
error: (err: any) => {
this.verifying[field] = false;
this.verificationStatus[field] = 'FAILED';
this.verificationError[field] = err?.error?.defaultUserMessage || `Failed to update ${field.toUpperCase()} number before verification.`;
}
});
} else {
callback();
}
}
verifyPAN(): void {
if (!this.kycId) {
this.verificationError.pan = 'KYC record not found. Save KYC details first.';
return;
}
this.verifying.pan = true;
this.verificationStatus.pan = 'PENDING';
this.verificationError.pan = null;
this.verificationDetails.pan = null;
this.saveKycBeforeVerify(() => {
this.clientsService
.verifyPAN(this.clientId, this.kycId)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (response: any) => {
this.verifying.pan = false;
if (response?.changes?.panVerified) {
this.verificationStatus.pan = 'VERIFIED';
this.verificationDetails.pan = response.changes;
this.loadKycData();
} else {
this.verificationStatus.pan = 'FAILED';
this.verificationError.pan = 'PAN verification failed.';
}
},
error: (err: any) => {
this.verifying.pan = false;
this.verificationStatus.pan = 'FAILED';
this.verificationError.pan =
err?.error?.developerMessage ||
err?.error?.defaultUserMessage ||
err?.message ||
'PAN verification failed. Please try again.';
}
});
}, 'pan');
}
/** Show Aadhaar input in summary view for re-verification */
startAadhaarReVerify(): void {
this.showAadhaarReVerify = true;
this.aadhaarFullNumber = '';
this.verificationError.aadhaar = null;
}
/** Step 1: Send OTP to Aadhaar-linked mobile */
sendAadhaarOtp(): void {
const aadhaar = this.isEditMode ? this.kycForm?.get('aadhaarNumber')?.value : this.aadhaarFullNumber;
if (!aadhaar || aadhaar.length !== 12) {
this.verificationError.aadhaar = 'Please enter the full 12-digit Aadhaar number to generate OTP.';
return;
}
if (!this.kycId) {
this.verificationError.aadhaar = 'KYC record not found. Save KYC details first.';
return;
}
this.verifying.aadhaar = true;
this.verificationStatus.aadhaar = 'PENDING';
this.verificationError.aadhaar = null;
this.aadhaarOtpSent = false;
this.aadhaarOtp = '';
this.saveKycBeforeVerify(() => {
this.clientsService
.sendAadhaarOtp(this.clientId, this.kycId, aadhaar)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (response: any) => {
this.verifying.aadhaar = false;
const refNumber =
response?.changes?.aadhaarRefNumber ||
response?.aadhaarRefNumber ||
response?.changes?.referenceNumber ||
response?.resourceId;
this.aadhaarOtpSent = true;
this.aadhaarOtpReferenceId = refNumber || null;
this.verificationStatus.aadhaar = 'PENDING';
},
error: (err: any) => {
this.verifying.aadhaar = false;
this.verificationStatus.aadhaar = 'FAILED';
this.verificationError.aadhaar =
err?.error?.developerMessage ||
err?.error?.defaultUserMessage ||
err?.message ||
'Failed to send OTP. Please try again.';
}
});
}, 'aadhaar');
}
/** Step 2: Verify OTP entered by user */
verifyAadhaarOtp(): void {
if (!this.kycId || !this.aadhaarOtp || this.aadhaarOtp.length < 4) {
return;
}
this.aadhaarOtpVerifying = true;
this.verificationError.aadhaar = null;
this.clientsService
.verifyAadhaarOtp(this.clientId, this.kycId, this.aadhaarOtp, this.aadhaarOtpReferenceId || '')
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (response: any) => {
this.aadhaarOtpVerifying = false;
if (response?.changes?.aadhaarVerified) {
this.verificationStatus.aadhaar = 'VERIFIED';
this.verificationDetails.aadhaar = response.changes;
this.resetOtpState();
this.loadKycData();
} else {
this.verificationStatus.aadhaar = 'FAILED';
this.verificationError.aadhaar = 'OTP verification failed.';
this.resetOtpState();
}
},
error: (err: any) => {
this.aadhaarOtpVerifying = false;
this.verificationStatus.aadhaar = 'FAILED';
this.verificationError.aadhaar =
err?.error?.developerMessage ||
err?.error?.defaultUserMessage ||
err?.message ||
'OTP verification failed. Please try again.';
this.resetOtpState();
}
});
}
verifyGSTIN(): void {
if (!this.kycId) {
this.verificationError.gstin = 'KYC record not found. Save KYC details first.';
return;
}
this.verifying.gstin = true;
this.verificationStatus.gstin = 'PENDING';
this.verificationError.gstin = null;
this.verificationDetails.gstin = null;
this.saveKycBeforeVerify(() => {
this.clientsService
.verifyGSTIN(this.clientId, this.kycId)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (response: any) => {
this.verifying.gstin = false;
if (response?.changes?.gstinVerified) {
this.verificationStatus.gstin = 'VERIFIED';
this.verificationDetails.gstin = response.changes;
this.loadKycData();
} else {
this.verificationStatus.gstin = 'FAILED';
this.verificationError.gstin = 'GSTIN verification failed.';
}
},
error: (err: any) => {
this.verifying.gstin = false;
this.verificationStatus.gstin = 'FAILED';
this.verificationError.gstin =
err?.error?.developerMessage ||
err?.error?.defaultUserMessage ||
err?.message ||
'GSTIN verification failed. Please try again.';
}
});
}, 'gstin');
}
formatDate(date: any): string {
if (!date) return '—';
let year: number, month: number, day: number;
if (Array.isArray(date) && date.length >= 3) {
[
year,
month,
day
] = date;
} else {
return date;
}
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
return `${day} ${months[month - 1]} ${year}`;
}
private resetOtpState(): void {
this.aadhaarOtpSent = false;
this.aadhaarOtp = '';
this.aadhaarOtpReferenceId = null;
this.aadhaarOtpVerifying = false;
this.showAadhaarReVerify = false;
this.aadhaarFullNumber = '';
}
getStatusLabel(code: string): string {
if (!code) return '';
const part = code.includes('.') ? code.split('.').pop() : code;
return (part.charAt(0).toUpperCase() + part.slice(1)).replace(/_/g, ' ');
}
getStatusClass(code: string): string {
if (!code) return '';
const part = code.includes('.') ? code.split('.').pop() : code;
return part.toLowerCase();
}
getPanAadhaarStatusLabel(status: string): string {
switch (status) {
case 'Y':
return 'Linked (Seeded)';
case 'N':
return 'Not Linked';
case 'E':
return 'Exempt';
default:
return 'Pending Verification';
}
}
/** Generate Self Declaration PDF with KYC details */
printSelfDeclaration(): void {
const client = this.clientData || {};
const kyc = this.kycData || {};
const clientName = client.displayName || client.fullname || '';
const cifNumber = client.accountNo || client.cifNumber || this.clientId;
const dob = this.clientDateOfBirth ? this.formatDate(client.dateOfBirth) : '—';
const officeName = client.officeName || '';
const today = new Date().toLocaleDateString('en-IN', { day: '2-digit', month: 'long', year: 'numeric' });
const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
const pageWidth = doc.internal.pageSize.getWidth();
const margin = 20;
const contentWidth = pageWidth - margin * 2;
let y = 20;
// Helper
const addLine = (label: string, value: string, yPos: number): number => {
doc.setFont('helvetica', 'bold');
doc.setFontSize(10);
doc.text(label, margin, yPos);
doc.setFont('helvetica', 'normal');
doc.text(value || '—', margin + 55, yPos);
return yPos + 7;
};
const drawHr = (yPos: number): number => {
doc.setDrawColor(200, 200, 200);
doc.setLineWidth(0.3);
doc.line(margin, yPos, pageWidth - margin, yPos);
return yPos + 5;
};
// ── Header ──
doc.setFillColor(15, 23, 42);
doc.rect(0, 0, pageWidth, 30, 'F');
doc.setTextColor(255, 255, 255);
doc.setFont('helvetica', 'bold');
doc.setFontSize(16);
doc.text('Self Declaration Form', pageWidth / 2, 13, { align: 'center' });
doc.setFontSize(9);
doc.setFont('helvetica', 'normal');
doc.text('KYC / Re-KYC Compliance — As per RBI Master Direction on KYC', pageWidth / 2, 21, { align: 'center' });
doc.setTextColor(0, 0, 0);
y = 40;
// ── Branch / Date ──
doc.setFontSize(10);
doc.setFont('helvetica', 'normal');
doc.text(`Branch: ${officeName}`, margin, y);
doc.text(`Date: ${today}`, pageWidth - margin, y, { align: 'right' });
y += 8;
y = drawHr(y);
// ── Section: Customer Details ──
doc.setFont('helvetica', 'bold');
doc.setFontSize(12);
doc.setTextColor(16, 116, 185);
doc.text('Customer Details', margin, y);
doc.setTextColor(0, 0, 0);
y += 8;
y = addLine('Customer Name:', clientName, y);
y = addLine('CIF Number:', cifNumber, y);
y = addLine('Date of Birth:', dob, y);
if (client.mobileNo) y = addLine('Mobile:', client.mobileNo, y);
if (client.emailAddress) y = addLine('Email:', client.emailAddress, y);
y += 3;
y = drawHr(y);
// ── Section: KYC Documents ──
doc.setFont('helvetica', 'bold');
doc.setFontSize(12);
doc.setTextColor(16, 116, 185);
doc.text('KYC Document Details', margin, y);
doc.setTextColor(0, 0, 0);
y += 8;
// PAN
y = addLine('PAN Number:', kyc.panNumber || '—', y);
y = addLine('PAN Status:', kyc.panVerified ? 'Verified' : 'Pending', y);
if (kyc.panVerifiedOn) y = addLine('PAN Verified On:', this.formatDate(kyc.panVerifiedOn), y);
// Aadhaar
y = addLine('Aadhaar Number:', kyc.aadhaarNumberMasked || '—', y);
y = addLine('Aadhaar Status:', kyc.aadhaarVerified ? 'Verified' : 'Pending', y);
if (kyc.aadhaarVerifiedOn) y = addLine('Aadhaar Verified On:', this.formatDate(kyc.aadhaarVerifiedOn), y);
// PAN-Aadhaar Link
const seedingStatus =
kyc.panAadhaarSeedingStatus === 'Y'
? 'Linked'
: kyc.panAadhaarSeedingStatus === 'N'
? 'Not Linked'
: kyc.panAadhaarSeedingStatus === 'E'
? 'Exempt'
: 'Pending';
y = addLine('PAN-Aadhaar Link:', seedingStatus, y);
// GSTIN (Non-Individual)
if (this.isNonIndividual && kyc.gstinNumber) {
y = addLine('GSTIN:', kyc.gstinNumber, y);
y = addLine('GSTIN Status:', kyc.gstinVerified ? 'Verified' : 'Pending', y);
}
// Enterprise PAN / CIN
if (this.isNonIndividual && kyc.enterprisePan) {
y = addLine('Enterprise PAN:', kyc.enterprisePan, y);
}
if (this.isNonIndividual && kyc.cinNumber) {
y = addLine('CIN:', kyc.cinNumber, y);
}
y += 3;
y = drawHr(y);
// ── Section: KYC Summary ──
doc.setFont('helvetica', 'bold');
doc.setFontSize(12);
doc.setTextColor(16, 116, 185);
doc.text('KYC Summary', margin, y);
doc.setTextColor(0, 0, 0);
y += 8;
y = addLine('KYC Type:', kyc.kycType === 1 ? 'Individual' : kyc.kycType === 2 ? 'Non-Individual' : '—', y);
y = addLine(
'Risk Category:',
kyc.riskCategory === 1 ? 'Low' : kyc.riskCategory === 2 ? 'Medium' : kyc.riskCategory === 3 ? 'High' : '—',
y
);
y = addLine('Initiated On:', this.formatDate(kyc.initiatedOn), y);
y = addLine('Last Verified:', this.formatDate(kyc.verifiedOn), y);
y = addLine('Expiry Date:', this.formatDate(kyc.expiryDate), y);
y += 3;
y = drawHr(y);
// ── Declaration Text ──
doc.setFont('helvetica', 'bold');
doc.setFontSize(12);
doc.setTextColor(16, 116, 185);
doc.text('Declaration', margin, y);
doc.setTextColor(0, 0, 0);
y += 8;
doc.setFont('helvetica', 'normal');
doc.setFontSize(9.5);
const declarationText = `I, ${clientName || '_______________'}, hereby declare that the information provided above is true, correct and complete to the best of my knowledge and belief. I understand that furnishing false information is punishable under applicable laws. I authorize the Bank to verify the above details through appropriate channels as per RBI Master Direction on KYC (DBR.AML.BC.No.81/14.01.001/2015-16) and its subsequent amendments.
I further confirm that:
1. I am not a Politically Exposed Person (PEP) / relative of a PEP (unless declared otherwise).
2. The source of funds in my account is from legitimate sources.
3. I shall promptly notify the Bank of any change in the above information.
4. I have read and understood the Bank's terms and conditions regarding KYC compliance.`;
const lines = doc.splitTextToSize(declarationText, contentWidth);
doc.text(lines, margin, y);
y += lines.length * 4.5 + 10;
// ── Signature section ──
y = drawHr(y);
y += 5;
// Left: Client signature
doc.setFont('helvetica', 'normal');
doc.setFontSize(9);
doc.text('________________________', margin, y + 15);
doc.text('Signature of Customer', margin, y + 21);
doc.text(`Name: ${clientName}`, margin, y + 27);
doc.text(`Date: _______________`, margin, y + 33);
doc.text(`Place: _______________`, margin, y + 39);
// Right: Bank official
doc.text('________________________', pageWidth - margin - 50, y + 15);
doc.text('Authorized Bank Official', pageWidth - margin - 50, y + 21);
doc.text('Name: _______________', pageWidth - margin - 50, y + 27);
doc.text('Designation: _______________', pageWidth - margin - 50, y + 33);
doc.text('Stamp & Seal', pageWidth - margin - 50, y + 39);
// ── Footer ──
const footerY = doc.internal.pageSize.getHeight() - 10;
doc.setFontSize(7);
doc.setTextColor(150, 150, 150);
doc.text('This is a system-generated document. Generated from CBS Nexora — KYC Module.', pageWidth / 2, footerY, {
align: 'center'
});
// Save
doc.save(`Self_Declaration_${cifNumber}_${clientName.replace(/\s/g, '_')}.pdf`);
}
/** Handle file selection for signed declaration upload */
onDeclarationFileSelected(event: Event): void {
const input = event.target as HTMLInputElement;
if (input.files?.length) {
this.selectedDeclarationFile = input.files[0];
this.reKycError = null;
}
}
/** Upload signed declaration and complete re-KYC */
completeReKyc(): void {
if (!this.selectedDeclarationFile || !this.clientId || !this.kycId) return;
this.reKycCompleting = true;
this.reKycError = null;
this.reKycSuccess = false;
// Step 1: Upload the signed declaration as a client document
const formData = new FormData();
formData.append('file', this.selectedDeclarationFile);
formData.append('name', 'KYC Self Declaration');
formData.append(
'description',
`Signed self-declaration form uploaded on ${new Date().toLocaleDateString('en-IN')}`
);
this.clientsService.uploadClientDocument(this.clientId, formData).subscribe({
next: () => {
// Step 2: Call re-KYC completion API to update verified/expiry dates
this.clientsService.completeReKyc(this.clientId, this.kycId!).subscribe({
next: () => {
this.reKycCompleting = false;
this.reKycSuccess = true;
this.selectedDeclarationFile = null;
this.loadKycData(); // Refresh to show updated dates
},
error: (err: any) => {
this.reKycCompleting = false;
this.reKycError =
err?.error?.defaultUserMessage ||
err?.error?.developerMessage ||
'Failed to complete re-KYC. Please try again.';
}
});
},
error: (err: any) => {
this.reKycCompleting = false;
this.reKycError =
err?.error?.defaultUserMessage ||
err?.error?.developerMessage ||
'Failed to upload declaration. Please try again.';
}
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}