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
@@ -89,6 +89,38 @@ export class KycManagementComponent {
toastVisible = false;
private toastTimer: any;
/** Home Visit scheduling dialog */
homeVisitOpen = false;
homeVisitOfficer = '';
homeVisitDateTime = '';
homeVisitNote = '';
bankOfficers = [
'Amit Sharma — Branch Officer (Bhopal Main)',
'Neha Verma — KYC Officer (Bhopal Main)',
'Ravi Kulkarni — Field Officer (Bhopal Zone-2)',
'Sunita Patel — Compliance Officer (Bhopal Main)',
'Manoj Tiwari — Field Officer (Indore Zone-1)'
];
scheduledHomeVisits: { client: string; address: string; officer: string; dateTime: string; note: string }[] = [];
/** Video KYC link dialog */
vkycOpen = false;
vkycOfficer = '';
vkycExpiry = '';
vkycChannels = { sms: true, email: true, whatsapp: false };
vkycMessage = '';
vkycSessionId = '';
sentVkycLinks: {
client: string;
mobile: string;
email: string;
officer: string;
expiry: string;
channels: string[];
sessionId: string;
link: string;
}[] = [];
clients: Record<string, KmClient> = {
rajesh: {
id: 'rajesh',
@@ -119,14 +151,35 @@ export class KycManagementComponent {
branch: 'Bhopal Main'
},
notices: [
{ no: 'N1', channel: 'SMS + Email', date: '31 Mar 2027', ref: 'MSG-2027-0331-001', delivery: 'Delivered', response: 'No Action' },
{ no: 'N2', channel: 'Reg. Post + IVR', date: '10 Apr 2027', ref: 'RP-BPL-20270410-443', delivery: 'In Transit', response: 'Pending' }
{
no: 'N1',
channel: 'SMS + Email',
date: '31 Mar 2027',
ref: 'MSG-2027-0331-001',
delivery: 'Delivered',
response: 'No Action'
},
{
no: 'N2',
channel: 'Reg. Post + IVR',
date: '10 Apr 2027',
ref: 'RP-BPL-20270410-443',
delivery: 'In Transit',
response: 'Pending'
}
],
noticesSent: 2,
noticePending: 'Notice 3',
nextStage: 'Debit Freeze',
pendingActions: ['Send Notice 3 (due 20 Apr 2027)', 'Schedule Branch Visit', 'Send Video KYC link'],
actionTaken: ['Notice 1 delivered', 'Notice 2 dispatched via Reg. Post'],
pendingActions: [
'Send Notice 3 (due 20 Apr 2027)',
'Schedule Home Visit',
'Send Video KYC link'
],
actionTaken: [
'Notice 1 delivered',
'Notice 2 dispatched via Reg. Post'
],
evidence: null
},
priya: {
@@ -158,15 +211,45 @@ export class KycManagementComponent {
branch: 'Bhopal Main'
},
notices: [
{ no: 'N1', channel: 'SMS + Email', date: '10 Mar 2027', ref: 'MSG-2027-0310-003', delivery: 'Delivered', response: 'No Response' },
{ no: 'N2', channel: 'Reg. Post', date: '20 Mar 2027', ref: 'RP-BPL-20270320-091', delivery: 'Delivered', response: 'No Response' },
{ no: 'N3', channel: 'Branch Visit', date: '30 Mar 2027', ref: 'VIS-BPL-20270330-012', delivery: 'Visit Done', response: 'Refused' }
{
no: 'N1',
channel: 'SMS + Email',
date: '10 Mar 2027',
ref: 'MSG-2027-0310-003',
delivery: 'Delivered',
response: 'No Response'
},
{
no: 'N2',
channel: 'Reg. Post',
date: '20 Mar 2027',
ref: 'RP-BPL-20270320-091',
delivery: 'Delivered',
response: 'No Response'
},
{
no: 'N3',
channel: 'Home Visit',
date: '30 Mar 2027',
ref: 'VIS-BPL-20270330-012',
delivery: 'Visit Done',
response: 'Refused'
}
],
noticesSent: 3,
noticePending: 'None — all 3 sent',
nextStage: 'Inoperative by 30 Jun 2027',
pendingActions: ['Account in Debit Freeze', 'Inoperative transition due 30 Jun 2027', 'Maker-checker unfreeze pending'],
actionTaken: ['Notice 1 delivered', 'Notice 2 delivered via Reg. Post', 'Notice 3 — Branch visit done, client refused', 'Debit Freeze applied 10 Apr 2027'],
pendingActions: [
'Account in Debit Freeze',
'Inoperative transition due 30 Jun 2027',
'Maker-checker unfreeze pending'
],
actionTaken: [
'Notice 1 delivered',
'Notice 2 delivered via Reg. Post',
'Notice 3 — Branch visit done, client refused',
'Debit Freeze applied 10 Apr 2027'
],
evidence: {
type: 'visit',
date: '30 Mar 2027',
@@ -206,15 +289,46 @@ export class KycManagementComponent {
branch: 'Bhopal South'
},
notices: [
{ no: 'N1', channel: 'SMS + Email', date: '13 Feb 2027', ref: 'MSG-2027-0213-006', delivery: 'Delivered', response: 'No Response' },
{ no: 'N2', channel: 'Reg. Post + IVR', date: '23 Feb 2027', ref: 'RP-BPL-20270223-044', delivery: 'Delivered', response: 'No Response' },
{ no: 'N3', channel: 'Video KYC', date: '5 Mar 2027', ref: 'VKYC-2027-0305-007', delivery: 'No Response', response: 'No Action' }
{
no: 'N1',
channel: 'SMS + Email',
date: '13 Feb 2027',
ref: 'MSG-2027-0213-006',
delivery: 'Delivered',
response: 'No Response'
},
{
no: 'N2',
channel: 'Reg. Post + IVR',
date: '23 Feb 2027',
ref: 'RP-BPL-20270223-044',
delivery: 'Delivered',
response: 'No Response'
},
{
no: 'N3',
channel: 'Video KYC',
date: '5 Mar 2027',
ref: 'VKYC-2027-0305-007',
delivery: 'No Response',
response: 'No Action'
}
],
noticesSent: 3,
noticePending: 'None',
nextStage: 'Dormant by 30 Sep 2027',
pendingActions: ['Account Inoperative — all txns blocked', 'Dormant transition due 30 Sep 2027', 'RBI Dormant Account reporting'],
actionTaken: ['Notice 1 delivered', 'Notice 2 delivered via Reg. Post', 'Notice 3 — Video KYC invite sent (no response)', 'Debit Freeze 15 Mar 2027', 'Inoperative 15 May 2027'],
pendingActions: [
'Account Inoperative — all txns blocked',
'Dormant transition due 30 Sep 2027',
'RBI Dormant Account reporting'
],
actionTaken: [
'Notice 1 delivered',
'Notice 2 delivered via Reg. Post',
'Notice 3 — Video KYC invite sent (no response)',
'Debit Freeze 15 Mar 2027',
'Inoperative 15 May 2027'
],
evidence: {
type: 'vkyc',
date: '5 Mar 2027',
@@ -252,22 +366,54 @@ export class KycManagementComponent {
branch: 'Bhopal East'
},
notices: [
{ no: 'N1', channel: 'SMS + Email', date: '1 Dec 2026', ref: 'MSG-2026-1201-009', delivery: 'Delivered', response: 'No Response' },
{ no: 'N2', channel: 'Reg. Post', date: '11 Dec 2026', ref: 'RP-BPL-20261211-077', delivery: 'Delivered', response: 'No Response' },
{ no: 'N3', channel: 'Branch Visit', date: '22 Dec 2026', ref: 'VIS-BPL-20261222-019', delivery: 'Not Found', response: 'Not Found at Address' }
{
no: 'N1',
channel: 'SMS + Email',
date: '1 Dec 2026',
ref: 'MSG-2026-1201-009',
delivery: 'Delivered',
response: 'No Response'
},
{
no: 'N2',
channel: 'Reg. Post',
date: '11 Dec 2026',
ref: 'RP-BPL-20261211-077',
delivery: 'Delivered',
response: 'No Response'
},
{
no: 'N3',
channel: 'Home Visit',
date: '22 Dec 2026',
ref: 'VIS-BPL-20261222-019',
delivery: 'Not Found',
response: 'Not Found at Address'
}
],
noticesSent: 3,
noticePending: 'None',
nextStage: 'Dormant (Final) — RBI Reporting',
pendingActions: ['RBI Dormant Account Report submission', 'Branch Manager approval for any reversal', 'Regional Manager sign-off required'],
actionTaken: ['Notice 1–3 sent, all no response', 'Debit Freeze 1 Jan 2027', 'Inoperative 2 Mar 2027', 'Dormant 1 Jun 2027', 'RBI report auto-generated'],
pendingActions: [
'RBI Dormant Account Report submission',
'Branch Manager approval for any reversal',
'Regional Manager sign-off required'
],
actionTaken: [
'Notice 1–3 sent, all no response',
'Debit Freeze 1 Jan 2027',
'Inoperative 2 Mar 2027',
'Dormant 1 Jun 2027',
'RBI report auto-generated'
],
evidence: {
type: 'visit',
date: '22 Dec 2026',
officer: 'Rekha Sharma',
gps: '23.2491° N, 77.4384° E',
location: 'Awadhpuri, Bhopal',
outcome: 'Client not found at registered address. Neighbours confirmed relocation. Address verification required.',
outcome:
'Client not found at registered address. Neighbours confirmed relocation. Address verification required.',
docket: 'VIS-BPL-20261222-019'
}
},
@@ -300,12 +446,23 @@ export class KycManagementComponent {
branch: 'Bhopal Main'
},
notices: [
{ no: 'N1', channel: 'SMS + Email', date: '5 Apr 2027', ref: 'MSG-2027-0405-011', delivery: 'Delivered', response: 'No Action' }
{
no: 'N1',
channel: 'SMS + Email',
date: '5 Apr 2027',
ref: 'MSG-2027-0405-011',
delivery: 'Delivered',
response: 'No Action'
}
],
noticesSent: 1,
noticePending: 'Notice 2 & 3',
nextStage: 'Debit Freeze if no action',
pendingActions: ['Send Notice 2 (due 15 Apr 2027)', 'Send Notice 3 (due 25 Apr 2027)', 'Branch visit if needed'],
pendingActions: [
'Send Notice 2 (due 15 Apr 2027)',
'Send Notice 3 (due 25 Apr 2027)',
'Branch visit if needed'
],
actionTaken: ['Notice 1 delivered 5 Apr 2027'],
evidence: null
},
@@ -338,14 +495,35 @@ export class KycManagementComponent {
branch: 'Bhopal North'
},
notices: [
{ no: 'N1', channel: 'SMS + Email', date: '29 Mar 2027', ref: 'MSG-2027-0329-014', delivery: 'Delivered', response: 'No Action' },
{ no: 'N2', channel: 'Reg. Post + IVR', date: '8 Apr 2027', ref: 'RP-BPL-20270408-088', delivery: 'Delivered', response: 'No Response' }
{
no: 'N1',
channel: 'SMS + Email',
date: '29 Mar 2027',
ref: 'MSG-2027-0329-014',
delivery: 'Delivered',
response: 'No Action'
},
{
no: 'N2',
channel: 'Reg. Post + IVR',
date: '8 Apr 2027',
ref: 'RP-BPL-20270408-088',
delivery: 'Delivered',
response: 'No Response'
}
],
noticesSent: 2,
noticePending: 'URGENT: Notice 3',
nextStage: 'Debit Freeze in 3 days',
pendingActions: ['URGENT: Send Notice 3 NOW', 'Schedule Branch Visit immediately', 'Consider Video KYC as last resort'],
actionTaken: ['Notice 1 delivered', 'Notice 2 delivered via Reg. Post + IVR'],
pendingActions: [
'URGENT: Send Notice 3 NOW',
'Schedule Home Visit immediately',
'Consider Video KYC as last resort'
],
actionTaken: [
'Notice 1 delivered',
'Notice 2 delivered via Reg. Post + IVR'
],
evidence: null
}
};
@@ -384,6 +562,108 @@ export class KycManagementComponent {
this.toastTimer = setTimeout(() => (this.toastVisible = false), 2800);
}
/**
* Routes a "Take Action" click. Opens the Home Visit dialog if the action is
* a home-visit scheduling action; otherwise falls back to a toast confirmation.
*/
takeAction(action: string): void {
if (!this.current) return;
const lower = action.toLowerCase();
if (lower.includes('home visit')) {
this.openHomeVisitDialog();
return;
}
if (lower.includes('video kyc')) {
this.openVkycDialog();
return;
}
this.toast(action + ' initiated for ' + this.current.name);
}
openHomeVisitDialog(): void {
this.homeVisitOfficer = '';
this.homeVisitNote = '';
// Default to tomorrow at 10:00 AM (datetime-local format: YYYY-MM-DDTHH:mm)
const t = new Date();
t.setDate(t.getDate() + 1);
t.setHours(10, 0, 0, 0);
const pad = (n: number) => n.toString().padStart(2, '0');
this.homeVisitDateTime = `${t.getFullYear()}-${pad(t.getMonth() + 1)}-${pad(t.getDate())}T${pad(t.getHours())}:${pad(t.getMinutes())}`;
this.homeVisitOpen = true;
}
closeHomeVisitDialog(): void {
this.homeVisitOpen = false;
}
saveHomeVisit(): void {
if (!this.current || !this.homeVisitOfficer || !this.homeVisitDateTime) return;
this.scheduledHomeVisits.push({
client: this.current.name,
address: this.current.contact.address,
officer: this.homeVisitOfficer,
dateTime: this.homeVisitDateTime,
note: this.homeVisitNote
});
const when = new Date(this.homeVisitDateTime).toLocaleString('en-IN', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
this.toast(`Home Visit scheduled for ${this.current.name} on ${when}`);
this.homeVisitOpen = false;
}
openVkycDialog(): void {
this.vkycOfficer = '';
this.vkycMessage = '';
this.vkycChannels = { sms: true, email: true, whatsapp: false };
// Default: link expires 48 hours from now (RBI Video-based KYC norms)
const t = new Date();
t.setHours(t.getHours() + 48);
const pad = (n: number) => n.toString().padStart(2, '0');
this.vkycExpiry = `${t.getFullYear()}-${pad(t.getMonth() + 1)}-${pad(t.getDate())}T${pad(t.getHours())}:${pad(t.getMinutes())}`;
// Generate a session id (token preview only — server would issue the real one)
this.vkycSessionId = 'VKYC-' + Math.random().toString(36).slice(2, 10).toUpperCase();
this.vkycOpen = true;
}
closeVkycDialog(): void {
this.vkycOpen = false;
}
get vkycSelectedChannels(): string[] {
const out: string[] = [];
if (this.vkycChannels.sms) out.push('SMS');
if (this.vkycChannels.email) out.push('Email');
if (this.vkycChannels.whatsapp) out.push('WhatsApp');
return out;
}
get vkycLink(): string {
return `https://vkyc.bank.example/session/${this.vkycSessionId}`;
}
saveVkyc(): void {
if (!this.current || !this.vkycOfficer || !this.vkycExpiry) return;
const channels = this.vkycSelectedChannels;
if (channels.length === 0) return;
this.sentVkycLinks.push({
client: this.current.name,
mobile: this.current.contact.mobile,
email: this.current.contact.email,
officer: this.vkycOfficer,
expiry: this.vkycExpiry,
channels,
sessionId: this.vkycSessionId,
link: this.vkycLink
});
this.toast(`Video KYC link sent to ${this.current.name} via ${channels.join(' + ')}`);
this.vkycOpen = false;
}
/** Compliance journey stepper state for a given client */
getStepperState(c: KmClient): { label: string; state: 'done' | 'pending' | 'current' | 'danger' }[] {
return [
@@ -393,19 +673,20 @@ export class KycManagementComponent {
{
label: 'Notice 3',
state:
c.noticesSent >= 3
? 'done'
: c.noticesSent >= 2
? c.state === 'urgent'
? 'danger'
: 'current'
: 'pending'
c.noticesSent >= 3 ? 'done' : c.noticesSent >= 2 ? (c.state === 'urgent' ? 'danger' : 'current') : 'pending'
},
{
label: 'Debit Freeze',
state: ['freeze', 'inop', 'dormant'].includes(c.state) ? 'done' : c.noticesSent >= 3 ? 'danger' : 'pending'
state: [
'freeze',
'inop',
'dormant'
].includes(c.state) ? 'done' : c.noticesSent >= 3 ? 'danger' : 'pending'
},
{ label: 'Inoperative', state: ['inop', 'dormant'].includes(c.state) ? 'done' : 'pending' },
{ label: 'Inoperative', state: [
'inop',
'dormant'
].includes(c.state) ? 'done' : 'pending' },
{ label: 'Dormant', state: c.state === 'dormant' ? 'danger' : 'pending' }
];
}
@@ -418,26 +699,30 @@ export class KycManagementComponent {
stateLabel(state: string): string {
return (
{
alert: 'Alert Window',
freeze: 'Debit Freeze',
inop: 'Inoperative',
dormant: 'Dormant',
urgent: 'Urgent Alert'
} as Record<string, string>
)[state] || state;
(
{
alert: 'Alert Window',
freeze: 'Debit Freeze',
inop: 'Inoperative',
dormant: 'Dormant',
urgent: 'Urgent Alert'
} as Record<string, string>
)[state] || state
);
}
stateClass(state: string): string {
return (
{
alert: 'sp-al',
freeze: 'sp-fr',
inop: 'sp-in',
dormant: 'sp-do',
urgent: 'sp-fr'
} as Record<string, string>
)[state] || 'sp-al';
(
{
alert: 'sp-al',
freeze: 'sp-fr',
inop: 'sp-in',
dormant: 'sp-do',
urgent: 'sp-fr'
} as Record<string, string>
)[state] || 'sp-al'
);
}
riskClass(risk: string): string {