feat: client extra fields + KYC home-visit & video-KYC dialogs

- Add Education Level, Degree/Qualification, Religion, Social Category dropdowns (individual clients) with dependent filtering on the create/edit/preview/view flows
- Add Agriculture Income & Non-Agriculture Income fields under Financial & Classification
- Gate Save with a Verify Client alert dialog when sanctions screening is not yet performed
- Trim Office Name and CKYC Number columns from clients list; widen table to fill container
- Remove DIP/Screening log IDs from preview and personal-data tabs
- Rename "Branch Visit" to "Home Visit" across KYC management
- Wire Take Action: open dedicated dialogs for Schedule Home Visit (officer + datetime) and Send Video KYC link (channels + officer + 48h expiry + session preview)
- Migrate Signatory and Guardian image dialogs to inject() for prefer-inject lint rule

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-04-27 16:36:58 +05:30
co-authored by Claude Opus 4.7
parent d72e969dc3
commit 52e3395345
15 changed files with 4021 additions and 997 deletions
@@ -198,6 +198,26 @@ export class CreateClientComponent {
return;
}
// Sanctions verification gate — Verify Client must be clicked before save
if (!this.clientGeneralStep?.sanctionsVerified) {
this.dialog
.open(VerifyClientAlertDialogComponent, {
width: '420px',
autoFocus: false
})
.afterClosed()
.subscribe(() => {
setTimeout(() => {
const verifyBtn = document.querySelector('.verify-name-btn') as HTMLElement;
if (verifyBtn) {
verifyBtn.scrollIntoView({ behavior: 'smooth', block: 'center' });
verifyBtn.focus();
}
}, 100);
});
return;
}
// Show confirmation dialog
const dialogRef = this.dialog.open(SaveConfirmDialogComponent, {
width: '440px',
@@ -236,10 +256,14 @@ export class CreateClientComponent {
}
// Upload photo & signature
if (this.clientKycStep?.photographFile) {
this.clientsService.uploadClientProfileImage(clientId, this.clientKycStep.photographFile).subscribe({ error: () => {} });
this.clientsService
.uploadClientProfileImage(clientId, this.clientKycStep.photographFile)
.subscribe({ error: () => {} });
}
if (this.clientKycStep?.signatureFile) {
this.clientsService.uploadClientSignatureImage(clientId, this.clientKycStep.signatureFile).subscribe({ error: () => {} });
this.clientsService
.uploadClientSignatureImage(clientId, this.clientKycStep.signatureFile)
.subscribe({ error: () => {} });
}
return;
}
@@ -368,16 +392,27 @@ export class CreateClientComponent {
* Finalize: upload photo/signature, save KYC, save addresses, then navigate.
*/
private finalizeClient(clientId: string) {
const navigateToClient = () => this.router.navigate(['../', clientId], { relativeTo: this.route });
const navigateToClient = () =>
this.router.navigate(
[
'../',
clientId
],
{ relativeTo: this.route }
);
// Upload photo if selected
if (this.clientKycStep?.photographFile) {
this.clientsService.uploadClientProfileImage(clientId, this.clientKycStep.photographFile).subscribe({ error: () => {} });
this.clientsService
.uploadClientProfileImage(clientId, this.clientKycStep.photographFile)
.subscribe({ error: () => {} });
}
// Upload signature if selected
if (this.clientKycStep?.signatureFile) {
this.clientsService.uploadClientSignatureImage(clientId, this.clientKycStep.signatureFile).subscribe({ error: () => {} });
this.clientsService
.uploadClientSignatureImage(clientId, this.clientKycStep.signatureFile)
.subscribe({ error: () => {} });
}
// Save addresses from India address step
@@ -409,16 +444,36 @@ export class CreateClientComponent {
const addressData = this.clientIndiaAddressStep.addressData;
// Map tab names to Fineract address type names (as defined in address template)
const typeNameMap: Record<string, string[]> = {
permanent: ['Permanent', 'Permanent Address'],
current: ['Residential', 'Current', 'Current / Residential', 'Communication'],
office: ['Office', 'Office / Work', 'Business', 'Work'],
correspondence: ['Correspondence', 'Mailing', 'Postal']
permanent: [
'Permanent',
'Permanent Address'
],
current: [
'Residential',
'Current',
'Current / Residential',
'Communication'
],
office: [
'Office',
'Office / Work',
'Business',
'Work'
],
correspondence: [
'Correspondence',
'Mailing',
'Postal'
]
};
this.clientsService.getClientAddressTemplate().subscribe({
next: (template: any) => {
const typeOptions = template?.addressTypeIdOptions || [];
for (const [tab, addr] of Object.entries(addressData)) {
for (const [
tab,
addr
] of Object.entries(addressData)) {
if (!addr || !(addr as any).addressLine1) continue;
// Find matching address type ID
@@ -448,8 +503,8 @@ export class CreateClientComponent {
// Try to resolve state to stateProvinceId
if ((addr as any).state && template?.stateProvinceIdOptions?.length) {
const stateMatch = template.stateProvinceIdOptions.find((s: any) =>
s.name?.toLowerCase() === (addr as any).state?.toLowerCase()
const stateMatch = template.stateProvinceIdOptions.find(
(s: any) => s.name?.toLowerCase() === (addr as any).state?.toLowerCase()
);
if (stateMatch) payload.stateProvinceId = stateMatch.id;
}
@@ -535,34 +590,228 @@ export class CreateClientComponent {
</div>
</div>
`,
styles: [`
.scd { background: #fff; border-radius: 12px; overflow: hidden; }
.scd-header {
display: flex; align-items: center; gap: 14px;
padding: 20px 24px 16px;
}
.scd-header__icon-wrap {
width: 44px; height: 44px; border-radius: 12px;
background: #eff6ff; display: flex; align-items: center; justify-content: center;
}
.scd-header__icon { color: #1074b9; font-size: 24px; width: 24px; height: 24px; }
.scd-header__title { margin: 0; font-size: 17px; font-weight: 700; color: #0f172a; }
.scd-header__sub { margin: 2px 0 0; font-size: 12px; color: #64748b; }
.scd-body { padding: 0 24px 20px; }
.scd-body p { margin: 0 0 14px; font-size: 14px; color: #374151; line-height: 1.5; }
.scd-info {
display: flex; align-items: flex-start; gap: 8px;
padding: 10px 14px; background: #f0f9ff; border: 1px solid #bae6fd;
border-radius: 8px; font-size: 12px; color: #0c4a6e;
}
.scd-info__icon { font-size: 16px !important; width: 16px !important; height: 16px !important; color: #0284c7; flex-shrink: 0; margin-top: 1px; }
.scd-actions {
display: flex; justify-content: flex-end; gap: 10px;
padding: 14px 24px; border-top: 1px solid #f1f5f9; background: #f8fafc;
}
.scd-cancel, .scd-confirm { display: flex !important; align-items: center; gap: 6px; font-size: 13px !important; height: 38px !important; border-radius: 8px !important; }
.scd-confirm { font-weight: 600 !important; }
`],
imports: [MatIcon, MatDialogClose]
styles: [
`
.scd {
background: #fff;
border-radius: 12px;
overflow: hidden;
}
.scd-header {
display: flex;
align-items: center;
gap: 14px;
padding: 20px 24px 16px;
}
.scd-header__icon-wrap {
width: 44px;
height: 44px;
border-radius: 12px;
background: #eff6ff;
display: flex;
align-items: center;
justify-content: center;
}
.scd-header__icon {
color: #1074b9;
font-size: 24px;
width: 24px;
height: 24px;
}
.scd-header__title {
margin: 0;
font-size: 17px;
font-weight: 700;
color: #0f172a;
}
.scd-header__sub {
margin: 2px 0 0;
font-size: 12px;
color: #64748b;
}
.scd-body {
padding: 0 24px 20px;
}
.scd-body p {
margin: 0 0 14px;
font-size: 14px;
color: #374151;
line-height: 1.5;
}
.scd-info {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 10px 14px;
background: #f0f9ff;
border: 1px solid #bae6fd;
border-radius: 8px;
font-size: 12px;
color: #0c4a6e;
}
.scd-info__icon {
font-size: 16px !important;
width: 16px !important;
height: 16px !important;
color: #0284c7;
flex-shrink: 0;
margin-top: 1px;
}
.scd-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid #f1f5f9;
background: #f8fafc;
}
.scd-cancel,
.scd-confirm {
display: flex !important;
align-items: center;
gap: 6px;
font-size: 13px !important;
height: 38px !important;
border-radius: 8px !important;
}
.scd-confirm {
font-weight: 600 !important;
}
`
],
imports: [
MatIcon,
MatDialogClose
]
})
export class SaveConfirmDialogComponent {}
/**
* Verify Client Required Alert Dialog
*/
@Component({
selector: 'mifosx-verify-client-alert-dialog',
template: `
<div class="vcad">
<div class="vcad-header">
<div class="vcad-header__icon-wrap">
<mat-icon class="vcad-header__icon">shield</mat-icon>
</div>
<div>
<h3 class="vcad-header__title">Verification Required</h3>
<p class="vcad-header__sub">Client name must be screened before saving</p>
</div>
</div>
<div class="vcad-body">
<p>
Please click <strong>Verify Client</strong> to screen the client name against the UN Security Council
Consolidated Sanctions List before proceeding.
</p>
<div class="vcad-info">
<mat-icon class="vcad-info__icon">gavel</mat-icon>
<span
>RBI KYC Master Direction (2016) — Section 51 &amp; PMLA 2002. Sanctions screening is mandatory before
account opening.</span
>
</div>
</div>
<div class="vcad-actions">
<button mat-flat-button color="primary" [mat-dialog-close]="true" class="vcad-ok">
<mat-icon>check</mat-icon>
OK, Verify Now
</button>
</div>
</div>
`,
styles: [
`
.vcad {
background: #fff;
border-radius: 12px;
overflow: hidden;
}
.vcad-header {
display: flex;
align-items: center;
gap: 14px;
padding: 20px 24px 16px;
}
.vcad-header__icon-wrap {
width: 44px;
height: 44px;
border-radius: 12px;
background: #fef3c7;
display: flex;
align-items: center;
justify-content: center;
}
.vcad-header__icon {
color: #d97706;
font-size: 24px;
width: 24px;
height: 24px;
}
.vcad-header__title {
margin: 0;
font-size: 17px;
font-weight: 700;
color: #0f172a;
}
.vcad-header__sub {
margin: 2px 0 0;
font-size: 12px;
color: #64748b;
}
.vcad-body {
padding: 0 24px 20px;
}
.vcad-body p {
margin: 0 0 14px;
font-size: 14px;
color: #374151;
line-height: 1.5;
}
.vcad-info {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 10px 14px;
background: #fff7ed;
border: 1px solid #fed7aa;
border-radius: 8px;
font-size: 12px;
color: #7c2d12;
}
.vcad-info__icon {
font-size: 16px !important;
width: 16px !important;
height: 16px !important;
color: #d97706;
flex-shrink: 0;
margin-top: 1px;
}
.vcad-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid #f1f5f9;
background: #f8fafc;
}
.vcad-ok {
display: flex !important;
align-items: center;
gap: 6px;
font-size: 13px !important;
height: 38px !important;
border-radius: 8px !important;
font-weight: 600 !important;
}
`
],
imports: [
MatIcon,
MatDialogClose
]
})
export class VerifyClientAlertDialogComponent {}