Files
cbsnexora/src/app/clients/clients-view/personal-data-tab/personal-data-tab.component.ts
T
keshavandClaude Opus 5 7ef75c902d
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
feat: add database export, welcome kit dialog, and DIP/sanctions updates
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 17:52:28 +05:30

203 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
/** Custom Imports */
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
import { DecimalPipe } from '@angular/common';
import { LegalFormId } from 'app/clients/models/legal-form.enum';
/** Interfaces */
interface ClientViewData {
id: number;
accountNo?: string;
externalId?: string;
status?: { id: number; code: string; value: string };
active?: boolean;
activationDate?: number[];
firstname?: string;
middlename?: string;
lastname?: string;
fullname?: string;
displayName?: string;
mobileNo?: string;
emailAddress?: string;
/**
* Current DIP/MNRL status resolved from the latest lookup, as opposed to the `dipVerified`
* flag captured when the client was onboarded.
*/
dipLiveStatus?: string;
alternateDipLiveStatus?: string;
/** Disposition of the latest flagged sanctions alert, or absent when the client carries none. */
sanctionsAlertStatus?: string;
dateOfBirth?: number[];
gender?: { id: number; name: string; active?: boolean };
clientType?: { id: number; name: string; active?: boolean };
clientClassification?: { id: number; name: string; active?: boolean };
legalForm?: { id: number; name: string };
officeId?: number;
officeName?: string;
staffId?: number;
staffName?: string;
savingsProductId?: number;
savingsProductName?: string;
[key: string]: any; // Allow additional properties from API
}
/**
* Personal Data Tab Component.
* Displays all personal/client details in a read-only format.
*/
@Component({
selector: 'mifosx-personal-data-tab',
templateUrl: './personal-data-tab.component.html',
styleUrls: ['./personal-data-tab.component.scss'],
standalone: true,
imports: [
...STANDALONE_SHARED_IMPORTS,
DateFormatPipe,
DecimalPipe
]
})
export class PersonalDataTabComponent {
private route = inject(ActivatedRoute);
/** Client View Data */
clientViewData!: ClientViewData;
/**
* Whether the number's CURRENT DIP/MNRL status is a hit.
*
* The onboarding-time `dipVerified` flag never changes, so a customer cleared when they joined
* kept reading "Verified" even after their number was reported for fraud or reassigned. These
* helpers read `dipLiveStatus`, which the API resolves from the most recent lookup.
*/
isDipFlagged(liveStatus: string | null | undefined): boolean {
return !!liveStatus && liveStatus !== 'CLEAN' && liveStatus !== 'ERROR';
}
/** True only when the number is affirmatively clean — not merely "not flagged". */
isDipClear(client: any): boolean {
if (client?.dipLiveStatus) {
return client.dipLiveStatus === 'CLEAN';
}
return (
client?.mobileVerified === true ||
client?.mobileVerified === 'true' ||
client?.mobileVerified === 1 ||
client?.dipVerified === true ||
client?.dipVerified === 'true' ||
client?.dipVerified === 1
);
}
dipStatusLabel(client: any): string {
return this.labelFor(client?.dipLiveStatus, this.isDipClear(client));
}
/** Headline for the sanctions banner, driven by how far the alert has progressed. */
sanctionsAlertHeadline(client: any): string {
switch (client?.sanctionsAlertStatus) {
case 'CONFIRMED_MATCH':
return 'Confirmed UN Security Council sanctions match.';
case 'FALSE_POSITIVE':
return 'Sanctions hit reviewed as a false positive.';
default:
return 'Sanctions match awaiting Principal Officer review.';
}
}
sanctionsAlertDetail(client: any): string {
switch (client?.sanctionsAlertStatus) {
case 'CONFIRMED_MATCH':
return (
'Debits are frozen on this client’s deposit accounts and the case has been escalated. ' +
'Report to FIU-IND and the nodal officer within 24 hours if not already done.'
);
case 'FALSE_POSITIVE':
return 'Recorded as a different person from the listed individual. See the Sanctions Logs for the reasoning.';
default:
return (
'This client matched the UN Security Council Consolidated List on a later screening. ' +
'Review the alert under Reports → Sanctions Screening & Alerts before transacting.'
);
}
}
alternateDipStatusLabel(client: any): string {
const clear =
client?.alternateDipLiveStatus === 'CLEAN' ||
client?.alternateDipVerified === true ||
client?.alternateDipVerified === 'true' ||
client?.alternateDipVerified === 1;
return this.labelFor(client?.alternateDipLiveStatus, clear);
}
private labelFor(liveStatus: string | null | undefined, clear: boolean): string {
switch (liveStatus) {
case 'SUSPECTED':
return 'Flagged — Suspected';
case 'DISCONNECTED':
return 'Flagged — Disconnected';
case 'SUSPECTED_AND_DISCONNECTED':
return 'Flagged — Suspected & Disconnected';
case 'SERVICE_UNAVAILABLE':
return 'Not Verified';
case 'CLEAN':
return 'Verified';
default:
return clear ? 'Verified' : 'Pending';
}
}
constructor() {
this.route.parent.data.pipe(takeUntilDestroyed()).subscribe((data: { clientViewData: ClientViewData }) => {
this.clientViewData = data.clientViewData;
});
}
/**
* Check if client is a person (individual)
*/
isPerson(): boolean {
return this.clientViewData?.legalForm?.id === LegalFormId.PERSON;
}
/**
* Check if client is a legal entity (organization)
*/
isLegalEntity(): boolean {
return this.clientViewData?.legalForm?.id === LegalFormId.ENTITY;
}
/**
* Format ISO timestamp to DD-MM-YYYY HH:MM:SS
*/
formatDateTime(value: string): string {
if (!value) return '-';
try {
const d = new Date(value);
if (isNaN(d.getTime())) return value;
const dd = String(d.getDate()).padStart(2, '0');
const mm = String(d.getMonth() + 1).padStart(2, '0');
const yyyy = d.getFullYear();
const hh = String(d.getHours()).padStart(2, '0');
const min = String(d.getMinutes()).padStart(2, '0');
const ss = String(d.getSeconds()).padStart(2, '0');
return `${dd}-${mm}-${yyyy} ${hh}:${min}:${ss}`;
} catch {
return value;
}
}
}