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:
co-authored by
Claude Opus 4.7
parent
d72e969dc3
commit
52e3395345
@@ -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 & 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 {}
|
||||
|
||||
Reference in New Issue
Block a user