merging code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nitesh Yadav
2026-04-20 14:26:25 +05:30
co-authored by Claude Sonnet 4.6
322 changed files with 28779 additions and 4976 deletions
@@ -9,7 +9,7 @@
/** Angular Imports */
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subject, takeUntil, catchError, of } from 'rxjs';
import { Subject, takeUntil, catchError, of, forkJoin } from 'rxjs';
/** Custom Services */
import { ClientsService } from '../../clients.service';
@@ -19,6 +19,17 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
interface ExistingArtefact {
present: boolean;
name?: string;
source?: string;
previewUrl?: string;
documentId?: string;
documentKey?: string;
fileCount?: number;
expanded?: boolean;
}
@Component({
selector: 'mifosx-ckyc-tab',
templateUrl: './ckyc-tab.component.html',
@@ -41,6 +52,10 @@ export class CkycTabComponent implements OnInit, OnDestroy {
/** Top section - read-only client info */
clientId: string;
clientName: string = '';
clientEmail: string = '';
clientMobile: string = '';
cifNumber: string = '';
ckycNumber: string = '';
allAccounts: any[] = [];
selectedAccount: any = null;
selectedAccountType: string = '';
@@ -48,6 +63,17 @@ export class CkycTabComponent implements OnInit, OnDestroy {
/** State */
loading = true;
ckycData: any = null;
ckycUploadedOn = '';
ckycBatchId = '';
checkingExisting = false;
/** Pre-existing artefacts detected from client data */
existing: Record<'ADDRESS_PROOF' | 'PHOTO_PROOF' | 'CLIENT_IMAGE' | 'CLIENT_SIGNATURE', ExistingArtefact> = {
ADDRESS_PROOF: { present: false },
PHOTO_PROOF: { present: false },
CLIENT_IMAGE: { present: false },
CLIENT_SIGNATURE: { present: false }
};
/** File references */
addressProofFile: File | null = null;
@@ -80,13 +106,18 @@ export class CkycTabComponent implements OnInit, OnDestroy {
this.loadClientInfo();
this.loadAccounts();
this.loadCkycData();
this.loadExistingArtefacts();
});
}
private loadClientInfo(): void {
this.route.parent.data.subscribe((data: any) => {
if (data?.clientViewData) {
this.clientName = data.clientViewData.displayName || '';
const cv = data?.clientViewData;
if (cv) {
this.clientName = cv.displayName || '';
this.clientEmail = cv.emailAddress || '';
this.clientMobile = cv.mobileNo || '';
this.cifNumber = cv.accountNo || cv.externalId || '';
}
});
}
@@ -122,8 +153,10 @@ export class CkycTabComponent implements OnInit, OnDestroy {
private loadCkycData(): void {
this.loading = true;
// Read CKYC registry data from /clients/{id}/kyc — that's where the
// SFTP upload flow persists ckycNumber / ckycUploadedOn / ckycBatchId.
this.clientsService
.getClientCkyc(this.clientId)
.getClientKyc(this.clientId)
.pipe(
takeUntil(this.destroy$),
catchError(() => {
@@ -134,9 +167,120 @@ export class CkycTabComponent implements OnInit, OnDestroy {
.subscribe((data: any) => {
this.loading = false;
this.ckycData = data;
this.ckycNumber = data?.ckycNumber || '';
this.ckycUploadedOn = this.formatJavaDate(data?.ckycUploadedOn);
this.ckycBatchId = data?.ckycBatchId || '';
});
}
private formatJavaDate(value: any): string {
if (!value) return '';
if (Array.isArray(value) && value.length >= 3) {
const [
y,
m,
d
] = value;
return `${String(d).padStart(2, '0')}-${String(m).padStart(2, '0')}-${y}`;
}
if (typeof value === 'string') return value;
return '';
}
/**
* Fetch photo (profile image), signature (client document),
* ID proof and address proof (client identifiers) so already-uploaded
* artefacts are shown here instead of forcing re-upload.
*/
private loadExistingArtefacts(): void {
this.checkingExisting = true;
forkJoin({
photo: this.clientsService.getClientProfileImage(this.clientId).pipe(catchError(() => of(null))),
documents: this.clientsService.getClientDocuments(this.clientId).pipe(catchError(() => of([]))),
identifiers: this.clientsService.getClientIdentifiers(this.clientId).pipe(catchError(() => of([]))),
proofTypes: this.clientsService.getKycProofTypes().pipe(catchError(() => of({ idProofs: [], addressProofs: [] })))
})
.pipe(takeUntil(this.destroy$))
.subscribe(({ photo, documents, identifiers, proofTypes }) => {
this.checkingExisting = false;
// 1. Photo / image of client — from m_client.image_id
if (photo && typeof photo === 'string' && photo.length > 0) {
this.existing.CLIENT_IMAGE = {
present: true,
source: 'Client registration',
previewUrl: photo,
name: 'Profile photo'
};
}
// 2. Signature — client document with name='clientSignature'
const signatureDoc = (documents as any[])?.find((d: any) => d?.name === 'clientSignature');
if (signatureDoc) {
this.existing.CLIENT_SIGNATURE = {
present: true,
source: 'Client registration',
name: signatureDoc.fileName || 'Client signature',
documentId: signatureDoc.id
};
// Fetch blob for preview
this.clientsService
.getClientSignatureImage(this.clientId, signatureDoc.id)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (blob: any) => {
if (blob) {
this.existing.CLIENT_SIGNATURE.previewUrl = URL.createObjectURL(blob);
}
},
error: () => undefined
});
}
// 3 & 4. ID proof + Address proof — client identifiers classified via proof-types
const idProofIds = new Set<string>((proofTypes?.idProofs || []).map((p: any) => String(p.id)));
const addressProofIds = new Set<string>((proofTypes?.addressProofs || []).map((p: any) => String(p.id)));
const idList = Array.isArray(identifiers) ? identifiers : (identifiers as any)?.pageItems || [];
for (const identity of idList) {
const docTypeId = String(identity?.documentType?.id ?? '');
const isActive =
!identity?.status ||
identity?.status?.code === 'clientIdentifierStatusType.active' ||
identity?.status === 'clientIdentifierStatusType.active' ||
identity?.status === 'Active';
if (!isActive) continue;
const proofName = identity?.documentType?.name || 'Identifier';
// Address proof documentKey is auto-generated as "<ProofName> - <clientId>"
// in identities-tab; hide it when it's just that synthetic key.
const rawKey = identity?.documentKey || '';
const isSyntheticKey = rawKey.startsWith(`${proofName} - `);
const common = {
present: true,
source: 'Identities section',
name: proofName,
documentKey: isSyntheticKey ? '' : rawKey,
documentId: identity?.id,
fileCount: (identity?.documents || []).length
};
if (idProofIds.has(docTypeId) && !this.existing.PHOTO_PROOF.present) {
this.existing.PHOTO_PROOF = common;
} else if (addressProofIds.has(docTypeId) && !this.existing.ADDRESS_PROOF.present) {
this.existing.ADDRESS_PROOF = common;
}
}
});
}
toggleReplace(documentType: string): void {
const entry = (this.existing as any)[documentType];
if (entry) entry.expanded = !entry.expanded;
}
onAccountSelect(accountNo: string): void {
this.selectedAccount = this.allAccounts.find((a) => a.accountNo === accountNo) || null;
this.selectedAccountType = this.selectedAccount?.accountType || '';
@@ -215,5 +359,9 @@ export class CkycTabComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
// Release object URLs
Object.values(this.existing).forEach((e) => {
if (e.previewUrl?.startsWith('blob:')) URL.revokeObjectURL(e.previewUrl);
});
}
}