feat: add 11 banking compliance & accounting report modules

Adds dialog components and feature modules for RBI/banking compliance,
KYC, and accounting reports. Wires them into the shell menu (RBI,
General Ledger, CKYC Mgmt, Others, Insurance) and dashboard cards.

Dialog components (lazy-loaded MatDialog):
- PSL Returns (Form III) — dashboard card + RBI menu
- ALM Statement of Structural Liquidity (Form ALM-1) — RBI menu
- CIMS / XBRL Returns (OSS-2 / PSA / RAL / RCP-UCB) — RBI menu, shared
  dialog with form-pill switcher
- CIC DQR (CIBIL / Experian / Equifax / CRIF, 72-col TUDF) — Others menu
  + dashboard card
- CKYC Compliance Reports (Re-KYC / Risk Review / CERSAI) — CKYC Mgmt
- Accounting Reports (P&L / Balance Sheet / Comparative B/S / GL DR/CR
  / Misc Expense Ledger) — General Ledger menu

Feature modules (Route.withShell):
- DEAF (/deaf): Form I, Form II, Annex 4, accounts register
- DICGC (/dicgc): Slab Report, SAHAY Depositor List
- FIU-IND (/fiu): FINnet 2.0 Statement of Financial Transactions
- SFT (/sft): Income Tax Section 285BA SFT
- CKYC Reports (/ckyc/ckyc-reports): standalone dashboard

UI:
- Theme-colored icons on 22 wired menu links across 5 menus —
  visually distinguishes wired (colored) from placeholder (gray) links.
- Backdrop classes per dialog in main.scss.

All using dummy data (Bharat Urban Bank Ltd, Karthik Kamath as Principal
Officer, dummy customer names) — no real bank or customer references.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-04-29 18:59:50 +05:30
co-authored by Claude Opus 4.7
parent 634003a75c
commit 12bfc81219
61 changed files with 22207 additions and 725 deletions
+8
View File
@@ -55,6 +55,10 @@ import { KycManagementModule } from './kyc-management/kyc-management.module';
import { RbiCircularsModule } from './rbi-circulars/rbi-circulars.module';
import { CfcfrmsModule } from './cfcfrms/cfcfrms.module';
import { BankCalendarModule } from './bank-calendar/bank-calendar.module';
import { DeafModule } from './deaf/deaf.module';
import { DicgcModule } from './dicgc/dicgc.module';
import { FiuModule } from './fiu/fiu.module';
import { SftModule } from './sft/sft.module';
import { PortalModule } from '@angular/cdk/portal';
/** Main Routing Module */
@@ -146,6 +150,10 @@ export function HttpLoaderFactory(http: HttpClient) {
RbiCircularsModule,
CfcfrmsModule,
BankCalendarModule,
DeafModule,
DicgcModule,
FiuModule,
SftModule,
AppRoutingModule,
NotFoundComponent,
CallbackComponent,
@@ -13,16 +13,9 @@
<fa-icon icon="chart-pie" class="ck-head-ic"></fa-icon>
<div>
<h2 class="ck-head-t">CKYC Reports</h2>
<div class="ck-head-s">Customer KYC analytics, file ingestion &amp; search</div>
<div class="ck-head-s">Customer KYC analytics &amp; search</div>
</div>
</div>
@if (upload.fileName) {
<div class="ck-file-tag">
<fa-icon icon="file-csv"></fa-icon>
<span>{{ upload.fileName }}</span>
<span class="ck-file-meta">· {{ upload.customers }} customers · {{ upload.totalRecords }} records</span>
</div>
}
</div>
<!-- TAB BAR -->
@@ -30,9 +23,6 @@
<button class="ck-tab" [class.active]="activeTab === 'dashboard'" (click)="showTab('dashboard')">
<fa-icon icon="chart-pie"></fa-icon> Dashboard
</button>
<button class="ck-tab" [class.active]="activeTab === 'upload'" (click)="showTab('upload')">
<fa-icon icon="upload"></fa-icon> File Upload
</button>
<button class="ck-tab" [class.active]="activeTab === 'customers'" (click)="showTab('customers')">
<fa-icon icon="users"></fa-icon> Customer Report
</button>
@@ -117,96 +107,6 @@
</div>
}
<!-- FILE UPLOAD -->
@if (activeTab === 'upload') {
<div class="ck-section">
<div class="ck-up-card">
<div
class="ck-up-drop"
[class.dragging]="isDragging"
(dragover)="onDragOver($event)"
(dragleave)="onDragLeave($event)"
(drop)="onDrop($event)"
(click)="filePicker.click()"
>
<fa-icon icon="upload" class="ck-up-ic"></fa-icon>
<h3 class="ck-up-t">Drag &amp; drop your CKYC file here</h3>
<p class="ck-up-s">or click to browse — pipe-delimited (.txt) format</p>
<input #filePicker type="file" accept=".txt,.csv,.dat,text/plain" hidden (change)="onFilePicked($event)" />
</div>
@if (upload.status !== 'idle') {
<div class="ck-up-progress">
<div class="ck-up-progress-h">
<span class="ck-up-progress-name">{{ upload.fileName }}</span>
<span class="ck-up-progress-pct">{{ uploadProgress | number: '1.0-0' }}%</span>
</div>
<div class="ck-up-progress-bar">
<div
class="ck-up-progress-fill"
[style.width.%]="uploadProgress"
[class.success]="upload.status === 'success'"
[class.error]="upload.status === 'error'"
></div>
</div>
</div>
}
@if (upload.status === 'success') {
<div class="ck-up-report ck-up-report--ok">
<div class="ck-up-report-h">
<fa-icon icon="check-circle" class="ck-up-report-ic"></fa-icon>
<span>Parsed successfully</span>
</div>
<div class="ck-up-stats">
<div class="ck-up-stat">
<div class="ck-up-stat-n">{{ upload.totalRecords }}</div>
<div class="ck-up-stat-l">Total Records</div>
</div>
<div class="ck-up-stat">
<div class="ck-up-stat-n">{{ upload.customers }}</div>
<div class="ck-up-stat-l">Customers (20)</div>
</div>
<div class="ck-up-stat">
<div class="ck-up-stat-n">{{ upload.ids }}</div>
<div class="ck-up-stat-l">KYC IDs (30)</div>
</div>
<div class="ck-up-stat">
<div class="ck-up-stat-n">{{ upload.documents }}</div>
<div class="ck-up-stat-l">Documents (70)</div>
</div>
</div>
</div>
}
@if (upload.status === 'error') {
<div class="ck-up-report ck-up-report--err">
<div class="ck-up-report-h">
<fa-icon icon="exclamation-triangle" class="ck-up-report-ic"></fa-icon>
<span>Upload failed</span>
</div>
@for (err of upload.errors; track err) {
<div class="ck-up-err">{{ err }}</div>
}
</div>
}
</div>
<!-- Schema reference -->
<div class="ck-info-card">
<h4>CKYC File Schema</h4>
<div class="ck-schema">
<div class="ck-schema-row"><code>10|...</code><span>File header (1 line)</span></div>
<div class="ck-schema-row"><code>20|seq|...</code><span>Customer demographic record</span></div>
<div class="ck-schema-row">
<code>30|seq|type|value</code><span>KYC ID — C=PAN, E=Aadhaar, B=Passport, V=Voter, D=DL</span>
</div>
<div class="ck-schema-row"><code>70|seq|file|...</code><span>Document attachment</span></div>
</div>
</div>
</div>
}
<!-- CUSTOMER REPORT -->
@if (activeTab === 'customers') {
<div class="ck-section">
@@ -247,6 +147,9 @@
<div class="ck-export-bar">
<span class="ck-export-meta">{{ filteredCustomers.length }} matching records</span>
<div class="ck-export-btns">
<button class="ck-btn ck-btn-txt" (click)="exportTxt()">
<fa-icon icon="file-csv"></fa-icon> TXT (CKYC)
</button>
<button class="ck-btn ck-btn-csv" (click)="exportCsv()"><fa-icon icon="file-csv"></fa-icon> CSV</button>
<button class="ck-btn ck-btn-xls" (click)="exportExcel()"><fa-icon icon="file-excel"></fa-icon> Excel</button>
<button class="ck-btn ck-btn-pdf" (click)="exportPdf()"><fa-icon icon="file-pdf"></fa-icon> PDF</button>
@@ -331,6 +234,9 @@
>
@if (searchResults.length > 0) {
<div class="ck-export-btns">
<button class="ck-btn ck-btn-txt" (click)="exportTxt(searchResults)">
<fa-icon icon="file-csv"></fa-icon> TXT (CKYC)
</button>
<button class="ck-btn ck-btn-csv" (click)="exportCsv(searchResults)">
<fa-icon icon="file-csv"></fa-icon> CSV
</button>
@@ -326,6 +326,7 @@
/* default style — already grey */
}
.ck-btn-txt,
.ck-btn-csv,
.ck-btn-xls,
.ck-btn-pdf {
@@ -333,6 +334,14 @@
border: none;
}
.ck-btn-txt {
background: var(--ck-teal);
&:hover:not(:disabled) {
background: #0a6d5c;
}
}
.ck-btn-csv {
background: #16a34a;
@@ -46,18 +46,12 @@ interface CkycCustomer {
voterId: string;
drivingLicense: string;
documents: string[];
}
interface UploadResult {
status: 'idle' | 'parsing' | 'success' | 'error';
fileName: string;
totalRecords: number;
customers: number;
ids: number;
documents: number;
errors: string[];
startedAt?: number;
finishedAt?: number;
/** Original `20|...` line from the source file — used for verbatim TXT export */
rawLine: string;
/** Original `30|...` lines linked to this customer */
rawIdLines: string[];
/** Original `70|...` lines linked to this customer */
rawDocLines: string[];
}
@Component({
@@ -76,23 +70,13 @@ export class CkycReportsComponent implements OnInit, AfterViewInit {
@ViewChild('trendChartCanvas') trendChartCanvas!: ElementRef<HTMLCanvasElement>;
/** Active tab */
activeTab: 'dashboard' | 'upload' | 'customers' | 'search' = 'dashboard';
activeTab: 'dashboard' | 'customers' | 'search' = 'dashboard';
/** All customers (parsed + seed) */
/** All customers (seeded from sample data) */
customers: CkycCustomer[] = [];
/** Upload state */
upload: UploadResult = {
status: 'idle',
fileName: '',
totalRecords: 0,
customers: 0,
ids: 0,
documents: 0,
errors: []
};
uploadProgress = 0;
isDragging = false;
/** Original `10|...` header line from the source file */
headerLine = '';
/** Customer list filters */
filterName = '';
@@ -113,9 +97,9 @@ export class CkycReportsComponent implements OnInit, AfterViewInit {
pageIndex = 0;
/** Charts */
private stateChart: Chart | null = null;
private branchChart: Chart | null = null;
private trendChart: Chart | null = null;
private stateChart: any = null;
private branchChart: any = null;
private trendChart: any = null;
/** Toast */
toastMsg = '';
@@ -123,8 +107,17 @@ export class CkycReportsComponent implements OnInit, AfterViewInit {
private toastTimer: any;
ngOnInit(): void {
// Seed data so the dashboard isn't empty on first load
this.parseAndStore(SAMPLE_CKYC_FILE, 'ckyc-sample-IN4711.txt');
this.parseAndStore(SAMPLE_CKYC_FILE);
// eslint-disable-next-line no-console
console.log(
'[CKYC] Sample loaded —',
this.customers.length,
'customers,',
new Set(this.customers.map((c) => c.branch)).size,
'unique branches,',
new Set(this.customers.map((c) => c.state)).size,
'unique states'
);
}
ngAfterViewInit(): void {
@@ -134,7 +127,7 @@ export class CkycReportsComponent implements OnInit, AfterViewInit {
// -------------------- Tabs --------------------
showTab(tab: 'dashboard' | 'upload' | 'customers' | 'search'): void {
showTab(tab: 'dashboard' | 'customers' | 'search'): void {
this.activeTab = tab;
if (tab === 'dashboard') {
setTimeout(() => this.renderCharts(), 0);
@@ -241,76 +234,6 @@ export class CkycReportsComponent implements OnInit, AfterViewInit {
});
}
// -------------------- Upload --------------------
onDragOver(e: DragEvent): void {
e.preventDefault();
e.stopPropagation();
this.isDragging = true;
}
onDragLeave(e: DragEvent): void {
e.preventDefault();
e.stopPropagation();
this.isDragging = false;
}
onDrop(e: DragEvent): void {
e.preventDefault();
e.stopPropagation();
this.isDragging = false;
const files = e.dataTransfer?.files;
if (files && files.length) this.handleFile(files[0]);
}
onFilePicked(e: Event): void {
const input = e.target as HTMLInputElement;
if (input.files && input.files.length) this.handleFile(input.files[0]);
input.value = '';
}
private handleFile(file: File): void {
this.upload = {
status: 'parsing',
fileName: file.name,
totalRecords: 0,
customers: 0,
ids: 0,
documents: 0,
errors: [],
startedAt: Date.now()
};
this.uploadProgress = 0;
// Animate progress while reading
const progressTimer = setInterval(() => {
if (this.uploadProgress < 90) this.uploadProgress += Math.random() * 12;
if (this.uploadProgress > 90) this.uploadProgress = 90;
}, 120);
const reader = new FileReader();
reader.onload = () => {
clearInterval(progressTimer);
try {
this.parseAndStore(reader.result as string, file.name);
this.uploadProgress = 100;
this.upload.status = 'success';
this.upload.finishedAt = Date.now();
this.toast(`Parsed ${this.upload.customers} customers from ${file.name}`);
// Re-render dashboard charts with new data
if (this.activeTab === 'dashboard') setTimeout(() => this.renderCharts(), 0);
} catch (err: any) {
this.upload.status = 'error';
this.upload.errors.push(err?.message || 'Failed to parse file');
this.uploadProgress = 0;
}
};
reader.onerror = () => {
clearInterval(progressTimer);
this.upload.status = 'error';
this.upload.errors.push('Failed to read file');
this.uploadProgress = 0;
};
reader.readAsText(file);
}
// -------------------- CKYC Parser --------------------
/**
@@ -320,36 +243,27 @@ export class CkycReportsComponent implements OnInit, AfterViewInit {
* 30 = KYC ID record (linked by sequence)
* 70 = document attachment (linked by sequence)
*/
private parseAndStore(text: string, fileName: string): void {
private parseAndStore(text: string): void {
const lines = text.split(/\r?\n/).filter((l) => l.trim().length > 0);
const newCustomers = new Map<number, CkycCustomer>();
let recordCount = 0;
let idCount = 0;
let docCount = 0;
let header = '';
for (const line of lines) {
const f = line.split('|');
const type = f[0];
recordCount++;
if (type === '20') {
if (type === '10') {
header = line;
} else if (type === '20') {
const seq = parseInt(f[1], 10);
const cust: CkycCustomer = {
id: 'CKYC-' + (f[15] || seq.toString()).padStart(8, '0'),
id: 'CKYC-' + (f[18] || seq.toString()).padStart(8, '0'),
seq,
title: (f[16] || '').trim(),
firstName: (f[17] || '').trim(),
middleName: (f[18] || '').trim(),
lastName: (f[19] || '').trim(),
title: (f[19] || '').trim(),
firstName: (f[20] || '').trim(),
middleName: (f[21] || '').trim(),
lastName: (f[22] || '').trim(),
fullName: '',
fatherName: [
f[26],
f[27],
f[28]
]
.map((s) => (s || '').trim())
.filter(Boolean)
.join(' '),
motherName: [
f[31],
f[32],
f[33]
@@ -357,31 +271,42 @@ export class CkycReportsComponent implements OnInit, AfterViewInit {
.map((s) => (s || '').trim())
.filter(Boolean)
.join(' '),
gender: ((f[35] || '').trim() as 'M' | 'F') || '',
dob: (f[39] || '').trim(),
occupationCode: (f[38] || '').trim(),
address1: (f[51] || '').trim(),
address2: (f[52] || '').trim(),
address3: (f[53] || '').trim(),
city: (f[54] || '').trim(),
district: (f[55] || '').trim(),
state: (f[56] || '').trim(),
country: (f[57] || '').trim(),
pin: (f[58] || '').trim(),
motherName: [
f[36],
f[37],
f[38]
]
.map((s) => (s || '').trim())
.filter(Boolean)
.join(' '),
gender: ((f[40] || '').trim() as 'M' | 'F') || '',
dob: (f[44] || '').trim(),
occupationCode: (f[43] || '').trim(),
address1: (f[60] || '').trim(),
address2: (f[61] || '').trim(),
address3: (f[62] || '').trim(),
city: (f[63] || '').trim(),
district: (f[64] || '').trim(),
state: (f[65] || '').trim(),
country: (f[66] || '').trim(),
pin: (f[67] || '').trim(),
mobile: this.firstNonEmpty([
f[80],
f[78],
f[83]
f[96],
f[94],
f[99]
]),
uploadDate: (f[88] || '').trim(),
branch: (f[89] || '').trim(),
officer: (f[92] || '').trim(),
uploadDate: (f[101] || '').trim(),
branch: (f[102] || '').trim(),
officer: (f[105] || '').trim(),
pan: '',
aadhaarMasked: '',
passport: '',
voterId: '',
drivingLicense: '',
documents: []
documents: [],
rawLine: line,
rawIdLines: [],
rawDocLines: []
};
cust.fullName = [
cust.title,
@@ -404,25 +329,21 @@ export class CkycReportsComponent implements OnInit, AfterViewInit {
else if (idType === 'B') cust.passport = idValue;
else if (idType === 'V') cust.voterId = idValue;
else if (idType === 'D') cust.drivingLicense = idValue;
cust.rawIdLines.push(line);
}
idCount++;
} else if (type === '70') {
const seq = parseInt(f[1], 10);
const file = (f[2] || '').trim();
const cust = newCustomers.get(seq);
if (cust && file) cust.documents.push(file);
docCount++;
if (cust) {
if (file) cust.documents.push(file);
cust.rawDocLines.push(line);
}
}
}
const list = Array.from(newCustomers.values());
// Replace store on each parse so KPIs reflect the latest file
this.customers = list;
this.upload.fileName = fileName;
this.upload.totalRecords = recordCount;
this.upload.customers = list.length;
this.upload.ids = idCount;
this.upload.documents = docCount;
this.customers = Array.from(newCustomers.values());
this.headerLine = header;
this.pageIndex = 0;
}
@@ -550,6 +471,31 @@ export class CkycReportsComponent implements OnInit, AfterViewInit {
// -------------------- Exports --------------------
/**
* Exports a CKYC `.txt` file in the original pipe-delimited record format
* (10 header + 20/30/70 records per customer). Lines are emitted verbatim
* from the source file, so output is byte-identical to the upload format.
*/
exportTxt(rows?: CkycCustomer[]): void {
const list = rows || this.filteredCustomers;
const out: string[] = [];
if (this.headerLine) out.push(this.headerLine);
for (const c of list) {
out.push(c.rawLine);
for (const id of c.rawIdLines) out.push(id);
for (const doc of c.rawDocLines) out.push(doc);
}
// Trailing newline matches the source file convention
const blob = new Blob([out.join('\n') + '\n'], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `ckyc-report-${new Date().toISOString().slice(0, 10)}.txt`;
a.click();
URL.revokeObjectURL(url);
this.toast(`Exported ${list.length} customers as TXT`);
}
exportCsv(rows?: CkycCustomer[]): void {
this.downloadDelimited(rows || this.filteredCustomers, ',', 'csv');
}
+134 -75
View File
@@ -7,80 +7,139 @@
*/
/**
* Sample CKYC pipe-delimited file (NAG BANK / GANJ BASODA branch).
* Used to seed the reports dashboard so the page is meaningful before any upload.
* Sample CKYC pipe-delimited file used to seed the reports dashboard.
* Spread across multiple banks, branches and states so charts and filters
* have meaningful variety out of the box.
*/
export const SAMPLE_CKYC_FILE = `10|24401|IN4711|MP|70|31-08-2020|V1.1|||||
20|1|01|01|||||||||||01|||01|78|MR|BRAHAM |PRAKASH |JAIN |||||||01|MR|MISHRI|LAL|JAIN||MRS|A|B|C||M|02|IN|B-01|10-08-1968|||||||||01|02|||||02|WARD N.11|BRAHAM KIRANA STORE|PATHASHALA ROAD BASODA|BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD N.11|BRAHAM KIRANA STORE|PATHASHALA ROAD BASODA|BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9827612655|||||31-08-2020|GANJ BASODA|31-08-2020|01|GYANENDRA SINGH GAUTAM|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|4|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|1|C|AAQPJ8560J||01|02|||||
70|1|78_Sign.jpeg|09|01|01||||
70|1|78_Photo.jpeg|02|01|01||||
20|2|01|01|||||||||||01|||01|2181|MR|DEVI |SHING |KUSHWA H|||||||01|MR|CHATAR SINGH||||MRS|BUDA|BAI|||M|01|IN|B-01|01-01-1973|||||||||01|02|||||02|WARD NO 13|JIWAJIPUR|BASODA|BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 13|JIWAJIPUR|BASODA|BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||8602462283|91|8602462283|||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|2|E|XXXXXXXX6509||01|02|||||
20|3|01|01|||||||||||01|||01|6576|MR|RAM |MOHAN |SINGH |||||||01|MR|MEHTAB||DANGI||MRS|A|B|C||M|01|IN|B-01|01-01-1963|||||||||01|02|||||02|WARD NO 18|PACHMA ROAD|BASODA|BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 18|PACHMA ROAD|BASODA|BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9826628312|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|3|C|AQPPR9351Q||01|02|||||
20|4|01|01|||||||||||01|||01|33502|MRS|RUKSHAR |BEE||||||||01|MR|HASIN|KHAN|||MRS|A|B|C||F|01|IN|O-04|01-01-1991|||||||||01|02|||||02|PACHAMA|GAMAKHAR VIDISHA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|PACHAMA|GAMAKHAR VIDISHA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9009372818|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|4|E|XXXXXXXX3695||01|02|||||
20|5|01|01|||||||||||01|||01|33720|MR|ANKUR||PINGLE|||||||01|MR|SANTOSH KUMAR||||MRS|A|B|C||M|02|IN|B-01|18-06-1982|||||||||01|02|||||02|WARD NO 16|PACHMA ROAD|HOUSE NO 7|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 16|PACHMA ROAD|HOUSE NO 7|GANJ BASODA|VIDISHA|MP|IN|464221|01|||||||||||91|9993262850||||9993262850|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|5|C|BDYPP8413Q||01|02|||||
20|6|01|01|||||||||||01|||01|33847|MR|RAGHUVEER||VISHWAKARMA|||||||01|MR|SHAYAMLAL ||||MRS|A|B|C||M|01|IN|X-01|01-01-1965|||||||||01|02|||||02|WARD NO 6|LADDHA AGENCY VALI GALI BARETH ROAD|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 6|LADDHA AGENCY VALI GALI BARETH ROAD|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||7869252915|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|6|E|XXXXXXXX2861||01|02|||||
20|7|01|01|||||||||||01|||01|34081|MR|ROHIT ||JAIN|||||||01|MR|MAHESH JAIN||||MRS|A|B|C||M|02|IN|O-05|01-01-1988|||||||||01|02|||||02|HOUSE NO 411 BAHADURPUR|DIST ASHOKNAGAR GUNA||BAHADURPUR|ASHOK NAGAR|MP|IN|473443|01||Y|02|HOUSE NO 411 BAHADURPUR|DIST ASHOKNAGAR GUNA||BAHADURPUR|ASHOK NAGAR|MP|IN|473443|01||||||||||||||||9713583272|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|7|E|XXXXXXXX6260||01|02|||||
20|8|01|01|||||||||||01|||01|34177|MR|MAHENDRA||SINGH|||||||01|MR|MOHAN |SINGH|||MRS|A|B|C||M|02|IN|B-01|12-08-1998|||||||||01|02|||||02|WARD 21|SARDA NAGAR RAJODA ROAD|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD 21|SARDA NAGAR RAJODA ROAD|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01|||||||||||||||||||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|8|E|XXXXXXXX3543||01|02|||||
20|9|01|01|||||||||||01|||01|34178|MRS|SHREYA||SHARMA|||||||01|MR|PRAVEEN SHARMA||||MRS|A|B|C||F|02|IN|O-05|27-09-1997|||||||||01|02|||||02|WARD NO 24 HOUSE 5 KRISHNAPURA GALI|HANUMAN CHOWK|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 24 HOUSE 5 KRISHNAPURA GALI|HANUMAN CHOWK|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||8962639307|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|9|C|GZCPS9037N||01|02|||||
20|10|01|01|||||||||||01|||01|34181|MR|SORABH||JAIN|||||||01|MR|SURESH JAIN||||MRS|A|B|C||M|02|IN|B-01|24-11-1986|||||||||01|02|||||02|WARD NO 8 PARASARI NADI KE PUL KE |PASS GANJ BASODA|VIDISHA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 8 PARASARI NADI KE PUL KE |PASS GANJ BASODA|VIDISHA|GANJ BASODA|VIDISHA|MP|IN|464221|01|||||||||||91|9131156740||||9131156740|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|10|C|APUPJ8338R||01|02|||||
20|11|01|01|||||||||||01|||01|34185|MR|DARMENDRA||KUSHWAH|||||||01|MR|GULAB||||MRS|A|B|C||M|02|IN|B-01|01-01-1991|||||||||01|02|||||02|WARD NO 13 CHAK SWROOP NAGAR|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 13 CHAK SWROOP NAGAR|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9907260317|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|11|E|XXXXXXXX7722||01|02|||||
20|12|01|01|||||||||||01|||01|34186|MRS|MANESHA||RAJPUT|||||||01|MR|VEER SINGH||||MRS|A|B|C||F|02|IN|O-04|01-01-1979|||||||||01|02|||||02|WARD NO 21|LOHA MEEL ROAD BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 21|LOHA MEEL ROAD BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||7987077923|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|12|E|XXXXXXXX5640||01|02|||||
20|13|01|01|||||||||||01|||01|34188|MR|YOGESH||DANGI|||||||01|MR|BIHARI SINGH||||MRS|A|B|C||M|02|IN|B-01|01-01-1994|||||||||01|02|||||02|SEMRA POST PABAI|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|SEMRA POST PABAI|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9009571392|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|13|E|XXXXXXXX8016||01|02|||||
20|14|01|01|||||||||||01|||01|34190|MRS|RAJNI||SEN|||||||01|MR|SACHIN SEN||||MRS|A|B|C||F|01|IN|O-04|01-01-1990|||||||||01|02|||||02|WARD NO 16|PANJABI DHARAM SHALA GALI|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 16|PANJABI DHARAM SHALA GALI|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9584363875|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|14|B|WHU2321339||01|02|||||
20|15|01|01|||||||||||01|||01|34191|MR|SACHIN||SEN|||||||01|MR|GANESH RAM||||MRS|A|B|C||M|01|IN|B-01|13-08-1984|||||||||01|02|||||02|WARD NO 16 PANJABI DHARMSHALA |GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 16 PANJABI DHARMSHALA |GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9584363875|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|15|C|HONPS2337E||01|02|||||
20|16|01|01|||||||||||01|||01|34194|MRS|JAMNA||MALVIYA|||||||01|MR|BABLU||MALVIYA||MRS|A|B|C||F|01|IN|O-04|01-01-1990|||||||||01|02|||||02|MIRJAPUR|WARD NO 15|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|MIRJAPUR|WARD NO 15|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9977516599|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|16|E|XXXXXXXX6332||01|02|||||
20|17|01|01|||||||||||01|||01|34195|MR|LAKY||AHIRWAR|||||||01|MR|MOHAN LAL||||MRS|A|B|C||M|02|IN|O-05|01-01-1996|||||||||01|02|||||02|WARD NO 10|GOUSHALA|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 10|GOUSHALA|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9131915301|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|17|E|XXXXXXXX2402||01|02|||||
20|18|01|01|||||||||||01|||01|34197|MRS|PRAVESHIKA ||JAIN|||||||01|MR|RISHI JAIN||||MRS|MAMTA||||F|01|IN|O-04|07-08-1989|||||||||01|02|||||02|WARD NO 21 GANDHI MARG SIRONJ CHORA|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 21 GANDHI MARG SIRONJ CHORA|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9893971160|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|18|E|XXXXXXXX0211||01|02|||||
20|19|01|01|||||||||||01|||01|34201|MR|HEERA LAL||JAIN HUF|||||||01|MR|PURAN CHAND||||MRS|A|B|C||M|01|IN|B-01|01-01-1939|||||||||01|02|||||02|WARD NO 12|JAIN MANDIR GALI NEHRU CHOWK|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 12|JAIN MANDIR GALI NEHRU CHOWK|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||8962445182|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|19|E|XXXXXXXX1723||01|02|||||
20|20|01|01|||||||||||01|||01|34202|MR|RAJESH|KUMAR|JAIN HUF|||||||01|MR|HEERA LAL||||MRS|A|B|C||M|01|IN|B-01|12-05-1962|||||||||01|02|||||02|WARD NO 12 JAIN MANDIR GALI|NEHRU CHOWK BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 12 JAIN MANDIR GALI|NEHRU CHOWK BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9408510555|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|20|E|XXXXXXXX9147||01|02|||||
20|21|01|01|||||||||||01|||01|34203|MR|SHIV|KUMAR|JAIN HUF|||||||01|MR|HEERA LAL||||MRS|A|B|C||M|01|IN|B-01|10-05-1976|||||||||01|02|||||02|WARD NO 12|JAIN MANDIR GALI NEHRU CHOWK|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 12|JAIN MANDIR GALI NEHRU CHOWK|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9425432076|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|21|E|XXXXXXXX9363||01|02|||||
20|22|01|01|||||||||||01|||01|34205|MRS|KIRTI||BAIRAGIE|||||||01|MR|MUKESH||||MRS|A|B|C||F|02|IN|O-05|01-01-2000|||||||||01|02|||||02|WARD NO 7 LODHI COLONY|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 7 LODHI COLONY|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9644750547|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|22|E|XXXXXXXX6035||01|02|||||
20|23|01|01|||||||||||01|||01|34208|MR|SANJIV| |RAIKWAR|||||||01|MR|SUNDAR|LAL|RAIKWAR||MRS|A|B|C||M|01|IN|B-01|03-05-1986|||||||||01|02|||||02|WARD NO. 1|FREE GANJ MACHCHLI MARKET|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO. 1|FREE GANJ MACHCHLI MARKET|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||6260405821|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|23|C|DMJPR6517R||01|02|||||
20|24|01|01|||||||||||01|||01|34209|MRS|NEETU| |RAIKWAR|||||||01|MR|SANJIV| |RAIKWAR||MRS|A|B|C||F|01|IN|O-04|06-10-1990|||||||||01|02|||||02|FREE GANJ|WARD NO.1 MACHLI MARKET|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|FREE GANJ|WARD NO.1 MACHLI MARKET|GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||6260405821|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|24|C|DMHPR0471Q||01|02|||||
20|25|01|01|||||||||||01|||01|34210|MRS|LATA| |BANGAD|||||||01|MR|SATYA|NARAYAN|BANGAD||MRS|A|B|C||F|01|IN|O-04|25-02-1975|||||||||01|02|||||02|WARD NO.12 |JAIN MANDIR GALI|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO.12 |JAIN MANDIR GALI|BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01|||||||||||91|7987163771||||7987163771|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|25|C|AJVPB1142J||01|02|||||
20|26|01|01|||||||||||01|||01|34227|MR|SEETA RAM|SINGH||||||||01|MR|UMRAV SINGH||||MRS|A|B|C||M|01|IN|S-01|01-01-1982|||||||||01|02|||||02|BALRAMPUR|PANCHAYAT SAKRAI POST FUFER NATERAN|VIDISHA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|BALRAMPUR|PANCHAYAT SAKRAI POST FUFER NATERAN|VIDISHA|GANJ BASODA|VIDISHA|MP|IN|464221|01|||||||||||||||||||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|26|B|BBK1122340||01|02|||||
20|27|01|01|||||||||||01|||01|34232|MRS|JANKE|DANGI||||||||01|MR|MEHTAB SINGH||||MRS|A|B|C||F|01|IN|O-04|01-01-1936|||||||||01|02|||||02|MAHAGAUR|VIDISHA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|MAHAGAUR|VIDISHA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9406522221|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|27|E|XXXXXXXX5383||01|02|||||
20|28|01|01|||||||||||01|||01|34245|MR|KESHAV|PRAJAPATI||||||||01|MR|KARAN|SINGH|||MRS|A|B|C||M|01|IN|B-01|01-01-1991|||||||||01|02|||||02|SARASWATI NAGAR SWROOP NAGAR|GIRAJ DHAM MANDIR|LAPATHER BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|SARASWATI NAGAR SWROOP NAGAR|GIRAJ DHAM MANDIR|LAPATHER BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||7440844734|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|28|E|XXXXXXXX0585||01|02|||||
20|29|01|01|||||||||||01|||01|34246|MRS|VANDANA|JAIN||||||||01|MR|NEELESH|JAIN|||MRS|A|B|C||F|01|IN|O-04|10-07-1979|||||||||01|02|||||02|ONLINE CYBER CAFE NAYA BAZAR CITY|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|ONLINE CYBER CAFE NAYA BAZAR CITY|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9406522223|||||31-08-2020|GANJ BASODA|31-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|29|E|XXXXXXXX5997||01|02|||||
20|30|01|01|||||||||||01|||01|34255|MRS|NENASHI|NAMDEV||||||||01|MR|RAM SEVAK|NAMDEV|||MRS|A|B|C||F|02|IN|S-01|27-12-1997|||||||||01|02|||||02|WARD NO 24 KRISHNA PURA|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 24 KRISHNA PURA|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9685768714|||||30-08-2020|GANJ BASODA|30-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|30|E|XXXXXXXX6697||01|02|||||
20|31|01|01|||||||||||01|||01|34258|MRS|RANI|AGRAWAL||||||||01|MR|MILIND|AGRAWAL|||MRS|A|B|C||F|01|IN|O-04|22-12-1996|||||||||01|02|||||02|WARD NO 12 MILL ROAD|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 12 MILL ROAD|BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9752979179|||||30-08-2020|GANJ BASODA|30-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|31|C|CVUPA9262K||01|02|||||
20|32|01|01|||||||||||01|||01|34260|MR|DEVI |PRASHAD||||||||01|MR|PRABHU |LAL|||MRS|A|B|C||M|01|IN|S-01|10-06-1965|||||||||01|02|||||02|WARD NO 7|KAMDHENU COLONY BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 7|KAMDHENU COLONY BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||8889089308|||||29-08-2020|GANJ BASODA|29-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|32|B|BBK0131060||01|02|||||
20|33|01|01|||||||||||01|||01|34275|MR|ANKIT|PINGLE||||||||01|MR|SHIRISH|PINGLE|||MRS|A|B|C||M|01|IN|B-01|28-05-1988|||||||||01|02|||||02|WARD NO 16 PACHMA ROAD|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|WARD NO 16 PACHMA ROAD|GANJ BASODA||GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||7723075050|||||29-08-2020|GANJ BASODA|29-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|33|C|BRAPP2466F||01|02|||||
20|34|01|01|||||||||||01|||01|34277|MR|SANJAY|PINGLE||||||||01|MR|NARAYAN|RAO|||MRS|A|B|C||M|01|IN|B-01|05-09-1957|||||||||01|02|||||02|M N 7|PACHAMA BALI GALI|WARD NO 16 GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||Y|02|M N 7|PACHAMA BALI GALI|WARD NO 16 GANJ BASODA|GANJ BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||9893485050|||||29-08-2020|GANJ BASODA|29-08-2020|01|PRADEEP AHIRWAR|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|12|BASODA NAG BANK|IN4711|1|0||0|4||||||
30|34|B|MP13247207128||01|02|||||
20|35|01|01|||||||||||01|||01|34885|MR|SHALENDRA|RAGHUWANSHI||||||||01|MR|MALAM|SINGH|||MRS|PUSPA||||M|01|IN|X-01|03-01-1983|||||||||01|02|||||02|GRAM MEVLI PANCHAYAT|MUDRA POSTKAKRAWDA|BASODA|BASODA|VIDISHA|MP|IN|464221|01||Y|02|GRAM MEVLI PANCHAYAT|MUDRA POSTKAKRAWDA|BASODA|BASODA|VIDISHA|MP|IN|464221|01||||||||||||||||6266886495|||||28-08-2020|GANJ BASODA|28-08-2020|01|BRIJESHKUMAR SHRIVASTVA|ASSTT ACCOUCHIEF CASHIERSCO |HEAD OFFICE|3|BASODA NAG BANK|IN4711|1|0||0|3||||||
30|35|E|XXXXXXXX7677||01|02|||||
export const SAMPLE_CKYC_FILE = `10|24401|IN4711|MH|30|31-08-2020|V1.1|||||
20|1|01|01|||||||||||01|||01|10078|MR|RAVI|KUMAR|SHARMA|||||||01|MR|RAJESH|KUMAR|SHARMA||MRS|SUNITA|DEVI|||M|02|IN|B-01|10-08-1985|||||||||01|02|||||02|FLAT 402, SUNRISE TOWERS|MAROL ANDHERI EAST|MUMBAI|MUMBAI|MUMBAI|MH|IN|400069|01||Y|02|FLAT 402, SUNRISE TOWERS|MAROL ANDHERI EAST|MUMBAI|MUMBAI|MUMBAI|MH|IN|400069|01||||||||||||||||9892011001|||||31-08-2020|MUMBAI MAIN|31-08-2020|01|ANJALI MEHTA|ASSISTANT MANAGER|HEAD OFFICE|4|BHARAT URBAN BANK|IN4711|1|0||0|4||||||
30|1|C|AAQPS8560J||01|02|||||
70|1|10078_Sign.jpeg|09|01|01||||
70|1|10078_Photo.jpeg|02|01|01||||
70|1|10078_AP.jpg|04|01|01||||
20|2|01|01|||||||||||01|||01|10079|MRS|PRIYA||PATEL|||||||01|MR|HARISH|PATEL|||MRS|MEENA|PATEL|||F|01|IN|O-04|22-03-1990|||||||||01|02|||||02|B-12, SHIVAJI NAGAR|FC ROAD|PUNE|PUNE|PUNE|MH|IN|411005|01||Y|02|B-12, SHIVAJI NAGAR|FC ROAD|PUNE|PUNE|PUNE|MH|IN|411005|01||||||||||||||||9822055002|||||31-08-2020|PUNE FC ROAD|31-08-2020|01|VIKRAM SINGH|BRANCH MANAGER|HEAD OFFICE|4|BHARAT URBAN BANK|IN4711|1|0||0|3||||||
30|2|E|XXXXXXXX2210||01|02|||||
70|2|10079_Sign.jpeg|09|01|01||||
70|2|10079_Photo.jpeg|02|01|01||||
20|3|01|01|||||||||||01|||01|10080|MR|ARJUN||DESHMUKH|||||||01|MR|VIJAY|DESHMUKH|||MRS|REKHA|DESHMUKH|||M|01|IN|B-01|15-01-1978|||||||||01|02|||||02|HOUSE NO 7, DHARAMPETH|RAMDASPETH|NAGPUR|NAGPUR|NAGPUR|MH|IN|440010|01||Y|02|HOUSE NO 7, DHARAMPETH|RAMDASPETH|NAGPUR|NAGPUR|NAGPUR|MH|IN|440010|01||||||||||||||||9823033003|||||30-08-2020|NAGPUR DHARAMPETH|30-08-2020|01|RAHUL JOSHI|ASSISTANT MANAGER|HEAD OFFICE|4|BHARAT URBAN BANK|IN4711|1|0||0|3||||||
30|3|C|AGTPD1144K||01|02|||||
70|3|10080_Sign.jpeg|09|01|01||||
70|3|10080_AP.jpg|04|01|01||||
20|4|01|01|||||||||||01|||01|20101|MR|VIKRAM||REDDY|||||||01|MR|SURESH|REDDY|||MRS|LAKSHMI|REDDY|||M|02|IN|B-01|07-07-1982|||||||||01|02|||||02|NO 23, 4TH CROSS|JAYANAGAR 5TH BLOCK|BENGALURU|BENGALURU|BENGALURU|KA|IN|560041|01||Y|02|NO 23, 4TH CROSS|JAYANAGAR 5TH BLOCK|BENGALURU|BENGALURU|BENGALURU|KA|IN|560041|01||||||||||||||||9845022004|||||31-08-2020|BENGALURU JAYANAGAR|31-08-2020|01|KARTHIK KAMATH|RELATIONSHIP OFFICER|HEAD OFFICE|7|KOTAK COOPERATIVE BANK|IN5712|1|0||0|4||||||
30|4|C|AAEPR9352L||01|02|||||
70|4|20101_Sign.jpeg|09|01|01||||
70|4|20101_Photo.jpeg|02|01|01||||
70|4|20101_AP.jpg|04|01|01||||
20|5|01|01|||||||||||01|||01|20102|MRS|LAKSHMI||IYER|||||||01|MR|RAMESH|IYER|||MRS|KAMALA|IYER|||F|01|IN|O-04|14-09-1986|||||||||01|02|||||02|NO 88, INDIRA NAGAR|2ND STAGE|BENGALURU|BENGALURU|BENGALURU|KA|IN|560038|01||Y|02|NO 88, INDIRA NAGAR|2ND STAGE|BENGALURU|BENGALURU|BENGALURU|KA|IN|560038|01||||||||||||||||9845066005|||||31-08-2020|BENGALURU INDIRANAGAR|31-08-2020|01|MEERA HEGDE|TELLER|HEAD OFFICE|7|KOTAK COOPERATIVE BANK|IN5712|1|0||0|3||||||
30|5|E|XXXXXXXX5511||01|02|||||
70|5|20102_Sign.jpeg|09|01|01||||
70|5|20102_Photo.jpeg|02|01|01||||
20|6|01|01|||||||||||01|||01|20103|MR|SHIVA||GOWDA|||||||01|MR|MAHESH|GOWDA|||MRS|GAYATRI|GOWDA|||M|01|IN|S-01|19-04-1972|||||||||01|02|||||02|HOUSE 14, KUVEMPU NAGAR|MYSORE PALACE ROAD|MYSURU|MYSURU|MYSURU|KA|IN|570023|01||Y|02|HOUSE 14, KUVEMPU NAGAR|MYSORE PALACE ROAD|MYSURU|MYSURU|MYSURU|KA|IN|570023|01||||||||||||||||9844077006|||||30-08-2020|MYSURU PALACE ROAD|30-08-2020|01|RAHUL SHETTY|FIELD OFFICER|HEAD OFFICE|7|KOTAK COOPERATIVE BANK|IN5712|1|0||0|3||||||
30|6|B|KA09KL5544||01|02|||||
70|6|20103_Sign.jpeg|09|01|01||||
70|6|20103_AP.jpg|04|01|01||||
20|7|01|01|||||||||||01|||01|30201|MR|MOHIT||AGARWAL|||||||01|MR|RAJU|AGARWAL|||MRS|SARITA|AGARWAL|||M|02|IN|B-01|25-12-1989|||||||||01|02|||||02|45 SUBHASH MARG|JOHARI BAZAR|JAIPUR|JAIPUR|JAIPUR|RJ|IN|302002|01||Y|02|45 SUBHASH MARG|JOHARI BAZAR|JAIPUR|JAIPUR|JAIPUR|RJ|IN|302002|01||||||||||||||||9829088007|||||31-08-2020|JAIPUR JOHARI BAZAR|31-08-2020|01|POOJA SHARMA|ASSISTANT MANAGER|HEAD OFFICE|9|MARWAR RURAL BANK|IN6713|1|0||0|4||||||
30|7|C|ACTPA8821M||01|02|||||
70|7|30201_Sign.jpeg|09|01|01||||
70|7|30201_Photo.jpeg|02|01|01||||
70|7|30201_AP.jpg|04|01|01||||
20|8|01|01|||||||||||01|||01|30202|MRS|SARITA||CHOUDHARY|||||||01|MR|MAHENDRA|CHOUDHARY|||MRS|ANITA|CHOUDHARY|||F|01|IN|O-04|03-06-1981|||||||||01|02|||||02|HOUSE 12, RATANADA|AIRPORT ROAD|JODHPUR|JODHPUR|JODHPUR|RJ|IN|342011|01||Y|02|HOUSE 12, RATANADA|AIRPORT ROAD|JODHPUR|JODHPUR|JODHPUR|RJ|IN|342011|01||||||||||||||||9829099008|||||31-08-2020|JODHPUR RATANADA|31-08-2020|01|VINOD MALI|TELLER|HEAD OFFICE|9|MARWAR RURAL BANK|IN6713|1|0||0|3||||||
30|8|E|XXXXXXXX6622||01|02|||||
70|8|30202_Sign.jpeg|09|01|01||||
70|8|30202_AP.jpg|04|01|01||||
20|9|01|01|||||||||||01|||01|30203|MR|RAVINDRA||SINGH|||||||01|MR|JAGDISH|SINGH|||MRS|KAMLA|SINGH|||M|01|IN|S-01|11-11-1965|||||||||01|02|||||02|GRAM POST DEORI|TEHSIL TONK|TONK|TONK|TONK|RJ|IN|304001|01||Y|02|GRAM POST DEORI|TEHSIL TONK|TONK|TONK|TONK|RJ|IN|304001|01||||||||||||||||9928110009|||||30-08-2020|TONK MAIN|30-08-2020|01|DINESH JAT|FIELD OFFICER|HEAD OFFICE|9|MARWAR RURAL BANK|IN6713|1|0||0|4||||||
30|9|C|BHJPS3322N||01|02|||||
70|9|30203_Sign.jpeg|09|01|01||||
70|9|30203_Photo.jpeg|02|01|01||||
20|10|01|01|||||||||||01|||01|40301|MR|NIRAV||SHAH|||||||01|MR|JAYESH|SHAH|||MRS|RITA|SHAH|||M|02|IN|B-01|18-05-1992|||||||||01|02|||||02|A-401, ATLANTIS APARTMENTS|SATELLITE ROAD|AHMEDABAD|AHMEDABAD|AHMEDABAD|GJ|IN|380015|01||Y|02|A-401, ATLANTIS APARTMENTS|SATELLITE ROAD|AHMEDABAD|AHMEDABAD|AHMEDABAD|GJ|IN|380015|01||||||||||||||||9824012010|||||31-08-2020|AHMEDABAD SATELLITE|31-08-2020|01|HARSH PATEL|RELATIONSHIP OFFICER|HEAD OFFICE|6|GUJARAT NAGRIK BANK|IN7714|1|0||0|4||||||
30|10|C|AKLPS9933P||01|02|||||
70|10|40301_Sign.jpeg|09|01|01||||
70|10|40301_Photo.jpeg|02|01|01||||
70|10|40301_AP.jpg|04|01|01||||
20|11|01|01|||||||||||01|||01|40302|MRS|HEENA||PATEL|||||||01|MR|KANTI|PATEL|||MRS|JYOTI|PATEL|||F|02|IN|O-05|07-08-1995|||||||||01|02|||||02|34 RING ROAD|UDHNA|SURAT|SURAT|SURAT|GJ|IN|394210|01||Y|02|34 RING ROAD|UDHNA|SURAT|SURAT|SURAT|GJ|IN|394210|01||||||||||||||||9824055011|||||31-08-2020|SURAT UDHNA|31-08-2020|01|TEJAS DESAI|ASSISTANT MANAGER|HEAD OFFICE|6|GUJARAT NAGRIK BANK|IN7714|1|0||0|3||||||
30|11|E|XXXXXXXX7733||01|02|||||
70|11|40302_Sign.jpeg|09|01|01||||
70|11|40302_AP.jpg|04|01|01||||
20|12|01|01|||||||||||01|||01|40303|MR|JAYESH||TRIVEDI|||||||01|MR|BHARAT|TRIVEDI|||MRS|JASHODA|TRIVEDI|||M|01|IN|B-01|29-02-1976|||||||||01|02|||||02|HOUSE 9, AKOTA STADIUM|RACECOURSE ROAD|VADODARA|VADODARA|VADODARA|GJ|IN|390020|01||Y|02|HOUSE 9, AKOTA STADIUM|RACECOURSE ROAD|VADODARA|VADODARA|VADODARA|GJ|IN|390020|01||||||||||||||||9824077012|||||30-08-2020|VADODARA AKOTA|30-08-2020|01|NEHA RAVAL|TELLER|HEAD OFFICE|6|GUJARAT NAGRIK BANK|IN7714|1|0||0|3||||||
30|12|B|GJ06BB7711||01|02|||||
70|12|40303_Sign.jpeg|09|01|01||||
70|12|40303_Photo.jpeg|02|01|01||||
20|13|01|01|||||||||||01|||01|50401|MR|ANIL||MISHRA|||||||01|MR|RAMESH|MISHRA|||MRS|VIDYA|MISHRA|||M|01|IN|S-01|14-10-1970|||||||||01|02|||||02|H-22 GOMTI NAGAR|VIPUL KHAND|LUCKNOW|LUCKNOW|LUCKNOW|UP|IN|226010|01||Y|02|H-22 GOMTI NAGAR|VIPUL KHAND|LUCKNOW|LUCKNOW|LUCKNOW|UP|IN|226010|01||||||||||||||||9415022013|||||31-08-2020|LUCKNOW GOMTI NAGAR|31-08-2020|01|SUMIT TIWARI|BRANCH MANAGER|HEAD OFFICE|11|AVADH NAGRIK BANK|IN8715|1|0||0|3||||||
30|13|E|XXXXXXXX1144||01|02|||||
70|13|50401_Sign.jpeg|09|01|01||||
70|13|50401_AP.jpg|04|01|01||||
20|14|01|01|||||||||||01|||01|50402|MRS|KAVITA||VERMA|||||||01|MR|DEEPAK|VERMA|||MRS|USHA|VERMA|||F|02|IN|O-04|01-04-1988|||||||||01|02|||||02|45 CIVIL LINES|MALL ROAD|KANPUR|KANPUR|KANPUR|UP|IN|208001|01||Y|02|45 CIVIL LINES|MALL ROAD|KANPUR|KANPUR|KANPUR|UP|IN|208001|01||||||||||||||||9415055014|||||31-08-2020|KANPUR CIVIL LINES|31-08-2020|01|RUCHI AGRAWAL|ASSISTANT MANAGER|HEAD OFFICE|11|AVADH NAGRIK BANK|IN8715|1|0||0|4||||||
30|14|C|AKHPV2244R||01|02|||||
70|14|50402_Sign.jpeg|09|01|01||||
70|14|50402_Photo.jpeg|02|01|01||||
70|14|50402_AP.jpg|04|01|01||||
20|15|01|01|||||||||||01|||01|50403|MR|SUDHIR||PANDEY|||||||01|MR|MUKESH|PANDEY|||MRS|SHANTI|PANDEY|||M|01|IN|B-01|20-07-1983|||||||||01|02|||||02|74 ASSI GHAT ROAD|LANKA|VARANASI|VARANASI|VARANASI|UP|IN|221005|01||Y|02|74 ASSI GHAT ROAD|LANKA|VARANASI|VARANASI|VARANASI|UP|IN|221005|01||||||||||||||||9415077015|||||30-08-2020|VARANASI ASSI GHAT|30-08-2020|01|ASHISH MISHRA|TELLER|HEAD OFFICE|11|AVADH NAGRIK BANK|IN8715|1|0||0|3||||||
30|15|E|XXXXXXXX0099||01|02|||||
70|15|50403_Sign.jpeg|09|01|01||||
70|15|50403_AP.jpg|04|01|01||||
20|16|01|01|||||||||||01|||01|60501|MR|KARTHIK||SUBRAMANIAN|||||||01|MR|RAMASWAMY|SUBRAMANIAN|||MRS|GEETHA|SUBRAMANIAN|||M|02|IN|B-01|05-05-1980|||||||||01|02|||||02|14 KAMAKOTI NAGAR|VALASARAVAKKAM|CHENNAI|CHENNAI|CHENNAI|TN|IN|600087|01||Y|02|14 KAMAKOTI NAGAR|VALASARAVAKKAM|CHENNAI|CHENNAI|CHENNAI|TN|IN|600087|01||||||||||||||||9444022016|||||31-08-2020|CHENNAI VALASARAVAKKAM|31-08-2020|01|VIDHYA RAJAN|RELATIONSHIP OFFICER|HEAD OFFICE|5|TAMIL COMMUNITY BANK|IN9716|1|0||0|4||||||
30|16|C|AGRPS5566T||01|02|||||
70|16|60501_Sign.jpeg|09|01|01||||
70|16|60501_Photo.jpeg|02|01|01||||
70|16|60501_AP.jpg|04|01|01||||
20|17|01|01|||||||||||01|||01|60502|MRS|SARANYA||KUMAR|||||||01|MR|VENKAT|KUMAR|||MRS|MEENAKSHI|KUMAR|||F|01|IN|O-04|17-12-1991|||||||||01|02|||||02|24 GANDHIPURAM|RS PURAM|COIMBATORE|COIMBATORE|COIMBATORE|TN|IN|641002|01||Y|02|24 GANDHIPURAM|RS PURAM|COIMBATORE|COIMBATORE|COIMBATORE|TN|IN|641002|01||||||||||||||||9444055017|||||31-08-2020|COIMBATORE RS PURAM|31-08-2020|01|MURUGAN VEL|ASSISTANT MANAGER|HEAD OFFICE|5|TAMIL COMMUNITY BANK|IN9716|1|0||0|3||||||
30|17|E|XXXXXXXX2255||01|02|||||
70|17|60502_Sign.jpeg|09|01|01||||
70|17|60502_AP.jpg|04|01|01||||
20|18|01|01|||||||||||01|||01|60503|MR|SELVAM||PILLAI|||||||01|MR|RAMAN|PILLAI|||MRS|SHANTHI|PILLAI|||M|01|IN|S-01|22-08-1969|||||||||01|02|||||02|HOUSE 56, EAST GATE|MEENAKSHI TEMPLE ROAD|MADURAI|MADURAI|MADURAI|TN|IN|625001|01||Y|02|HOUSE 56, EAST GATE|MEENAKSHI TEMPLE ROAD|MADURAI|MADURAI|MADURAI|TN|IN|625001|01||||||||||||||||9444077018|||||30-08-2020|MADURAI EAST|30-08-2020|01|RAMYA KRISHNAN|TELLER|HEAD OFFICE|5|TAMIL COMMUNITY BANK|IN9716|1|0||0|3||||||
30|18|B|TN14CH4422||01|02|||||
70|18|60503_Sign.jpeg|09|01|01||||
70|18|60503_Photo.jpeg|02|01|01||||
20|19|01|01|||||||||||01|||01|70601|MR|SAMEER||KAPOOR|||||||01|MR|RAJINDER|KAPOOR|||MRS|NEELAM|KAPOOR|||M|02|IN|B-01|03-02-1987|||||||||01|02|||||02|B-148 GREATER KAILASH|GK PART 1|NEW DELHI|NEW DELHI|SOUTH DELHI|DL|IN|110048|01||Y|02|B-148 GREATER KAILASH|GK PART 1|NEW DELHI|NEW DELHI|SOUTH DELHI|DL|IN|110048|01||||||||||||||||9811022019|||||31-08-2020|DELHI GREATER KAILASH|31-08-2020|01|ROHAN MALHOTRA|RELATIONSHIP OFFICER|HEAD OFFICE|2|RAJDHANI URBAN BANK|IN1717|1|0||0|4||||||
30|19|C|AHKPK7788Q||01|02|||||
70|19|70601_Sign.jpeg|09|01|01||||
70|19|70601_Photo.jpeg|02|01|01||||
70|19|70601_AP.jpg|04|01|01||||
20|20|01|01|||||||||||01|||01|70602|MRS|RITU||ARORA|||||||01|MR|VINOD|ARORA|||MRS|SUSHMA|ARORA|||F|01|IN|O-04|26-06-1984|||||||||01|02|||||02|FLAT 92 ROHINI SECTOR 9|MAIN ROAD|NEW DELHI|NEW DELHI|NORTH WEST DELHI|DL|IN|110085|01||Y|02|FLAT 92 ROHINI SECTOR 9|MAIN ROAD|NEW DELHI|NEW DELHI|NORTH WEST DELHI|DL|IN|110085|01||||||||||||||||9811055020|||||31-08-2020|DELHI ROHINI|31-08-2020|01|SAURABH GUPTA|ASSISTANT MANAGER|HEAD OFFICE|2|RAJDHANI URBAN BANK|IN1717|1|0||0|3||||||
30|20|E|XXXXXXXX8877||01|02|||||
70|20|70602_Sign.jpeg|09|01|01||||
70|20|70602_AP.jpg|04|01|01||||
20|21|01|01|||||||||||01|||01|80701|MR|ABHISHEK||BANERJEE|||||||01|MR|SANJOY|BANERJEE|||MRS|MONISHA|BANERJEE|||M|01|IN|B-01|09-09-1979|||||||||01|02|||||02|14/B SALT LAKE SECTOR V|BIDHANNAGAR|KOLKATA|KOLKATA|KOLKATA|WB|IN|700091|01||Y|02|14/B SALT LAKE SECTOR V|BIDHANNAGAR|KOLKATA|KOLKATA|KOLKATA|WB|IN|700091|01||||||||||||||||9831022021|||||31-08-2020|KOLKATA SALT LAKE|31-08-2020|01|DEBANGSHU ROY|BRANCH MANAGER|HEAD OFFICE|10|BENGAL HERITAGE BANK|IN2718|1|0||0|3||||||
30|21|E|XXXXXXXX3344||01|02|||||
70|21|80701_Sign.jpeg|09|01|01||||
70|21|80701_AP.jpg|04|01|01||||
20|22|01|01|||||||||||01|||01|80702|MRS|MAITRYEE||DAS|||||||01|MR|SUBHASH|DAS|||MRS|JAYA|DAS|||F|01|IN|O-04|11-03-1993|||||||||01|02|||||02|82 RABINDRA SARANI|HOWRAH STATION ROAD|HOWRAH|HOWRAH|HOWRAH|WB|IN|711101|01||Y|02|82 RABINDRA SARANI|HOWRAH STATION ROAD|HOWRAH|HOWRAH|HOWRAH|WB|IN|711101|01||||||||||||||||9831055022|||||30-08-2020|HOWRAH STATION|30-08-2020|01|RUPA SEN|TELLER|HEAD OFFICE|10|BENGAL HERITAGE BANK|IN2718|1|0||0|3||||||
30|22|E|XXXXXXXX9911||01|02|||||
70|22|80702_Sign.jpeg|09|01|01||||
70|22|80702_AP.jpg|04|01|01||||
20|23|01|01|||||||||||01|||01|90801|MR|GAURAV||THAKUR|||||||01|MR|VIKRAM|THAKUR|||MRS|SUNITA|THAKUR|||M|02|IN|B-01|13-06-1986|||||||||01|02|||||02|HOUSE 7, NEW SHIMLA SECTOR 4|MIST CHAMBER ROAD|SHIMLA|SHIMLA|SHIMLA|HP|IN|171002|01||Y|02|HOUSE 7, NEW SHIMLA SECTOR 4|MIST CHAMBER ROAD|SHIMLA|SHIMLA|SHIMLA|HP|IN|171002|01||||||||||||||||9418022023|||||31-08-2020|SHIMLA NEW SECTOR 4|31-08-2020|01|ANIL CHAUHAN|ASSISTANT MANAGER|HEAD OFFICE|3|HIMACHAL PARVAT BANK|IN3719|1|0||0|4||||||
30|23|C|AILPT4455G||01|02|||||
70|23|90801_Sign.jpeg|09|01|01||||
70|23|90801_Photo.jpeg|02|01|01||||
70|23|90801_AP.jpg|04|01|01||||
20|24|01|01|||||||||||01|||01|90802|MRS|AANCHAL||SHARMA|||||||01|MR|RAJESH|SHARMA|||MRS|REKHA|SHARMA|||F|01|IN|O-05|28-11-1996|||||||||01|02|||||02|14 LOWER BAZAR|BUS STAND ROAD|MANDI|MANDI|MANDI|HP|IN|175001|01||Y|02|14 LOWER BAZAR|BUS STAND ROAD|MANDI|MANDI|MANDI|HP|IN|175001|01||||||||||||||||9418055024|||||31-08-2020|MANDI BUS STAND|31-08-2020|01|POOJA NEGI|TELLER|HEAD OFFICE|3|HIMACHAL PARVAT BANK|IN3719|1|0||0|3||||||
30|24|E|XXXXXXXX5566||01|02|||||
70|24|90802_Sign.jpeg|09|01|01||||
70|24|90802_AP.jpg|04|01|01||||
20|25|01|01|||||||||||01|||01|10500|MR|FAIZAN||KHAN|||||||01|MR|MAQBOOL|KHAN|||MRS|RUKHSANA|KHAN|||M|01|IN|S-01|16-04-1990|||||||||01|02|||||02|22 LAL DARWAZA|HAJI ALI ROAD|HYDERABAD|HYDERABAD|HYDERABAD|TS|IN|500002|01||Y|02|22 LAL DARWAZA|HAJI ALI ROAD|HYDERABAD|HYDERABAD|HYDERABAD|TS|IN|500002|01||||||||||||||||9849022025|||||30-08-2020|HYDERABAD CHARMINAR|30-08-2020|01|ZAINAB ALI|RELATIONSHIP OFFICER|HEAD OFFICE|8|DECCAN URBAN BANK|IN4720|1|0||0|3||||||
30|25|E|XXXXXXXX7788||01|02|||||
70|25|10500_Sign.jpeg|09|01|01||||
70|25|10500_AP.jpg|04|01|01||||
20|26|01|01|||||||||||01|||01|10501|MRS|SWATI||RAO|||||||01|MR|MURALI|RAO|||MRS|VANI|RAO|||F|02|IN|S-01|02-10-1988|||||||||01|02|||||02|FLAT 12B SAINIKPURI|ECIL CROSS ROAD|HYDERABAD|HYDERABAD|HYDERABAD|TS|IN|500094|01||Y|02|FLAT 12B SAINIKPURI|ECIL CROSS ROAD|HYDERABAD|HYDERABAD|HYDERABAD|TS|IN|500094|01||||||||||||||||9849055026|||||31-08-2020|HYDERABAD SAINIKPURI|31-08-2020|01|RAVI TEJA|ASSISTANT MANAGER|HEAD OFFICE|8|DECCAN URBAN BANK|IN4720|1|0||0|3||||||
30|26|E|XXXXXXXX1199||01|02|||||
70|26|10501_Sign.jpeg|09|01|01||||
70|26|10501_AP.jpg|04|01|01||||
20|27|01|01|||||||||||01|||01|11500|MR|MANMOHAN|SINGH|GILL|||||||01|MR|HARDEEP|SINGH|GILL||MRS|SIMRAN|KAUR|||M|01|IN|B-01|12-05-1974|||||||||01|02|||||02|H 32 MODEL TOWN|MAJITHA ROAD|AMRITSAR|AMRITSAR|AMRITSAR|PB|IN|143001|01||Y|02|H 32 MODEL TOWN|MAJITHA ROAD|AMRITSAR|AMRITSAR|AMRITSAR|PB|IN|143001|01||||||||||||||||9876022027|||||31-08-2020|AMRITSAR MODEL TOWN|31-08-2020|01|JASPREET KAUR|TELLER|HEAD OFFICE|13|PUNJAB SAHAKARI BANK|IN5721|1|0||0|4||||||
30|27|C|ANBPG2233J||01|02|||||
70|27|11500_Sign.jpeg|09|01|01||||
70|27|11500_Photo.jpeg|02|01|01||||
70|27|11500_AP.jpg|04|01|01||||
20|28|01|01|||||||||||01|||01|11501|MRS|HARPREET||KAUR|||||||01|MR|MANJIT|SINGH|||MRS|GURMEET|KAUR|||F|01|IN|O-04|24-07-1985|||||||||01|02|||||02|HOUSE 18 SARABHA NAGAR|FEROZEPUR ROAD|LUDHIANA|LUDHIANA|LUDHIANA|PB|IN|141001|01||Y|02|HOUSE 18 SARABHA NAGAR|FEROZEPUR ROAD|LUDHIANA|LUDHIANA|LUDHIANA|PB|IN|141001|01||||||||||||||||9876055028|||||31-08-2020|LUDHIANA SARABHA NAGAR|31-08-2020|01|RANBIR SANDHU|RELATIONSHIP OFFICER|HEAD OFFICE|13|PUNJAB SAHAKARI BANK|IN5721|1|0||0|3||||||
30|28|E|XXXXXXXX0044||01|02|||||
70|28|11501_Sign.jpeg|09|01|01||||
70|28|11501_AP.jpg|04|01|01||||
20|29|01|01|||||||||||01|||01|12500|MR|DEEPAK||TIWARI|||||||01|MR|RAKESH|TIWARI|||MRS|KAMINI|TIWARI|||M|01|IN|B-01|31-01-1981|||||||||01|02|||||02|MIG 102 ARERA COLONY|E-7 EXTENSION|BHOPAL|BHOPAL|BHOPAL|MP|IN|462016|01||Y|02|MIG 102 ARERA COLONY|E-7 EXTENSION|BHOPAL|BHOPAL|BHOPAL|MP|IN|462016|01||||||||||||||||9425022029|||||30-08-2020|BHOPAL ARERA COLONY|30-08-2020|01|RAJESH MALVIYA|ASSISTANT MANAGER|HEAD OFFICE|4|MADHYA PRADESH NAGRIK BANK|IN6722|1|0||0|3||||||
30|29|E|XXXXXXXX2233||01|02|||||
70|29|12500_Sign.jpeg|09|01|01||||
70|29|12500_AP.jpg|04|01|01||||
20|30|01|01|||||||||||01|||01|12501|MRS|SHALINI||JAIN|||||||01|MR|ARVIND|JAIN|||MRS|MEENAKSHI|JAIN|||F|02|IN|O-04|18-09-1991|||||||||01|02|||||02|FLAT 7 VIJAY NAGAR|RING ROAD|INDORE|INDORE|INDORE|MP|IN|452010|01||Y|02|FLAT 7 VIJAY NAGAR|RING ROAD|INDORE|INDORE|INDORE|MP|IN|452010|01||||||||||||||||9425055030|||||31-08-2020|INDORE VIJAY NAGAR|31-08-2020|01|NIDHI AGRAWAL|TELLER|HEAD OFFICE|4|MADHYA PRADESH NAGRIK BANK|IN6722|1|0||0|3||||||
30|30|C|AKSPJ6677Y||01|02|||||
70|30|12501_Sign.jpeg|09|01|01||||
70|30|12501_Photo.jpeg|02|01|01||||
70|30|12501_AP.jpg|04|01|01||||
`;
+158 -37
View File
@@ -27,22 +27,29 @@
<mat-sidenav-content class="sidenav">
<!-- Sidebar Toggle Handle (hidden on small/handset screens) -->
@if ((isHandset$ | async) === false) {
<div
class="sidebar-handle"
[class.collapsed]="sidenavCollapsed"
(click)="toggleSidebarCollapse()"
matTooltip="{{ sidenavCollapsed ? 'Expand sidebar' : 'Collapse sidebar' }}"
matTooltipPosition="right"
>
<svg viewBox="0 0 8 14" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@if (!sidenavCollapsed) {
<path d="M6 1L1 7l5 6"/>
}
@if (sidenavCollapsed) {
<path d="M2 1l5 6-5 6"/>
}
</svg>
</div>
<div
class="sidebar-handle"
[class.collapsed]="sidenavCollapsed"
(click)="toggleSidebarCollapse()"
matTooltip="{{ sidenavCollapsed ? 'Expand sidebar' : 'Collapse sidebar' }}"
matTooltipPosition="right"
>
<svg
viewBox="0 0 8 14"
fill="none"
stroke="#fff"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
@if (!sidenavCollapsed) {
<path d="M6 1L1 7l5 6" />
}
@if (sidenavCollapsed) {
<path d="M2 1l5 6-5 6" />
}
</svg>
</div>
}
<!-- Toolbar -->
@@ -56,7 +63,12 @@
<fa-icon icon="university" class="ni-ico"></fa-icon>Institution
<fa-icon icon="caret-down" class="ni-caret"></fa-icon>
</a>
<a class="ni" tabindex="0" routerLink="/checker-inbox-and-tasks/checker-inbox" *mifosxHasPermission="'MENU_TASKS'">
<a
class="ni"
tabindex="0"
routerLink="/checker-inbox-and-tasks/checker-inbox"
*mifosxHasPermission="'MENU_TASKS'"
>
<fa-icon icon="check" class="ni-ico"></fa-icon>Tasks
</a>
<a class="ni" tabindex="0" routerLink="/accounting" *mifosxHasPermission="'MENU_ACCOUNTING'">
@@ -70,11 +82,21 @@
<fa-icon icon="shield-alt" class="ni-ico"></fa-icon>Admin
<fa-icon icon="caret-down" class="ni-caret"></fa-icon>
</a>
<a class="ni" tabindex="0" (click)="subLedgerOpen = !subLedgerOpen; glMenuOpen = false" *mifosxHasPermission="'MENU_SUB_LEDGER'">
<a
class="ni"
tabindex="0"
(click)="subLedgerOpen = !subLedgerOpen; glMenuOpen = false"
*mifosxHasPermission="'MENU_SUB_LEDGER'"
>
<fa-icon icon="book" class="ni-ico"></fa-icon>Sub Ledger
<fa-icon [icon]="subLedgerOpen ? 'caret-up' : 'caret-down'" class="ni-caret"></fa-icon>
</a>
<a class="ni" tabindex="0" (click)="glMenuOpen = !glMenuOpen; subLedgerOpen = false" *mifosxHasPermission="'MENU_GENERAL_LEDGER'">
<a
class="ni"
tabindex="0"
(click)="glMenuOpen = !glMenuOpen; subLedgerOpen = false"
*mifosxHasPermission="'MENU_GENERAL_LEDGER'"
>
<fa-icon icon="book-open" class="ni-ico"></fa-icon>General Ledger
<fa-icon [icon]="glMenuOpen ? 'caret-up' : 'caret-down'" class="ni-caret"></fa-icon>
</a>
@@ -121,7 +143,12 @@
<fa-icon icon="shield-alt" class="ni-ico"></fa-icon>Insurance
<fa-icon icon="caret-down" class="ni-caret"></fa-icon>
</a>
<a class="ni" tabindex="0" (click)="rbiMenuOpen = !rbiMenuOpen; glMenuOpen = false" *mifosxHasPermission="'MENU_RBI'">
<a
class="ni"
tabindex="0"
(click)="rbiMenuOpen = !rbiMenuOpen; glMenuOpen = false"
*mifosxHasPermission="'MENU_RBI'"
>
<fa-icon icon="university" class="ni-ico"></fa-icon>RBI
<fa-icon [icon]="rbiMenuOpen ? 'caret-up' : 'caret-down'" class="ni-caret"></fa-icon>
</a>
@@ -373,14 +400,24 @@
<a class="gl-link" [class.hidden]="!matchesGlSearch('Trial Balance')" (click)="glMenuOpen = false"
><fa-icon icon="balance-scale" class="gl-icon"></fa-icon> Trial Balance</a
>
<a class="gl-link" [class.hidden]="!matchesGlSearch('Profit and Loss')" (click)="glMenuOpen = false"
><fa-icon icon="chart-line" class="gl-icon"></fa-icon> Profit and Loss</a
<a
class="gl-link"
[class.hidden]="!matchesGlSearch('Profit and Loss')"
(click)="glMenuOpen = false; openAccountingReportsDialog('PNL')"
><fa-icon icon="chart-line" class="gl-icon" style="color: #b45309"></fa-icon> Profit and Loss</a
>
<a
class="gl-link"
[class.hidden]="!matchesGlSearch('Consolidate Balance Sheet')"
(click)="glMenuOpen = false"
><fa-icon icon="file-alt" class="gl-icon"></fa-icon> Consolidate Balance Sheet</a
(click)="glMenuOpen = false; openAccountingReportsDialog('BALANCE-SHEET')"
><fa-icon icon="file-alt" class="gl-icon" style="color: #b45309"></fa-icon> Consolidate Balance Sheet</a
>
<a
class="gl-link"
[class.hidden]="!matchesGlSearch('Comparative Balance Sheet')"
(click)="glMenuOpen = false; openAccountingReportsDialog('COMPARATIVE-BS')"
><fa-icon icon="balance-scale" class="gl-icon" style="color: #b45309"></fa-icon> Comparative Balance
Sheet</a
>
</div>
<div class="gl-group">
@@ -400,6 +437,13 @@
<a class="gl-link" [class.hidden]="!matchesGlSearch('Distribution of Balance')" (click)="glMenuOpen = false"
><fa-icon icon="chart-pie" class="gl-icon"></fa-icon> Distribution of Balance</a
>
<a
class="gl-link"
[class.hidden]="!matchesGlSearch('GL Wise DR CR')"
(click)="glMenuOpen = false; openAccountingReportsDialog('GL-DR-CR')"
><fa-icon icon="project-diagram" class="gl-icon" style="color: #b45309"></fa-icon> GL Wise DR / CR
Report</a
>
</div>
<div class="gl-group">
<div class="gl-group-title">Account Reports</div>
@@ -424,6 +468,12 @@
<a class="gl-link" [class.hidden]="!matchesGlSearch('Voucher Register')" (click)="glMenuOpen = false"
><fa-icon icon="receipt" class="gl-icon"></fa-icon> Voucher Register</a
>
<a
class="gl-link"
[class.hidden]="!matchesGlSearch('Misc Expense Ledger')"
(click)="glMenuOpen = false; openAccountingReportsDialog('MISC-EXP')"
><fa-icon icon="receipt" class="gl-icon" style="color: #b45309"></fa-icon> Misc Expense Ledger</a
>
</div>
</div>
</div>
@@ -649,8 +699,8 @@
<button mat-menu-item tabindex="0">
<fa-icon icon="chart-line" class="menu-item-icon"></fa-icon> SLR and Non SLR Investment Report
</button>
<button mat-menu-item tabindex="0">
<fa-icon icon="file-alt" class="menu-item-icon"></fa-icon> CIC Reporting
<button mat-menu-item tabindex="0" (click)="openCicReportingDialog()">
<fa-icon icon="file-alt" class="menu-item-icon" style="color: #6d28d9"></fa-icon> CIC Reporting
</button>
<button mat-menu-item tabindex="0">
<fa-icon icon="database" class="menu-item-icon"></fa-icon> Inventory Stock Register
@@ -658,6 +708,10 @@
<button mat-menu-item tabindex="0">
<fa-icon icon="chart-bar" class="menu-item-icon"></fa-icon> Depreciation Register
</button>
<button mat-menu-item routerLink="/fiu" tabindex="0">
<fa-icon icon="shield-alt" class="menu-item-icon" style="color: #dc2626"></fa-icon> Financial Intelligence
Unit (FIU-IND)
</button>
</div>
<!-- Investment -->
<div class="deposit-menu-group">
@@ -671,8 +725,8 @@
<button mat-menu-item tabindex="0">
<fa-icon icon="piggy-bank" class="menu-item-icon"></fa-icon> Bucket Wise Investment
</button>
<button mat-menu-item tabindex="0">
<fa-icon icon="file-invoice" class="menu-item-icon"></fa-icon> Deaf Report
<button mat-menu-item routerLink="/deaf" tabindex="0">
<fa-icon icon="file-invoice" class="menu-item-icon" style="color: #0e7e6b"></fa-icon> Deaf Report
</button>
</div>
<!-- Reports & Admin -->
@@ -745,12 +799,21 @@
<button mat-menu-item tabindex="0">
<fa-icon icon="file-signature" class="menu-item-icon"></fa-icon> Signature Report
</button>
<button mat-menu-item tabindex="0">
<fa-icon icon="exclamation-triangle" class="menu-item-icon"></fa-icon> Risk Category
<button mat-menu-item tabindex="0" (click)="openCkycReportsDialog('RISK-REVIEW')">
<fa-icon icon="exclamation-triangle" class="menu-item-icon" style="color: #be185d"></fa-icon> Risk Category
</button>
<button mat-menu-item tabindex="0">
<fa-icon icon="chart-bar" class="menu-item-icon"></fa-icon> Risk Category and Turnover Profile
</button>
<button mat-menu-item tabindex="0" (click)="openCkycReportsDialog('REKYC')">
<fa-icon icon="sync-alt" class="menu-item-icon" style="color: #be185d"></fa-icon> Re-KYC Report
</button>
<button mat-menu-item tabindex="0" (click)="openCkycReportsDialog('RISK-REVIEW')">
<fa-icon icon="history" class="menu-item-icon" style="color: #be185d"></fa-icon> Risk Review
</button>
<button mat-menu-item tabindex="0" (click)="openCkycReportsDialog('CERSAI')">
<fa-icon icon="shield-alt" class="menu-item-icon" style="color: #be185d"></fa-icon> CERSAI Report
</button>
</mat-menu>
<mat-menu #advancesMenu="matMenu" class="advances-mega-panel" [overlapTrigger]="false">
@@ -858,7 +921,9 @@
</mat-menu>
<mat-menu #insuranceMenu="matMenu" [overlapTrigger]="false" class="nav-dropdown">
<button mat-menu-item tabindex="0"><fa-icon icon="building" class="menu-item-icon"></fa-icon> DICGC</button>
<button mat-menu-item routerLink="/dicgc" tabindex="0">
<fa-icon icon="building" class="menu-item-icon" style="color: #c2410c"></fa-icon> DICGC
</button>
<button mat-menu-item tabindex="0"><fa-icon icon="heart" class="menu-item-icon"></fa-icon> PMJBY</button>
<button mat-menu-item tabindex="0"><fa-icon icon="heart" class="menu-item-icon"></fa-icon> PMSBY</button>
<button mat-menu-item tabindex="0">
@@ -918,8 +983,13 @@
<a class="gl-link" [class.hidden]="!matchesRbiSearch('Loan Dump')" (click)="rbiMenuOpen = false">
<fa-icon icon="database" class="gl-icon"></fa-icon> Loan Dump
</a>
<a class="gl-link" [class.hidden]="!matchesRbiSearch('ALM')" (click)="rbiMenuOpen = false">
<fa-icon icon="chart-bar" class="gl-icon"></fa-icon> ALM
<a
class="gl-link"
[class.hidden]="!matchesRbiSearch('ALM')"
(click)="rbiMenuOpen = false; openAlmReportDialog()"
>
<fa-icon icon="balance-scale" class="gl-icon" style="color: #4f46e5"></fa-icon> ALM (Asset Liability
Management)
</a>
<a class="gl-link" [class.hidden]="!matchesRbiSearch('Balance Sheet')" (click)="rbiMenuOpen = false">
<fa-icon icon="file-alt" class="gl-icon"></fa-icon> Balance Sheet (BR Act & Others)
@@ -944,8 +1014,21 @@
<a class="gl-link" [class.hidden]="!matchesRbiSearch('No Interest Report')" (click)="rbiMenuOpen = false">
<fa-icon icon="ban" class="gl-icon"></fa-icon> No Interest Report
</a>
<a class="gl-link" [class.hidden]="!matchesRbiSearch('Income Tax Report')" (click)="rbiMenuOpen = false">
<fa-icon icon="file-invoice" class="gl-icon"></fa-icon> Income Tax Report
<a
class="gl-link"
[class.hidden]="!matchesRbiSearch('Income Tax Report')"
routerLink="/sft"
(click)="rbiMenuOpen = false"
>
<fa-icon icon="file-invoice" class="gl-icon" style="color: #1d4ed8"></fa-icon> Income Tax Report (SFT)
</a>
<a
class="gl-link"
[class.hidden]="!matchesRbiSearch('Priority Sector Lending')"
(click)="rbiMenuOpen = false; openPslReportDialog()"
>
<fa-icon icon="flag" class="gl-icon" style="color: #16a34a"></fa-icon> Priority Sector Lending (PSL
Returns)
</a>
</div>
<!-- Statement Reports -->
@@ -982,8 +1065,12 @@
<a class="gl-link" [class.hidden]="!matchesRbiSearch('OSS 1 XBRL/CIMS')" (click)="rbiMenuOpen = false">
<fa-icon icon="file-alt" class="gl-icon"></fa-icon> OSS 1 XBRL/CIMS
</a>
<a class="gl-link" [class.hidden]="!matchesRbiSearch('OSS 2 XBRL')" (click)="rbiMenuOpen = false">
<fa-icon icon="file-alt" class="gl-icon"></fa-icon> OSS 2 XBRL
<a
class="gl-link"
[class.hidden]="!matchesRbiSearch('OSS 2 XBRL')"
(click)="rbiMenuOpen = false; openCimsXbrlDialog('OSS-2')"
>
<fa-icon icon="file-alt" class="gl-icon" style="color: #0891b2"></fa-icon> OSS 2 XBRL
</a>
<a class="gl-link" [class.hidden]="!matchesRbiSearch('OSS 3 XBRL/CIMS')" (click)="rbiMenuOpen = false">
<fa-icon icon="file-alt" class="gl-icon"></fa-icon> OSS 3 XBRL/CIMS
@@ -995,6 +1082,40 @@
<fa-icon icon="file-alt" class="gl-icon"></fa-icon> OSS 7
</a>
</div>
<!-- CIMS / XBRL Returns -->
<div class="gl-group">
<div class="gl-group-title">CIMS / XBRL Returns</div>
<a
class="gl-link"
[class.hidden]="!matchesRbiSearch('Form OSS-2 CIMS')"
(click)="rbiMenuOpen = false; openCimsXbrlDialog('OSS-2')"
>
<fa-icon icon="shield-alt" class="gl-icon" style="color: #0891b2"></fa-icon> Form OSS-2 (Off-site
Surveillance)
</a>
<a
class="gl-link"
[class.hidden]="!matchesRbiSearch('Form PSA Priority Sector')"
(click)="rbiMenuOpen = false; openCimsXbrlDialog('PSA')"
>
<fa-icon icon="flag" class="gl-icon" style="color: #0891b2"></fa-icon> Form PSA (Priority Sector Advances)
</a>
<a
class="gl-link"
[class.hidden]="!matchesRbiSearch('Form RAL Assets Liabilities')"
(click)="rbiMenuOpen = false; openCimsXbrlDialog('RAL')"
>
<fa-icon icon="balance-scale" class="gl-icon" style="color: #0891b2"></fa-icon> Form RAL (Return on Assets
&amp; Liabilities)
</a>
<a
class="gl-link"
[class.hidden]="!matchesRbiSearch('Form RCP UCB Cash Position')"
(click)="rbiMenuOpen = false; openCimsXbrlDialog('RCP-UCB')"
>
<fa-icon icon="coins" class="gl-icon" style="color: #0891b2"></fa-icon> Form RCP UCB (Cash Position)
</a>
</div>
<!-- Annual & Sector -->
<div class="gl-group">
<div class="gl-group-title">Annual &amp; Sector</div>
+122
View File
@@ -9,6 +9,7 @@
/** Angular Imports */
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
/** rxjs Imports */
import { Observable, Subscription } from 'rxjs';
@@ -56,6 +57,7 @@ export class ShellComponent implements OnInit, OnDestroy {
private breakpointObserver = inject(BreakpointObserver);
private progressBarService = inject(ProgressBarService);
private cdr = inject(ChangeDetectorRef);
private dialog = inject(MatDialog);
/** Subscription to breakpoint observer for handset. */
isHandset$: Observable<boolean> = this.breakpointObserver
@@ -123,6 +125,126 @@ export class ShellComponent implements OnInit, OnDestroy {
// Placeholder — config wizard is handled by toolbar component
}
/**
* Opens the Credit Information Companies — Data Quality Report dialog.
* Lazy-loads the same component used by the home dashboard's CIC card.
*/
async openCicReportingDialog(): Promise<void> {
const { CicDqrDialogComponent } = await import('../../home/dashboard/cic-dqr-dialog/cic-dqr-dialog.component');
this.dialog.open(CicDqrDialogComponent, {
width: '88vw',
maxWidth: '1200px',
maxHeight: '92vh',
panelClass: 'crr-slr-dialog-panel',
autoFocus: false,
disableClose: false,
hasBackdrop: true,
backdropClass: 'cic-dlg-backdrop'
});
}
/**
* Opens the Priority Sector Lending Returns dialog.
* Lazy-loads the same component used by the home dashboard's PSL card.
*/
async openPslReportDialog(): Promise<void> {
const { PslReportDialogComponent } =
await import('../../home/dashboard/psl-report-dialog/psl-report-dialog.component');
this.dialog.open(PslReportDialogComponent, {
width: '92vw',
maxWidth: '1280px',
maxHeight: '92vh',
panelClass: 'crr-slr-dialog-panel',
autoFocus: false,
disableClose: false,
hasBackdrop: true,
backdropClass: 'psl-dlg-backdrop'
});
}
/**
* Opens the Accounting Reports dialog (P&L / Balance Sheet / Comparative B/S /
* GL Wise DR/CR / Misc Expense Ledger). Initial report selected via menu link.
*/
async openAccountingReportsDialog(
initialReport: 'PNL' | 'BALANCE-SHEET' | 'COMPARATIVE-BS' | 'GL-DR-CR' | 'MISC-EXP' = 'PNL'
): Promise<void> {
const { AccountingReportsDialogComponent } =
await import('../../home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component');
this.dialog.open(AccountingReportsDialogComponent, {
width: '94vw',
maxWidth: '1360px',
maxHeight: '94vh',
panelClass: 'crr-slr-dialog-panel',
autoFocus: false,
disableClose: false,
hasBackdrop: true,
backdropClass: 'acct-rpt-dlg-backdrop',
data: { initialReport }
});
}
/**
* Opens the CKYC Compliance Reports dialog (Re-KYC / Risk Review / CERSAI).
* The initial report is selected from the menu link the user clicked; the user
* can switch reports via the pill selector inside the dialog.
*/
async openCkycReportsDialog(initialReport: 'REKYC' | 'RISK-REVIEW' | 'CERSAI' = 'REKYC'): Promise<void> {
const { CkycReportsDialogComponent } =
await import('../../home/dashboard/ckyc-reports-dialog/ckyc-reports-dialog.component');
this.dialog.open(CkycReportsDialogComponent, {
width: '94vw',
maxWidth: '1320px',
maxHeight: '94vh',
panelClass: 'crr-slr-dialog-panel',
autoFocus: false,
disableClose: false,
hasBackdrop: true,
backdropClass: 'ckyc-rpt-dlg-backdrop',
data: { initialReport }
});
}
/**
* Opens the CIMS / XBRL Returns dialog. The initial form (OSS-2, PSA, RAL, RCP-UCB)
* is selected based on the menu link the user clicked; user can switch between
* forms via the form-pill selector inside the dialog.
*/
async openCimsXbrlDialog(initialForm: 'OSS-2' | 'PSA' | 'RAL' | 'RCP-UCB' = 'OSS-2'): Promise<void> {
const { CimsXbrlDialogComponent } =
await import('../../home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component');
this.dialog.open(CimsXbrlDialogComponent, {
width: '94vw',
maxWidth: '1320px',
maxHeight: '94vh',
panelClass: 'crr-slr-dialog-panel',
autoFocus: false,
disableClose: false,
hasBackdrop: true,
backdropClass: 'cims-dlg-backdrop',
data: { initialForm }
});
}
/**
* Opens the Asset Liability Management dialog (Form ALM-1).
* Covers maturity ladder, monthly snapshot, and structural liquidity returns.
*/
async openAlmReportDialog(): Promise<void> {
const { AlmReportDialogComponent } =
await import('../../home/dashboard/alm-report-dialog/alm-report-dialog.component');
this.dialog.open(AlmReportDialogComponent, {
width: '94vw',
maxWidth: '1320px',
maxHeight: '92vh',
panelClass: 'crr-slr-dialog-panel',
autoFocus: false,
disableClose: false,
hasBackdrop: true,
backdropClass: 'alm-dlg-backdrop'
});
}
/**
* Unsubscribes from progress bar.
*/
+130 -130
View File
@@ -130,145 +130,145 @@
<a matLine>{{ 'labels.menus.Individual Collection Sheet' | translate }}</a>
</mat-list-item>
<ng-container *mifosxHasPermission="'SIDEBAR_REMITTANCES'">
<mat-list-item
*ngIf="mifosRemittanceEnabled"
[routerLink]="['/remittances/process']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Process Remittance' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="money-bill-wave" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Remittances' | translate }}</a>
</mat-list-item>
<mat-list-item
*ngIf="mifosRemittanceEnabled"
[routerLink]="['/remittances/process']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Process Remittance' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="money-bill-wave" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Remittances' | translate }}</a>
</mat-list-item>
</ng-container>
<ng-container *mifosxHasPermission="'SIDEBAR_ANALYTICS'">
<mat-list-item
*ngIf="supersetEnabled"
[routerLink]="['/analytics']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Analytics' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="chart-bar" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Analytics' | translate }}</a>
</mat-list-item>
<mat-list-item
*ngIf="supersetEnabled"
[routerLink]="['/analytics']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Analytics' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="chart-bar" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Analytics' | translate }}</a>
</mat-list-item>
</ng-container>
<!-- CFCFRMS Section -->
<ng-container *mifosxHasPermission="'SIDEBAR_CFCFRMS'">
<div class="nav-item-sep mt-1">
<mat-divider></mat-divider>
<span class="text-muted">{{ 'labels.menus.CFCFRMS' | translate }}</span>
</div>
<mat-list-item
[routerLink]="['/cfcfrms/dashboard']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.CFCFRMS Dashboard' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: true }"
>
<mat-icon matListIcon>
<fa-icon icon="shield-alt" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.CFCFRMS Dashboard' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/complaints']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Complaints' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="list-alt" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Complaints' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/escalate']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Escalate Complaint' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="arrow-up" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Escalate' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/report-suspect']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Report Suspect' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="user-shield" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Report Suspect' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/report-mule']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Report Mule Account' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="user-slash" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Report Mule' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/report-fraud-txn']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Report Fraud Transaction' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="exclamation-triangle" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Report Fraud Txn' | translate }}</a>
</mat-list-item>
<div class="nav-item-sep mt-1">
<mat-divider></mat-divider>
<span class="text-muted">{{ 'labels.menus.CFCFRMS' | translate }}</span>
</div>
<mat-list-item
[routerLink]="['/cfcfrms/dashboard']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.CFCFRMS Dashboard' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: true }"
>
<mat-icon matListIcon>
<fa-icon icon="shield-alt" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.CFCFRMS Dashboard' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/complaints']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Complaints' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="list-alt" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Complaints' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/escalate']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Escalate Complaint' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="arrow-up" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Escalate' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/report-suspect']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Report Suspect' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="user-shield" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Report Suspect' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/report-mule']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Report Mule Account' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="user-slash" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Report Mule' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/report-fraud-txn']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.Report Fraud Transaction' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="exclamation-triangle" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.Report Fraud Txn' | translate }}</a>
</mat-list-item>
</ng-container>
<!-- RBI Integration Section -->
<ng-container *mifosxHasPermission="'SIDEBAR_RBI_INTEGRATION'">
<div class="nav-item-sep mt-1">
<mat-divider></mat-divider>
<span class="text-muted">{{ 'labels.menus.RBI Integration' | translate }}</span>
</div>
<mat-list-item
[routerLink]="['/cfcfrms/fmr-submission']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.FMR Submission' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="university" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.FMR Submission' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/cfr-lookup']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.CFR Lookup' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="search" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.CFR Lookup' | translate }}</a>
</mat-list-item>
<div class="nav-item-sep mt-1">
<mat-divider></mat-divider>
<span class="text-muted">{{ 'labels.menus.RBI Integration' | translate }}</span>
</div>
<mat-list-item
[routerLink]="['/cfcfrms/fmr-submission']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.FMR Submission' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="university" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.FMR Submission' | translate }}</a>
</mat-list-item>
<mat-list-item
[routerLink]="['/cfcfrms/cfr-lookup']"
[matTooltipPosition]="tooltipPosition"
matTooltip="{{ 'tooltips.CFR Lookup' | translate }}"
routerLinkActive="active-menu"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-icon matListIcon>
<fa-icon icon="search" size="sm"></fa-icon>
</mat-icon>
<a matLine>{{ 'labels.menus.CFR Lookup' | translate }}</a>
</mat-list-item>
</ng-container>
<div class="nav-item-sep mt-1">
@@ -54,10 +54,21 @@ $hover-bg: #f1f5f9;
span {
border-radius: 2px;
&:nth-child(1) { background: $brand; }
&:nth-child(2) { background: #0ea5e9; }
&:nth-child(3) { background: #10b981; }
&:nth-child(4) { background: #cbd5e1; }
&:nth-child(1) {
background: $brand;
}
&:nth-child(2) {
background: #0ea5e9;
}
&:nth-child(3) {
background: #10b981;
}
&:nth-child(4) {
background: #cbd5e1;
}
}
}
+475
View File
@@ -0,0 +1,475 @@
/**
* 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/.
*/
/**
* Sample DEAF data — schema and totals match the actual CIBIL/RBI return template.
* Bank/customer/officer/auditor names are dummy placeholders.
*
* Three RBI-prescribed reports:
* Form I — Monthly Return of unclaimed deposits transferred to DEAF (filed online by 15th)
* Form II — Same return submitted as original hardcopy
* Annex 4 — Refund Claim Form (when bank claims back from DEAF for paid-out customer refunds)
*/
export interface DeafFormRow {
/** 1=Opening, 2=Wrongly reported earlier, 3=Transferred this month, 4=Claims settled, 5=Net, 6=Total at end */
sl: number;
particulars: string;
/** (a) Interest Bearing Deposits */
ibdAccounts: number;
ibdAmount: number;
/** (b) Non-Interest Bearing Deposits */
nibdAccounts: number;
nibdAmount: number;
/** (c) Other Credits (Non-Interest bearing) */
ocAccounts: number;
ocAmount: number;
/** (d) Total = (a) + (b) + (c) */
totalAccounts: number;
totalAmount: number;
}
export interface DeafForm {
bankName: string;
bankDeafCode: string;
remittedThroughSponsor: 'Y' | 'N';
sponsorBankName: string;
monthYear: string;
dateOfTransfer: string;
rows: DeafFormRow[];
/** Authorized officer + auditor (signature block) */
officer: string;
officerDesignation: string;
auditor: string;
auditorAddress: string;
}
export interface DeafClaim {
slNo: number;
ibdPrincipal: number;
ibdInterest: number;
ibdTotal: number;
nibdAccounts: number;
nibdAmount: number;
ocAccounts: number;
ocAmount: number;
grandTotalAccounts: number;
grandTotalAmount: number;
}
export interface DeafEligibleAccount {
acNo: string;
customerName: string;
acType: 'SB' | 'CA' | 'FD' | 'RD' | 'BG' | 'DD' | 'PO';
branch: string;
openedDate: string;
lastTxnDate: string;
inactiveYears: number;
/** IBD = Interest Bearing Deposit; NIBD = Non-Interest Bearing; OC = Other Credit */
bucket: 'IBD' | 'NIBD' | 'OC';
principal: number;
interest: number;
total: number;
status: 'eligible-this-month' | 'transferred' | 'claim-pending' | 'refunded';
transferredOn?: string;
}
/** Form I — Dec 2025 cycle */
export const FORM_I: DeafForm = {
bankName: 'Bharat Urban Bank Ltd, Mumbai Main Branch',
bankDeafCode: '1100',
remittedThroughSponsor: 'Y',
sponsorBankName: 'State Bank of India',
monthYear: '12/2025',
dateOfTransfer: '29-12-2025',
officer: 'Anjali Mehta',
officerDesignation: 'Chief Executive Officer',
auditor: 'Karthik Kamath',
auditorAddress: '12 MG Road, Worli, Mumbai 400018',
rows: [
{
sl: 1,
particulars: 'Opening balance of accounts transferred to the Fund at the beginning of the month',
ibdAccounts: 1208,
ibdAmount: 2571461.6,
nibdAccounts: 138,
nibdAmount: 177338.98,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 1346,
totalAmount: 2748800.58
},
{
sl: 2,
particulars: 'Accounts if any wrongly reported in past and rectified during this month',
ibdAccounts: 10,
ibdAmount: 7582.7,
nibdAccounts: 0,
nibdAmount: 0,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 10,
totalAmount: 7582.7
},
{
sl: 3,
particulars:
'Accounts transferred to the DEA Fund during this month (incl. inadvertently omitted in previous month)',
ibdAccounts: 0,
ibdAmount: 0,
nibdAccounts: 0,
nibdAmount: 0,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 0,
totalAmount: 0
},
{
sl: 4,
particulars: 'Claims settled and refund received from the DEA Fund during this month (only the principal)',
ibdAccounts: 2,
ibdAmount: 9177.0,
nibdAccounts: 0,
nibdAmount: 0,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 2,
totalAmount: 9177.0
},
{
sl: 5,
particulars: 'Net amount transferred to the Fund during the month (2 + 3 − 4)',
ibdAccounts: 8,
ibdAmount: -1594.3,
nibdAccounts: 0,
nibdAmount: 0,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 8,
totalAmount: -1594.3
},
{
sl: 6,
particulars: 'Total amount with the Fund at the end of the month (1 + 5)',
ibdAccounts: 1216,
ibdAmount: 2569867.3,
nibdAccounts: 138,
nibdAmount: 177338.98,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 1354,
totalAmount: 2747206.28
}
]
};
/** Form II — Jan 2026 (head office consolidated) */
export const FORM_II: DeafForm = {
bankName: 'Bharat Urban Bank Ltd, Head Office',
bankDeafCode: '1100',
remittedThroughSponsor: 'Y',
sponsorBankName: 'State Bank of India',
monthYear: '01/2026',
dateOfTransfer: '15-01-2026',
officer: 'Anjali Mehta',
officerDesignation: 'Chief Executive Officer',
auditor: 'Karthik Kamath',
auditorAddress: '12 MG Road, Worli, Mumbai 400018',
rows: [
{
sl: 1,
particulars: 'Opening balance at the beginning of the month',
ibdAccounts: 699,
ibdAmount: 2040493.18,
nibdAccounts: 140,
nibdAmount: 187649.0,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 839,
totalAmount: 2228142.18
},
{
sl: 2,
particulars: 'Accounts and amount inadvertently omitted in previous month return and transferred this month',
ibdAccounts: 0,
ibdAmount: 0,
nibdAccounts: 0,
nibdAmount: 0,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 0,
totalAmount: 0
},
{
sl: 3,
particulars: 'Accounts and amount due and transferred to the Fund during this month',
ibdAccounts: 5,
ibdAmount: 19956.75,
nibdAccounts: 0,
nibdAmount: 0,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 5,
totalAmount: 19956.75
},
{
sl: 4,
particulars: 'Accounts and amount claimed during the previous month and paid from the Fund during this month',
ibdAccounts: 0,
ibdAmount: 0,
nibdAccounts: 0,
nibdAmount: 0,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 0,
totalAmount: 0
},
{
sl: 5,
particulars: 'Total amount transferred less amount paid from the Fund during the month (2 + 3 − 4)',
ibdAccounts: 5,
ibdAmount: 19956.75,
nibdAccounts: 0,
nibdAmount: 0,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 5,
totalAmount: 19956.75
},
{
sl: 6,
particulars: 'Total amount with the Fund at the end of the month (1 + 5)',
ibdAccounts: 704,
ibdAmount: 2060449.93,
nibdAccounts: 140,
nibdAmount: 187649.0,
ocAccounts: 0,
ocAmount: 0,
totalAccounts: 844,
totalAmount: 2248098.93
}
]
};
/** Annex 4 — Refund claim form (Jan 2026) */
export const REFUND_CLAIMS: {
bankName: string;
bankDeafCode: string;
currentAcWithRbi: string;
sponsorBank: string;
month: string;
year: string;
firstSignatory: string;
firstDesignation: string;
secondSignatory: string;
secondDesignation: string;
auditor: string;
rows: DeafClaim[];
} = {
bankName: 'Bharat Urban Bank Ltd',
bankDeafCode: '1100',
currentAcWithRbi: '03010200099001',
sponsorBank: 'State Bank of India',
month: 'January',
year: '2026',
firstSignatory: 'Vikram Singh',
firstDesignation: 'IT Manager',
secondSignatory: 'Rohan Malhotra',
secondDesignation: 'Clerk / Cashier',
auditor: 'Karthik Kamath',
rows: [
{
slNo: 4,
ibdPrincipal: 5754.0,
ibdInterest: 457,
ibdTotal: 6211.0,
nibdAccounts: 0,
nibdAmount: 0,
ocAccounts: 0,
ocAmount: 0,
grandTotalAccounts: 4,
grandTotalAmount: 6211.0
}
]
};
/** Eligible / transferred / claimed accounts list (dummy customers across multiple branches) */
export const ELIGIBLE_ACCOUNTS: DeafEligibleAccount[] = [
{
acNo: '110001301002013',
customerName: 'RAVI KUMAR SHARMA',
acType: 'SB',
branch: 'Mumbai Main',
openedDate: '12-04-2010',
lastTxnDate: '15-03-2016',
inactiveYears: 10,
bucket: 'IBD',
principal: 4250.0,
interest: 312.5,
total: 4562.5,
status: 'eligible-this-month'
},
{
acNo: '110001301004221',
customerName: 'PRIYA PATEL',
acType: 'SB',
branch: 'Pune FC Road',
openedDate: '08-09-2009',
lastTxnDate: '20-01-2016',
inactiveYears: 10,
bucket: 'IBD',
principal: 1850.75,
interest: 132.0,
total: 1982.75,
status: 'eligible-this-month'
},
{
acNo: '110001302001145',
customerName: 'ARJUN DESHMUKH',
acType: 'FD',
branch: 'Nagpur Dharampeth',
openedDate: '01-01-2011',
lastTxnDate: '01-01-2016',
inactiveYears: 10,
bucket: 'IBD',
principal: 25000.0,
interest: 4180.0,
total: 29180.0,
status: 'eligible-this-month'
},
{
acNo: '110001301005612',
customerName: 'VIKRAM REDDY',
acType: 'SB',
branch: 'Bengaluru Jayanagar',
openedDate: '25-07-2008',
lastTxnDate: '12-12-2015',
inactiveYears: 10,
bucket: 'IBD',
principal: 8350.5,
interest: 654.0,
total: 9004.5,
status: 'eligible-this-month'
},
{
acNo: '110001303002284',
customerName: 'LAKSHMI IYER',
acType: 'RD',
branch: 'Bengaluru Indiranagar',
openedDate: '15-06-2010',
lastTxnDate: '15-06-2015',
inactiveYears: 10,
bucket: 'IBD',
principal: 12500.0,
interest: 1850.0,
total: 14350.0,
status: 'eligible-this-month'
},
{
acNo: '110001305000412',
customerName: 'NIRAV SHAH',
acType: 'DD',
branch: 'Ahmedabad Satellite',
openedDate: '03-10-2014',
lastTxnDate: '03-10-2014',
inactiveYears: 11,
bucket: 'NIBD',
principal: 3450.0,
interest: 0,
total: 3450.0,
status: 'eligible-this-month'
},
{
acNo: '110001301001780',
customerName: 'KAVITA VERMA',
acType: 'SB',
branch: 'Lucknow Gomti Nagar',
openedDate: '11-02-2008',
lastTxnDate: '11-09-2014',
inactiveYears: 11,
bucket: 'IBD',
principal: 6240.0,
interest: 478.0,
total: 6718.0,
status: 'transferred',
transferredOn: '15-12-2025'
},
{
acNo: '110001302002091',
customerName: 'KARTHIK SUBRAMANIAN',
acType: 'FD',
branch: 'Chennai Valasaravakkam',
openedDate: '04-08-2009',
lastTxnDate: '04-08-2014',
inactiveYears: 11,
bucket: 'IBD',
principal: 18500.0,
interest: 2725.0,
total: 21225.0,
status: 'transferred',
transferredOn: '15-11-2025'
},
{
acNo: '110001301008764',
customerName: 'SAMEER KAPOOR',
acType: 'SB',
branch: 'Delhi Greater Kailash',
openedDate: '17-05-2009',
lastTxnDate: '17-05-2014',
inactiveYears: 11,
bucket: 'IBD',
principal: 4587.0,
interest: 290.0,
total: 4877.0,
status: 'transferred',
transferredOn: '15-09-2024'
},
{
acNo: '110001302003145',
customerName: 'ABHISHEK BANERJEE',
acType: 'FD',
branch: 'Kolkata Salt Lake',
openedDate: '22-03-2010',
lastTxnDate: '22-03-2015',
inactiveYears: 10,
bucket: 'IBD',
principal: 15000.0,
interest: 2150.0,
total: 17150.0,
status: 'claim-pending'
},
{
acNo: '110001301010224',
customerName: 'GAURAV THAKUR',
acType: 'SB',
branch: 'Shimla Sector 4',
openedDate: '08-12-2008',
lastTxnDate: '08-06-2014',
inactiveYears: 11,
bucket: 'IBD',
principal: 3275.0,
interest: 195.0,
total: 3470.0,
status: 'claim-pending'
},
{
acNo: '110001302004088',
customerName: 'MANMOHAN SINGH GILL',
acType: 'FD',
branch: 'Amritsar Model Town',
openedDate: '19-09-2009',
lastTxnDate: '19-09-2014',
inactiveYears: 11,
bucket: 'IBD',
principal: 8500.0,
interest: 1124.0,
total: 9624.0,
status: 'refunded',
transferredOn: '15-04-2024'
}
];
+29
View File
@@ -0,0 +1,29 @@
/**
* 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/.
*/
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Route } from '../core/route/route.service';
import { DeafComponent } from './deaf.component';
const routes: Routes = [
Route.withShell([
{
path: 'deaf',
component: DeafComponent,
data: { title: 'DEAF', breadcrumb: 'DEAF' }
}
])
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DeafRoutingModule {}
+416
View File
@@ -0,0 +1,416 @@
<!--
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/.
-->
<div class="deaf-root">
<!-- HEADER -->
<div class="deaf-head">
<div class="deaf-head-left">
<fa-icon icon="university" class="deaf-head-ic"></fa-icon>
<div>
<h2 class="deaf-head-t">Depositor Education and Awareness Fund (DEAF)</h2>
<div class="deaf-head-s">RBI Master Direction on DEAF, 2014 &middot; Returns &amp; refund register</div>
</div>
</div>
<div class="deaf-head-meta">
<div>
<span class="deaf-meta-l">Bank DEAF Code</span><span class="deaf-meta-v">{{ formII.bankDeafCode }}</span>
</div>
<div>
<span class="deaf-meta-l">Sponsor Bank</span><span class="deaf-meta-v">{{ formII.sponsorBankName }}</span>
</div>
</div>
</div>
<!-- TABS -->
<div class="deaf-tabs">
<button class="deaf-tab" [class.active]="activeTab === 'dashboard'" (click)="showTab('dashboard')">
<fa-icon icon="chart-pie"></fa-icon> Dashboard
</button>
<button class="deaf-tab" [class.active]="activeTab === 'form1'" (click)="showTab('form1')">
<fa-icon icon="file-invoice"></fa-icon> Form I (Transfer Return)
</button>
<button class="deaf-tab" [class.active]="activeTab === 'form2'" (click)="showTab('form2')">
<fa-icon icon="file-invoice"></fa-icon> Form II (Position with Fund)
</button>
<button class="deaf-tab" [class.active]="activeTab === 'refund'" (click)="showTab('refund')">
<fa-icon icon="hand-holding-usd"></fa-icon> Annex 4 (Refund Claim)
</button>
<button class="deaf-tab" [class.active]="activeTab === 'eligible'" (click)="showTab('eligible')">
<fa-icon icon="users"></fa-icon> Accounts Register
</button>
</div>
<!-- DASHBOARD -->
@if (activeTab === 'dashboard') {
<div class="deaf-section">
<div class="deaf-kpis">
<div class="deaf-kpi deaf-kpi--blue">
<div class="deaf-kpi-ic"><fa-icon icon="piggy-bank"></fa-icon></div>
<div>
<div class="deaf-kpi-n">&#8377;{{ totalWithFund | number: '1.2-2' }}</div>
<div class="deaf-kpi-l">Total with Fund</div>
<div class="deaf-kpi-sub">{{ totalAccountsWithFund | number }} accounts</div>
</div>
</div>
<div class="deaf-kpi deaf-kpi--teal">
<div class="deaf-kpi-ic"><fa-icon icon="upload"></fa-icon></div>
<div>
<div class="deaf-kpi-n">&#8377;{{ transferredThisMonth | number: '1.2-2' }}</div>
<div class="deaf-kpi-l">Transferred this Month</div>
<div class="deaf-kpi-sub">{{ accountsTransferredThisMonth | number }} accounts</div>
</div>
</div>
<div class="deaf-kpi deaf-kpi--green">
<div class="deaf-kpi-ic"><fa-icon icon="hand-holding-usd"></fa-icon></div>
<div>
<div class="deaf-kpi-n">&#8377;{{ refundedThisCycle | number: '1.2-2' }}</div>
<div class="deaf-kpi-l">Claims Paid (refunded)</div>
<div class="deaf-kpi-sub">{{ refundAccountsThisCycle | number }} accounts</div>
</div>
</div>
<div class="deaf-kpi deaf-kpi--amber">
<div class="deaf-kpi-ic"><fa-icon icon="exclamation-triangle"></fa-icon></div>
<div>
<div class="deaf-kpi-n">{{ accountsCrossing10Y | number }}</div>
<div class="deaf-kpi-l">Crossing 10y this Cycle</div>
<div class="deaf-kpi-sub">eligible to transfer</div>
</div>
</div>
<div class="deaf-kpi deaf-kpi--red">
<div class="deaf-kpi-ic"><fa-icon icon="clock"></fa-icon></div>
<div>
<div class="deaf-kpi-n">{{ pendingClaims | number }}</div>
<div class="deaf-kpi-l">Refund Claims Pending</div>
<div class="deaf-kpi-sub">awaiting RBI settlement</div>
</div>
</div>
</div>
<!-- Compliance summary -->
<div class="deaf-compliance">
<div class="dc-card">
<div class="dc-card-h">
<fa-icon icon="check-circle" class="dc-ok"></fa-icon><span>Form I — Online Submission</span>
</div>
<div class="dc-meta">Last filed: <strong>15 Jan 2026</strong> &middot; Cycle: {{ formI.monthYear }}</div>
<div class="dc-meta">Next due: <strong>15 Feb 2026</strong></div>
<button class="dc-btn" (click)="showTab('form1')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
</div>
<div class="dc-card">
<div class="dc-card-h">
<fa-icon icon="check-circle" class="dc-ok"></fa-icon><span>Form II — Physical Hardcopy</span>
</div>
<div class="dc-meta">Last filed: <strong>15 Jan 2026</strong> &middot; Cycle: {{ formII.monthYear }}</div>
<div class="dc-meta">Next due: <strong>15 Feb 2026</strong></div>
<button class="dc-btn" (click)="showTab('form2')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
</div>
<div class="dc-card">
<div class="dc-card-h">
<fa-icon icon="hand-holding-usd" class="dc-info"></fa-icon><span>Annex 4 — Refund Claim</span>
</div>
<div class="dc-meta">
Cycle: <strong>{{ refund.month }} {{ refund.year }}</strong>
</div>
<div class="dc-meta">
Pending claims: <strong>{{ pendingClaims }}</strong>
</div>
<button class="dc-btn" (click)="showTab('refund')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
</div>
</div>
</div>
}
<!-- FORM I / FORM II — shared template -->
@if (activeTab === 'form1' || activeTab === 'form2') {
@let _form = activeTab === 'form1' ? formI : formII;
<div class="deaf-section">
<div class="deaf-form-meta">
<div class="deaf-form-title">
@if (activeTab === 'form1') {
<h3>Form I — Monthly Return of Unclaimed Deposits Transferred to DEAF</h3>
<p>Filed online with RBI by 15th of the succeeding month</p>
} @else {
<h3>Form II — Position with the Fund (snapshot)</h3>
<p>Original hardcopy submitted to RBI by 15th of the succeeding month</p>
}
</div>
<button class="deaf-btn deaf-btn-xls" (click)="activeTab === 'form1' ? exportFormI() : exportFormII()">
<fa-icon icon="file-excel"></fa-icon> Download Excel
</button>
</div>
<div class="deaf-meta-grid">
<div>
<label>Name of the Bank</label><span>{{ _form.bankName }}</span>
</div>
<div>
<label>DEAF Code (RBI)</label><span>{{ _form.bankDeafCode }}</span>
</div>
<div>
<label>Remitted via Sponsor</label><span>{{ _form.remittedThroughSponsor === 'Y' ? 'Yes' : 'No' }}</span>
</div>
<div>
<label>Sponsor Bank</label><span>{{ _form.sponsorBankName }}</span>
</div>
<div>
<label>Month / Year</label><span>{{ _form.monthYear }}</span>
</div>
<div>
<label>Date of Transfer</label><span>{{ _form.dateOfTransfer }}</span>
</div>
</div>
<div class="deaf-tbl-wrap">
<table class="deaf-tbl">
<thead>
<tr class="hd-1">
<th rowspan="2" class="ta-c">Sr.No</th>
<th rowspan="2">Particulars</th>
<th colspan="2" class="ta-c">Interest Bearing Deposits (a)</th>
<th colspan="2" class="ta-c">Non-Interest Bearing Deposits (b)</th>
<th colspan="2" class="ta-c">Other Credits (c)</th>
<th colspan="2" class="ta-c">Total (d) = (a)+(b)+(c)</th>
</tr>
<tr class="hd-2">
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
</tr>
</thead>
<tbody>
@for (r of _form.rows; track r.sl) {
<tr [class]="amountClass(r)">
<td class="ta-c">{{ r.sl }}</td>
<td>{{ r.particulars }}</td>
<td class="ta-r">{{ r.ibdAccounts | number }}</td>
<td class="ta-r mono">{{ r.ibdAmount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.nibdAccounts | number }}</td>
<td class="ta-r mono">{{ r.nibdAmount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.ocAccounts | number }}</td>
<td class="ta-r mono">{{ r.ocAmount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.totalAccounts | number }}</td>
<td class="ta-r mono">{{ r.totalAmount | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
<div class="deaf-sign">
<div class="deaf-sign-block">
<label>Authorised Officer</label>
<div class="sign-name">{{ _form.officer }}</div>
<div class="sign-desg">{{ _form.officerDesignation }}</div>
</div>
<div class="deaf-sign-block">
<label>Concurrent Auditor</label>
<div class="sign-name">{{ _form.auditor }}</div>
<div class="sign-desg">{{ _form.auditorAddress }}</div>
</div>
</div>
</div>
}
<!-- ANNEX 4 — REFUND CLAIM -->
@if (activeTab === 'refund') {
<div class="deaf-section">
<div class="deaf-form-meta">
<div class="deaf-form-title">
<h3>Annex 4 — Monthly Return claiming refund from DEA Fund</h3>
<p>Submitted by 15th of succeeding month for the cycle to which the claim pertains</p>
</div>
<button class="deaf-btn deaf-btn-xls" (click)="exportRefund()">
<fa-icon icon="file-excel"></fa-icon> Download Excel
</button>
</div>
<div class="deaf-meta-grid">
<div>
<label>Name of the Bank</label><span>{{ refund.bankName }}</span>
</div>
<div>
<label>DEAF Code (RBI)</label><span>{{ refund.bankDeafCode }}</span>
</div>
<div>
<label>Current A/c with RBI</label><span class="mono">{{ refund.currentAcWithRbi }}</span>
</div>
<div>
<label>Maintained with</label><span>{{ refund.sponsorBank }}</span>
</div>
<div>
<label>Month / Year</label><span>{{ refund.month }} {{ refund.year }}</span>
</div>
</div>
<div class="deaf-tbl-wrap">
<table class="deaf-tbl">
<thead>
<tr class="hd-1">
<th rowspan="2" class="ta-c">Sr.No</th>
<th colspan="3" class="ta-c">Interest Bearing Deposits</th>
<th colspan="2" class="ta-c">Non-Interest Bearing Deposits</th>
<th colspan="2" class="ta-c">Other Credits</th>
<th colspan="2" class="ta-c">Grand Total</th>
</tr>
<tr class="hd-2">
<th class="ta-r">Principal</th>
<th class="ta-r">Interest</th>
<th class="ta-r">Total</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
</tr>
</thead>
<tbody>
@for (r of refund.rows; track r.slNo) {
<tr>
<td class="ta-c">{{ r.slNo }}</td>
<td class="ta-r mono">{{ r.ibdPrincipal | number: '1.2-2' }}</td>
<td class="ta-r mono">{{ r.ibdInterest | number: '1.2-2' }}</td>
<td class="ta-r mono">{{ r.ibdTotal | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.nibdAccounts | number }}</td>
<td class="ta-r mono">{{ r.nibdAmount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.ocAccounts | number }}</td>
<td class="ta-r mono">{{ r.ocAmount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.grandTotalAccounts | number }}</td>
<td class="ta-r mono">{{ r.grandTotalAmount | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
<div class="deaf-note">
<fa-icon icon="info-circle"></fa-icon>
<span
><strong>Note:</strong> No individual customer/depositor wise details should be furnished. In case of refund
of part amount by the depositor whose unclaimed amount/inoperative deposit had been transferred to the Fund,
the bank shall claim the entire amount transferred to the Fund in respect of such depositor, along with
interest payable, if any.</span
>
</div>
<div class="deaf-sign">
<div class="deaf-sign-block">
<label>First Authorised Signatory</label>
<div class="sign-name">{{ refund.firstSignatory }}</div>
<div class="sign-desg">{{ refund.firstDesignation }}</div>
</div>
<div class="deaf-sign-block">
<label>Second Authorised Signatory</label>
<div class="sign-name">{{ refund.secondSignatory }}</div>
<div class="sign-desg">{{ refund.secondDesignation }}</div>
</div>
<div class="deaf-sign-block">
<label>Concurrent Auditor</label>
<div class="sign-name">{{ refund.auditor }}</div>
</div>
</div>
</div>
}
<!-- ELIGIBLE / TRANSFERRED ACCOUNTS REGISTER -->
@if (activeTab === 'eligible') {
<div class="deaf-section">
<div class="deaf-form-meta">
<div class="deaf-form-title">
<h3>Accounts Register</h3>
<p>Inoperative / unclaimed accounts crossing 10y, transferred, claimed and refunded</p>
</div>
<button class="deaf-btn deaf-btn-xls" (click)="exportEligible()">
<fa-icon icon="file-excel"></fa-icon> Download Excel
</button>
</div>
<div class="deaf-filters">
<div class="deaf-filter">
<label>Status</label>
<select class="deaf-inp" [(ngModel)]="filterStatus">
<option value="">All</option>
<option value="eligible-this-month">Eligible (≥10y)</option>
<option value="transferred">Transferred</option>
<option value="claim-pending">Claim Pending</option>
<option value="refunded">Refunded</option>
</select>
</div>
<div class="deaf-filter">
<label>Bucket</label>
<select class="deaf-inp" [(ngModel)]="filterBucket">
<option value="">All</option>
<option value="IBD">Interest Bearing</option>
<option value="NIBD">Non-Interest Bearing</option>
<option value="OC">Other Credits</option>
</select>
</div>
<div class="deaf-filter">
<label>Branch</label>
<input class="deaf-inp" type="text" [(ngModel)]="filterBranch" placeholder="Branch" />
</div>
<button class="deaf-btn deaf-btn-g" (click)="resetFilters()">Reset</button>
</div>
<div class="deaf-tbl-wrap">
<table class="deaf-tbl">
<thead>
<tr>
<th>A/c No</th>
<th>Customer</th>
<th class="ta-c">Type</th>
<th>Branch</th>
<th>Opened</th>
<th>Last Txn</th>
<th class="ta-c">Inactive</th>
<th class="ta-c">Bucket</th>
<th class="ta-r">Principal</th>
<th class="ta-r">Interest</th>
<th class="ta-r">Total</th>
<th class="ta-c">Status</th>
</tr>
</thead>
<tbody>
@for (a of filteredAccounts; track a.acNo) {
<tr>
<td class="mono">{{ a.acNo }}</td>
<td>{{ a.customerName }}</td>
<td class="ta-c">{{ a.acType }}</td>
<td>{{ a.branch }}</td>
<td>{{ a.openedDate }}</td>
<td>{{ a.lastTxnDate }}</td>
<td class="ta-c">{{ a.inactiveYears }}y</td>
<td class="ta-c">
<span class="bucket-pill" [class]="'bk-' + a.bucket.toLowerCase()">{{ a.bucket }}</span>
</td>
<td class="ta-r mono">{{ a.principal | number: '1.2-2' }}</td>
<td class="ta-r mono">{{ a.interest | number: '1.2-2' }}</td>
<td class="ta-r mono">{{ a.total | number: '1.2-2' }}</td>
<td class="ta-c">
<span class="status-pill" [class]="'st-' + a.status">{{ statusLabel(a.status) }}</span>
</td>
</tr>
} @empty {
<tr>
<td colspan="12" class="deaf-empty">No accounts match the filters.</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
<!-- TOAST -->
<div class="deaf-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
</div>
+672
View File
@@ -0,0 +1,672 @@
/**
* 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/.
*/
:host {
--d-navy: #0b1e3d;
--d-blue: #1d4ed8;
--d-blue-l: #eff6ff;
--d-teal: #0e7e6b;
--d-teal-l: #e0f4f0;
--d-green: #15803d;
--d-green-l: #dcfce7;
--d-amber: #d97706;
--d-amber-l: #fef3c7;
--d-red: #c0392b;
--d-red-l: #fdecea;
--d-purple: #6d28d9;
--d-purple-l: #ede9fe;
--d-g50: #f8fafc;
--d-g100: #f1f5f9;
--d-g200: #e2e8f0;
--d-g300: #cbd5e1;
--d-g500: #64748b;
--d-g700: #334155;
--d-g900: #0f172a;
display: block;
font-family:
'DM Sans',
system-ui,
-apple-system,
sans-serif;
background: var(--d-g50);
color: var(--d-g900);
font-size: 14px;
line-height: 1.5;
}
.deaf-root {
padding: 18px 22px;
max-width: 1400px;
margin: 0 auto;
}
/* HEADER */
.deaf-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.deaf-head-left {
display: flex;
align-items: center;
gap: 14px;
}
.deaf-head-ic {
font-size: 28px;
color: var(--d-purple);
}
.deaf-head-t {
margin: 0;
font-size: 18px;
font-weight: 700;
color: var(--d-g900);
}
.deaf-head-s {
font-size: 12px;
color: var(--d-g500);
}
.deaf-head-meta {
display: flex;
gap: 22px;
> div {
display: flex;
flex-direction: column;
.deaf-meta-l {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--d-g500);
}
.deaf-meta-v {
font-size: 14px;
font-weight: 600;
color: var(--d-g900);
}
}
}
/* TABS */
.deaf-tabs {
display: flex;
gap: 4px;
background: #fff;
border: 1px solid var(--d-g200);
border-radius: 10px;
padding: 4px;
margin-bottom: 16px;
overflow-x: auto;
}
.deaf-tab {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 9px 14px;
border: none;
background: transparent;
font-size: 13px;
font-weight: 500;
color: var(--d-g500);
cursor: pointer;
border-radius: 7px;
white-space: nowrap;
transition: all 0.15s;
font-family: inherit;
&:hover {
color: var(--d-g900);
}
&.active {
background: var(--d-purple);
color: #fff;
box-shadow: 0 2px 6px rgb(109 40 217 / 25%);
}
}
.deaf-section {
display: flex;
flex-direction: column;
gap: 16px;
}
/* KPIs */
.deaf-kpis {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
}
.deaf-kpi {
background: #fff;
border: 1px solid var(--d-g200);
border-radius: 12px;
padding: 14px 16px;
display: flex;
align-items: center;
gap: 12px;
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 14px rgb(0 0 0 / 10%);
}
}
.deaf-kpi-ic {
width: 42px;
height: 42px;
border-radius: 11px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.deaf-kpi--blue .deaf-kpi-ic {
background: var(--d-blue-l);
color: var(--d-blue);
}
.deaf-kpi--teal .deaf-kpi-ic {
background: var(--d-teal-l);
color: var(--d-teal);
}
.deaf-kpi--green .deaf-kpi-ic {
background: var(--d-green-l);
color: var(--d-green);
}
.deaf-kpi--amber .deaf-kpi-ic {
background: var(--d-amber-l);
color: var(--d-amber);
}
.deaf-kpi--red .deaf-kpi-ic {
background: var(--d-red-l);
color: var(--d-red);
}
.deaf-kpi-n {
font-size: 18px;
font-weight: 700;
color: var(--d-g900);
line-height: 1.1;
}
.deaf-kpi-l {
font-size: 12px;
color: var(--d-g700);
margin-top: 2px;
font-weight: 500;
}
.deaf-kpi-sub {
font-size: 11px;
color: var(--d-g500);
margin-top: 1px;
}
/* COMPLIANCE CARDS */
.deaf-compliance {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
}
.dc-card {
background: #fff;
border: 1px solid var(--d-g200);
border-radius: 12px;
padding: 14px 18px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.dc-card-h {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 600;
color: var(--d-g900);
margin-bottom: 8px;
}
.dc-ok {
color: var(--d-green);
}
.dc-info {
color: var(--d-blue);
}
.dc-meta {
font-size: 12px;
color: var(--d-g500);
margin: 2px 0;
strong {
color: var(--d-g900);
font-weight: 600;
}
}
.dc-btn {
margin-top: 10px;
padding: 6px 12px;
border: 1px solid var(--d-purple);
background: var(--d-purple-l);
color: var(--d-purple);
border-radius: 8px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
font-family: inherit;
display: inline-flex;
align-items: center;
gap: 6px;
&:hover {
background: var(--d-purple);
color: #fff;
}
}
/* FORM HEADER ROW */
.deaf-form-meta {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 14px;
}
.deaf-form-title {
h3 {
margin: 0;
font-size: 16px;
font-weight: 700;
color: var(--d-g900);
}
p {
margin: 2px 0 0;
font-size: 12px;
color: var(--d-g500);
}
}
.deaf-meta-grid {
background: #fff;
border: 1px solid var(--d-g200);
border-radius: 12px;
padding: 14px 18px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px 24px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
> div {
display: flex;
flex-direction: column;
label {
font-size: 10px;
font-weight: 600;
color: var(--d-g500);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 2px;
}
span {
font-size: 13px;
color: var(--d-g900);
font-weight: 500;
}
}
}
/* BUTTONS */
.deaf-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: 1px solid var(--d-g300);
background: #fff;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
color: var(--d-g700);
cursor: pointer;
font-family: inherit;
transition: all 0.15s;
&:hover {
background: var(--d-g50);
border-color: var(--d-g500);
}
}
.deaf-btn-xls {
background: #16a34a;
border-color: #16a34a;
color: #fff;
&:hover {
background: #15803d;
border-color: #15803d;
color: #fff;
}
}
.deaf-btn-g {
align-self: end;
}
/* INPUTS */
.deaf-inp {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--d-g200);
border-radius: 8px;
font-size: 13px;
background: #fff;
font-family: inherit;
&:focus {
outline: none;
border-color: var(--d-purple);
box-shadow: 0 0 0 3px rgb(109 40 217 / 15%);
}
}
/* FILTERS */
.deaf-filters {
background: #fff;
border: 1px solid var(--d-g200);
border-radius: 12px;
padding: 14px;
display: grid;
grid-template-columns: repeat(3, 1fr) auto;
gap: 12px;
align-items: end;
}
.deaf-filter {
display: flex;
flex-direction: column;
gap: 4px;
label {
font-size: 11px;
font-weight: 600;
color: var(--d-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
}
/* TABLE */
.deaf-tbl-wrap {
background: #fff;
border: 1px solid var(--d-g200);
border-radius: 12px;
overflow: auto;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.deaf-tbl {
width: 100%;
border-collapse: collapse;
th {
text-align: left;
padding: 10px 12px;
font-size: 11px;
font-weight: 600;
color: var(--d-g500);
text-transform: uppercase;
letter-spacing: 0.04em;
background: var(--d-g50);
border-bottom: 1px solid var(--d-g200);
}
th.hd-1,
tr.hd-1 th {
background: #f0f4ff;
color: var(--d-blue);
}
td {
padding: 10px 12px;
border-bottom: 1px solid var(--d-g100);
font-size: 13px;
color: var(--d-g700);
vertical-align: top;
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: var(--d-g50);
}
tr.amt-bold td {
background: #f0fdf4;
font-weight: 600;
color: var(--d-g900);
}
tr.amt-neg td {
color: var(--d-red);
}
}
.ta-c {
text-align: center;
}
.ta-r {
text-align: right;
}
.mono {
font-family: 'DM Mono', monospace;
font-size: 12px;
letter-spacing: 0.02em;
}
.deaf-empty {
text-align: center;
padding: 30px;
color: var(--d-g500);
font-style: italic;
}
/* SIGNATURE BLOCKS */
.deaf-sign {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
}
.deaf-sign-block {
background: #fff;
border: 1px solid var(--d-g200);
border-radius: 12px;
padding: 14px 16px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
label {
font-size: 10px;
font-weight: 600;
color: var(--d-g500);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 6px;
display: block;
}
.sign-name {
font-size: 14px;
font-weight: 600;
color: var(--d-g900);
}
.sign-desg {
font-size: 12px;
color: var(--d-g500);
margin-top: 2px;
}
}
/* NOTE / INFO */
.deaf-note {
background: var(--d-blue-l);
border: 1px solid #bfdbfe;
border-radius: 10px;
padding: 12px 16px;
display: flex;
gap: 10px;
font-size: 12px;
color: #1e3a8a;
line-height: 1.5;
fa-icon {
font-size: 14px;
flex-shrink: 0;
margin-top: 2px;
}
}
/* PILLS */
.bucket-pill {
display: inline-block;
padding: 2px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
&.bk-ibd {
background: var(--d-blue-l);
color: var(--d-blue);
}
&.bk-nibd {
background: var(--d-teal-l);
color: var(--d-teal);
}
&.bk-oc {
background: var(--d-purple-l);
color: var(--d-purple);
}
}
.status-pill {
display: inline-block;
padding: 2px 10px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
&.st-eligible-this-month {
background: var(--d-amber-l);
color: var(--d-amber);
}
&.st-transferred {
background: var(--d-green-l);
color: var(--d-green);
}
&.st-claim-pending {
background: var(--d-red-l);
color: var(--d-red);
}
&.st-refunded {
background: var(--d-blue-l);
color: var(--d-blue);
}
}
/* TOAST */
.deaf-toast {
position: fixed;
bottom: 32px;
left: 50%;
transform: translateX(-50%) translateY(20px);
background: var(--d-navy);
color: #fff;
padding: 12px 22px;
border-radius: 10px;
font-size: 13px;
font-weight: 500;
box-shadow: 0 6px 20px rgb(0 0 0 / 25%);
opacity: 0;
pointer-events: none;
transition: all 0.25s;
z-index: 1500;
&.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
/* RESPONSIVE */
@media (width <= 1100px) {
.deaf-kpis {
grid-template-columns: repeat(3, 1fr);
}
.deaf-compliance {
grid-template-columns: 1fr;
}
.deaf-meta-grid {
grid-template-columns: 1fr 1fr;
}
.deaf-sign {
grid-template-columns: 1fr;
}
.deaf-filters {
grid-template-columns: 1fr 1fr;
}
}
@media (width <= 720px) {
.deaf-kpis {
grid-template-columns: 1fr 1fr;
}
.deaf-meta-grid {
grid-template-columns: 1fr;
}
}
+358
View File
@@ -0,0 +1,358 @@
/**
* 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/.
*/
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import {
DeafEligibleAccount,
DeafForm,
DeafFormRow,
ELIGIBLE_ACCOUNTS,
FORM_I,
FORM_II,
REFUND_CLAIMS
} from './deaf-data';
@Component({
selector: 'mifosx-deaf',
templateUrl: './deaf.component.html',
styleUrls: ['./deaf.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
FormsModule
]
})
export class DeafComponent {
/** Active tab */
activeTab: 'dashboard' | 'form1' | 'form2' | 'refund' | 'eligible' = 'dashboard';
formI: DeafForm = FORM_I;
formII: DeafForm = FORM_II;
refund = REFUND_CLAIMS;
accounts: DeafEligibleAccount[] = ELIGIBLE_ACCOUNTS;
/** Eligible-list filters */
filterStatus: '' | DeafEligibleAccount['status'] = '';
filterBranch = '';
filterBucket: '' | DeafEligibleAccount['bucket'] = '';
/** Toast */
toastMsg = '';
toastVisible = false;
private toastTimer: any;
// -------------------- Tabs --------------------
showTab(tab: 'dashboard' | 'form1' | 'form2' | 'refund' | 'eligible'): void {
this.activeTab = tab;
}
// -------------------- KPIs --------------------
/** Total amount currently with the DEAF Fund (closing balance from Form II row 6) */
get totalWithFund(): number {
return this.formII.rows[5]?.totalAmount || 0;
}
get totalAccountsWithFund(): number {
return this.formII.rows[5]?.totalAccounts || 0;
}
get transferredThisMonth(): number {
return this.formII.rows[2]?.totalAmount || 0;
}
get accountsTransferredThisMonth(): number {
return this.formII.rows[2]?.totalAccounts || 0;
}
get refundedThisCycle(): number {
return this.refund.rows.reduce((s, r) => s + r.grandTotalAmount, 0);
}
get refundAccountsThisCycle(): number {
return this.refund.rows.reduce((s, r) => s + r.grandTotalAccounts, 0);
}
get accountsCrossing10Y(): number {
return this.accounts.filter((a) => a.status === 'eligible-this-month').length;
}
get pendingClaims(): number {
return this.accounts.filter((a) => a.status === 'claim-pending').length;
}
// -------------------- Eligible filter --------------------
get filteredAccounts(): DeafEligibleAccount[] {
const branch = this.filterBranch.trim().toLowerCase();
return this.accounts.filter((a) => {
if (this.filterStatus && a.status !== this.filterStatus) return false;
if (this.filterBucket && a.bucket !== this.filterBucket) return false;
if (branch && !a.branch.toLowerCase().includes(branch)) return false;
return true;
});
}
resetFilters(): void {
this.filterStatus = '';
this.filterBranch = '';
this.filterBucket = '';
}
statusLabel(s: DeafEligibleAccount['status']): string {
switch (s) {
case 'eligible-this-month':
return 'Eligible';
case 'transferred':
return 'Transferred';
case 'claim-pending':
return 'Claim Pending';
case 'refunded':
return 'Refunded';
}
}
// -------------------- Excel export --------------------
/** Builds Form I / Form II rows as a 2-D array — same layout as the RBI return */
private buildFormRows(form: DeafForm, label: 'Form I' | 'Form II'): string[][] {
const rows: string[][] = [];
rows.push([
label,
'Monthly Return of Unclaimed Deposits Transferred to DEAF Fund'
]);
rows.push([]);
rows.push([
'Name of the Bank',
form.bankName
]);
rows.push([
'Bank DEAF Code allotted by RBI',
form.bankDeafCode
]);
rows.push([
'If remitted through Sponsor Bank',
form.remittedThroughSponsor
]);
rows.push([
'Name of the Sponsor Bank',
form.sponsorBankName
]);
rows.push([
'Month / Year',
form.monthYear
]);
rows.push([
'Date of Transfer to the Fund',
form.dateOfTransfer
]);
rows.push([]);
rows.push(['Particulars (Amount in Rupees)']);
rows.push([
'Sr.No',
'Particulars',
'IBD No. of A/c',
'IBD Amount',
'NIBD No. of A/c',
'NIBD Amount',
'OC No. of A/c',
'OC Amount',
'Total No. of A/c',
'Total Amount'
]);
for (const r of form.rows) {
rows.push([
r.sl.toString(),
r.particulars,
r.ibdAccounts.toString(),
r.ibdAmount.toFixed(2),
r.nibdAccounts.toString(),
r.nibdAmount.toFixed(2),
r.ocAccounts.toString(),
r.ocAmount.toFixed(2),
r.totalAccounts.toString(),
r.totalAmount.toFixed(2)
]);
}
rows.push([]);
rows.push([
'Authorised Officer',
form.officer
]);
rows.push([
'Designation',
form.officerDesignation
]);
rows.push([
'Concurrent Auditor',
form.auditor
]);
rows.push([
'Auditor Address',
form.auditorAddress
]);
return rows;
}
private buildRefundRows(): string[][] {
const c = this.refund;
const rows: string[][] = [];
rows.push([
'Annex 4',
'Monthly Return claiming refund from DEA Fund'
]);
rows.push([]);
rows.push([
'Name of the Bank',
c.bankName
]);
rows.push([
'Bank DEAF Code allotted by RBI',
c.bankDeafCode
]);
rows.push([
'Current A/c with RBI',
`${c.currentAcWithRbi} via ${c.sponsorBank}`
]);
rows.push([
'Month / Year',
`${c.month} ${c.year}`
]);
rows.push([]);
rows.push(['Details of claims made during the month (Amount in Rupees)']);
rows.push([
'Sr.No',
'IBD Principal',
'IBD Interest',
'IBD Total',
'NIBD No. of A/c',
'NIBD Amount',
'OC No. of A/c',
'OC Amount',
'Grand Total No. of A/c',
'Grand Total Amount'
]);
for (const r of c.rows) {
rows.push([
r.slNo.toString(),
r.ibdPrincipal.toFixed(2),
r.ibdInterest.toFixed(2),
r.ibdTotal.toFixed(2),
r.nibdAccounts.toString(),
r.nibdAmount.toFixed(2),
r.ocAccounts.toString(),
r.ocAmount.toFixed(2),
r.grandTotalAccounts.toString(),
r.grandTotalAmount.toFixed(2)
]);
}
rows.push([]);
rows.push([
'Note',
'No individual customer/depositor wise details should be furnished.'
]);
rows.push([
'First Authorised Signatory',
`${c.firstSignatory} (${c.firstDesignation})`
]);
rows.push([
'Second Authorised Signatory',
`${c.secondSignatory} (${c.secondDesignation})`
]);
rows.push([
'Concurrent Auditor',
c.auditor
]);
return rows;
}
private buildEligibleRows(): string[][] {
const rows: string[][] = [];
rows.push(['DEAF — Accounts Register']);
rows.push([]);
rows.push([
'A/c No',
'Customer Name',
'A/c Type',
'Branch',
'Opened',
'Last Txn',
'Inactive (yrs)',
'Bucket',
'Principal',
'Interest',
'Total',
'Status',
'Transferred On'
]);
for (const a of this.filteredAccounts) {
rows.push([
a.acNo,
a.customerName,
a.acType,
a.branch,
a.openedDate,
a.lastTxnDate,
a.inactiveYears.toString(),
a.bucket,
a.principal.toFixed(2),
a.interest.toFixed(2),
a.total.toFixed(2),
this.statusLabel(a.status),
a.transferredOn || ''
]);
}
return rows;
}
exportFormI(): void {
this.saveTsv(this.buildFormRows(this.formI, 'Form I'), `DEAF-FormI-${this.fileTag(this.formI.monthYear)}.xls`);
this.toast('Form I downloaded');
}
exportFormII(): void {
this.saveTsv(this.buildFormRows(this.formII, 'Form II'), `DEAF-FormII-${this.fileTag(this.formII.monthYear)}.xls`);
this.toast('Form II downloaded');
}
exportRefund(): void {
this.saveTsv(this.buildRefundRows(), `DEAF-Annex4-${this.refund.month}-${this.refund.year}.xls`);
this.toast('Annex 4 (Refund Claim) downloaded');
}
exportEligible(): void {
this.saveTsv(this.buildEligibleRows(), `DEAF-Accounts-${this.dateStamp()}.xls`);
this.toast(`${this.filteredAccounts.length} accounts exported`);
}
private saveTsv(rows: string[][], filename: string): void {
const tsv = rows.map((r) => r.map((v) => (v || '').replace(/\t/g, ' ')).join('\t')).join('\n');
const blob = new Blob([tsv], { type: 'application/vnd.ms-excel;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
private fileTag(monthYear: string): string {
return monthYear.replace('/', '-');
}
private dateStamp(): string {
const d = new Date();
const dd = String(d.getDate()).padStart(2, '0');
const mm = String(d.getMonth() + 1).padStart(2, '0');
return `${dd}${mm}${d.getFullYear()}`;
}
/** Coerce a row's raw amounts for display — handles negatives in Form I row 5 */
amountClass(r: DeafFormRow): string {
if (r.sl === 5 && r.totalAmount < 0) return 'amt-neg';
if (r.sl === 6) return 'amt-bold';
return '';
}
toast(msg: string): void {
this.toastMsg = msg;
this.toastVisible = true;
if (this.toastTimer) clearTimeout(this.toastTimer);
this.toastTimer = setTimeout(() => (this.toastVisible = false), 2800);
}
}
+22
View File
@@ -0,0 +1,22 @@
/**
* 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/.
*/
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { DeafRoutingModule } from './deaf-routing.module';
import { DeafComponent } from './deaf.component';
@NgModule({
imports: [
SharedModule,
DeafRoutingModule,
DeafComponent
]
})
export class DeafModule {}
+485
View File
@@ -0,0 +1,485 @@
/**
* 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/.
*/
/**
* DICGC (Deposit Insurance and Credit Guarantee Corporation) sample data.
* Schema reproduces the actual report formats but bank/customer names are dummy.
*
* Two reports:
* Slab-wise A/C Report — accounts grouped by deposit-amount slabs
* (≤1L, 1L-2L, 2L-3L, >3L) for both deposits and loans
* SAHAY Depositor List — customer/account-wise CSV input template DICGC consumes
* when paying out claims after a bank failure
*/
/** Single product row in the slab-wise table */
export interface SlabRow {
sl: number;
description: string;
upto1Lakh: { amount: number; accounts: number };
oneTo2Lakh: { amount: number; accounts: number };
twoTo3Lakh: { amount: number; accounts: number };
above3Lakh: { amount: number; accounts: number };
total: { amount: number; accounts: number };
}
/** SAHAY depositor input record (19 columns) */
export interface SahayDepositor {
inputSerialNumber: number;
customerIdUcic: string;
accountNumber: string;
typeOfAccount: string;
natureOfAccount: string;
numberOfHolders: number;
nameFirst: string;
nameSecond: string;
nameThird: string;
dateOfBirthOrIncorporation: string;
amountOfDeposit: number;
insuredAmount: number;
loanAgainstDeposit: 'LAD' | 'LAFD' | '';
panNumber: string;
aadhaarNumber: string;
otherKycType: string;
otherKycNumber: string;
mobileNumber: string;
typeOfDeposit: string;
lastCustomerInducedTxnDate: string;
}
export const DICGC_BANK = {
name: 'Bharat Urban Bank Ltd',
branch: 'Head Office',
reportAsOn: '29-01-2026',
printDate: '29-01-2026 04:19 PM',
reportRefNo: '1290/2',
userCode: 'JTU',
insuranceLimit: 500000, // ₹5 lakh per depositor
premiumRatePer100: 0.12, // 12 paise per ₹100 of deposits
registrationNo: 'REG-CO-1290'
};
/** ── DEPOSITS — Section 1 (Credit Balance) ── */
export const DEPOSIT_SLABS: SlabRow[] = [
{
sl: 1,
description: 'CURRENT DEPOSIT INDIVIDUAL A/C',
upto1Lakh: { amount: 4161663.56, accounts: 231 },
oneTo2Lakh: { amount: 1104137.57, accounts: 7 },
twoTo3Lakh: { amount: 953186.92, accounts: 4 },
above3Lakh: { amount: 8451113.76, accounts: 12 },
total: { amount: 14670101.81, accounts: 254 }
},
{
sl: 2,
description: 'FIX DEPOSIT DOUBLE A/C',
upto1Lakh: { amount: 38612382.0, accounts: 824 },
oneTo2Lakh: { amount: 50661835.0, accounts: 371 },
twoTo3Lakh: { amount: 41999156.0, accounts: 175 },
above3Lakh: { amount: 204955057.0, accounts: 324 },
total: { amount: 336228430.0, accounts: 1694 }
},
{
sl: 3,
description: 'FIXED DEPOSIT SINGLE A/C',
upto1Lakh: { amount: 5488157.0, accounts: 694 },
oneTo2Lakh: { amount: 689874.0, accounts: 6 },
twoTo3Lakh: { amount: 250000.0, accounts: 1 },
above3Lakh: { amount: 1018485.0, accounts: 2 },
total: { amount: 7446516.0, accounts: 703 }
},
{
sl: 4,
description: 'FIXED DEPOSIT MONTHLY',
upto1Lakh: { amount: 445000.0, accounts: 6 },
oneTo2Lakh: { amount: 400000.0, accounts: 2 },
twoTo3Lakh: { amount: 1400000.0, accounts: 5 },
above3Lakh: { amount: 14500000.0, accounts: 14 },
total: { amount: 16745000.0, accounts: 27 }
},
{
sl: 5,
description: 'FIXED DEPOSIT QUARTERLY',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 3250000.0, accounts: 3 },
total: { amount: 3250000.0, accounts: 3 }
},
{
sl: 6,
description: 'GENERAL LEDGER',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 0, accounts: 0 }
},
{
sl: 7,
description: 'LINKING SHARE',
upto1Lakh: { amount: 11600908.0, accounts: 937 },
oneTo2Lakh: { amount: 8580300.0, accounts: 61 },
twoTo3Lakh: { amount: 3290000.0, accounts: 13 },
above3Lakh: { amount: 2876800.0, accounts: 7 },
total: { amount: 26348008.0, accounts: 1018 }
},
{
sl: 8,
description: 'MANGAL PARINAY A/C',
upto1Lakh: { amount: 197496.0, accounts: 2 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 197496.0, accounts: 2 }
},
{
sl: 9,
description: 'RECURRING DEPOSIT',
upto1Lakh: { amount: 558352.0, accounts: 22 },
oneTo2Lakh: { amount: 206756.0, accounts: 2 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 765108.0, accounts: 24 }
},
{
sl: 10,
description: 'SUNDRY CREDITORS A/C',
upto1Lakh: { amount: 46000.0, accounts: 2 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 607913.3, accounts: 1 },
total: { amount: 653913.3, accounts: 3 }
},
{
sl: 11,
description: 'SAVINGS DEPOSIT INDIVIDUAL A/C',
upto1Lakh: { amount: 37172591.62, accounts: 3954 },
oneTo2Lakh: { amount: 28097261.58, accounts: 199 },
twoTo3Lakh: { amount: 19583645.69, accounts: 84 },
above3Lakh: { amount: 108860739.06, accounts: 191 },
total: { amount: 193714237.95, accounts: 4428 }
}
];
/** ── LOANS — Section A (Credit Balance Loan A/c) ── */
export const LOAN_SLABS: SlabRow[] = [
{
sl: 1,
description: 'CC AGAINST DEPOSIT',
upto1Lakh: { amount: 10214.45, accounts: 4 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 10214.45, accounts: 4 }
},
{
sl: 2,
description: 'CASH CREDIT LOAN A/C',
upto1Lakh: { amount: 37223.99, accounts: 5 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 257039.67, accounts: 1 },
above3Lakh: { amount: 3869523.24, accounts: 3 },
total: { amount: 4163786.9, accounts: 9 }
},
{
sl: 3,
description: 'OVER DRAFT BMC',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 0, accounts: 0 }
},
{
sl: 4,
description: 'LOAN AGAINST GODOWN PLEDGE',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 0, accounts: 0 }
},
{
sl: 5,
description: 'KISHAN CREDIT CARD',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 0, accounts: 0 }
},
{
sl: 6,
description: 'LOAN AGAINST SECURITIES',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 0, accounts: 0 }
},
{
sl: 7,
description: 'LOAN AGAINST DEPOSIT',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 0, accounts: 0 }
},
{
sl: 8,
description: 'LOAN AGAINST GOLD',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 0, accounts: 0 }
},
{
sl: 9,
description: 'LOAN AGAINST WAREHOUSE',
upto1Lakh: { amount: 13265.0, accounts: 1 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 13265.0, accounts: 1 }
},
{
sl: 10,
description: 'SUNDRY DEBTORS',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 0, accounts: 0 }
},
{
sl: 11,
description: 'ADVANCE TO EMPLOYEES',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 0, accounts: 0 }
},
{
sl: 12,
description: 'LONG TERM LOAN A/C',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 0, accounts: 0 }
},
{
sl: 13,
description: 'PROPERTY MORTGAGE LIMIT',
upto1Lakh: { amount: 37564.07, accounts: 2 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 37564.07, accounts: 2 }
},
{
sl: 14,
description: 'MEDIUM TERM LOAN',
upto1Lakh: { amount: 0, accounts: 0 },
oneTo2Lakh: { amount: 0, accounts: 0 },
twoTo3Lakh: { amount: 0, accounts: 0 },
above3Lakh: { amount: 0, accounts: 0 },
total: { amount: 0, accounts: 0 }
}
];
/** Section D: Final summary */
export const SUMMARY_D = [
{ sl: 1, description: 'CREDIT BALANCE (1 to 16)', amount: 600018811.06, accounts: 8156 },
{ sl: 2, description: 'CREDIT BALANCE (LOAN A/C)', amount: 4224830.42, accounts: 16 },
{ sl: 3, description: 'OTHER LIABILITY', amount: 3436255.94, accounts: 18 }
];
/** SAHAY depositor list — sample customers (same dummy set used in CKYC + DEAF) */
export const SAHAY_DEPOSITORS: SahayDepositor[] = [
{
inputSerialNumber: 1,
customerIdUcic: 'UCIC0010078',
accountNumber: '110001302000312',
typeOfAccount: 'SAVINGS',
natureOfAccount: 'INDIVIDUAL',
numberOfHolders: 1,
nameFirst: 'RAVI KUMAR SHARMA',
nameSecond: '',
nameThird: '',
dateOfBirthOrIncorporation: '10-08-1985',
amountOfDeposit: 425000,
insuredAmount: 425000,
loanAgainstDeposit: '',
panNumber: 'AAQPS8560J',
aadhaarNumber: 'XXXXXXXX1234',
otherKycType: '',
otherKycNumber: '',
mobileNumber: '9892011001',
typeOfDeposit: 'SB',
lastCustomerInducedTxnDate: '05-04-2026'
},
{
inputSerialNumber: 2,
customerIdUcic: 'UCIC0010079',
accountNumber: '110001302000418',
typeOfAccount: 'FIXED',
natureOfAccount: 'INDIVIDUAL',
numberOfHolders: 1,
nameFirst: 'PRIYA PATEL',
nameSecond: '',
nameThird: '',
dateOfBirthOrIncorporation: '22-03-1990',
amountOfDeposit: 320000,
insuredAmount: 320000,
loanAgainstDeposit: '',
panNumber: 'AKLPS9933P',
aadhaarNumber: 'XXXXXXXX2210',
otherKycType: '',
otherKycNumber: '',
mobileNumber: '9822055002',
typeOfDeposit: 'FD',
lastCustomerInducedTxnDate: '15-03-2026'
},
{
inputSerialNumber: 3,
customerIdUcic: 'UCIC0010080',
accountNumber: '110001303000522',
typeOfAccount: 'RECURRING',
natureOfAccount: 'JOINT',
numberOfHolders: 2,
nameFirst: 'ARJUN DESHMUKH',
nameSecond: 'REKHA DESHMUKH',
nameThird: '',
dateOfBirthOrIncorporation: '15-01-1978',
amountOfDeposit: 148500,
insuredAmount: 148500,
loanAgainstDeposit: '',
panNumber: 'AGTPD1144K',
aadhaarNumber: 'XXXXXXXX5544',
otherKycType: '',
otherKycNumber: '',
mobileNumber: '9823033003',
typeOfDeposit: 'RD',
lastCustomerInducedTxnDate: '10-04-2026'
},
{
inputSerialNumber: 4,
customerIdUcic: 'UCIC0020101',
accountNumber: '110001302000711',
typeOfAccount: 'FIXED',
natureOfAccount: 'INDIVIDUAL',
numberOfHolders: 1,
nameFirst: 'VIKRAM REDDY',
nameSecond: '',
nameThird: '',
dateOfBirthOrIncorporation: '07-07-1982',
amountOfDeposit: 600000,
insuredAmount: 500000,
loanAgainstDeposit: 'LAFD',
panNumber: 'AAEPR9352L',
aadhaarNumber: 'XXXXXXXX6622',
otherKycType: '',
otherKycNumber: '',
mobileNumber: '9845022004',
typeOfDeposit: 'FD',
lastCustomerInducedTxnDate: '20-02-2026'
},
{
inputSerialNumber: 5,
customerIdUcic: 'UCIC0020102',
accountNumber: '110001301000814',
typeOfAccount: 'SAVINGS',
natureOfAccount: 'INDIVIDUAL',
numberOfHolders: 1,
nameFirst: 'LAKSHMI IYER',
nameSecond: '',
nameThird: '',
dateOfBirthOrIncorporation: '14-09-1986',
amountOfDeposit: 280000,
insuredAmount: 280000,
loanAgainstDeposit: '',
panNumber: '',
aadhaarNumber: 'XXXXXXXX5511',
otherKycType: 'VOTER_ID',
otherKycNumber: 'KA09KL5544',
mobileNumber: '9845066005',
typeOfDeposit: 'SB',
lastCustomerInducedTxnDate: '01-04-2026'
},
{
inputSerialNumber: 6,
customerIdUcic: 'UCIC0040301',
accountNumber: '110001302001012',
typeOfAccount: 'FIXED',
natureOfAccount: 'JOINT',
numberOfHolders: 2,
nameFirst: 'NIRAV SHAH',
nameSecond: 'RITA SHAH',
nameThird: '',
dateOfBirthOrIncorporation: '18-05-1992',
amountOfDeposit: 750000,
insuredAmount: 500000,
loanAgainstDeposit: '',
panNumber: 'AHKPS5544Z',
aadhaarNumber: 'XXXXXXXX9933',
otherKycType: 'PASSPORT',
otherKycNumber: 'M5544321',
mobileNumber: '9824012010',
typeOfDeposit: 'FD',
lastCustomerInducedTxnDate: '20-12-2025'
},
{
inputSerialNumber: 7,
customerIdUcic: 'UCIC0050401',
accountNumber: '110001301001145',
typeOfAccount: 'SAVINGS',
natureOfAccount: 'INDIVIDUAL',
numberOfHolders: 1,
nameFirst: 'KAVITA VERMA',
nameSecond: '',
nameThird: '',
dateOfBirthOrIncorporation: '01-04-1988',
amountOfDeposit: 95000,
insuredAmount: 95000,
loanAgainstDeposit: '',
panNumber: 'AKHPV2244R',
aadhaarNumber: 'XXXXXXXX1144',
otherKycType: '',
otherKycNumber: '',
mobileNumber: '9415055014',
typeOfDeposit: 'SB',
lastCustomerInducedTxnDate: '15-03-2026'
},
{
inputSerialNumber: 8,
customerIdUcic: 'UCIC0070601',
accountNumber: '110001304001458',
typeOfAccount: 'TERM_LOAN',
natureOfAccount: 'INDIVIDUAL',
numberOfHolders: 1,
nameFirst: 'SAMEER KAPOOR',
nameSecond: '',
nameThird: '',
dateOfBirthOrIncorporation: '03-02-1987',
amountOfDeposit: 1200000,
insuredAmount: 500000,
loanAgainstDeposit: 'LAD',
panNumber: 'AHKPK7788Q',
aadhaarNumber: 'XXXXXXXX8877',
otherKycType: '',
otherKycNumber: '',
mobileNumber: '9811022019',
typeOfDeposit: 'TL',
lastCustomerInducedTxnDate: '12-04-2026'
}
];
+29
View File
@@ -0,0 +1,29 @@
/**
* 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/.
*/
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Route } from '../core/route/route.service';
import { DicgcComponent } from './dicgc.component';
const routes: Routes = [
Route.withShell([
{
path: 'dicgc',
component: DicgcComponent,
data: { title: 'DICGC', breadcrumb: 'DICGC' }
}
])
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DicgcRoutingModule {}
+425
View File
@@ -0,0 +1,425 @@
<!--
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/.
-->
<div class="dic-root">
<!-- HEADER -->
<div class="dic-head">
<div class="dic-head-left">
<fa-icon icon="shield-alt" class="dic-head-ic"></fa-icon>
<div>
<h2 class="dic-head-t">Deposit Insurance and Credit Guarantee (DICGC)</h2>
<div class="dic-head-s">
{{ bank.name }} &middot; {{ bank.branch }} &middot; As on <strong>{{ bank.reportAsOn }}</strong>
</div>
</div>
</div>
<div class="dic-head-meta">
<div>
<span class="dic-meta-l">Insurance Limit</span
><span class="dic-meta-v">&#8377;{{ bank.insuranceLimit | number }}</span>
</div>
<div>
<span class="dic-meta-l">Premium Rate</span><span class="dic-meta-v">{{ bank.premiumRatePer100 }}% p.a.</span>
</div>
<div>
<span class="dic-meta-l">Reg. No.</span><span class="dic-meta-v mono">{{ bank.registrationNo }}</span>
</div>
</div>
</div>
<!-- TABS -->
<div class="dic-tabs">
<button class="dic-tab" [class.active]="activeTab === 'dashboard'" (click)="showTab('dashboard')">
<fa-icon icon="chart-pie"></fa-icon> Dashboard
</button>
<button class="dic-tab" [class.active]="activeTab === 'slab'" (click)="showTab('slab')">
<fa-icon icon="file-invoice"></fa-icon> A/C-Wise Slab Report
</button>
<button class="dic-tab" [class.active]="activeTab === 'sahay'" (click)="showTab('sahay')">
<fa-icon icon="users"></fa-icon> SAHAY Depositor List
</button>
</div>
<!-- DASHBOARD -->
@if (activeTab === 'dashboard') {
<div class="dic-section">
<div class="dic-kpis">
<div class="dic-kpi dic-kpi--blue">
<div class="dic-kpi-ic"><fa-icon icon="piggy-bank"></fa-icon></div>
<div>
<div class="dic-kpi-n">&#8377;{{ depositTotal.amount | number: '1.0-0' }}</div>
<div class="dic-kpi-l">Total Deposits</div>
<div class="dic-kpi-sub">{{ depositTotal.accounts | number }} accounts</div>
</div>
</div>
<div class="dic-kpi dic-kpi--green">
<div class="dic-kpi-ic"><fa-icon icon="check-circle"></fa-icon></div>
<div>
<div class="dic-kpi-n">{{ fullyInsuredAccounts | number }}</div>
<div class="dic-kpi-l">Fully Insured A/cs</div>
<div class="dic-kpi-sub">balance &le; &#8377;{{ bank.insuranceLimit | number }}</div>
</div>
</div>
<div class="dic-kpi dic-kpi--amber">
<div class="dic-kpi-ic"><fa-icon icon="exclamation-triangle"></fa-icon></div>
<div>
<div class="dic-kpi-n">{{ above3LakhAccounts | number }}</div>
<div class="dic-kpi-l">Above &#8377;3L A/cs</div>
<div class="dic-kpi-sub">partial / above-cap exposure</div>
</div>
</div>
<div class="dic-kpi dic-kpi--teal">
<div class="dic-kpi-ic"><fa-icon icon="hand-holding-usd"></fa-icon></div>
<div>
<div class="dic-kpi-n">&#8377;{{ loanTotal.amount | number: '1.0-0' }}</div>
<div class="dic-kpi-l">Loans Against Deposits</div>
<div class="dic-kpi-sub">{{ loanTotal.accounts | number }} accounts</div>
</div>
</div>
<div class="dic-kpi dic-kpi--red">
<div class="dic-kpi-ic"><fa-icon icon="coins"></fa-icon></div>
<div>
<div class="dic-kpi-n">&#8377;{{ premiumDue | number: '1.0-0' }}</div>
<div class="dic-kpi-l">DICGC Premium (Half-Yearly)</div>
<div class="dic-kpi-sub">{{ bank.premiumRatePer100 }}% of deposits</div>
</div>
</div>
</div>
<!-- Compliance / shortcuts -->
<div class="dic-compliance">
<div class="dc-card">
<div class="dc-card-h">
<fa-icon icon="check-circle" class="dc-ok"></fa-icon><span>A/C-Wise Slab Report</span>
</div>
<div class="dc-meta">
As on: <strong>{{ bank.reportAsOn }}</strong>
</div>
<div class="dc-meta">
Total deposits: <strong>&#8377;{{ depositTotal.amount | number: '1.0-0' }}</strong> across
{{ depositTotal.accounts | number }} A/cs
</div>
<button class="dc-btn" (click)="showTab('slab')">
<fa-icon icon="external-link-alt"></fa-icon> Open Report
</button>
</div>
<div class="dc-card">
<div class="dc-card-h"><fa-icon icon="users" class="dc-info"></fa-icon><span>SAHAY Depositor List</span></div>
<div class="dc-meta">Format: <strong>DICGC SAHAY 2.0</strong></div>
<div class="dc-meta">
Total entries: <strong>{{ depositors.length }}</strong>
</div>
<button class="dc-btn" (click)="showTab('sahay')">
<fa-icon icon="external-link-alt"></fa-icon> Open Register
</button>
</div>
<div class="dc-card">
<div class="dc-card-h">
<fa-icon icon="info-circle" class="dc-info"></fa-icon><span>Premium Schedule</span>
</div>
<div class="dc-meta">
Half-yearly assessment as of <strong>{{ bank.reportAsOn }}</strong>
</div>
<div class="dc-meta">Next due: <strong>31 May 2026</strong></div>
</div>
</div>
</div>
}
<!-- A/C-WISE SLAB REPORT -->
@if (activeTab === 'slab') {
<div class="dic-section">
<div class="dic-form-meta">
<div class="dic-form-title">
<h3>A/C-Wise Deposit Insurance &amp; Credit Guarantee Report</h3>
<p>
{{ bank.name }} · {{ bank.branch }} &middot; As On {{ bank.reportAsOn }} &middot; Ref {{ bank.reportRefNo }}
</p>
</div>
<button class="dic-btn dic-btn-xls" (click)="exportSlabReport()">
<fa-icon icon="file-excel"></fa-icon> Download Excel
</button>
</div>
<!-- Deposits -->
<div class="dic-tbl-wrap">
<div class="dic-tbl-h">CREDIT BALANCE — DEPOSITS</div>
<table class="dic-tbl">
<thead>
<tr class="hd-1">
<th rowspan="2" class="ta-c">Sr.No</th>
<th rowspan="2">Description</th>
<th colspan="2" class="ta-c">Up to &#8377;1,00,000</th>
<th colspan="2" class="ta-c">&#8377;1,00,001 – 2,00,000</th>
<th colspan="2" class="ta-c">&#8377;2,00,001 – 3,00,000</th>
<th colspan="2" class="ta-c">Above &#8377;3,00,000</th>
<th colspan="2" class="ta-c">Total</th>
</tr>
<tr class="hd-2">
<th class="ta-r">Amount</th>
<th class="ta-r">#A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">#A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">#A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">#A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">#A/c</th>
</tr>
</thead>
<tbody>
@for (r of deposits; track r.sl) {
<tr>
<td class="ta-c">{{ r.sl }}</td>
<td>{{ r.description }}</td>
<td class="ta-r mono">{{ r.upto1Lakh.amount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.upto1Lakh.accounts | number }}</td>
<td class="ta-r mono">{{ r.oneTo2Lakh.amount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.oneTo2Lakh.accounts | number }}</td>
<td class="ta-r mono">{{ r.twoTo3Lakh.amount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.twoTo3Lakh.accounts | number }}</td>
<td class="ta-r mono">{{ r.above3Lakh.amount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.above3Lakh.accounts | number }}</td>
<td class="ta-r mono">
<strong>{{ r.total.amount | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">
<strong>{{ r.total.accounts | number }}</strong>
</td>
</tr>
}
<tr class="row-total">
<td></td>
<td>TOTAL</td>
<td class="ta-r mono">600018811.06</td>
<td class="ta-r">{{ depositTotal.accounts | number }}</td>
<td class="ta-r mono">—</td>
<td class="ta-r">—</td>
<td class="ta-r mono">—</td>
<td class="ta-r">—</td>
<td class="ta-r mono">—</td>
<td class="ta-r">—</td>
<td class="ta-r mono">
<strong>{{ depositTotal.amount | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">
<strong>{{ depositTotal.accounts | number }}</strong>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Loans -->
<div class="dic-tbl-wrap">
<div class="dic-tbl-h">CREDIT BALANCE (LOAN A/C) — Section A</div>
<table class="dic-tbl">
<thead>
<tr class="hd-1">
<th rowspan="2" class="ta-c">Sr.No</th>
<th rowspan="2">Description</th>
<th colspan="2" class="ta-c">Up to &#8377;1,00,000</th>
<th colspan="2" class="ta-c">&#8377;1,00,001 – 2,00,000</th>
<th colspan="2" class="ta-c">&#8377;2,00,001 – 3,00,000</th>
<th colspan="2" class="ta-c">Above &#8377;3,00,000</th>
<th colspan="2" class="ta-c">Total</th>
</tr>
<tr class="hd-2">
<th class="ta-r">Amount</th>
<th class="ta-r">#A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">#A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">#A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">#A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">#A/c</th>
</tr>
</thead>
<tbody>
@for (r of loans; track r.sl) {
<tr>
<td class="ta-c">{{ r.sl }}</td>
<td>{{ r.description }}</td>
<td class="ta-r mono">{{ r.upto1Lakh.amount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.upto1Lakh.accounts | number }}</td>
<td class="ta-r mono">{{ r.oneTo2Lakh.amount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.oneTo2Lakh.accounts | number }}</td>
<td class="ta-r mono">{{ r.twoTo3Lakh.amount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.twoTo3Lakh.accounts | number }}</td>
<td class="ta-r mono">{{ r.above3Lakh.amount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.above3Lakh.accounts | number }}</td>
<td class="ta-r mono">
<strong>{{ r.total.amount | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">
<strong>{{ r.total.accounts | number }}</strong>
</td>
</tr>
}
<tr class="row-total">
<td></td>
<td>TOTAL</td>
<td class="ta-r mono">—</td>
<td class="ta-r">—</td>
<td class="ta-r mono">—</td>
<td class="ta-r">—</td>
<td class="ta-r mono">—</td>
<td class="ta-r">—</td>
<td class="ta-r mono">—</td>
<td class="ta-r">—</td>
<td class="ta-r mono">
<strong>{{ loanTotal.amount | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">
<strong>{{ loanTotal.accounts | number }}</strong>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Section D summary -->
<div class="dic-tbl-wrap">
<div class="dic-tbl-h">Section D — Final Summary</div>
<table class="dic-tbl dic-summary-tbl">
<thead>
<tr>
<th class="ta-c">Sr.No</th>
<th>Description</th>
<th class="ta-r">Amount</th>
<th class="ta-r">#A/c</th>
</tr>
</thead>
<tbody>
@for (s of summary; track s.sl) {
<tr>
<td class="ta-c">{{ s.sl }}</td>
<td>{{ s.description }}</td>
<td class="ta-r mono">{{ s.amount | number: '1.2-2' }}</td>
<td class="ta-r">{{ s.accounts | number }}</td>
</tr>
}
<tr class="row-total">
<td></td>
<td>TOTAL</td>
<td class="ta-r mono"><strong>607,679,897.42</strong></td>
<td class="ta-r"><strong>8,190</strong></td>
</tr>
</tbody>
</table>
</div>
</div>
}
<!-- SAHAY DEPOSITOR LIST -->
@if (activeTab === 'sahay') {
<div class="dic-section">
<div class="dic-form-meta">
<div class="dic-form-title">
<h3>SAHAY — Depositor-wise Input File</h3>
<p>DICGC claim payout format (CSV/Excel) submitted on bank failure under Section 18A</p>
</div>
<div class="dic-action-group">
<button class="dic-btn dic-btn-csv" (click)="exportSahayCsv()">
<fa-icon icon="file-csv"></fa-icon> Download CSV
</button>
<button class="dic-btn dic-btn-xls" (click)="exportSahay()">
<fa-icon icon="file-excel"></fa-icon> Download Excel
</button>
</div>
</div>
<div class="dic-filters">
<div class="dic-filter">
<label>Customer Name</label>
<input class="dic-inp" type="text" [(ngModel)]="filterName" placeholder="Search customer" />
</div>
<div class="dic-filter">
<label>Account Number</label>
<input class="dic-inp" type="text" [(ngModel)]="filterAccountNumber" placeholder="A/c contains…" />
</div>
<div class="dic-filter dic-filter-check">
<label class="check-row">
<input type="checkbox" [(ngModel)]="filterAboveLimit" />
<span>Only above &#8377;{{ bank.insuranceLimit | number }} (uninsured portion)</span>
</label>
</div>
<button class="dic-btn dic-btn-g" (click)="resetFilters()">Reset</button>
</div>
<div class="dic-tbl-wrap">
<table class="dic-tbl">
<thead>
<tr>
<th class="ta-c">SR</th>
<th>UCIC</th>
<th>A/c No</th>
<th>Type</th>
<th class="ta-c">Holders</th>
<th>Customer Name(s)</th>
<th>DOB</th>
<th class="ta-r">Deposit</th>
<th class="ta-r">Insured</th>
<th class="ta-c">LAD/LAFD</th>
<th>PAN</th>
<th>Aadhaar</th>
<th>Mobile</th>
<th>Last Txn</th>
</tr>
</thead>
<tbody>
@for (d of filteredDepositors; track d.inputSerialNumber) {
<tr [class.uninsured]="isUninsured(d)">
<td class="ta-c">{{ d.inputSerialNumber }}</td>
<td class="mono">{{ d.customerIdUcic }}</td>
<td class="mono">{{ d.accountNumber }}</td>
<td>{{ d.typeOfDeposit }}</td>
<td class="ta-c">{{ d.numberOfHolders }}</td>
<td>
{{ d.nameFirst }}
@if (d.nameSecond) {
<span class="cust-2"> &amp; {{ d.nameSecond }}</span>
}
@if (d.nameThird) {
<span class="cust-2"> &amp; {{ d.nameThird }}</span>
}
</td>
<td>{{ d.dateOfBirthOrIncorporation }}</td>
<td class="ta-r mono">{{ d.amountOfDeposit | number: '1.0-0' }}</td>
<td class="ta-r mono">{{ d.insuredAmount | number: '1.0-0' }}</td>
<td class="ta-c">{{ d.loanAgainstDeposit || '—' }}</td>
<td class="mono">{{ d.panNumber || '—' }}</td>
<td class="mono">{{ d.aadhaarNumber || '—' }}</td>
<td class="mono">{{ d.mobileNumber }}</td>
<td>{{ d.lastCustomerInducedTxnDate }}</td>
</tr>
} @empty {
<tr>
<td colspan="14" class="dic-empty">No depositors match the filters.</td>
</tr>
}
</tbody>
</table>
</div>
<div class="dic-note">
<fa-icon icon="info-circle"></fa-icon>
<span
><strong>SAHAY Note:</strong> Insured Amount is capped at &#8377;{{ bank.insuranceLimit | number }} per
depositor per bank. Rows highlighted are above the cap — DICGC pays the cap, residual is unsecured.</span
>
</div>
</div>
}
<!-- TOAST -->
<div class="dic-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
</div>
+602
View File
@@ -0,0 +1,602 @@
/**
* 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/.
*/
:host {
--dc-navy: #0b1e3d;
--dc-blue: #1d4ed8;
--dc-blue-l: #eff6ff;
--dc-teal: #0e7e6b;
--dc-teal-l: #e0f4f0;
--dc-green: #15803d;
--dc-green-l: #dcfce7;
--dc-amber: #d97706;
--dc-amber-l: #fef3c7;
--dc-red: #c0392b;
--dc-red-l: #fdecea;
--dc-purple: #6d28d9;
--dc-purple-l: #ede9fe;
--dc-g50: #f8fafc;
--dc-g100: #f1f5f9;
--dc-g200: #e2e8f0;
--dc-g300: #cbd5e1;
--dc-g500: #64748b;
--dc-g700: #334155;
--dc-g900: #0f172a;
display: block;
font-family:
'DM Sans',
system-ui,
-apple-system,
sans-serif;
background: var(--dc-g50);
color: var(--dc-g900);
font-size: 14px;
line-height: 1.5;
}
.dic-root {
padding: 18px 22px;
max-width: 1500px;
margin: 0 auto;
}
/* HEADER */
.dic-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.dic-head-left {
display: flex;
align-items: center;
gap: 14px;
}
.dic-head-ic {
font-size: 28px;
color: var(--dc-blue);
}
.dic-head-t {
margin: 0;
font-size: 18px;
font-weight: 700;
color: var(--dc-g900);
}
.dic-head-s {
font-size: 12px;
color: var(--dc-g500);
}
.dic-head-meta {
display: flex;
gap: 22px;
> div {
display: flex;
flex-direction: column;
.dic-meta-l {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--dc-g500);
}
.dic-meta-v {
font-size: 14px;
font-weight: 600;
color: var(--dc-g900);
}
}
}
/* TABS */
.dic-tabs {
display: flex;
gap: 4px;
background: #fff;
border: 1px solid var(--dc-g200);
border-radius: 10px;
padding: 4px;
margin-bottom: 16px;
overflow-x: auto;
}
.dic-tab {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 9px 14px;
border: none;
background: transparent;
font-size: 13px;
font-weight: 500;
color: var(--dc-g500);
cursor: pointer;
border-radius: 7px;
white-space: nowrap;
transition: all 0.15s;
font-family: inherit;
&:hover {
color: var(--dc-g900);
}
&.active {
background: var(--dc-blue);
color: #fff;
box-shadow: 0 2px 6px rgb(29 78 216 / 25%);
}
}
.dic-section {
display: flex;
flex-direction: column;
gap: 16px;
}
/* KPI CARDS */
.dic-kpis {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
}
.dic-kpi {
background: #fff;
border: 1px solid var(--dc-g200);
border-radius: 12px;
padding: 14px 16px;
display: flex;
align-items: center;
gap: 12px;
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 14px rgb(0 0 0 / 10%);
}
}
.dic-kpi-ic {
width: 42px;
height: 42px;
border-radius: 11px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.dic-kpi--blue .dic-kpi-ic {
background: var(--dc-blue-l);
color: var(--dc-blue);
}
.dic-kpi--green .dic-kpi-ic {
background: var(--dc-green-l);
color: var(--dc-green);
}
.dic-kpi--amber .dic-kpi-ic {
background: var(--dc-amber-l);
color: var(--dc-amber);
}
.dic-kpi--teal .dic-kpi-ic {
background: var(--dc-teal-l);
color: var(--dc-teal);
}
.dic-kpi--red .dic-kpi-ic {
background: var(--dc-red-l);
color: var(--dc-red);
}
.dic-kpi-n {
font-size: 18px;
font-weight: 700;
color: var(--dc-g900);
line-height: 1.1;
}
.dic-kpi-l {
font-size: 12px;
color: var(--dc-g700);
font-weight: 500;
margin-top: 2px;
}
.dic-kpi-sub {
font-size: 11px;
color: var(--dc-g500);
margin-top: 1px;
}
/* COMPLIANCE CARDS */
.dic-compliance {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
}
.dc-card {
background: #fff;
border: 1px solid var(--dc-g200);
border-radius: 12px;
padding: 14px 18px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.dc-card-h {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 600;
color: var(--dc-g900);
margin-bottom: 8px;
}
.dc-ok {
color: var(--dc-green);
}
.dc-info {
color: var(--dc-blue);
}
.dc-meta {
font-size: 12px;
color: var(--dc-g500);
margin: 2px 0;
strong {
color: var(--dc-g900);
font-weight: 600;
}
}
.dc-btn {
margin-top: 10px;
padding: 6px 12px;
border: 1px solid var(--dc-blue);
background: var(--dc-blue-l);
color: var(--dc-blue);
border-radius: 8px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
font-family: inherit;
display: inline-flex;
align-items: center;
gap: 6px;
&:hover {
background: var(--dc-blue);
color: #fff;
}
}
/* FORM HEADER */
.dic-form-meta {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 14px;
flex-wrap: wrap;
}
.dic-form-title {
h3 {
margin: 0;
font-size: 16px;
font-weight: 700;
color: var(--dc-g900);
}
p {
margin: 2px 0 0;
font-size: 12px;
color: var(--dc-g500);
}
}
.dic-action-group {
display: flex;
gap: 8px;
}
/* BUTTONS */
.dic-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: 1px solid var(--dc-g300);
background: #fff;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
color: var(--dc-g700);
cursor: pointer;
font-family: inherit;
transition: all 0.15s;
&:hover {
background: var(--dc-g50);
border-color: var(--dc-g500);
}
}
.dic-btn-xls {
background: #16a34a;
border-color: #16a34a;
color: #fff;
&:hover {
background: #15803d;
border-color: #15803d;
color: #fff;
}
}
.dic-btn-csv {
background: #1d4ed8;
border-color: #1d4ed8;
color: #fff;
&:hover {
background: #1e40af;
border-color: #1e40af;
color: #fff;
}
}
.dic-btn-g {
align-self: end;
}
/* INPUTS */
.dic-inp {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--dc-g200);
border-radius: 8px;
font-size: 13px;
background: #fff;
font-family: inherit;
&:focus {
outline: none;
border-color: var(--dc-blue);
box-shadow: 0 0 0 3px rgb(29 78 216 / 15%);
}
}
/* FILTERS */
.dic-filters {
background: #fff;
border: 1px solid var(--dc-g200);
border-radius: 12px;
padding: 14px;
display: grid;
grid-template-columns: 1fr 1fr 1.4fr auto;
gap: 12px;
align-items: end;
}
.dic-filter {
display: flex;
flex-direction: column;
gap: 4px;
label {
font-size: 11px;
font-weight: 600;
color: var(--dc-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
}
.dic-filter-check .check-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--dc-g700);
cursor: pointer;
user-select: none;
margin-top: 14px;
input {
width: 16px;
height: 16px;
accent-color: var(--dc-blue);
}
}
/* TABLE */
.dic-tbl-wrap {
background: #fff;
border: 1px solid var(--dc-g200);
border-radius: 12px;
overflow: auto;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.dic-tbl-h {
background: var(--dc-blue-l);
color: var(--dc-blue);
padding: 10px 16px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid var(--dc-g200);
}
.dic-tbl {
width: 100%;
border-collapse: collapse;
th {
text-align: left;
padding: 9px 10px;
font-size: 11px;
font-weight: 600;
color: var(--dc-g500);
text-transform: uppercase;
letter-spacing: 0.04em;
background: var(--dc-g50);
border-bottom: 1px solid var(--dc-g200);
}
th.hd-1,
tr.hd-1 th {
background: #f0f4ff;
color: var(--dc-blue);
}
td {
padding: 9px 10px;
border-bottom: 1px solid var(--dc-g100);
font-size: 12px;
color: var(--dc-g700);
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: var(--dc-g50);
}
tr.row-total td {
background: #f0fdf4;
font-weight: 700;
color: var(--dc-g900);
border-top: 2px solid var(--dc-green);
}
tr.uninsured td {
background: #fff7ed;
}
tr.uninsured:hover td {
background: #ffedd5;
}
}
.dic-summary-tbl {
td,
th {
padding: 11px 14px;
font-size: 13px;
}
}
.ta-c {
text-align: center;
}
.ta-r {
text-align: right;
}
.mono {
font-family: 'DM Mono', monospace;
font-size: 11px;
letter-spacing: 0.02em;
}
.cust-2 {
color: var(--dc-g500);
font-size: 11px;
}
.dic-empty {
text-align: center;
padding: 30px;
color: var(--dc-g500);
font-style: italic;
}
/* NOTE */
.dic-note {
background: var(--dc-amber-l);
border: 1px solid #fed7aa;
border-radius: 10px;
padding: 12px 16px;
display: flex;
gap: 10px;
font-size: 12px;
color: #92400e;
line-height: 1.5;
fa-icon {
font-size: 14px;
flex-shrink: 0;
margin-top: 2px;
}
}
/* TOAST */
.dic-toast {
position: fixed;
bottom: 32px;
left: 50%;
transform: translateX(-50%) translateY(20px);
background: var(--dc-navy);
color: #fff;
padding: 12px 22px;
border-radius: 10px;
font-size: 13px;
font-weight: 500;
box-shadow: 0 6px 20px rgb(0 0 0 / 25%);
opacity: 0;
pointer-events: none;
transition: all 0.25s;
z-index: 1500;
&.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
@media (width <= 1100px) {
.dic-kpis {
grid-template-columns: repeat(3, 1fr);
}
.dic-compliance {
grid-template-columns: 1fr;
}
.dic-filters {
grid-template-columns: 1fr 1fr;
}
}
@media (width <= 720px) {
.dic-kpis {
grid-template-columns: 1fr 1fr;
}
}
+398
View File
@@ -0,0 +1,398 @@
/**
* 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/.
*/
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import {
DEPOSIT_SLABS,
DICGC_BANK,
LOAN_SLABS,
SahayDepositor,
SAHAY_DEPOSITORS,
SlabRow,
SUMMARY_D
} from './dicgc-data';
@Component({
selector: 'mifosx-dicgc',
templateUrl: './dicgc.component.html',
styleUrls: ['./dicgc.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
FormsModule
]
})
export class DicgcComponent {
/** Active tab */
activeTab: 'dashboard' | 'slab' | 'sahay' = 'dashboard';
bank = DICGC_BANK;
deposits: SlabRow[] = DEPOSIT_SLABS;
loans: SlabRow[] = LOAN_SLABS;
summary = SUMMARY_D;
depositors: SahayDepositor[] = SAHAY_DEPOSITORS;
/** SAHAY filters */
filterName = '';
filterAccountNumber = '';
filterAboveLimit = false;
/** Toast */
toastMsg = '';
toastVisible = false;
private toastTimer: any;
showTab(tab: 'dashboard' | 'slab' | 'sahay'): void {
this.activeTab = tab;
}
// -------------------- KPI helpers --------------------
private sumSlab(
rows: SlabRow[],
slab: keyof Pick<SlabRow, 'upto1Lakh' | 'oneTo2Lakh' | 'twoTo3Lakh' | 'above3Lakh' | 'total'>
): { amount: number; accounts: number } {
let amount = 0;
let accounts = 0;
for (const r of rows) {
amount += r[slab].amount;
accounts += r[slab].accounts;
}
return { amount, accounts };
}
get depositTotal(): { amount: number; accounts: number } {
return this.sumSlab(this.deposits, 'total');
}
get loanTotal(): { amount: number; accounts: number } {
return this.sumSlab(this.loans, 'total');
}
/** Insured = full amount for ≤5L, capped at 5L for >5L (sum across slabs ≤3L is fully insured here) */
get fullyInsuredAccounts(): number {
const u1 = this.sumSlab(this.deposits, 'upto1Lakh').accounts;
const u2 = this.sumSlab(this.deposits, 'oneTo2Lakh').accounts;
const u3 = this.sumSlab(this.deposits, 'twoTo3Lakh').accounts;
return u1 + u2 + u3;
}
get above3LakhAccounts(): number {
return this.sumSlab(this.deposits, 'above3Lakh').accounts;
}
get insuredAmount(): number {
return this.depositors.reduce((s, d) => s + d.insuredAmount, 0);
}
/** DICGC premium = 0.12% of total assessable deposits per half year */
get premiumDue(): number {
return this.depositTotal.amount * (this.bank.premiumRatePer100 / 100);
}
// SAHAY filtered list
get filteredDepositors(): SahayDepositor[] {
const name = this.filterName.trim().toLowerCase();
const acc = this.filterAccountNumber.trim();
return this.depositors.filter((d) => {
if (name && !`${d.nameFirst} ${d.nameSecond} ${d.nameThird}`.toLowerCase().includes(name)) return false;
if (acc && !d.accountNumber.includes(acc)) return false;
if (this.filterAboveLimit && d.amountOfDeposit <= this.bank.insuranceLimit) return false;
return true;
});
}
resetFilters(): void {
this.filterName = '';
this.filterAccountNumber = '';
this.filterAboveLimit = false;
}
/** True if depositor's amount exceeds the DICGC ₹5L cap */
isUninsured(d: SahayDepositor): boolean {
return d.amountOfDeposit > this.bank.insuranceLimit;
}
// -------------------- Excel exports --------------------
exportSlabReport(): void {
const rows: string[][] = [];
rows.push([
this.bank.name,
this.bank.branch
]);
rows.push([`A/C-Wise Deposit Insurance and Credit Guarantee Report — As On ${this.bank.reportAsOn}`]);
rows.push([]);
rows.push([
`Report Ref: ${this.bank.reportRefNo}`,
`Print Date: ${this.bank.printDate}`,
`User Code: ${this.bank.userCode}`
]);
rows.push([]);
// Header section
rows.push(['CREDIT BALANCE — DEPOSITS']);
rows.push([
'Sr.No',
'Description',
'Up to 1L Amount',
'Up to 1L #A/c',
'1L-2L Amount',
'1L-2L #A/c',
'2L-3L Amount',
'2L-3L #A/c',
'Above 3L Amount',
'Above 3L #A/c',
'Total Amount',
'Total #A/c'
]);
for (const r of this.deposits) {
rows.push(this.flattenSlabRow(r));
}
const dt = this.depositTotal;
rows.push([
'',
'TOTAL',
this.sumSlab(this.deposits, 'upto1Lakh').amount.toFixed(2),
this.sumSlab(this.deposits, 'upto1Lakh').accounts.toString(),
this.sumSlab(this.deposits, 'oneTo2Lakh').amount.toFixed(2),
this.sumSlab(this.deposits, 'oneTo2Lakh').accounts.toString(),
this.sumSlab(this.deposits, 'twoTo3Lakh').amount.toFixed(2),
this.sumSlab(this.deposits, 'twoTo3Lakh').accounts.toString(),
this.sumSlab(this.deposits, 'above3Lakh').amount.toFixed(2),
this.sumSlab(this.deposits, 'above3Lakh').accounts.toString(),
dt.amount.toFixed(2),
dt.accounts.toString()
]);
rows.push([]);
rows.push(['CREDIT BALANCE (LOAN A/C) — Section A']);
rows.push([
'Sr.No',
'Description',
'Up to 1L Amount',
'Up to 1L #A/c',
'1L-2L Amount',
'1L-2L #A/c',
'2L-3L Amount',
'2L-3L #A/c',
'Above 3L Amount',
'Above 3L #A/c',
'Total Amount',
'Total #A/c'
]);
for (const r of this.loans) {
rows.push(this.flattenSlabRow(r));
}
const lt = this.loanTotal;
rows.push([
'',
'TOTAL',
this.sumSlab(this.loans, 'upto1Lakh').amount.toFixed(2),
this.sumSlab(this.loans, 'upto1Lakh').accounts.toString(),
this.sumSlab(this.loans, 'oneTo2Lakh').amount.toFixed(2),
this.sumSlab(this.loans, 'oneTo2Lakh').accounts.toString(),
this.sumSlab(this.loans, 'twoTo3Lakh').amount.toFixed(2),
this.sumSlab(this.loans, 'twoTo3Lakh').accounts.toString(),
this.sumSlab(this.loans, 'above3Lakh').amount.toFixed(2),
this.sumSlab(this.loans, 'above3Lakh').accounts.toString(),
lt.amount.toFixed(2),
lt.accounts.toString()
]);
rows.push([]);
rows.push(['Section D — Final Summary']);
rows.push([
'Sr.No',
'Description',
'Amount',
'#A/c'
]);
for (const s of this.summary) {
rows.push([
s.sl.toString(),
s.description,
s.amount.toFixed(2),
s.accounts.toString()
]);
}
const grandAmount = this.summary.reduce((a, x) => a + x.amount, 0);
const grandAcc = this.summary.reduce((a, x) => a + x.accounts, 0);
rows.push([
'',
'TOTAL',
grandAmount.toFixed(2),
grandAcc.toString()
]);
this.saveTsv(rows, `DICGC-SlabReport-${this.fileTag()}.xls`);
this.toast('DICGC slab report downloaded');
}
exportSahay(): void {
const headers = [
'Input Serial Number',
'Customer ID (UCIC) Number',
'Account Number',
'Type of account',
'Nature of account',
'Number of Acct. Holders (in case of Joint)',
'Name Of First Customer',
'Name Of Second Customer',
'Name Of Third Customer',
'Date of Birth (Primary Holder) or Date of incorporation in case of Entity',
'Amount of Deposit',
'Insured Amount',
'Loan against deposits only (LAD / LAFD)',
'PAN Number',
'Aadhaar Number',
'Any Other OVD / KYC Document Type',
'Any Other OVD / KYC Number',
'Mobile Number',
'Type of Deposit',
'Date of Last Customer inducted transaction'
];
const rows: string[][] = [headers];
for (const d of this.filteredDepositors) {
rows.push([
d.inputSerialNumber.toString(),
d.customerIdUcic,
d.accountNumber,
d.typeOfAccount,
d.natureOfAccount,
d.numberOfHolders.toString(),
d.nameFirst,
d.nameSecond,
d.nameThird,
d.dateOfBirthOrIncorporation,
d.amountOfDeposit.toFixed(2),
d.insuredAmount.toFixed(2),
d.loanAgainstDeposit,
d.panNumber,
d.aadhaarNumber,
d.otherKycType,
d.otherKycNumber,
d.mobileNumber,
d.typeOfDeposit,
d.lastCustomerInducedTxnDate
]);
}
this.saveTsv(rows, `DICGC-SAHAY-Depositors-${this.fileTag()}.xls`);
this.toast(`${this.filteredDepositors.length} depositors exported (SAHAY format)`);
}
exportSahayCsv(): void {
// CSV mirrors the Input File Template DICGC ships
const headers = [
'Input Serial Number',
'Customer ID (UCIC) Number',
'Account Number',
'Type of account',
'Nature of account',
'Number of Acct. Holders (in case of Joint)',
'Name Of First Customer',
'Name Of Second Customer',
'Name Of Third Customer',
'Date of Birth (Primary Holder) or Date of incorporation in case of Entity',
'Amount of Deposit',
'Insured Amount',
'Loan against deposits only (LAD / LAFD)',
'PAN Number',
'Aadhaar Number',
'Any Other OVD / KYC Document Type',
'Any Other OVD / KYC Number',
'Mobile Number',
'Type of Deposit',
'Date of Last Customer inducted transaction'
];
const escape = (v: string): string => {
const s = (v || '').replace(/"/g, '""');
if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s}"`;
return s;
};
const lines: string[] = [headers.map(escape).join(',')];
for (const d of this.filteredDepositors) {
lines.push(
[
d.inputSerialNumber.toString(),
d.customerIdUcic,
d.accountNumber,
d.typeOfAccount,
d.natureOfAccount,
d.numberOfHolders.toString(),
d.nameFirst,
d.nameSecond,
d.nameThird,
d.dateOfBirthOrIncorporation,
d.amountOfDeposit.toFixed(2),
d.insuredAmount.toFixed(2),
d.loanAgainstDeposit,
d.panNumber,
d.aadhaarNumber,
d.otherKycType,
d.otherKycNumber,
d.mobileNumber,
d.typeOfDeposit,
d.lastCustomerInducedTxnDate
]
.map(escape)
.join(',')
);
}
const blob = new Blob([lines.join('\n')], { type: 'text/csv;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `DICGC-SAHAY-Depositors-${this.fileTag()}.csv`;
a.click();
URL.revokeObjectURL(url);
this.toast(`${this.filteredDepositors.length} depositors exported (CSV)`);
}
private flattenSlabRow(r: SlabRow): string[] {
return [
r.sl.toString(),
r.description,
r.upto1Lakh.amount.toFixed(2),
r.upto1Lakh.accounts.toString(),
r.oneTo2Lakh.amount.toFixed(2),
r.oneTo2Lakh.accounts.toString(),
r.twoTo3Lakh.amount.toFixed(2),
r.twoTo3Lakh.accounts.toString(),
r.above3Lakh.amount.toFixed(2),
r.above3Lakh.accounts.toString(),
r.total.amount.toFixed(2),
r.total.accounts.toString()
];
}
private saveTsv(rows: string[][], filename: string): void {
const tsv = rows.map((r) => r.map((v) => (v || '').replace(/\t/g, ' ')).join('\t')).join('\n');
const blob = new Blob([tsv], { type: 'application/vnd.ms-excel;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
private fileTag(): string {
const d = new Date();
const dd = String(d.getDate()).padStart(2, '0');
const mm = String(d.getMonth() + 1).padStart(2, '0');
return `${dd}${mm}${d.getFullYear()}`;
}
toast(msg: string): void {
this.toastMsg = msg;
this.toastVisible = true;
if (this.toastTimer) clearTimeout(this.toastTimer);
this.toastTimer = setTimeout(() => (this.toastVisible = false), 2800);
}
}
+22
View File
@@ -0,0 +1,22 @@
/**
* 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/.
*/
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { DicgcRoutingModule } from './dicgc-routing.module';
import { DicgcComponent } from './dicgc.component';
@NgModule({
imports: [
SharedModule,
DicgcRoutingModule,
DicgcComponent
]
})
export class DicgcModule {}
+560
View File
@@ -0,0 +1,560 @@
/**
* 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/.
*/
/**
* Sample FIU-IND data — schema mirrors the FINnet 2.0 CSV submission templates
* (TC1, TC2, KC1, KC2, Account_Detail, Account_Person_Relation).
* Bank/customer names are dummy placeholders.
*
* Reports filed with FIU-IND under PMLA 2002:
* CTR — Cash Transaction Report (≥ ₹10 L cash, monthly by 15th)
* STR — Suspicious Transaction Report (within 7 days of detection)
* CCR — Counterfeit Currency Report
* NTR — NPO Transaction Report (≥ ₹10 L, NGO/NPO clients)
* CBWTR — Cross-Border Wire Transfer Report (≥ ₹5 L)
*/
export type ReportType = 'CTR' | 'STR' | 'CCR' | 'NTR' | 'CBWTR';
export type StrStatus = 'draft' | 'po-review' | 'filed' | 'rejected';
/** TC2 — Branch / cash transaction record */
export interface CtrTransaction {
reportType: ReportType;
reportRefNo: number;
branchCode: string;
accountNumber: string;
txnBranchCode: string;
depositOrWithdrawal: 'Deposit' | 'Withdrawal';
txnId: number;
txnDate: string;
txnTime: string;
txnAmount: number;
instrumentType: 'Cash' | 'Cheque' | 'DD' | 'NEFT' | 'RTGS' | 'Others';
instrumentId: string;
nonCustomerRef: string;
relationshipFlag: 'Customer' | 'Non-Customer';
thirdPartyPan: string;
declaration: string;
/** Customer name resolved from KC1 (UI convenience, not in actual TC2 schema) */
customerName?: string;
}
/** STR record — workflow extension on top of TC2 */
export interface StrTransaction extends CtrTransaction {
detectedOn: string;
flaggedBy: string;
riskCategory:
| 'structuring'
| 'dormant-revival'
| 'high-risk-country'
| 'unusual-pattern'
| 'pep-related'
| 'sanctions-hit';
narrative: string;
status: StrStatus;
poRemarks?: string;
}
/** KC1 — Individual customer card (subset of the 70+ fields, only what UI shows) */
export interface KycIndividual {
reportRefNo: number;
ucic: string;
pan: string;
ckycNumber: string;
firstName: string;
middleName: string;
lastName: string;
fatherName: string;
motherName: string;
gender: 'Male' | 'Female' | 'Other';
dob: string;
mobileNumber: string;
email: string;
primaryAddress: string;
state: string;
city: string;
pincode: string;
customerType: string;
annualIncome: number;
occupation: string;
onboardingDate: string;
lastKycDate: string;
riskLevel: 'Low' | 'Medium' | 'High';
reportType: ReportType;
otherIdentifierName?: string;
otherIdentifierNumber?: string;
}
/** KC2 — Entity / non-individual customer card */
export interface KycEntity {
reportRefNo: number;
ucic: string;
entityName: string;
panNumber: string;
gstin: string;
mobileNumber: string;
address: string;
state: string;
city: string;
pincode: string;
dateOfIncorporation: string;
customerType: string;
riskLevel: 'Low' | 'Medium' | 'High';
onboardingDate: string;
lastKycDate: string;
isSoleProprietor: boolean;
ownerUcic: string;
ownerName: string;
reportType: ReportType;
}
export const FIU_BANK = {
name: 'Bharat Urban Bank Ltd',
branch: 'Head Office',
reBankingFlag: 'SCB',
reportingMonth: 'January 2026',
reBuid: 'REBANK-2031',
principalOfficer: 'Karthik Kamath',
principalOfficerEmail: 'po.fiu@bharaturbanbank.example',
filerUserId: 'PO-001',
cashThreshold: 1000000, // ₹10 L per single cash txn
monthlyAggThreshold: 1000000, // aggregate ≥ ₹10 L in a month
cbwtrThreshold: 500000 // CBWTR threshold ₹5 L
};
/** ── Sample CTR transactions (FINnet TC2-style) ── */
export const CTR_TXNS: CtrTransaction[] = [
{
reportType: 'CTR',
reportRefNo: 1,
branchCode: '1',
accountNumber: '110001101000838',
txnBranchCode: '1',
depositOrWithdrawal: 'Withdrawal',
txnId: 1,
txnDate: '20-01-2026',
txnTime: '16:51:28',
txnAmount: 1500000,
instrumentType: 'Cheque',
instrumentId: '24304',
nonCustomerRef: '',
relationshipFlag: 'Customer',
thirdPartyPan: '',
declaration: 'NA',
customerName: 'RAVI KUMAR SHARMA'
},
{
reportType: 'CTR',
reportRefNo: 1,
branchCode: '1',
accountNumber: '110001101000838',
txnBranchCode: '1',
depositOrWithdrawal: 'Withdrawal',
txnId: 2,
txnDate: '27-01-2026',
txnTime: '16:14:22',
txnAmount: 932000,
instrumentType: 'Cheque',
instrumentId: '24305',
nonCustomerRef: '',
relationshipFlag: 'Customer',
thirdPartyPan: '',
declaration: 'NA',
customerName: 'RAVI KUMAR SHARMA'
},
{
reportType: 'CTR',
reportRefNo: 2,
branchCode: '1',
accountNumber: '110001352000357',
txnBranchCode: '1',
depositOrWithdrawal: 'Withdrawal',
txnId: 3,
txnDate: '02-01-2026',
txnTime: '15:05:36',
txnAmount: 600000,
instrumentType: 'Cheque',
instrumentId: '100039',
nonCustomerRef: '',
relationshipFlag: 'Customer',
thirdPartyPan: '',
declaration: 'NA',
customerName: 'PRIYA PATEL'
},
{
reportType: 'CTR',
reportRefNo: 2,
branchCode: '1',
accountNumber: '110001352000357',
txnBranchCode: '1',
depositOrWithdrawal: 'Withdrawal',
txnId: 4,
txnDate: '07-01-2026',
txnTime: '14:14:17',
txnAmount: 700000,
instrumentType: 'Cheque',
instrumentId: '100040',
nonCustomerRef: '',
relationshipFlag: 'Customer',
thirdPartyPan: '',
declaration: 'NA',
customerName: 'PRIYA PATEL'
},
{
reportType: 'CTR',
reportRefNo: 3,
branchCode: '7',
accountNumber: '110001352000420',
txnBranchCode: '7',
depositOrWithdrawal: 'Deposit',
txnId: 5,
txnDate: '12-01-2026',
txnTime: '11:28:50',
txnAmount: 1200000,
instrumentType: 'Cash',
instrumentId: 'CASH',
nonCustomerRef: '',
relationshipFlag: 'Customer',
thirdPartyPan: '',
declaration: 'NA',
customerName: 'VIKRAM REDDY'
},
{
reportType: 'CTR',
reportRefNo: 4,
branchCode: '4',
accountNumber: '110001352000511',
txnBranchCode: '4',
depositOrWithdrawal: 'Deposit',
txnId: 6,
txnDate: '18-01-2026',
txnTime: '10:14:05',
txnAmount: 2500000,
instrumentType: 'Cash',
instrumentId: 'CASH',
nonCustomerRef: '',
relationshipFlag: 'Customer',
thirdPartyPan: '',
declaration: 'NA',
customerName: 'NIRAV SHAH'
},
{
reportType: 'CTR',
reportRefNo: 5,
branchCode: '11',
accountNumber: '110001352000604',
txnBranchCode: '11',
depositOrWithdrawal: 'Withdrawal',
txnId: 7,
txnDate: '24-01-2026',
txnTime: '13:42:11',
txnAmount: 1100000,
instrumentType: 'Cheque',
instrumentId: '88123',
nonCustomerRef: 'NCR-001',
relationshipFlag: 'Non-Customer',
thirdPartyPan: 'AKHPV2244R',
declaration: 'NA',
customerName: 'KAVITA VERMA'
}
];
/** ── Sample STR transactions ── */
export const STR_TXNS: StrTransaction[] = [
{
reportType: 'STR',
reportRefNo: 101,
branchCode: '1',
accountNumber: '110001101000841',
txnBranchCode: '1',
depositOrWithdrawal: 'Deposit',
txnId: 8,
txnDate: '15-01-2026',
txnTime: '11:32:00',
txnAmount: 950000,
instrumentType: 'Cash',
instrumentId: 'CASH',
nonCustomerRef: '',
relationshipFlag: 'Customer',
thirdPartyPan: '',
declaration: 'NA',
customerName: 'ARJUN DESHMUKH',
detectedOn: '16-01-2026',
flaggedBy: 'AML Engine — Rule R-007',
riskCategory: 'structuring',
narrative:
'Three sub-₹10L cash deposits within 5 days totalling ₹28.4L; pattern indicative of structuring to avoid CTR threshold.',
status: 'po-review'
},
{
reportType: 'STR',
reportRefNo: 102,
branchCode: '7',
accountNumber: '110001101000922',
txnBranchCode: '7',
depositOrWithdrawal: 'Deposit',
txnId: 9,
txnDate: '21-01-2026',
txnTime: '14:18:30',
txnAmount: 4800000,
instrumentType: 'NEFT',
instrumentId: 'N1264821',
nonCustomerRef: '',
relationshipFlag: 'Customer',
thirdPartyPan: '',
declaration: 'NA',
customerName: 'LAKSHMI IYER',
detectedOn: '22-01-2026',
flaggedBy: 'Branch Compliance Officer',
riskCategory: 'dormant-revival',
narrative:
'Account dormant for 18 months suddenly received ₹48L NEFT followed by 4 immediate cash withdrawals of ₹2L each.',
status: 'filed',
poRemarks: 'STR filed with FIU-IND. Acknowledgement no: STR-2026-9821.'
},
{
reportType: 'STR',
reportRefNo: 103,
branchCode: '2',
accountNumber: '110001302001012',
txnBranchCode: '2',
depositOrWithdrawal: 'Withdrawal',
txnId: 10,
txnDate: '23-01-2026',
txnTime: '15:42:18',
txnAmount: 1800000,
instrumentType: 'RTGS',
instrumentId: 'R889013',
nonCustomerRef: '',
relationshipFlag: 'Customer',
thirdPartyPan: '',
declaration: 'NA',
customerName: 'SAMEER KAPOOR',
detectedOn: '24-01-2026',
flaggedBy: 'AML Engine — Rule R-014',
riskCategory: 'high-risk-country',
narrative:
'RTGS transfer to beneficiary in jurisdiction listed under FATF grey list (Cayman Islands intermediary). KYC documentation incomplete.',
status: 'draft'
},
{
reportType: 'STR',
reportRefNo: 104,
branchCode: '13',
accountNumber: '110001352001145',
txnBranchCode: '13',
depositOrWithdrawal: 'Deposit',
txnId: 11,
txnDate: '08-01-2026',
txnTime: '12:55:42',
txnAmount: 6500000,
instrumentType: 'Cash',
instrumentId: 'CASH',
nonCustomerRef: '',
relationshipFlag: 'Customer',
thirdPartyPan: '',
declaration: 'NA',
customerName: 'MANMOHAN SINGH GILL',
detectedOn: '09-01-2026',
flaggedBy: 'AML Engine — Rule R-022',
riskCategory: 'unusual-pattern',
narrative:
'Account avg monthly turnover ₹1.5L; received ₹65L cash in single deposit, source-of-funds documentation pending.',
status: 'po-review'
}
];
/** ── KC1 sample individual customers ── */
export const KC1_INDIVIDUALS: KycIndividual[] = [
{
reportRefNo: 1,
ucic: '10078',
pan: 'AAQPS8560J',
ckycNumber: '60011225033445',
firstName: 'RAVI',
middleName: 'KUMAR',
lastName: 'SHARMA',
fatherName: 'RAJESH KUMAR SHARMA',
motherName: 'SUNITA DEVI',
gender: 'Male',
dob: '10-08-1985',
mobileNumber: '91-9892011001',
email: 'ravi.sharma@example.com',
primaryAddress: 'FLAT 402 SUNRISE TOWERS MAROL ANDHERI EAST',
state: 'MH',
city: 'MUMBAI',
pincode: '400069',
customerType: 'Individual',
annualIncome: 750000,
occupation: 'Service',
onboardingDate: '15-03-2020',
lastKycDate: '12-04-2025',
riskLevel: 'Medium',
reportType: 'CTR',
otherIdentifierName: 'AADHAAR',
otherIdentifierNumber: 'XXXXXXXX1234'
},
{
reportRefNo: 2,
ucic: '10079',
pan: 'AKLPS9933P',
ckycNumber: '60022144558899',
firstName: 'PRIYA',
middleName: '',
lastName: 'PATEL',
fatherName: 'HARISH PATEL',
motherName: 'MEENA PATEL',
gender: 'Female',
dob: '22-03-1990',
mobileNumber: '91-9822055002',
email: 'priya.patel@example.com',
primaryAddress: 'B-12 SHIVAJI NAGAR FC ROAD',
state: 'MH',
city: 'PUNE',
pincode: '411005',
customerType: 'Individual',
annualIncome: 480000,
occupation: 'Self-Employed',
onboardingDate: '10-09-2023',
lastKycDate: '15-03-2025',
riskLevel: 'Low',
reportType: 'CTR',
otherIdentifierName: 'AADHAAR',
otherIdentifierNumber: 'XXXXXXXX2210'
},
{
reportRefNo: 3,
ucic: '20101',
pan: 'AAEPR9352L',
ckycNumber: '',
firstName: 'VIKRAM',
middleName: '',
lastName: 'REDDY',
fatherName: 'SURESH REDDY',
motherName: 'LAKSHMI REDDY',
gender: 'Male',
dob: '07-07-1982',
mobileNumber: '91-9845022004',
email: '',
primaryAddress: 'NO 23 4TH CROSS JAYANAGAR 5TH BLOCK',
state: 'KA',
city: 'BENGALURU',
pincode: '560041',
customerType: 'Individual',
annualIncome: 950000,
occupation: 'Profession',
onboardingDate: '12-11-2021',
lastKycDate: '20-02-2025',
riskLevel: 'Medium',
reportType: 'CTR',
otherIdentifierName: 'AADHAAR',
otherIdentifierNumber: 'XXXXXXXX6622'
},
{
reportRefNo: 4,
ucic: '40301',
pan: 'AHKPS5544Z',
ckycNumber: '60040301229988',
firstName: 'NIRAV',
middleName: '',
lastName: 'SHAH',
fatherName: 'JAYESH SHAH',
motherName: 'RITA SHAH',
gender: 'Male',
dob: '18-05-1992',
mobileNumber: '91-9824012010',
email: 'nirav.shah@example.com',
primaryAddress: 'A-401 ATLANTIS APARTMENTS SATELLITE ROAD',
state: 'GJ',
city: 'AHMEDABAD',
pincode: '380015',
customerType: 'Individual',
annualIncome: 1800000,
occupation: 'Service',
onboardingDate: '14-02-2018',
lastKycDate: '20-12-2024',
riskLevel: 'High',
reportType: 'CTR',
otherIdentifierName: 'PASSPORT',
otherIdentifierNumber: 'M5544321'
},
{
reportRefNo: 5,
ucic: '50402',
pan: 'AKHPV2244R',
ckycNumber: '',
firstName: 'KAVITA',
middleName: '',
lastName: 'VERMA',
fatherName: 'DEEPAK VERMA',
motherName: 'USHA VERMA',
gender: 'Female',
dob: '01-04-1988',
mobileNumber: '91-9415055014',
email: '',
primaryAddress: '45 CIVIL LINES MALL ROAD',
state: 'UP',
city: 'KANPUR',
pincode: '208001',
customerType: 'Individual',
annualIncome: 540000,
occupation: 'Service',
onboardingDate: '08-09-2022',
lastKycDate: '15-09-2024',
riskLevel: 'Low',
reportType: 'CTR',
otherIdentifierName: 'AADHAAR',
otherIdentifierNumber: 'XXXXXXXX2244'
}
];
/** ── KC2 sample entity customers ── */
export const KC2_ENTITIES: KycEntity[] = [
{
reportRefNo: 11,
ucic: 'E10101',
entityName: 'BHARAT TRADING COMPANY',
panNumber: 'ABCFB1234N',
gstin: '27ABCFB1234N1ZT',
mobileNumber: '91-9892011010',
address: 'GALA 22 LOWER PAREL',
state: 'MH',
city: 'MUMBAI',
pincode: '400013',
dateOfIncorporation: '24-03-2008',
customerType: 'Sole Proprietor',
riskLevel: 'High',
onboardingDate: '24-03-2008',
lastKycDate: '03-09-2025',
isSoleProprietor: true,
ownerUcic: '10078',
ownerName: 'RAVI KUMAR SHARMA',
reportType: 'CTR'
},
{
reportRefNo: 12,
ucic: 'E10102',
entityName: 'NEXTGEN TECH PVT LTD',
panNumber: 'AABCN5566K',
gstin: '27AABCN5566K1Z2',
mobileNumber: '91-9824012011',
address: 'TOWER B SUNRISE BUSINESS PARK ANDHERI EAST',
state: 'MH',
city: 'MUMBAI',
pincode: '400093',
dateOfIncorporation: '18-09-2015',
customerType: 'Private Limited',
riskLevel: 'Medium',
onboardingDate: '18-09-2015',
lastKycDate: '12-08-2025',
isSoleProprietor: false,
ownerUcic: '',
ownerName: '',
reportType: 'CTR'
}
];
+29
View File
@@ -0,0 +1,29 @@
/**
* 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/.
*/
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Route } from '../core/route/route.service';
import { FiuComponent } from './fiu.component';
const routes: Routes = [
Route.withShell([
{
path: 'fiu',
component: FiuComponent,
data: { title: 'FIU-IND', breadcrumb: 'FIU-IND' }
}
])
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class FiuRoutingModule {}
+480
View File
@@ -0,0 +1,480 @@
<!--
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/.
-->
<div class="fiu-root">
<!-- HEADER -->
<div class="fiu-head">
<div class="fiu-head-left">
<fa-icon icon="shield-alt" class="fiu-head-ic"></fa-icon>
<div>
<h2 class="fiu-head-t">Financial Intelligence Unit – India (FIU-IND)</h2>
<div class="fiu-head-s">
{{ bank.name }} &middot; {{ bank.branch }} &middot; Reporting Month <strong>{{ bank.reportingMonth }}</strong>
&middot; PMLA 2002 / FINnet 2.0
</div>
</div>
</div>
<div class="fiu-head-meta">
<div>
<span class="fiu-meta-l">Principal Officer</span><span class="fiu-meta-v">{{ bank.principalOfficer }}</span>
</div>
<div>
<span class="fiu-meta-l">REBANK BUID</span><span class="fiu-meta-v mono">{{ bank.reBuid }}</span>
</div>
</div>
</div>
<!-- TABS -->
<div class="fiu-tabs">
<button class="fiu-tab" [class.active]="activeTab === 'dashboard'" (click)="showTab('dashboard')">
<fa-icon icon="chart-pie"></fa-icon> Dashboard
</button>
<button class="fiu-tab" [class.active]="activeTab === 'ctr'" (click)="showTab('ctr')">
<fa-icon icon="coins"></fa-icon> CTR — Cash Transactions
</button>
<button class="fiu-tab" [class.active]="activeTab === 'str'" (click)="showTab('str')">
<fa-icon icon="exclamation-triangle"></fa-icon> STR — Suspicious
</button>
<button class="fiu-tab" [class.active]="activeTab === 'customers'" (click)="showTab('customers')">
<fa-icon icon="users"></fa-icon> Customer Cards
</button>
<button class="fiu-tab" [class.active]="activeTab === 'files'" (click)="showTab('files')">
<fa-icon icon="file-csv"></fa-icon> FINnet Files
</button>
</div>
<!-- DASHBOARD -->
@if (activeTab === 'dashboard') {
<div class="fiu-section">
<div class="fiu-kpis">
<div class="fiu-kpi fiu-kpi--blue">
<div class="fiu-kpi-ic"><fa-icon icon="file-alt"></fa-icon></div>
<div>
<div class="fiu-kpi-n">{{ totalReports | number }}</div>
<div class="fiu-kpi-l">CTR Reports</div>
<div class="fiu-kpi-sub">{{ totalCtrTxns | number }} txns this cycle</div>
</div>
</div>
<div class="fiu-kpi fiu-kpi--teal">
<div class="fiu-kpi-ic"><fa-icon icon="coins"></fa-icon></div>
<div>
<div class="fiu-kpi-n">&#8377;{{ totalCtrAmount | number: '1.0-0' }}</div>
<div class="fiu-kpi-l">Total CTR Amount</div>
<div class="fiu-kpi-sub">aggregate cash movement</div>
</div>
</div>
<div class="fiu-kpi fiu-kpi--amber">
<div class="fiu-kpi-ic"><fa-icon icon="exclamation-triangle"></fa-icon></div>
<div>
<div class="fiu-kpi-n">{{ pendingStr | number }}</div>
<div class="fiu-kpi-l">STR Pending</div>
<div class="fiu-kpi-sub">draft / PO review</div>
</div>
</div>
<div class="fiu-kpi fiu-kpi--green">
<div class="fiu-kpi-ic"><fa-icon icon="check-circle"></fa-icon></div>
<div>
<div class="fiu-kpi-n">{{ filedStr | number }}</div>
<div class="fiu-kpi-l">STR Filed</div>
<div class="fiu-kpi-sub">acknowledged by FIU</div>
</div>
</div>
<div class="fiu-kpi fiu-kpi--purple">
<div class="fiu-kpi-ic"><fa-icon icon="users"></fa-icon></div>
<div>
<div class="fiu-kpi-n">{{ totalCustomers | number }}</div>
<div class="fiu-kpi-l">Customers in Cycle</div>
<div class="fiu-kpi-sub">{{ kc1.length }} ind &middot; {{ kc2.length }} entity</div>
</div>
</div>
</div>
<!-- Compliance cards -->
<div class="fiu-compliance">
<div class="dc-card">
<div class="dc-card-h">
<fa-icon icon="check-circle" class="dc-ok"></fa-icon><span>CTR — Cash Transaction Report</span>
</div>
<div class="dc-meta">
Threshold: <strong>&#8377;{{ bank.cashThreshold | number }}</strong> per single txn / aggregate per month
</div>
<div class="dc-meta">Due: <strong>15 Feb 2026</strong></div>
<button class="dc-btn" (click)="showTab('ctr')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
</div>
<div class="dc-card">
<div class="dc-card-h">
<fa-icon icon="exclamation-triangle" class="dc-warn"></fa-icon><span>STR — Suspicious Transaction</span>
</div>
<div class="dc-meta">Filing window: <strong>within 7 days of detection</strong></div>
<div class="dc-meta">
Pending: <strong>{{ pendingStr }}</strong> &middot; Filed: <strong>{{ filedStr }}</strong>
</div>
<button class="dc-btn" (click)="showTab('str')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
</div>
<div class="dc-card">
<div class="dc-card-h"><fa-icon icon="file-csv" class="dc-info"></fa-icon><span>FINnet 2.0 Files</span></div>
<div class="dc-meta">6 CSVs: TC1 / TC2 / KC1 / KC2 / Account_Detail / Account_Person_Relation</div>
<div class="dc-meta">+ STR XML for suspicious filings</div>
<button class="dc-btn" (click)="showTab('files')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
</div>
</div>
</div>
}
<!-- CTR -->
@if (activeTab === 'ctr') {
<div class="fiu-section">
<div class="fiu-form-meta">
<div class="fiu-form-title">
<h3>CTR — Cash Transaction Report</h3>
<p>
All cash transactions ≥ &#8377;{{ bank.cashThreshold | number }} (single or aggregate per month per account)
</p>
</div>
<button class="fiu-btn fiu-btn-csv" (click)="exportTc2()">
<fa-icon icon="file-csv"></fa-icon> Download TC2.csv
</button>
</div>
<div class="fiu-filters">
<div class="fiu-filter">
<label>Type</label>
<select class="fiu-inp" [(ngModel)]="ctrFilterMode">
<option value="">All</option>
<option value="Deposit">Deposit</option>
<option value="Withdrawal">Withdrawal</option>
</select>
</div>
<div class="fiu-filter">
<label>Account contains</label>
<input class="fiu-inp" type="text" [(ngModel)]="ctrFilterAccount" placeholder="A/c partial" />
</div>
<div class="fiu-filter">
<label>Min Amount (&#8377;)</label>
<input class="fiu-inp" type="number" [(ngModel)]="ctrFilterMinAmount" placeholder="0" />
</div>
<button class="fiu-btn fiu-btn-g" (click)="resetCtrFilters()">Reset</button>
</div>
<div class="fiu-tbl-wrap">
<table class="fiu-tbl">
<thead>
<tr>
<th class="ta-c">Ref</th>
<th>Account</th>
<th>Customer</th>
<th class="ta-c">Type</th>
<th>Date / Time</th>
<th class="ta-r">Amount</th>
<th>Instrument</th>
<th>Branch</th>
<th class="ta-c">Relationship</th>
</tr>
</thead>
<tbody>
@for (t of filteredCtr; track t.txnId) {
<tr>
<td class="ta-c">{{ t.reportRefNo }}</td>
<td class="mono">{{ t.accountNumber }}</td>
<td>{{ t.customerName }}</td>
<td class="ta-c">
<span
class="dir-pill"
[class.deposit]="t.depositOrWithdrawal === 'Deposit'"
[class.withdrawal]="t.depositOrWithdrawal === 'Withdrawal'"
>
{{ t.depositOrWithdrawal }}
</span>
</td>
<td>
{{ t.txnDate }} <span class="muted">{{ t.txnTime }}</span>
</td>
<td class="ta-r mono">
<strong>&#8377;{{ t.txnAmount | number: '1.0-0' }}</strong>
</td>
<td>
{{ t.instrumentType }} <span class="muted">{{ t.instrumentId }}</span>
</td>
<td class="mono">{{ t.branchCode }}</td>
<td class="ta-c">
<span class="rel-pill" [class.non-cust]="t.relationshipFlag === 'Non-Customer'">{{
t.relationshipFlag
}}</span>
</td>
</tr>
} @empty {
<tr>
<td colspan="9" class="fiu-empty">No transactions match the filters.</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
<!-- STR -->
@if (activeTab === 'str') {
<div class="fiu-section">
<div class="fiu-form-meta">
<div class="fiu-form-title">
<h3>STR — Suspicious Transaction Report</h3>
<p>Detected by AML rules engine + branch compliance officers; filed within 7 days</p>
</div>
<button class="fiu-btn fiu-btn-xml" (click)="exportStrXml()">
<fa-icon icon="file-alt"></fa-icon> Download STR XML
</button>
</div>
<div class="fiu-filters">
<div class="fiu-filter">
<label>Status</label>
<select class="fiu-inp" [(ngModel)]="strFilterStatus">
<option value="">All</option>
<option value="draft">Draft</option>
<option value="po-review">PO Review</option>
<option value="filed">Filed</option>
<option value="rejected">Rejected</option>
</select>
</div>
<div class="fiu-filter">
<label>Risk Category</label>
<select class="fiu-inp" [(ngModel)]="strFilterRisk">
<option value="">All</option>
<option value="structuring">Structuring</option>
<option value="dormant-revival">Dormant Revival</option>
<option value="high-risk-country">High-Risk Country</option>
<option value="unusual-pattern">Unusual Pattern</option>
<option value="pep-related">PEP Related</option>
<option value="sanctions-hit">Sanctions Hit</option>
</select>
</div>
<button class="fiu-btn fiu-btn-g" (click)="resetStrFilters()">Reset</button>
</div>
<div class="fiu-str-list">
@for (s of filteredStr; track s.reportRefNo) {
<div class="str-card" [class.str-card--filed]="s.status === 'filed'">
<div class="str-card-h">
<div class="str-card-id">
<span class="str-ref">STR-{{ s.reportRefNo }}</span>
<span class="status-pill" [class]="'st-' + s.status">{{ statusLabel(s.status) }}</span>
<span class="risk-pill" [class]="'rk-' + s.riskCategory">{{ riskLabel(s.riskCategory) }}</span>
</div>
<div class="str-card-amt">
<span class="muted">{{ s.depositOrWithdrawal }}</span>
<strong>&#8377;{{ s.txnAmount | number: '1.0-0' }}</strong>
</div>
</div>
<div class="str-card-body">
<div class="str-meta">
<div>
<label>Customer</label><span>{{ s.customerName }}</span>
</div>
<div>
<label>Account</label><span class="mono">{{ s.accountNumber }}</span>
</div>
<div>
<label>Txn Date</label><span>{{ s.txnDate }} {{ s.txnTime }}</span>
</div>
<div>
<label>Detected</label><span>{{ s.detectedOn }}</span>
</div>
<div>
<label>Flagged By</label><span>{{ s.flaggedBy }}</span>
</div>
<div>
<label>Instrument</label><span>{{ s.instrumentType }} {{ s.instrumentId }}</span>
</div>
</div>
<div class="str-narrative">
<label>Narrative</label>
<p>{{ s.narrative }}</p>
</div>
@if (s.poRemarks) {
<div class="str-po-remarks">
<label><fa-icon icon="check-circle"></fa-icon> PO Remarks</label>
<p>{{ s.poRemarks }}</p>
</div>
}
</div>
</div>
} @empty {
<div class="fiu-empty-card">No STR records match the filters.</div>
}
</div>
</div>
}
<!-- CUSTOMER CARDS -->
@if (activeTab === 'customers') {
<div class="fiu-section">
<div class="fiu-form-meta">
<div class="fiu-form-title">
<h3>Customer Cards — KC1 (Individuals) &amp; KC2 (Entities)</h3>
<p>Customers attached to reports filed in this cycle</p>
</div>
<div class="fiu-action-group">
<button class="fiu-btn fiu-btn-csv" (click)="exportKc1()"><fa-icon icon="file-csv"></fa-icon> KC1.csv</button>
<button class="fiu-btn fiu-btn-csv" (click)="exportKc2()"><fa-icon icon="file-csv"></fa-icon> KC2.csv</button>
</div>
</div>
<div class="fiu-tbl-wrap">
<div class="fiu-tbl-h">KC1 — Individuals</div>
<table class="fiu-tbl">
<thead>
<tr>
<th class="ta-c">Ref</th>
<th>UCIC</th>
<th>Name</th>
<th class="ta-c">Gender</th>
<th>DOB</th>
<th>PAN</th>
<th>Mobile</th>
<th>City</th>
<th class="ta-r">Annual Income</th>
<th class="ta-c">Risk</th>
</tr>
</thead>
<tbody>
@for (k of kc1; track k.ucic) {
<tr>
<td class="ta-c">{{ k.reportRefNo }}</td>
<td class="mono">{{ k.ucic }}</td>
<td>{{ k.firstName }} {{ k.middleName }} {{ k.lastName }}</td>
<td class="ta-c">{{ k.gender }}</td>
<td>{{ k.dob }}</td>
<td class="mono">{{ k.pan }}</td>
<td class="mono">{{ k.mobileNumber }}</td>
<td>{{ k.city }}, {{ k.state }}</td>
<td class="ta-r mono">&#8377;{{ k.annualIncome | number }}</td>
<td class="ta-c">
<span class="risk-tag" [class]="'rk-tag-' + k.riskLevel.toLowerCase()">{{ k.riskLevel }}</span>
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="fiu-tbl-wrap">
<div class="fiu-tbl-h">KC2 — Entities</div>
<table class="fiu-tbl">
<thead>
<tr>
<th class="ta-c">Ref</th>
<th>UCIC</th>
<th>Entity Name</th>
<th>PAN</th>
<th>GSTIN</th>
<th>City</th>
<th>Type</th>
<th>Owner (if Sole Prop)</th>
<th class="ta-c">Risk</th>
</tr>
</thead>
<tbody>
@for (k of kc2; track k.ucic) {
<tr>
<td class="ta-c">{{ k.reportRefNo }}</td>
<td class="mono">{{ k.ucic }}</td>
<td>{{ k.entityName }}</td>
<td class="mono">{{ k.panNumber }}</td>
<td class="mono">{{ k.gstin }}</td>
<td>{{ k.city }}, {{ k.state }}</td>
<td>{{ k.customerType }}</td>
<td>{{ k.isSoleProprietor ? k.ownerName : '—' }}</td>
<td class="ta-c">
<span class="risk-tag" [class]="'rk-tag-' + k.riskLevel.toLowerCase()">{{ k.riskLevel }}</span>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
<!-- FINNET FILES -->
@if (activeTab === 'files') {
<div class="fiu-section">
<div class="fiu-form-meta">
<div class="fiu-form-title">
<h3>FINnet 2.0 Submission Files</h3>
<p>6 CSV files in FIU-IND prescribed schema, plus STR XML</p>
</div>
<button class="fiu-btn fiu-btn-xls" (click)="exportAll()">
<fa-icon icon="file-csv"></fa-icon> Download All
</button>
</div>
<div class="files-grid">
<div class="file-card">
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>TC1.csv</div>
<p>ATM / card transaction template (header only — no card txns this cycle)</p>
<button class="fiu-btn" (click)="exportTc1()"><fa-icon icon="file-csv"></fa-icon> Download</button>
</div>
<div class="file-card">
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>TC2.csv</div>
<p>Branch / cash transactions ({{ ctrTxns.length }} rows)</p>
<button class="fiu-btn fiu-btn-csv" (click)="exportTc2()">
<fa-icon icon="file-csv"></fa-icon> Download
</button>
</div>
<div class="file-card">
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>KC1.csv</div>
<p>Individual customer cards ({{ kc1.length }} rows)</p>
<button class="fiu-btn fiu-btn-csv" (click)="exportKc1()">
<fa-icon icon="file-csv"></fa-icon> Download
</button>
</div>
<div class="file-card">
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>KC2.csv</div>
<p>Entity customer cards ({{ kc2.length }} rows)</p>
<button class="fiu-btn fiu-btn-csv" (click)="exportKc2()">
<fa-icon icon="file-csv"></fa-icon> Download
</button>
</div>
<div class="file-card">
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>Account_Detail.csv</div>
<p>Per-account 12-month aggregates derived from CTR list</p>
<button class="fiu-btn fiu-btn-csv" (click)="exportAccountDetail()">
<fa-icon icon="file-csv"></fa-icon> Download
</button>
</div>
<div class="file-card">
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>Account_Person_Relation.csv</div>
<p>Account ↔ customer relationship mapping</p>
<button class="fiu-btn fiu-btn-csv" (click)="exportAccountPersonRelation()">
<fa-icon icon="file-csv"></fa-icon> Download
</button>
</div>
<div class="file-card file-card--xml">
<div class="file-card-h"><fa-icon icon="file-alt"></fa-icon>STR.xml</div>
<p>Suspicious Transaction Report ({{ filedStr + pendingStr }} suspects)</p>
<button class="fiu-btn fiu-btn-xml" (click)="exportStrXml()">
<fa-icon icon="file-alt"></fa-icon> Download XML
</button>
</div>
</div>
<div class="fiu-note">
<fa-icon icon="info-circle"></fa-icon>
<span
><strong>Note:</strong> Files are emitted in FIU-IND FINnet 2.0 CSV schema. Submit via the FINnet portal under
{{ bank.principalOfficer }}'s digital signature. CTR is due by the 15th of the following month; STR within 7
days of detection.</span
>
</div>
</div>
}
<!-- TOAST -->
<div class="fiu-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
</div>
+894
View File
@@ -0,0 +1,894 @@
/**
* 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/.
*/
:host {
--f-navy: #0b1e3d;
--f-blue: #1d4ed8;
--f-blue-l: #eff6ff;
--f-teal: #0e7e6b;
--f-teal-l: #e0f4f0;
--f-green: #15803d;
--f-green-l: #dcfce7;
--f-amber: #d97706;
--f-amber-l: #fef3c7;
--f-red: #c0392b;
--f-red-l: #fdecea;
--f-purple: #6d28d9;
--f-purple-l: #ede9fe;
--f-g50: #f8fafc;
--f-g100: #f1f5f9;
--f-g200: #e2e8f0;
--f-g300: #cbd5e1;
--f-g500: #64748b;
--f-g700: #334155;
--f-g900: #0f172a;
display: block;
font-family:
'DM Sans',
system-ui,
-apple-system,
sans-serif;
background: var(--f-g50);
color: var(--f-g900);
font-size: 14px;
line-height: 1.5;
}
.fiu-root {
padding: 18px 22px;
max-width: 1500px;
margin: 0 auto;
}
.fiu-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.fiu-head-left {
display: flex;
align-items: center;
gap: 14px;
}
.fiu-head-ic {
font-size: 28px;
color: var(--f-red);
}
.fiu-head-t {
margin: 0;
font-size: 18px;
font-weight: 700;
color: var(--f-g900);
}
.fiu-head-s {
font-size: 12px;
color: var(--f-g500);
}
.fiu-head-meta {
display: flex;
gap: 22px;
> div {
display: flex;
flex-direction: column;
.fiu-meta-l {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--f-g500);
}
.fiu-meta-v {
font-size: 14px;
font-weight: 600;
color: var(--f-g900);
}
}
}
/* TABS */
.fiu-tabs {
display: flex;
gap: 4px;
background: #fff;
border: 1px solid var(--f-g200);
border-radius: 10px;
padding: 4px;
margin-bottom: 16px;
overflow-x: auto;
}
.fiu-tab {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 9px 14px;
border: none;
background: transparent;
font-size: 13px;
font-weight: 500;
color: var(--f-g500);
cursor: pointer;
border-radius: 7px;
white-space: nowrap;
transition: all 0.15s;
font-family: inherit;
&:hover {
color: var(--f-g900);
}
&.active {
background: var(--f-red);
color: #fff;
box-shadow: 0 2px 6px rgb(192 57 43 / 25%);
}
}
.fiu-section {
display: flex;
flex-direction: column;
gap: 16px;
}
/* KPIs */
.fiu-kpis {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
}
.fiu-kpi {
background: #fff;
border: 1px solid var(--f-g200);
border-radius: 12px;
padding: 14px 16px;
display: flex;
align-items: center;
gap: 12px;
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 14px rgb(0 0 0 / 10%);
}
}
.fiu-kpi-ic {
width: 42px;
height: 42px;
border-radius: 11px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.fiu-kpi--blue .fiu-kpi-ic {
background: var(--f-blue-l);
color: var(--f-blue);
}
.fiu-kpi--teal .fiu-kpi-ic {
background: var(--f-teal-l);
color: var(--f-teal);
}
.fiu-kpi--amber .fiu-kpi-ic {
background: var(--f-amber-l);
color: var(--f-amber);
}
.fiu-kpi--green .fiu-kpi-ic {
background: var(--f-green-l);
color: var(--f-green);
}
.fiu-kpi--purple .fiu-kpi-ic {
background: var(--f-purple-l);
color: var(--f-purple);
}
.fiu-kpi-n {
font-size: 18px;
font-weight: 700;
color: var(--f-g900);
line-height: 1.1;
}
.fiu-kpi-l {
font-size: 12px;
color: var(--f-g700);
font-weight: 500;
margin-top: 2px;
}
.fiu-kpi-sub {
font-size: 11px;
color: var(--f-g500);
margin-top: 1px;
}
/* COMPLIANCE CARDS */
.fiu-compliance {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
}
.dc-card {
background: #fff;
border: 1px solid var(--f-g200);
border-radius: 12px;
padding: 14px 18px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.dc-card-h {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 600;
color: var(--f-g900);
margin-bottom: 8px;
}
.dc-ok {
color: var(--f-green);
}
.dc-warn {
color: var(--f-amber);
}
.dc-info {
color: var(--f-blue);
}
.dc-meta {
font-size: 12px;
color: var(--f-g500);
margin: 2px 0;
strong {
color: var(--f-g900);
font-weight: 600;
}
}
.dc-btn {
margin-top: 10px;
padding: 6px 12px;
border: 1px solid var(--f-red);
background: var(--f-red-l);
color: var(--f-red);
border-radius: 8px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
font-family: inherit;
display: inline-flex;
align-items: center;
gap: 6px;
&:hover {
background: var(--f-red);
color: #fff;
}
}
/* FORM HEADER */
.fiu-form-meta {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 14px;
flex-wrap: wrap;
}
.fiu-form-title {
h3 {
margin: 0;
font-size: 16px;
font-weight: 700;
color: var(--f-g900);
}
p {
margin: 2px 0 0;
font-size: 12px;
color: var(--f-g500);
}
}
.fiu-action-group {
display: flex;
gap: 8px;
}
/* BUTTONS */
.fiu-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: 1px solid var(--f-g300);
background: #fff;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
color: var(--f-g700);
cursor: pointer;
font-family: inherit;
transition: all 0.15s;
&:hover {
background: var(--f-g50);
border-color: var(--f-g500);
}
}
.fiu-btn-csv {
background: #1d4ed8;
border-color: #1d4ed8;
color: #fff;
&:hover {
background: #1e40af;
border-color: #1e40af;
color: #fff;
}
}
.fiu-btn-xls {
background: #16a34a;
border-color: #16a34a;
color: #fff;
&:hover {
background: #15803d;
border-color: #15803d;
color: #fff;
}
}
.fiu-btn-xml {
background: var(--f-purple);
border-color: var(--f-purple);
color: #fff;
&:hover {
background: #5b21b6;
border-color: #5b21b6;
color: #fff;
}
}
.fiu-btn-g {
align-self: end;
}
/* INPUTS */
.fiu-inp {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--f-g200);
border-radius: 8px;
font-size: 13px;
background: #fff;
font-family: inherit;
&:focus {
outline: none;
border-color: var(--f-red);
box-shadow: 0 0 0 3px rgb(192 57 43 / 15%);
}
}
/* FILTERS */
.fiu-filters {
background: #fff;
border: 1px solid var(--f-g200);
border-radius: 12px;
padding: 14px;
display: grid;
grid-template-columns: 1fr 1fr 1fr auto;
gap: 12px;
align-items: end;
}
.fiu-filter {
display: flex;
flex-direction: column;
gap: 4px;
label {
font-size: 11px;
font-weight: 600;
color: var(--f-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
}
/* TABLE */
.fiu-tbl-wrap {
background: #fff;
border: 1px solid var(--f-g200);
border-radius: 12px;
overflow: auto;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.fiu-tbl-h {
background: var(--f-blue-l);
color: var(--f-blue);
padding: 10px 16px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid var(--f-g200);
}
.fiu-tbl {
width: 100%;
border-collapse: collapse;
th {
text-align: left;
padding: 10px 12px;
font-size: 11px;
font-weight: 600;
color: var(--f-g500);
text-transform: uppercase;
letter-spacing: 0.04em;
background: var(--f-g50);
border-bottom: 1px solid var(--f-g200);
}
td {
padding: 10px 12px;
border-bottom: 1px solid var(--f-g100);
font-size: 13px;
color: var(--f-g700);
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: var(--f-g50);
}
}
.ta-c {
text-align: center;
}
.ta-r {
text-align: right;
}
.mono {
font-family: 'DM Mono', monospace;
font-size: 12px;
letter-spacing: 0.02em;
}
.muted {
color: var(--f-g500);
font-size: 11px;
}
.fiu-empty {
text-align: center;
padding: 30px;
color: var(--f-g500);
font-style: italic;
}
.fiu-empty-card {
background: #fff;
border: 1px dashed var(--f-g300);
border-radius: 12px;
padding: 50px;
text-align: center;
color: var(--f-g500);
font-size: 13px;
}
/* PILLS */
.dir-pill {
display: inline-block;
padding: 2px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
background: var(--f-g100);
color: var(--f-g700);
&.deposit {
background: var(--f-green-l);
color: var(--f-green);
}
&.withdrawal {
background: var(--f-red-l);
color: var(--f-red);
}
}
.rel-pill {
display: inline-block;
padding: 2px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
background: var(--f-blue-l);
color: var(--f-blue);
&.non-cust {
background: var(--f-amber-l);
color: var(--f-amber);
}
}
.status-pill {
display: inline-block;
padding: 2px 10px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
&.st-draft {
background: var(--f-g100);
color: var(--f-g700);
}
&.st-po-review {
background: var(--f-amber-l);
color: var(--f-amber);
}
&.st-filed {
background: var(--f-green-l);
color: var(--f-green);
}
&.st-rejected {
background: var(--f-red-l);
color: var(--f-red);
}
}
.risk-pill {
display: inline-block;
padding: 2px 10px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
background: var(--f-purple-l);
color: var(--f-purple);
&.rk-structuring {
background: #fff7ed;
color: #9a3412;
}
&.rk-dormant-revival {
background: #ecfeff;
color: #155e75;
}
&.rk-high-risk-country {
background: var(--f-red-l);
color: var(--f-red);
}
&.rk-unusual-pattern {
background: var(--f-amber-l);
color: var(--f-amber);
}
&.rk-pep-related {
background: var(--f-purple-l);
color: var(--f-purple);
}
&.rk-sanctions-hit {
background: #fee2e2;
color: #991b1b;
}
}
.risk-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 6px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
&.rk-tag-low {
background: var(--f-green-l);
color: var(--f-green);
}
&.rk-tag-medium {
background: var(--f-amber-l);
color: var(--f-amber);
}
&.rk-tag-high {
background: var(--f-red-l);
color: var(--f-red);
}
}
/* STR cards */
.fiu-str-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.str-card {
background: #fff;
border: 1px solid var(--f-g200);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
&--filed {
border-color: #86efac;
}
}
.str-card-h {
padding: 12px 16px;
background: var(--f-g50);
border-bottom: 1px solid var(--f-g200);
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.str-card-id {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.str-ref {
font-family: 'DM Mono', monospace;
font-size: 13px;
font-weight: 700;
color: var(--f-g900);
}
.str-card-amt {
display: flex;
align-items: baseline;
gap: 6px;
strong {
font-size: 16px;
color: var(--f-g900);
font-family: 'DM Mono', monospace;
}
}
.str-card-body {
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.str-meta {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px 18px;
> div {
display: flex;
flex-direction: column;
label {
font-size: 10px;
font-weight: 600;
color: var(--f-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 1px;
}
span {
font-size: 13px;
color: var(--f-g900);
}
}
}
.str-narrative,
.str-po-remarks {
background: var(--f-g50);
border-radius: 8px;
padding: 10px 14px;
label {
font-size: 10px;
font-weight: 700;
color: var(--f-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
display: flex;
align-items: center;
gap: 5px;
margin-bottom: 4px;
}
p {
margin: 0;
font-size: 13px;
color: var(--f-g700);
line-height: 1.5;
}
}
.str-po-remarks {
background: var(--f-green-l);
label {
color: var(--f-green);
}
p {
color: #14532d;
}
}
/* FILES TAB */
.files-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.file-card {
background: #fff;
border: 1px solid var(--f-g200);
border-radius: 12px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 8px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
&--xml {
border-color: var(--f-purple);
background: linear-gradient(135deg, #fff 0%, #faf5ff 100%);
}
}
.file-card-h {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 700;
color: var(--f-g900);
font-family: 'DM Mono', monospace;
}
.file-card p {
margin: 0;
font-size: 12px;
color: var(--f-g500);
flex: 1;
}
.file-card .fiu-btn {
align-self: flex-start;
}
/* NOTE */
.fiu-note {
background: var(--f-blue-l);
border: 1px solid #bfdbfe;
border-radius: 10px;
padding: 12px 16px;
display: flex;
gap: 10px;
font-size: 12px;
color: #1e3a8a;
line-height: 1.5;
fa-icon {
font-size: 14px;
flex-shrink: 0;
margin-top: 2px;
}
}
/* TOAST */
.fiu-toast {
position: fixed;
bottom: 32px;
left: 50%;
transform: translateX(-50%) translateY(20px);
background: var(--f-navy);
color: #fff;
padding: 12px 22px;
border-radius: 10px;
font-size: 13px;
font-weight: 500;
box-shadow: 0 6px 20px rgb(0 0 0 / 25%);
opacity: 0;
pointer-events: none;
transition: all 0.25s;
z-index: 1500;
&.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
@media (width <= 1100px) {
.fiu-kpis {
grid-template-columns: repeat(3, 1fr);
}
.fiu-compliance {
grid-template-columns: 1fr;
}
.fiu-filters {
grid-template-columns: 1fr 1fr;
}
.files-grid {
grid-template-columns: 1fr 1fr;
}
.str-meta {
grid-template-columns: 1fr 1fr;
}
}
@media (width <= 720px) {
.fiu-kpis {
grid-template-columns: 1fr 1fr;
}
.files-grid {
grid-template-columns: 1fr;
}
.str-meta {
grid-template-columns: 1fr;
}
}
+547
View File
@@ -0,0 +1,547 @@
/**
* 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/.
*/
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import {
CtrTransaction,
CTR_TXNS,
FIU_BANK,
KC1_INDIVIDUALS,
KC2_ENTITIES,
KycEntity,
KycIndividual,
StrStatus,
StrTransaction,
STR_TXNS
} from './fiu-data';
@Component({
selector: 'mifosx-fiu',
templateUrl: './fiu.component.html',
styleUrls: ['./fiu.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
FormsModule
]
})
export class FiuComponent {
/** Active tab */
activeTab: 'dashboard' | 'ctr' | 'str' | 'customers' | 'files' = 'dashboard';
bank = FIU_BANK;
ctrTxns: CtrTransaction[] = CTR_TXNS;
strTxns: StrTransaction[] = STR_TXNS;
kc1: KycIndividual[] = KC1_INDIVIDUALS;
kc2: KycEntity[] = KC2_ENTITIES;
/** CTR filters */
ctrFilterMode: '' | 'Deposit' | 'Withdrawal' = '';
ctrFilterAccount = '';
ctrFilterMinAmount = 0;
/** STR filters */
strFilterStatus: '' | StrStatus = '';
strFilterRisk: string = '';
/** Toast */
toastMsg = '';
toastVisible = false;
private toastTimer: any;
showTab(tab: 'dashboard' | 'ctr' | 'str' | 'customers' | 'files'): void {
this.activeTab = tab;
}
// -------------------- KPIs --------------------
get totalReports(): number {
return new Set(this.ctrTxns.map((t) => t.reportRefNo)).size;
}
get totalCtrTxns(): number {
return this.ctrTxns.length;
}
get totalCtrAmount(): number {
return this.ctrTxns.reduce((s, t) => s + t.txnAmount, 0);
}
get pendingStr(): number {
return this.strTxns.filter((s) => s.status === 'draft' || s.status === 'po-review').length;
}
get filedStr(): number {
return this.strTxns.filter((s) => s.status === 'filed').length;
}
get totalCustomers(): number {
return this.kc1.length + this.kc2.length;
}
// -------------------- Filtered lists --------------------
get filteredCtr(): CtrTransaction[] {
const acc = this.ctrFilterAccount.trim();
return this.ctrTxns.filter((t) => {
if (this.ctrFilterMode && t.depositOrWithdrawal !== this.ctrFilterMode) return false;
if (acc && !t.accountNumber.includes(acc)) return false;
if (this.ctrFilterMinAmount > 0 && t.txnAmount < this.ctrFilterMinAmount) return false;
return true;
});
}
get filteredStr(): StrTransaction[] {
return this.strTxns.filter((s) => {
if (this.strFilterStatus && s.status !== this.strFilterStatus) return false;
if (this.strFilterRisk && s.riskCategory !== this.strFilterRisk) return false;
return true;
});
}
resetCtrFilters(): void {
this.ctrFilterMode = '';
this.ctrFilterAccount = '';
this.ctrFilterMinAmount = 0;
}
resetStrFilters(): void {
this.strFilterStatus = '';
this.strFilterRisk = '';
}
// -------------------- Helpers --------------------
statusLabel(s: StrStatus): string {
return s === 'draft' ? 'Draft' : s === 'po-review' ? 'PO Review' : s === 'filed' ? 'Filed' : 'Rejected';
}
riskLabel(c: StrTransaction['riskCategory']): string {
const map: Record<StrTransaction['riskCategory'], string> = {
structuring: 'Structuring',
'dormant-revival': 'Dormant Revival',
'high-risk-country': 'High-Risk Country',
'unusual-pattern': 'Unusual Pattern',
'pep-related': 'PEP Related',
'sanctions-hit': 'Sanctions Hit'
};
return map[c];
}
// -------------------- Exports --------------------
/** TC2 — Branch / cash transaction CSV */
exportTc2(): void {
const headers = [
'Report Type',
'Report Reference Number',
'Branch Code of Account',
'Account Number',
'Transaction Branch Code',
'Deposit/Withdrawal',
'Transaction ID',
'Transaction Date',
'Transaction Time',
'Transaction Amount',
'Instrument Type',
'Instrument ID (Card Number/ Cash/ Cheque/ DD/ Others)',
'Non-Customer Reference Number',
'Relationship Flag',
'Third Party PAN (Non-Customer)',
'Declaration (If Third Party PAN (Non-Customer) is not available)'
];
const rows = [headers];
for (const t of this.filteredCtr) {
rows.push([
t.reportType,
t.reportRefNo.toString(),
t.branchCode,
t.accountNumber,
t.txnBranchCode,
t.depositOrWithdrawal,
t.txnId.toString(),
t.txnDate.replace(/-/g, '/'),
t.txnTime,
t.txnAmount.toString(),
t.instrumentType,
t.instrumentId,
t.nonCustomerRef,
t.relationshipFlag,
t.thirdPartyPan,
t.declaration
]);
}
this.saveCsv(rows, 'TC2.csv');
this.toast(`TC2 exported (${this.filteredCtr.length} rows)`);
}
/** TC1 header-only template (UI doesn't track ATM/card txns) */
exportTc1(): void {
const headers = [
'Relationship Flag',
'Transaction Status',
'Transaction ID',
'Transaction Date',
'Transaction Time',
'Deposit/Withdrawal',
'Transaction Amount',
'Account Number',
'Account Type',
'Card Type',
'Card Number',
'Card Issuing Country',
'Card Issuing Bank',
'Report Reference Number',
'Switch Provider Transaction ID',
'ATM ID/ CAM ID',
'Branch Code of Account',
'Report Type'
];
this.saveCsv([headers], 'TC1.csv');
this.toast('TC1 template exported (header only)');
}
/** KC1 — Individual customers */
exportKc1(): void {
const headers = [
'Report Reference Number',
'UCIC',
'Customer ID',
'PAN',
'Declaration (If PAN is not available)',
'CKYC Number',
'Declaration (If CKYC is not available)',
'Passport Number',
'Voter ID',
'Other Occupation',
'Drivers License Number',
'NREGA Card',
'DIN / DPIN',
'First Name',
'Middle Name',
'Last Name',
'Declaration (If Last name is not available)',
'Name of Father',
'Name of Mother',
'Spouse/Partner Name',
'Gender',
'Date of Birth',
'Nationality',
'Mobile Number',
'Alternate Mobile Number',
'Telephone Number',
'Email ID',
'Primary Address 1',
'Primary Address Country',
'Primary Address Pin Code',
'Primary Address Locality',
'Primary Address State',
'Primary Address District',
'Primary Address City',
'Customer Type',
'Annual Income (INR)',
'Occupation',
'Date of Customer On-boarding',
'Date of Last KYC / re-KYC',
'Customer Risk Level',
'Report Type',
'Other Identifier Name',
'Other Identifier Number',
'Primary Entity'
];
const rows = [headers];
for (const k of this.kc1) {
rows.push([
k.reportRefNo.toString(),
k.ucic,
k.ucic,
k.pan,
k.pan ? '' : 'NA',
k.ckycNumber,
k.ckycNumber ? '' : 'NA',
'',
'',
'',
'',
'',
'',
k.firstName,
k.middleName,
k.lastName,
'',
k.fatherName,
k.motherName,
'',
k.gender,
k.dob.replace(/-/g, '/'),
'97',
k.mobileNumber,
'',
'',
k.email,
k.primaryAddress,
'95',
k.pincode,
k.primaryAddress.split(' ').slice(0, 4).join(' '),
k.state,
'501',
k.city,
k.customerType,
k.annualIncome.toString(),
k.occupation,
k.onboardingDate.replace(/-/g, '/'),
k.lastKycDate.replace(/-/g, '/'),
k.riskLevel,
k.reportType,
k.otherIdentifierName || '',
k.otherIdentifierNumber || '',
'Yes'
]);
}
this.saveCsv(rows, 'KC1.csv');
this.toast(`KC1 exported (${this.kc1.length} individuals)`);
}
/** KC2 — Entity customers */
exportKc2(): void {
const headers = [
'Report Reference Number',
'UCIC',
'Entity Name',
'Company ID Number',
'Company ID Type',
'PAN Number',
'Declaration (If PAN is not available)',
'TAN',
'GSTIN',
'IEC',
'Declaration (If IEC is not available)',
'No identifier available',
'Telephone Number',
'Mobile Number',
'Email ID',
'Company Website',
'Country',
'Address Line 1',
'PIN Code',
'Locality',
'State',
'District',
'City / Village / Town',
'Date Of Incorporation',
'Customer Type',
'Last KYC Date',
'Onboarding Date',
'Customer Risk Level',
'Report Type',
'Sole Proprietor Firm',
'UCIC of Owner of Sole Proprietor Firm',
'Name of Owner of Sole Proprietor Firm'
];
const rows = [headers];
for (const k of this.kc2) {
rows.push([
k.reportRefNo.toString(),
k.ucic,
k.entityName,
'',
'',
k.panNumber,
'',
'',
k.gstin,
'',
'NA',
'',
'',
k.mobileNumber,
'',
'',
'95',
k.address,
k.pincode,
k.address.split(' ').slice(0, 4).join(' '),
k.state,
'501',
k.city,
k.dateOfIncorporation.replace(/-/g, '/'),
k.customerType,
k.lastKycDate.replace(/-/g, '/'),
k.onboardingDate.replace(/-/g, '/'),
k.riskLevel,
k.reportType,
k.isSoleProprietor ? 'Yes' : 'No',
k.ownerUcic,
k.ownerName
]);
}
this.saveCsv(rows, 'KC2.csv');
this.toast(`KC2 exported (${this.kc2.length} entities)`);
}
/** Account_Detail.csv — per-report account aggregates (12-month) */
exportAccountDetail(): void {
const headers = [
'Report Type',
'Report Reference Number',
'Account Type',
'Account Number',
'Branch Code of Account',
'Date of Account Opening',
'Date of Account Closing',
'Account Status',
'Reason for Account Freeze',
'No Of Debits (In last 12 months)',
'Total Debit Amount (In last 12 months)',
'No Of Credits (In last 12 months)',
'Total Credit Amount (In last 12 months)',
'No Of Cash Transaction (In last 12 months)',
'Total Cash Deposit (In last 12 months)',
'Total Cash Withdrawal (In last 12 months) Amount'
];
const rows = [headers];
// Build one aggregate row per (reportRefNo, accountNumber) pair
const seen = new Set<string>();
for (const t of this.ctrTxns) {
const key = `${t.reportRefNo}|${t.accountNumber}`;
if (seen.has(key)) continue;
seen.add(key);
const sameKey = this.ctrTxns.filter(
(x) => x.reportRefNo === t.reportRefNo && x.accountNumber === t.accountNumber
);
const debits = sameKey.filter((x) => x.depositOrWithdrawal === 'Withdrawal');
const credits = sameKey.filter((x) => x.depositOrWithdrawal === 'Deposit');
const cashTxns = sameKey.filter((x) => x.instrumentType === 'Cash');
rows.push([
t.reportType,
t.reportRefNo.toString(),
'212',
t.accountNumber,
t.branchCode,
'20/05/2018',
'',
'ACTIVE',
'',
debits.length.toString(),
debits.reduce((s, x) => s + x.txnAmount, 0).toString(),
credits.length.toString(),
credits.reduce((s, x) => s + x.txnAmount, 0).toString(),
cashTxns.length.toString(),
cashTxns
.filter((x) => x.depositOrWithdrawal === 'Deposit')
.reduce((s, x) => s + x.txnAmount, 0)
.toString(),
cashTxns
.filter((x) => x.depositOrWithdrawal === 'Withdrawal')
.reduce((s, x) => s + x.txnAmount, 0)
.toString()
]);
}
this.saveCsv(rows, 'Account_Detail.csv');
this.toast(`Account_Detail exported (${rows.length - 1} accounts)`);
}
/** Account_Person_Relation.csv — account ↔ customer mapping */
exportAccountPersonRelation(): void {
const headers = [
'Report Type',
'Report Reference Number',
'Branch Code of Account',
'Account Number',
'Relationship Type',
'Unique Reference Number',
'Individual / Non-individual',
'Name of Non-customer'
];
const rows = [headers];
const seen = new Set<string>();
for (const t of this.ctrTxns) {
const key = `${t.reportRefNo}|${t.accountNumber}`;
if (seen.has(key)) continue;
seen.add(key);
// Find matching KC1 by reportRefNo (best-effort dummy mapping)
const k = this.kc1.find((x) => x.reportRefNo === t.reportRefNo);
rows.push([
t.reportType,
t.reportRefNo.toString(),
t.branchCode,
t.accountNumber,
'1',
k ? k.ucic : '0',
'Individual',
t.relationshipFlag === 'Non-Customer' ? t.customerName || '' : ''
]);
}
this.saveCsv(rows, 'Account_Person_Relation.csv');
this.toast(`Account_Person_Relation exported (${rows.length - 1} rows)`);
}
/** STR XML stub (FINnet 2.0 STR XML) */
exportStrXml(): void {
const xml: string[] = [];
xml.push('<?xml version="1.0" encoding="UTF-8"?>');
xml.push(`<STR xmlns="http://fiuind.gov.in/finnet2/str" reportingMonth="${this.bank.reportingMonth}">`);
xml.push(` <REBank buid="${this.bank.reBuid}" name="${this.bank.name}" />`);
xml.push(` <PrincipalOfficer name="${this.bank.principalOfficer}" email="${this.bank.principalOfficerEmail}" />`);
for (const s of this.filteredStr) {
xml.push(` <Suspect ref="${s.reportRefNo}" status="${s.status}">`);
xml.push(` <Account>${s.accountNumber}</Account>`);
xml.push(
` <Transaction id="${s.txnId}" date="${s.txnDate}" time="${s.txnTime}" amount="${s.txnAmount}" type="${s.depositOrWithdrawal}" instrument="${s.instrumentType}" />`
);
xml.push(` <RiskCategory>${s.riskCategory}</RiskCategory>`);
xml.push(` <Detection date="${s.detectedOn}" by="${this.escapeXml(s.flaggedBy)}" />`);
xml.push(` <Narrative>${this.escapeXml(s.narrative)}</Narrative>`);
if (s.poRemarks) xml.push(` <PORemarks>${this.escapeXml(s.poRemarks)}</PORemarks>`);
xml.push(' </Suspect>');
}
xml.push('</STR>');
const blob = new Blob([xml.join('\n')], { type: 'application/xml;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `STR-${this.bank.reportingMonth.replace(/\s+/g, '-')}.xml`;
a.click();
URL.revokeObjectURL(url);
this.toast(`STR XML exported (${this.filteredStr.length} suspects)`);
}
/** Bundle: all 6 CSVs zipped — emitted as separate downloads sequentially */
exportAll(): void {
setTimeout(() => this.exportTc1(), 0);
setTimeout(() => this.exportTc2(), 200);
setTimeout(() => this.exportKc1(), 400);
setTimeout(() => this.exportKc2(), 600);
setTimeout(() => this.exportAccountDetail(), 800);
setTimeout(() => this.exportAccountPersonRelation(), 1000);
}
private escapeXml(s: string): string {
return (s || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}
private saveCsv(rows: string[][], filename: string): void {
const escape = (v: string): string => {
const s = (v || '').replace(/"/g, '""');
if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s}"`;
return s;
};
const csv = rows.map((r) => r.map(escape).join(',')).join('\n');
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
toast(msg: string): void {
this.toastMsg = msg;
this.toastVisible = true;
if (this.toastTimer) clearTimeout(this.toastTimer);
this.toastTimer = setTimeout(() => (this.toastVisible = false), 2800);
}
}
+22
View File
@@ -0,0 +1,22 @@
/**
* 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/.
*/
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { FiuRoutingModule } from './fiu-routing.module';
import { FiuComponent } from './fiu.component';
@NgModule({
imports: [
SharedModule,
FiuRoutingModule,
FiuComponent
]
})
export class FiuModule {}
@@ -0,0 +1,646 @@
<!--
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/.
-->
<div class="acr-dlg">
<!-- HEADER -->
<div class="acr-head">
<div class="acr-head-left">
<div class="acr-head-ic"><fa-icon icon="book-open"></fa-icon></div>
<div>
<h2 class="acr-title">Accounting Reports &mdash; {{ currentReport.name }}</h2>
<p class="acr-sub">{{ currentReport.description }}</p>
</div>
</div>
<div class="acr-head-actions">
<span class="acr-meta"> <fa-icon icon="university"></fa-icon>&nbsp;{{ bank.name }} </span>
<span class="acr-meta">
<fa-icon icon="calendar"></fa-icon>&nbsp;FY <strong>{{ bank.fiscalYear }}</strong>
</span>
<button class="acr-close" (click)="close()" matTooltip="Close" aria-label="Close">&#10005;</button>
</div>
</div>
<!-- REPORT SELECTOR -->
<div class="acr-sel-bar">
<span class="acr-sel-l">Report:</span>
<button
*ngFor="let r of reports"
class="acr-sel-pill"
[class.active]="activeReport === r.code"
(click)="setReport(r.code)"
>
<fa-icon [icon]="r.icon"></fa-icon>&nbsp;&nbsp;{{ r.name }}
</button>
<span class="acr-sel-group-tag">{{ currentReport.group }}</span>
</div>
<div class="acr-body">
<!-- ════════════ PROFIT & LOSS ════════════ -->
<ng-container *ngIf="activeReport === 'PNL'">
<div class="acr-stats">
<div class="acr-stat-card good">
<div class="acr-stat-l">Total Income</div>
<div class="acr-stat-v">&#8377; {{ totalIncome / 100 | number: '1.2-2' }} Cr</div>
<div class="acr-stat-meta">{{ totalIncome | number: '1.2-2' }} Lakh</div>
</div>
<div class="acr-stat-card alert">
<div class="acr-stat-l">Total Expenditure</div>
<div class="acr-stat-v">&#8377; {{ totalExpenditure / 100 | number: '1.2-2' }} Cr</div>
<div class="acr-stat-meta">{{ totalExpenditure | number: '1.2-2' }} Lakh</div>
</div>
<div class="acr-stat-card" [ngClass]="netProfit >= 0 ? 'good' : 'alert'">
<div class="acr-stat-l">Net Profit / (Loss)</div>
<div class="acr-stat-v">
{{ netProfit >= 0 ? '+' : '' }}&#8377; {{ netProfit / 100 | number: '1.2-2' }} Cr
</div>
<div class="acr-stat-meta">Margin: {{ netProfitPct.toFixed(2) }}%</div>
</div>
<div class="acr-stat-card">
<div class="acr-stat-l">Reporting Period</div>
<div class="acr-stat-v" style="font-size: 14px">{{ bank.period }}</div>
<div class="acr-stat-meta">{{ bank.branchCount }} branches consolidated</div>
</div>
</div>
<div class="acr-section">
<div class="acr-sec-h">
<h3 class="acr-sec-t">Profit &amp; Loss A/c &mdash; FY {{ bank.fiscalYear }} (₹ in Lakhs)</h3>
<button mat-stroked-button class="acr-btn-xls" (click)="exportPnlCsv()">
<fa-icon icon="file-csv"></fa-icon>&nbsp; Export CSV
</button>
</div>
<div class="acr-pnl-grid">
<!-- INCOME side -->
<div class="acr-pnl-side">
<h4 class="acr-pnl-side-t income">Income</h4>
<div class="acr-tbl-wrap">
<table class="acr-tbl">
<ng-container *ngFor="let sub of pnlIncomeSubgroups">
<thead>
<tr>
<th colspan="2" class="acr-sub-h">{{ sub }}</th>
<th class="ta-r">₹ Lakh</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let l of pnlLinesIn('INCOME', sub)">
<td class="mono">{{ l.code }}</td>
<td>{{ l.label }}</td>
<td class="ta-r">{{ l.amount | number: '1.2-2' }}</td>
</tr>
<tr class="row-sub-total">
<td colspan="2">
<strong>Sub-total &mdash; {{ sub }}</strong>
</td>
<td class="ta-r">
<strong>{{ subTotal('INCOME', sub) | number: '1.2-2' }}</strong>
</td>
</tr>
</tbody>
</ng-container>
<tfoot>
<tr class="row-total income">
<td colspan="2"><strong>Total Income</strong></td>
<td class="ta-r">
<strong>{{ totalIncome | number: '1.2-2' }}</strong>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<!-- EXPENDITURE side -->
<div class="acr-pnl-side">
<h4 class="acr-pnl-side-t expense">Expenditure</h4>
<div class="acr-tbl-wrap">
<table class="acr-tbl">
<ng-container *ngFor="let sub of pnlExpSubgroups">
<thead>
<tr>
<th colspan="2" class="acr-sub-h">{{ sub }}</th>
<th class="ta-r">₹ Lakh</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let l of pnlLinesIn('EXPENDITURE', sub)">
<td class="mono">{{ l.code }}</td>
<td>{{ l.label }}</td>
<td class="ta-r">{{ l.amount | number: '1.2-2' }}</td>
</tr>
<tr class="row-sub-total">
<td colspan="2">
<strong>Sub-total &mdash; {{ sub }}</strong>
</td>
<td class="ta-r">
<strong>{{ subTotal('EXPENDITURE', sub) | number: '1.2-2' }}</strong>
</td>
</tr>
</tbody>
</ng-container>
<tfoot>
<tr class="row-total expense">
<td colspan="2"><strong>Total Expenditure</strong></td>
<td class="ta-r">
<strong>{{ totalExpenditure | number: '1.2-2' }}</strong>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<!-- Net profit row -->
<div class="acr-pnl-net" [ngClass]="netProfit >= 0 ? 'good' : 'alert'">
<span class="acr-pnl-net-l">
<fa-icon [icon]="netProfit >= 0 ? 'arrow-up' : 'arrow-down'"></fa-icon>&nbsp;
<strong>Net {{ netProfit >= 0 ? 'Profit' : 'Loss' }} for the Year</strong>
</span>
<span class="acr-pnl-net-v">
₹ {{ netProfit | number: '1.2-2' }} Lakh
<span class="acr-pnl-net-pct">({{ netProfitPct.toFixed(2) }}% of Income)</span>
</span>
</div>
</div>
</ng-container>
<!-- ════════════ BALANCE SHEET (single period) ════════════ -->
<ng-container *ngIf="activeReport === 'BALANCE-SHEET'">
<div class="acr-stats">
<div class="acr-stat-card">
<div class="acr-stat-l">Total Capital &amp; Liabilities</div>
<div class="acr-stat-v">&#8377; {{ totalLiabilities / 100 | number: '1.2-2' }} Cr</div>
<div class="acr-stat-meta">{{ totalLiabilities | number: '1.2-2' }} Lakh</div>
</div>
<div class="acr-stat-card">
<div class="acr-stat-l">Total Assets</div>
<div class="acr-stat-v">&#8377; {{ totalAssets / 100 | number: '1.2-2' }} Cr</div>
<div class="acr-stat-meta">{{ totalAssets | number: '1.2-2' }} Lakh</div>
</div>
<div class="acr-stat-card" [ngClass]="totalLiabilities === totalAssets ? 'good' : 'alert'">
<div class="acr-stat-l">B/S Tally</div>
<div class="acr-stat-v">
{{ totalLiabilities === totalAssets ? 'BALANCED' : 'MISMATCH' }}
</div>
<div class="acr-stat-meta">Diff: {{ totalAssets - totalLiabilities | number: '1.2-2' }}</div>
</div>
<div class="acr-stat-card">
<div class="acr-stat-l">As On Date</div>
<div class="acr-stat-v" style="font-size: 16px">{{ bank.asOnDate }}</div>
<div class="acr-stat-meta">{{ bank.quarter }}</div>
</div>
</div>
<div class="acr-section">
<div class="acr-sec-h">
<h3 class="acr-sec-t">Balance Sheet &mdash; As on {{ bank.asOnDate }} (₹ in Lakhs)</h3>
<button mat-stroked-button class="acr-btn-xls" (click)="exportBsCsv(false)">
<fa-icon icon="file-csv"></fa-icon>&nbsp; Export CSV
</button>
</div>
<div class="acr-bs-grid">
<!-- Liabilities -->
<div class="acr-pnl-side">
<h4 class="acr-pnl-side-t expense">Capital &amp; Liabilities</h4>
<div class="acr-tbl-wrap">
<table class="acr-tbl">
<ng-container *ngFor="let g of bsGroupsBySide('CAPITAL-LIAB')">
<thead>
<tr>
<th colspan="2" class="acr-sub-h">{{ g }}</th>
<th class="ta-r">₹ Lakh</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let l of bsLinesInGroup('CAPITAL-LIAB', g)">
<td class="mono">{{ l.code }}</td>
<td>{{ l.label }}</td>
<td class="ta-r">{{ l.amount | number: '1.2-2' }}</td>
</tr>
<tr class="row-sub-total">
<td colspan="2"><strong>Sub-total</strong></td>
<td class="ta-r">
<strong>{{ bsSubTotal('CAPITAL-LIAB', g) | number: '1.2-2' }}</strong>
</td>
</tr>
</tbody>
</ng-container>
<tfoot>
<tr class="row-total expense">
<td colspan="2"><strong>Total Capital &amp; Liabilities</strong></td>
<td class="ta-r">
<strong>{{ totalLiabilities | number: '1.2-2' }}</strong>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<!-- Assets -->
<div class="acr-pnl-side">
<h4 class="acr-pnl-side-t income">Assets</h4>
<div class="acr-tbl-wrap">
<table class="acr-tbl">
<ng-container *ngFor="let g of bsGroupsBySide('ASSETS')">
<thead>
<tr>
<th colspan="2" class="acr-sub-h">{{ g }}</th>
<th class="ta-r">₹ Lakh</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let l of bsLinesInGroup('ASSETS', g)">
<td class="mono">{{ l.code }}</td>
<td>{{ l.label }}</td>
<td class="ta-r">{{ l.amount | number: '1.2-2' }}</td>
</tr>
<tr class="row-sub-total">
<td colspan="2"><strong>Sub-total</strong></td>
<td class="ta-r">
<strong>{{ bsSubTotal('ASSETS', g) | number: '1.2-2' }}</strong>
</td>
</tr>
</tbody>
</ng-container>
<tfoot>
<tr class="row-total income">
<td colspan="2"><strong>Total Assets</strong></td>
<td class="ta-r">
<strong>{{ totalAssets | number: '1.2-2' }}</strong>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
</ng-container>
<!-- ════════════ COMPARATIVE B/S ════════════ -->
<ng-container *ngIf="activeReport === 'COMPARATIVE-BS'">
<div class="acr-stats">
<div class="acr-stat-card">
<div class="acr-stat-l">Total Liab — {{ bank.fiscalYear }}</div>
<div class="acr-stat-v">&#8377; {{ totalLiabilities / 100 | number: '1.2-2' }} Cr</div>
<div class="acr-stat-meta">vs {{ totalLiabilitiesPrev / 100 | number: '1.2-2' }} Cr (PY)</div>
</div>
<div class="acr-stat-card">
<div class="acr-stat-l">Total Assets — {{ bank.fiscalYear }}</div>
<div class="acr-stat-v">&#8377; {{ totalAssets / 100 | number: '1.2-2' }} Cr</div>
<div class="acr-stat-meta">vs {{ totalAssetsPrev / 100 | number: '1.2-2' }} Cr (PY)</div>
</div>
<div class="acr-stat-card good">
<div class="acr-stat-l">Asset Growth %</div>
<div class="acr-stat-v">+{{ variationPct(totalAssets, totalAssetsPrev).toFixed(2) }}%</div>
<div class="acr-stat-meta">{{ variation(totalAssets, totalAssetsPrev) | number: '1.2-2' }} Lakh</div>
</div>
<div class="acr-stat-card good">
<div class="acr-stat-l">Deposit Growth %</div>
<div class="acr-stat-v">
+{{
variationPct(
bsSubTotal('CAPITAL-LIAB', 'Deposits'),
bsSubTotal('CAPITAL-LIAB', 'Deposits', true)
).toFixed(2)
}}%
</div>
<div class="acr-stat-meta">All deposit categories</div>
</div>
</div>
<div class="acr-section">
<div class="acr-sec-h">
<h3 class="acr-sec-t">
Comparative Balance Sheet &mdash; FY {{ bank.fiscalYear }} vs FY {{ bank.prevFiscalYear }}
</h3>
<button mat-stroked-button class="acr-btn-xls" (click)="exportBsCsv(true)">
<fa-icon icon="file-csv"></fa-icon>&nbsp; Export CSV
</button>
</div>
<div class="acr-tbl-wrap">
<table class="acr-tbl acr-comp-tbl">
<thead>
<tr>
<th>Code</th>
<th>Particulars</th>
<th class="ta-c">Side / Group</th>
<th class="ta-r">FY {{ bank.fiscalYear }}</th>
<th class="ta-r">FY {{ bank.prevFiscalYear }}</th>
<th class="ta-r">Variation</th>
<th class="ta-r">Variation %</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let l of bs">
<tr>
<td class="mono">{{ l.code }}</td>
<td>{{ l.label }}</td>
<td class="ta-c">
<span class="side-pill" [ngClass]="l.side === 'CAPITAL-LIAB' ? 'side-liab' : 'side-asset'">{{
l.group
}}</span>
</td>
<td class="ta-r">
<strong>{{ l.amount | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">{{ l.prevAmount | number: '1.2-2' }}</td>
<td class="ta-r" [ngClass]="variation(l.amount, l.prevAmount) >= 0 ? 'pos' : 'neg'">
{{ variation(l.amount, l.prevAmount) >= 0 ? '+' : ''
}}{{ variation(l.amount, l.prevAmount) | number: '1.2-2' }}
</td>
<td class="ta-r" [ngClass]="variation(l.amount, l.prevAmount) >= 0 ? 'pos' : 'neg'">
<strong>
{{ variationPct(l.amount, l.prevAmount) >= 0 ? '+' : ''
}}{{ variationPct(l.amount, l.prevAmount).toFixed(2) }}%
</strong>
</td>
</tr>
</ng-container>
<tr class="row-total">
<td colspan="3"><strong>TOTAL CAPITAL &amp; LIABILITIES</strong></td>
<td class="ta-r">
<strong>{{ totalLiabilities | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">{{ totalLiabilitiesPrev | number: '1.2-2' }}</td>
<td class="ta-r" [ngClass]="variation(totalLiabilities, totalLiabilitiesPrev) >= 0 ? 'pos' : 'neg'">
{{ variation(totalLiabilities, totalLiabilitiesPrev) | number: '1.2-2' }}
</td>
<td class="ta-r" [ngClass]="variation(totalLiabilities, totalLiabilitiesPrev) >= 0 ? 'pos' : 'neg'">
<strong>+{{ variationPct(totalLiabilities, totalLiabilitiesPrev).toFixed(2) }}%</strong>
</td>
</tr>
<tr class="row-total">
<td colspan="3"><strong>TOTAL ASSETS</strong></td>
<td class="ta-r">
<strong>{{ totalAssets | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">{{ totalAssetsPrev | number: '1.2-2' }}</td>
<td class="ta-r pos">{{ variation(totalAssets, totalAssetsPrev) | number: '1.2-2' }}</td>
<td class="ta-r pos">
<strong>+{{ variationPct(totalAssets, totalAssetsPrev).toFixed(2) }}%</strong>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</ng-container>
<!-- ════════════ GL DR/CR ════════════ -->
<ng-container *ngIf="activeReport === 'GL-DR-CR'">
<div class="acr-stats">
<div class="acr-stat-card">
<div class="acr-stat-l">GL Accounts</div>
<div class="acr-stat-v">{{ glReport.length }}</div>
<div class="acr-stat-meta">Filtered: {{ filteredGl.length }}</div>
</div>
<div class="acr-stat-card good">
<div class="acr-stat-l">Total Credits</div>
<div class="acr-stat-v">&#8377; {{ glTotalCredit / 100 | number: '1.2-2' }} Cr</div>
<div class="acr-stat-meta">{{ glTotalCredit | number: '1.2-2' }} Lakh</div>
</div>
<div class="acr-stat-card alert">
<div class="acr-stat-l">Total Debits</div>
<div class="acr-stat-v">&#8377; {{ glTotalDebit / 100 | number: '1.2-2' }} Cr</div>
<div class="acr-stat-meta">{{ glTotalDebit | number: '1.2-2' }} Lakh</div>
</div>
<div class="acr-stat-card">
<div class="acr-stat-l">Period</div>
<div class="acr-stat-v" style="font-size: 14px">
{{ bank.quarterFrom }}
<fa-icon icon="long-arrow-alt-right"></fa-icon>
{{ bank.quarterTo }}
</div>
<div class="acr-stat-meta">Consolidated across {{ bank.branchCount }} branches</div>
</div>
</div>
<div class="acr-section">
<div class="acr-sec-h">
<h3 class="acr-sec-t">GL Wise Debit / Credit Report</h3>
<button mat-stroked-button class="acr-btn-xls" (click)="exportGlCsv()">
<fa-icon icon="file-csv"></fa-icon>&nbsp; Export CSV
</button>
</div>
<div class="acr-filters">
<div class="acr-f">
<label>Group</label>
<select [(ngModel)]="glFilterGroup">
<option value="">All Groups</option>
<option value="ASSET">Asset</option>
<option value="LIABILITY">Liability</option>
<option value="INCOME">Income</option>
<option value="EXPENSE">Expense</option>
</select>
</div>
<div class="acr-f">
<label>Search (Code or Description)</label>
<input type="text" placeholder="e.g. 110001 or Capital…" [(ngModel)]="glFilterSearch" />
</div>
<button class="acr-f-reset" (click)="resetGlFilters()" matTooltip="Reset">
<fa-icon icon="undo"></fa-icon>
</button>
</div>
<div class="acr-tbl-wrap">
<table class="acr-tbl">
<thead>
<tr>
<th>A/C Code</th>
<th>Description</th>
<th class="ta-c">Group</th>
<th class="ta-r">Opening</th>
<th class="ta-r">Debit</th>
<th class="ta-r">Credit</th>
<th class="ta-r">Closing</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let g of filteredGl">
<td class="mono cell-acc">{{ g.acCode }}</td>
<td>{{ g.description }}</td>
<td class="ta-c">
<span class="grp-pill" [ngClass]="glGroupClass(g.group)">{{ g.group }}</span>
</td>
<td class="ta-r">{{ g.opening | number: '1.2-2' }}</td>
<td class="ta-r" [class.neg]="g.debit > 0">{{ g.debit | number: '1.2-2' }}</td>
<td class="ta-r" [class.pos]="g.credit > 0">{{ g.credit | number: '1.2-2' }}</td>
<td class="ta-r">
<strong>{{ g.closing | number: '1.2-2' }}</strong>
</td>
</tr>
<tr class="row-total">
<td colspan="3"><strong>TOTAL</strong></td>
<td class="ta-r">&mdash;</td>
<td class="ta-r">
<strong>{{ glTotalDebit | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">
<strong>{{ glTotalCredit | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">&mdash;</td>
</tr>
<tr *ngIf="filteredGl.length === 0">
<td colspan="7" class="empty">No GL accounts match the current filters.</td>
</tr>
</tbody>
</table>
</div>
</div>
</ng-container>
<!-- ════════════ MISC EXPENSE LEDGER ════════════ -->
<ng-container *ngIf="activeReport === 'MISC-EXP'">
<div class="acr-stats">
<div class="acr-stat-card">
<div class="acr-stat-l">Total Vouchers</div>
<div class="acr-stat-v">{{ miscExp.length }}</div>
<div class="acr-stat-meta">Filtered: {{ filteredMisc.length }}</div>
</div>
<div class="acr-stat-card alert">
<div class="acr-stat-l">Total Withdrawal</div>
<div class="acr-stat-v">&#8377; {{ miscTotalWithdrawal | number }}</div>
<div class="acr-stat-meta">Filtered subset</div>
</div>
<div class="acr-stat-card good">
<div class="acr-stat-l">Total Deposit (Reversals)</div>
<div class="acr-stat-v">&#8377; {{ miscTotalDeposit | number }}</div>
<div class="acr-stat-meta">Cancelled / refunded</div>
</div>
<div class="acr-stat-card">
<div class="acr-stat-l">Period</div>
<div class="acr-stat-v" style="font-size: 14px">
{{ bank.quarterFrom }}
<fa-icon icon="long-arrow-alt-right"></fa-icon>
{{ bank.quarterTo }}
</div>
<div class="acr-stat-meta">Report Ref {{ bank.reportRefNo }}</div>
</div>
</div>
<div class="acr-section">
<div class="acr-sec-h">
<h3 class="acr-sec-t">Misc Expense Ledger &mdash; {{ bank.quarterFrom }} TO {{ bank.quarterTo }}</h3>
<button mat-stroked-button class="acr-btn-xls" (click)="exportMiscCsv()">
<fa-icon icon="file-csv"></fa-icon>&nbsp; Export CSV
</button>
</div>
<div class="acr-filters">
<div class="acr-f">
<label>Branch</label>
<input type="text" placeholder="Search branch…" [(ngModel)]="miscFilterBranch" />
</div>
<div class="acr-f">
<label>Mode</label>
<select [(ngModel)]="miscFilterMode">
<option value="">All</option>
<option value="CASH">Cash</option>
<option value="CHEQUE">Cheque</option>
<option value="TRF">Transfer</option>
<option value="NEFT">NEFT</option>
</select>
</div>
<button class="acr-f-reset" (click)="resetMiscFilters()" matTooltip="Reset">
<fa-icon icon="undo"></fa-icon>
</button>
</div>
<div class="acr-tbl-wrap">
<table class="acr-tbl">
<thead>
<tr>
<th>Date</th>
<th>Branch</th>
<th>Voucher</th>
<th>CHQ_NO</th>
<th>Description</th>
<th class="ta-c">Mode</th>
<th class="ta-r">Withdrawal</th>
<th class="ta-r">Deposit</th>
<th class="ta-r">Balance</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let r of filteredMisc">
<td class="mono">{{ r.txnDate }}</td>
<td>
{{ r.branch }} <span class="muted">({{ r.branchCode }})</span>
</td>
<td class="mono">{{ r.voucherNo }}</td>
<td class="mono">{{ r.chequeNo }}</td>
<td>{{ r.description }}</td>
<td class="ta-c">
<span class="mode-pill" [ngClass]="'mode-' + r.mode.toLowerCase()">{{ r.mode }}</span>
</td>
<td class="ta-r" [class.neg]="r.withdrawal > 0">
{{ r.withdrawal > 0 ? (r.withdrawal | number) : '—' }}
</td>
<td class="ta-r" [class.pos]="r.deposit > 0">
{{ r.deposit > 0 ? (r.deposit | number) : '—' }}
</td>
<td class="ta-r">
<strong>{{ r.balance | number }}</strong>
</td>
</tr>
<tr class="row-total">
<td colspan="6"><strong>GRAND TOTAL</strong></td>
<td class="ta-r">
<strong>{{ miscTotalWithdrawal | number }}</strong>
</td>
<td class="ta-r">
<strong>{{ miscTotalDeposit | number }}</strong>
</td>
<td></td>
</tr>
<tr *ngIf="filteredMisc.length === 0">
<td colspan="9" class="empty">No vouchers match the current filters.</td>
</tr>
</tbody>
</table>
</div>
<h4 class="acr-sub-tt">Branch-wise Net Total</h4>
<div class="acr-tbl-wrap">
<table class="acr-tbl">
<thead>
<tr>
<th>Branch</th>
<th class="ta-r">Net Withdrawal (₹)</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let bt of miscBranchTotals">
<td>{{ bt.branch }}</td>
<td class="ta-r">{{ bt.total | number }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</ng-container>
</div>
<!-- FOOTER -->
<div class="acr-footer">
<div class="acr-footer-l">
<fa-icon icon="check-double"></fa-icon>&nbsp; Certified by <strong>{{ bank.principalOfficer }}</strong> &mdash;
{{ bank.poDesignation }} &middot; Auditor: {{ bank.auditor }}
</div>
<button mat-stroked-button class="acr-btn-xls" (click)="exportCurrentCsv()">
<fa-icon icon="file-export"></fa-icon>&nbsp; Export {{ currentReport.name }}
</button>
</div>
</div>
@@ -0,0 +1,706 @@
/**
* 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/.
*/
:host {
--acr-good: #15803d;
--acr-good-l: #dcfce7;
--acr-mid: #d97706;
--acr-mid-l: #fef3c7;
--acr-low: #c0392b;
--acr-low-l: #fdecea;
--acr-amber: #b45309;
--acr-amber-l: #fef3c7;
--acr-emerald: #059669;
--acr-emerald-l: #d1fae5;
--acr-blue: #2563eb;
--acr-blue-l: #dbeafe;
--acr-purple: #6d28d9;
--acr-purple-l: #ede9fe;
--acr-pink: #be185d;
--acr-pink-l: #fce7f3;
--acr-g50: #f8fafc;
--acr-g100: #f1f5f9;
--acr-g200: #e2e8f0;
--acr-g300: #cbd5e1;
--acr-g500: #64748b;
--acr-g700: #334155;
--acr-g900: #0f172a;
display: block;
font-family:
'DM Sans',
system-ui,
-apple-system,
sans-serif;
color: var(--acr-g900);
}
.acr-dlg {
background: #fff;
border-radius: 14px;
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 92vh;
}
/* HEADER */
.acr-head {
padding: 18px 22px;
border-bottom: 1px solid var(--acr-g200);
display: flex;
align-items: center;
gap: 16px;
flex-shrink: 0;
background: linear-gradient(135deg, #fffbeb 0%, #fff 100%);
}
.acr-head-left {
display: flex;
align-items: center;
gap: 14px;
flex: 1;
}
.acr-head-ic {
width: 44px;
height: 44px;
border-radius: 11px;
background: var(--acr-amber-l);
color: var(--acr-amber);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.acr-title {
margin: 0;
font-size: 17px;
font-weight: 700;
color: var(--acr-g900);
}
.acr-sub {
margin: 2px 0 0;
font-size: 12px;
color: var(--acr-g500);
}
.acr-head-actions {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.acr-meta {
display: inline-flex;
align-items: center;
font-size: 11px;
color: var(--acr-g500);
padding: 5px 10px;
background: #fff;
border: 1px solid var(--acr-g200);
border-radius: 6px;
fa-icon {
color: var(--acr-amber);
margin-right: 4px;
}
}
.acr-close {
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid var(--acr-g200);
background: #fff;
font-size: 14px;
color: var(--acr-g500);
cursor: pointer;
transition: all 0.15s;
&:hover {
background: var(--acr-g100);
color: var(--acr-g900);
}
}
/* SELECTOR */
.acr-sel-bar {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 22px;
background: var(--acr-g50);
border-bottom: 1px solid var(--acr-g200);
flex-shrink: 0;
flex-wrap: wrap;
}
.acr-sel-l {
font-size: 11px;
font-weight: 700;
color: var(--acr-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-right: 4px;
}
.acr-sel-pill {
padding: 7px 16px;
border-radius: 999px;
border: 1px solid var(--acr-g200);
background: #fff;
cursor: pointer;
font-family: inherit;
font-size: 12px;
font-weight: 600;
color: var(--acr-g700);
transition: all 0.15s;
display: inline-flex;
align-items: center;
fa-icon {
color: var(--acr-g500);
}
&:hover {
background: var(--acr-amber-l);
border-color: var(--acr-amber);
color: var(--acr-amber);
fa-icon {
color: var(--acr-amber);
}
}
&.active {
background: var(--acr-amber);
border-color: var(--acr-amber);
color: #fff;
box-shadow: 0 2px 6px rgb(180 83 9 / 25%);
fa-icon {
color: #fff;
}
}
}
.acr-sel-group-tag {
margin-left: auto;
font-size: 10px;
font-weight: 700;
color: var(--acr-g500);
padding: 4px 10px;
background: #fff;
border: 1px solid var(--acr-g200);
border-radius: 6px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* BODY */
.acr-body {
padding: 18px 22px;
display: flex;
flex-direction: column;
gap: 16px;
overflow: auto;
flex: 1;
background: var(--acr-g50);
}
/* STATS */
.acr-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.acr-stat-card {
background: #fff;
border: 1px solid var(--acr-g200);
border-radius: 12px;
padding: 14px 16px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
&.alert {
background: var(--acr-low-l);
border-color: var(--acr-low);
.acr-stat-v {
color: var(--acr-low);
}
}
&.warn {
background: var(--acr-mid-l);
border-color: var(--acr-mid);
.acr-stat-v {
color: var(--acr-mid);
}
}
&.good {
background: var(--acr-good-l);
border-color: var(--acr-good);
.acr-stat-v {
color: var(--acr-good);
}
}
}
.acr-stat-l {
font-size: 11px;
font-weight: 600;
color: var(--acr-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.acr-stat-v {
font-size: 22px;
font-weight: 700;
color: var(--acr-g900);
line-height: 1.2;
margin-top: 2px;
}
.acr-stat-meta {
font-size: 11px;
color: var(--acr-g500);
margin-top: 4px;
}
/* SECTION */
.acr-section {
background: #fff;
border: 1px solid var(--acr-g200);
border-radius: 12px;
padding: 16px 18px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.acr-sec-h {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.acr-sec-t {
margin: 0;
font-size: 14px;
font-weight: 700;
color: var(--acr-g700);
}
.acr-sub-tt {
margin: 18px 0 8px;
font-size: 12px;
font-weight: 700;
color: var(--acr-g700);
text-transform: uppercase;
letter-spacing: 0.05em;
padding-bottom: 4px;
border-bottom: 1px dashed var(--acr-g200);
}
.acr-btn-xls {
font-size: 12px !important;
height: 34px !important;
color: #fff !important;
background: var(--acr-amber) !important;
}
/* P&L / B/S TWO-COLUMN GRID */
.acr-pnl-grid,
.acr-bs-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.acr-pnl-side {
display: flex;
flex-direction: column;
gap: 8px;
}
.acr-pnl-side-t {
margin: 0;
padding: 8px 12px;
font-size: 13px;
font-weight: 700;
border-radius: 6px;
&.income {
background: var(--acr-good-l);
color: var(--acr-good);
}
&.expense {
background: var(--acr-low-l);
color: var(--acr-low);
}
}
.acr-pnl-net {
margin-top: 14px;
padding: 14px 18px;
border-radius: 10px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
&.good {
background: var(--acr-good-l);
color: var(--acr-good);
}
&.alert {
background: var(--acr-low-l);
color: var(--acr-low);
}
}
.acr-pnl-net-l {
display: flex;
align-items: center;
font-weight: 700;
}
.acr-pnl-net-v {
font-weight: 700;
font-size: 16px;
.acr-pnl-net-pct {
margin-left: 6px;
font-size: 12px;
font-weight: 600;
opacity: 0.8;
}
}
/* FILTERS */
.acr-filters {
display: flex;
gap: 12px;
padding: 12px 14px;
background: var(--acr-g50);
border: 1px solid var(--acr-g200);
border-radius: 8px;
margin-bottom: 12px;
align-items: flex-end;
flex-wrap: wrap;
}
.acr-f {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 160px;
label {
font-size: 10px;
font-weight: 600;
color: var(--acr-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
select,
input {
height: 32px;
padding: 0 10px;
border: 1px solid var(--acr-g200);
border-radius: 6px;
background: #fff;
font-family: inherit;
font-size: 12px;
color: var(--acr-g900);
&:focus {
outline: none;
border-color: var(--acr-amber);
box-shadow: 0 0 0 3px rgb(180 83 9 / 12%);
}
}
}
.acr-f-reset {
height: 32px;
width: 32px;
border-radius: 6px;
border: 1px solid var(--acr-g200);
background: #fff;
color: var(--acr-g500);
cursor: pointer;
transition: all 0.15s;
&:hover {
background: var(--acr-low-l);
color: var(--acr-low);
border-color: var(--acr-low);
}
}
/* TABLE */
.acr-tbl-wrap {
border: 1px solid var(--acr-g200);
border-radius: 8px;
overflow: auto;
}
.acr-tbl {
width: 100%;
border-collapse: collapse;
th {
text-align: left;
padding: 10px 14px;
font-size: 11px;
font-weight: 600;
color: var(--acr-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
background: var(--acr-g50);
border-bottom: 1px solid var(--acr-g200);
white-space: nowrap;
}
td {
padding: 8px 14px;
border-bottom: 1px solid var(--acr-g100);
font-size: 13px;
color: var(--acr-g700);
vertical-align: middle;
&.pos {
color: var(--acr-good);
font-weight: 600;
}
&.neg {
color: var(--acr-low);
font-weight: 600;
}
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: var(--acr-g50);
}
.row-sub-total td {
background: var(--acr-g100);
color: var(--acr-g900);
}
.row-total td {
background: var(--acr-amber-l);
color: var(--acr-g900);
font-weight: 700;
border-top: 2px solid var(--acr-amber);
}
.row-total.income td {
background: var(--acr-good-l);
border-top-color: var(--acr-good);
color: var(--acr-good);
}
.row-total.expense td {
background: var(--acr-low-l);
border-top-color: var(--acr-low);
color: var(--acr-low);
}
.acr-sub-h {
font-size: 11px;
color: var(--acr-amber);
font-weight: 700;
background: #fff;
border-bottom: 1px solid var(--acr-g200);
}
}
.cell-acc {
color: var(--acr-amber);
font-weight: 700;
}
/* GROUP PILLS */
.grp-pill {
display: inline-block;
padding: 2px 9px;
border-radius: 6px;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.04em;
&.gl-group-asset {
background: var(--acr-blue-l);
color: var(--acr-blue);
}
&.gl-group-liability {
background: var(--acr-purple-l);
color: var(--acr-purple);
}
&.gl-group-income {
background: var(--acr-good-l);
color: var(--acr-good);
}
&.gl-group-expense {
background: var(--acr-low-l);
color: var(--acr-low);
}
}
.side-pill {
display: inline-block;
padding: 2px 8px;
border-radius: 6px;
font-size: 10px;
font-weight: 600;
&.side-liab {
background: var(--acr-low-l);
color: var(--acr-low);
}
&.side-asset {
background: var(--acr-good-l);
color: var(--acr-good);
}
}
.mode-pill {
display: inline-block;
padding: 2px 8px;
border-radius: 6px;
font-family: 'DM Mono', monospace;
font-size: 10px;
font-weight: 700;
&.mode-cash {
background: var(--acr-mid-l);
color: var(--acr-mid);
}
&.mode-cheque {
background: var(--acr-blue-l);
color: var(--acr-blue);
}
&.mode-trf {
background: var(--acr-purple-l);
color: var(--acr-purple);
}
&.mode-neft {
background: var(--acr-emerald-l);
color: var(--acr-emerald);
}
}
.muted {
color: var(--acr-g300);
font-weight: 400;
}
.ta-c {
text-align: center;
}
.ta-r {
text-align: right;
}
.mono {
font-family: 'DM Mono', monospace;
font-size: 12px;
}
.empty {
text-align: center;
color: var(--acr-g500);
padding: 24px !important;
font-style: italic;
}
.pos {
color: var(--acr-good);
}
.neg {
color: var(--acr-low);
}
/* DIALOG FOOTER */
.acr-footer {
padding: 12px 22px;
border-top: 1px solid var(--acr-g200);
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-shrink: 0;
flex-wrap: wrap;
}
.acr-footer-l {
font-size: 12px;
color: var(--acr-g500);
display: flex;
align-items: center;
fa-icon {
color: var(--acr-amber);
margin-right: 4px;
}
}
/* RESPONSIVE */
@media (width <= 1100px) {
.acr-stats {
grid-template-columns: repeat(2, 1fr);
}
.acr-pnl-grid,
.acr-bs-grid {
grid-template-columns: 1fr;
}
}
@media (width <= 720px) {
.acr-stats {
grid-template-columns: 1fr;
}
.acr-head,
.acr-sel-bar,
.acr-footer {
flex-wrap: wrap;
}
}
@@ -0,0 +1,478 @@
<!--
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/.
-->
<div class="alm-dlg">
<!-- HEADER -->
<div class="alm-head">
<div class="alm-head-left">
<div class="alm-head-ic"><fa-icon icon="balance-scale"></fa-icon></div>
<div>
<h2 class="alm-title">Asset Liability Management — Structural Liquidity</h2>
<p class="alm-sub">
{{ bank.name }} &middot; <strong>{{ bank.formCode }}</strong> &middot; {{ bank.quarter }} &middot; As on
<strong>{{ bank.asOnDate }}</strong>
</p>
</div>
</div>
<div class="alm-head-actions">
<span class="alm-overall" [ngClass]="gap14dWithinTolerance && gap28dWithinTolerance ? 'on-target' : 'below'">
<span class="alm-overall-l">14d Tolerance</span>
<span class="alm-overall-v">{{ gap14dPct.toFixed(2) }}%</span>
<span class="alm-overall-t">/ -{{ bank.tolerance14d }}%</span>
</span>
<button class="alm-close" (click)="close()" matTooltip="Close" aria-label="Close">&#10005;</button>
</div>
</div>
<!-- TABS -->
<div class="alm-tabs">
<button class="alm-tab" [class.active]="activeTab === 'dashboard'" (click)="setTab('dashboard')">
<fa-icon icon="chart-pie"></fa-icon>&nbsp; Dashboard
</button>
<button class="alm-tab" [class.active]="activeTab === 'ladder'" (click)="setTab('ladder')">
<fa-icon icon="layer-group"></fa-icon>&nbsp; Maturity Ladder
</button>
<button class="alm-tab" [class.active]="activeTab === 'monthly'" (click)="setTab('monthly')">
<fa-icon icon="calendar-alt"></fa-icon>&nbsp; Monthly Snapshot
</button>
<button class="alm-tab" [class.active]="activeTab === 'returns'" (click)="setTab('returns')">
<fa-icon icon="file-invoice"></fa-icon>&nbsp; Form ALM-1
</button>
</div>
<div class="alm-body">
<!-- ════════════ DASHBOARD ════════════ -->
<ng-container *ngIf="activeTab === 'dashboard'">
<div class="alm-stats">
<div class="alm-stat-card alm-stat-gauge">
<div
class="gauge"
[style.--pct.%]="totalGapPct > 0 ? (totalGapPct > 100 ? 100 : totalGapPct) : 0"
[ngClass]="totalGap >= 0 ? 'on-target' : 'below'"
>
<div class="gauge-inner">
<div class="gauge-pct">{{ totalGapPct.toFixed(1) }}%</div>
<div class="gauge-label">Net Gap / Outflow</div>
</div>
</div>
</div>
<div class="alm-stat-card">
<div class="alm-stat-l">NDTL</div>
<div class="alm-stat-v">&#8377; {{ bank.ndtl / 100 | number: '1.2-2' }} Cr</div>
<div class="alm-stat-meta">Net Demand &amp; Time Liabilities</div>
</div>
<div class="alm-stat-card">
<div class="alm-stat-l">Total Outflows</div>
<div class="alm-stat-v">&#8377; {{ totalOutflow / 100 | number: '1.2-2' }} Cr</div>
<div class="alm-stat-meta">All maturity buckets</div>
</div>
<div class="alm-stat-card">
<div class="alm-stat-l">Total Inflows</div>
<div class="alm-stat-v">&#8377; {{ totalInflow / 100 | number: '1.2-2' }} Cr</div>
<div class="alm-stat-meta" [ngClass]="totalGap >= 0 ? 'pos' : 'neg'">
Net: {{ totalGap >= 0 ? '+' : '' }}{{ totalGap / 100 | number: '1.2-2' }} Cr
</div>
</div>
</div>
<!-- TOLERANCE BARS -->
<div class="alm-section">
<div class="alm-sec-h">
<h3 class="alm-sec-t">RBI Tolerance Limits — Cumulative Gap</h3>
<span class="alm-circular">RBI MD: {{ bank.rbiCircular }}</span>
</div>
<div class="alm-tolerance">
<div class="alm-tol-row" [ngClass]="gap14dWithinTolerance ? 'good' : 'bad'">
<div class="alm-tol-h">
<span class="alm-tol-l"
><fa-icon icon="hourglass-half"></fa-icon>&nbsp; Cumulative Gap up to 14 days</span
>
<span class="alm-tol-p">
<strong>{{ gap14dPct.toFixed(2) }}%</strong>
<span class="alm-tol-lim">limit -{{ bank.tolerance14d }}%</span>
<span class="alm-tol-st">{{ gap14dWithinTolerance ? 'WITHIN' : 'BREACH' }}</span>
</span>
</div>
<div class="alm-tol-track">
<div
class="alm-tol-fill"
[style.width.%]="gap14dPct >= 0 ? 100 : 100 + (gap14dPct / bank.tolerance14d) * 100"
></div>
<div class="alm-tol-marker"></div>
</div>
</div>
<div class="alm-tol-row" [ngClass]="gap28dWithinTolerance ? 'good' : 'bad'">
<div class="alm-tol-h">
<span class="alm-tol-l"><fa-icon icon="clock"></fa-icon>&nbsp; Cumulative Gap up to 30 days</span>
<span class="alm-tol-p">
<strong>{{ gap28dPct.toFixed(2) }}%</strong>
<span class="alm-tol-lim">limit -{{ bank.tolerance28d }}%</span>
<span class="alm-tol-st">{{ gap28dWithinTolerance ? 'WITHIN' : 'BREACH' }}</span>
</span>
</div>
<div class="alm-tol-track">
<div
class="alm-tol-fill"
[style.width.%]="gap28dPct >= 0 ? 100 : 100 + (gap28dPct / bank.tolerance28d) * 100"
></div>
<div class="alm-tol-marker"></div>
</div>
</div>
</div>
</div>
<!-- BUCKET MINI HEATMAP -->
<div class="alm-section">
<div class="alm-sec-h">
<h3 class="alm-sec-t">Bucket-wise Mismatch Heatmap</h3>
</div>
<div class="alm-heatmap">
<div
*ngFor="let b of buckets"
class="heat-cell"
[ngClass]="bucketStatus(b)"
[matTooltip]="b.label + ' — Gap: ' + bucketGapPct(b).toFixed(2) + '%'"
>
<div class="heat-code">{{ b.code }}</div>
<div class="heat-pct">{{ bucketGapPct(b).toFixed(1) }}%</div>
<div class="heat-lbl">{{ b.label }}</div>
</div>
</div>
</div>
<div class="alm-section">
<div class="alm-sec-h">
<h3 class="alm-sec-t">Reporting Snapshot</h3>
</div>
<div class="alm-info-grid">
<div class="alm-info">
<div class="alm-info-l">Reporting Bank</div>
<div class="alm-info-v">{{ bank.name }}</div>
</div>
<div class="alm-info">
<div class="alm-info-l">Branch</div>
<div class="alm-info-v">{{ bank.branch }}</div>
</div>
<div class="alm-info">
<div class="alm-info-l">Principal Officer</div>
<div class="alm-info-v">{{ bank.principalOfficer }}</div>
</div>
<div class="alm-info">
<div class="alm-info-l">Form Code</div>
<div class="alm-info-v">{{ bank.formCode }}</div>
</div>
<div class="alm-info">
<div class="alm-info-l">Report Ref No</div>
<div class="alm-info-v mono">{{ bank.reportRefNo }}</div>
</div>
<div class="alm-info">
<div class="alm-info-l">User Code</div>
<div class="alm-info-v mono">{{ bank.userCode }}</div>
</div>
</div>
</div>
</ng-container>
<!-- ════════════ MATURITY LADDER ════════════ -->
<ng-container *ngIf="activeTab === 'ladder'">
<div class="alm-section">
<div class="alm-sec-h">
<h3 class="alm-sec-t">Statement of Structural Liquidity — Maturity Ladder</h3>
<div class="alm-sec-actions">
<select class="alm-sel" [(ngModel)]="filterBucket">
<option value="">All Buckets</option>
<option value="short">Short Term (≤ 30 days)</option>
<option value="medium">Medium Term (1–12 months)</option>
<option value="long">Long Term (&gt; 1 year)</option>
</select>
<button mat-stroked-button class="alm-btn-xls" (click)="exportLadderCsv()">
<fa-icon icon="file-csv"></fa-icon>&nbsp; Export CSV
</button>
</div>
</div>
<div class="alm-tbl-wrap">
<table class="alm-tbl">
<thead>
<tr>
<th>Bucket</th>
<th>Time Period</th>
<th class="ta-r">Outflow (Liab)</th>
<th class="ta-r">Inflow (Assets)</th>
<th class="ta-r">Gap</th>
<th class="ta-r">Gap %</th>
<th class="ta-c">Status</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let b of filteredBuckets">
<td class="mono">{{ b.code }}</td>
<td>{{ b.label }}</td>
<td class="ta-r">{{ b.outflow | number: '1.2-2' }}</td>
<td class="ta-r">{{ b.inflow | number: '1.2-2' }}</td>
<td class="ta-r" [ngClass]="bucketGap(b) >= 0 ? 'pos' : 'neg'">
<strong>{{ bucketGap(b) >= 0 ? '+' : '' }}{{ bucketGap(b) | number: '1.2-2' }}</strong>
</td>
<td class="ta-r" [ngClass]="bucketGap(b) >= 0 ? 'pos' : 'neg'">
{{ bucketGapPct(b) >= 0 ? '+' : '' }}{{ bucketGapPct(b).toFixed(2) }}%
</td>
<td class="ta-c">
<span class="status-pill" [ngClass]="bucketStatus(b)">
<ng-container *ngIf="bucketStatus(b) === 'surplus'">SURPLUS</ng-container>
<ng-container *ngIf="bucketStatus(b) === 'deficit'">DEFICIT</ng-container>
<ng-container *ngIf="bucketStatus(b) === 'balanced'">BALANCED</ng-container>
</span>
</td>
</tr>
<tr class="row-total">
<td colspan="2"><strong>TOTAL</strong></td>
<td class="ta-r">
<strong>{{ totalOutflow | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">
<strong>{{ totalInflow | number: '1.2-2' }}</strong>
</td>
<td class="ta-r" [ngClass]="totalGap >= 0 ? 'pos' : 'neg'">
<strong>{{ totalGap >= 0 ? '+' : '' }}{{ totalGap | number: '1.2-2' }}</strong>
</td>
<td class="ta-r" [ngClass]="totalGap >= 0 ? 'pos' : 'neg'">
<strong>{{ totalGapPct >= 0 ? '+' : '' }}{{ totalGapPct.toFixed(2) }}%</strong>
</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<!-- Cumulative table -->
<h4 class="form-iii-t">Cumulative Position</h4>
<div class="alm-tbl-wrap">
<table class="alm-tbl">
<thead>
<tr>
<th>Through Bucket</th>
<th class="ta-r">Cum. Outflow</th>
<th class="ta-r">Cum. Inflow</th>
<th class="ta-r">Cum. Gap</th>
<th class="ta-r">Cum. Gap %</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let b of buckets; let i = index">
<td>{{ b.code }} — {{ b.label }}</td>
<td class="ta-r">{{ cumulativeOutflow(i) | number: '1.2-2' }}</td>
<td class="ta-r">{{ cumulativeInflow(i) | number: '1.2-2' }}</td>
<td class="ta-r" [ngClass]="cumulativeGap(i) >= 0 ? 'pos' : 'neg'">
{{ cumulativeGap(i) >= 0 ? '+' : '' }}{{ cumulativeGap(i) | number: '1.2-2' }}
</td>
<td class="ta-r" [ngClass]="cumulativeGap(i) >= 0 ? 'pos' : 'neg'">
<strong>{{ cumulativeGapPct(i) >= 0 ? '+' : '' }}{{ cumulativeGapPct(i).toFixed(2) }}%</strong>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</ng-container>
<!-- ════════════ MONTHLY ════════════ -->
<ng-container *ngIf="activeTab === 'monthly'">
<div class="alm-section">
<div class="alm-sec-h">
<h3 class="alm-sec-t">Monthly Asset / Liability Snapshot — FY {{ bank.fiscalYear }}</h3>
<button mat-stroked-button class="alm-btn-xls" (click)="exportMonthlyXls()">
<fa-icon icon="file-excel"></fa-icon>&nbsp; Export Excel
</button>
</div>
<div class="alm-tbl-wrap">
<table class="alm-tbl">
<thead>
<tr>
<th rowspan="2">Month</th>
<th colspan="4" class="ta-c grp-liab">Liabilities (₹ Lakh)</th>
<th colspan="4" class="ta-c grp-asset">Assets (₹ Lakh)</th>
</tr>
<tr>
<th class="ta-r">Reserve/Prov</th>
<th class="ta-r">Borrowings</th>
<th class="ta-r">FDs</th>
<th class="ta-r">Total Liab</th>
<th class="ta-r">Cash</th>
<th class="ta-r">Investments</th>
<th class="ta-r">Other Assets</th>
<th class="ta-r">Total Assets</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let m of monthly">
<td class="mono">{{ m.month }}</td>
<td class="ta-r">{{ m.reserveProv | number: '1.2-2' }}</td>
<td class="ta-r">{{ m.borrowings | number: '1.2-2' }}</td>
<td class="ta-r">{{ m.fds | number: '1.2-2' }}</td>
<td class="ta-r">
<strong>{{ monthTotalLiab(m) | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">{{ m.cashOnHand | number: '1.2-2' }}</td>
<td class="ta-r">{{ m.investments | number: '1.2-2' }}</td>
<td class="ta-r">{{ m.otherAssets | number: '1.2-2' }}</td>
<td class="ta-r">
<strong>{{ monthTotalAssets(m) | number: '1.2-2' }}</strong>
</td>
</tr>
<tr class="row-total">
<td><strong>AVERAGE</strong></td>
<td class="ta-r">{{ avg('reserveProv') | number: '1.2-2' }}</td>
<td class="ta-r">{{ avg('borrowings') | number: '1.2-2' }}</td>
<td class="ta-r">{{ avg('fds') | number: '1.2-2' }}</td>
<td class="ta-r">
<strong>{{ avg('reserveProv') + avg('borrowings') + avg('fds') | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">{{ avg('cashOnHand') | number: '1.2-2' }}</td>
<td class="ta-r">{{ avg('investments') | number: '1.2-2' }}</td>
<td class="ta-r">{{ avg('otherAssets') | number: '1.2-2' }}</td>
<td class="ta-r">
<strong>{{ avg('cashOnHand') + avg('investments') + avg('otherAssets') | number: '1.2-2' }}</strong>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</ng-container>
<!-- ════════════ RETURNS / FORM ALM-1 ════════════ -->
<ng-container *ngIf="activeTab === 'returns'">
<div class="alm-section">
<div class="alm-sec-h">
<h3 class="alm-sec-t">{{ bank.formCode }} — Statement of Structural Liquidity</h3>
<button mat-stroked-button class="alm-btn-xls" (click)="exportFormAlm()">
<fa-icon icon="file-excel"></fa-icon>&nbsp; Generate {{ bank.formCode }}
</button>
</div>
<div class="form-iii">
<div class="form-iii-h">
<div class="form-iii-bank">
<strong>{{ bank.name }}</strong> &mdash; {{ bank.branch }}
</div>
<div class="form-iii-meta">
<span><fa-icon icon="calendar"></fa-icon>&nbsp; As On {{ bank.asOnDate }}</span>
<span><fa-icon icon="university"></fa-icon>&nbsp; {{ bank.quarter }}</span>
<span class="mono"><fa-icon icon="hashtag"></fa-icon>&nbsp; Ref: {{ bank.reportRefNo }}</span>
</div>
</div>
<h4 class="form-iii-t">A. NDTL &amp; Reference</h4>
<table class="alm-tbl form-tbl">
<tbody>
<tr>
<td>Net Demand &amp; Time Liabilities (NDTL) (₹ Lakhs)</td>
<td class="ta-r">
<strong>{{ bank.ndtl | number: '1.2-2' }}</strong>
</td>
</tr>
<tr>
<td>RBI Master Direction</td>
<td class="ta-r mono">{{ bank.rbiCircular }}</td>
</tr>
</tbody>
</table>
<h4 class="form-iii-t">B. Maturity Ladder (₹ in Lakhs)</h4>
<table class="alm-tbl form-tbl">
<thead>
<tr>
<th>Bucket</th>
<th>Time Period</th>
<th class="ta-r">Outflows</th>
<th class="ta-r">Inflows</th>
<th class="ta-r">Mismatch</th>
<th class="ta-r">Cum. Mismatch</th>
<th class="ta-r">Cum. % of Outflow</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let b of buckets; let i = index">
<td class="mono">{{ b.code }}</td>
<td>{{ b.label }}</td>
<td class="ta-r">{{ b.outflow | number: '1.2-2' }}</td>
<td class="ta-r">{{ b.inflow | number: '1.2-2' }}</td>
<td class="ta-r" [ngClass]="bucketGap(b) >= 0 ? 'pos' : 'neg'">
{{ bucketGap(b) >= 0 ? '+' : '' }}{{ bucketGap(b) | number: '1.2-2' }}
</td>
<td class="ta-r" [ngClass]="cumulativeGap(i) >= 0 ? 'pos' : 'neg'">
{{ cumulativeGap(i) >= 0 ? '+' : '' }}{{ cumulativeGap(i) | number: '1.2-2' }}
</td>
<td class="ta-r">
<strong [ngClass]="cumulativeGap(i) >= 0 ? 'pos' : 'neg'"
>{{ cumulativeGapPct(i) >= 0 ? '+' : '' }}{{ cumulativeGapPct(i).toFixed(2) }}%</strong
>
</td>
</tr>
<tr class="form-total">
<td colspan="2"><strong>TOTAL</strong></td>
<td class="ta-r">
<strong>{{ totalOutflow | number: '1.2-2' }}</strong>
</td>
<td class="ta-r">
<strong>{{ totalInflow | number: '1.2-2' }}</strong>
</td>
<td class="ta-r" [ngClass]="totalGap >= 0 ? 'pos' : 'neg'">
<strong>{{ totalGap >= 0 ? '+' : '' }}{{ totalGap | number: '1.2-2' }}</strong>
</td>
<td colspan="2"></td>
</tr>
</tbody>
</table>
<h4 class="form-iii-t">C. RBI Tolerance Compliance</h4>
<table class="alm-tbl form-tbl">
<tbody>
<tr>
<td>Cumulative Negative Gap up to 14 days</td>
<td class="ta-r">
<strong>{{ gap14dPct.toFixed(2) }}%</strong>
</td>
<td class="ta-r">Limit: -{{ bank.tolerance14d }}%</td>
<td class="ta-c">
<span class="status-pill" [ngClass]="gap14dWithinTolerance ? 'on-target' : 'below'">
{{ gap14dWithinTolerance ? 'WITHIN' : 'BREACH' }}
</span>
</td>
</tr>
<tr>
<td>Cumulative Negative Gap up to 30 days</td>
<td class="ta-r">
<strong>{{ gap28dPct.toFixed(2) }}%</strong>
</td>
<td class="ta-r">Limit: -{{ bank.tolerance28d }}%</td>
<td class="ta-c">
<span class="status-pill" [ngClass]="gap28dWithinTolerance ? 'on-target' : 'below'">
{{ gap28dWithinTolerance ? 'WITHIN' : 'BREACH' }}
</span>
</td>
</tr>
</tbody>
</table>
<div class="form-iii-foot">
<div class="cert">
<div class="cert-l">Certified by</div>
<div class="cert-v">{{ bank.principalOfficer }}</div>
<div class="cert-r">Principal Officer — {{ bank.name }}</div>
</div>
<div class="cert">
<div class="cert-l">Submitted under</div>
<div class="cert-v mono">{{ bank.rbiCircular }}</div>
<div class="cert-r">RBI Master Direction on Asset Liability Management</div>
</div>
</div>
</div>
</div>
</ng-container>
</div>
</div>
@@ -0,0 +1,772 @@
/**
* 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/.
*/
:host {
--alm-good: #15803d;
--alm-good-l: #dcfce7;
--alm-mid: #d97706;
--alm-mid-l: #fef3c7;
--alm-low: #c0392b;
--alm-low-l: #fdecea;
--alm-blue: #2563eb;
--alm-blue-l: #dbeafe;
--alm-indigo: #4f46e5;
--alm-indigo-l: #e0e7ff;
--alm-g50: #f8fafc;
--alm-g100: #f1f5f9;
--alm-g200: #e2e8f0;
--alm-g300: #cbd5e1;
--alm-g500: #64748b;
--alm-g700: #334155;
--alm-g900: #0f172a;
display: block;
font-family:
'DM Sans',
system-ui,
-apple-system,
sans-serif;
color: var(--alm-g900);
}
.alm-dlg {
background: #fff;
border-radius: 14px;
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 90vh;
}
/* HEADER */
.alm-head {
padding: 18px 22px;
border-bottom: 1px solid var(--alm-g200);
display: flex;
align-items: center;
gap: 16px;
flex-shrink: 0;
background: linear-gradient(135deg, #eef2ff 0%, #fff 100%);
}
.alm-head-left {
display: flex;
align-items: center;
gap: 14px;
flex: 1;
}
.alm-head-ic {
width: 44px;
height: 44px;
border-radius: 11px;
background: var(--alm-indigo-l);
color: var(--alm-indigo);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.alm-title {
margin: 0;
font-size: 17px;
font-weight: 700;
color: var(--alm-g900);
}
.alm-sub {
margin: 2px 0 0;
font-size: 12px;
color: var(--alm-g500);
}
.alm-head-actions {
display: flex;
align-items: center;
gap: 12px;
}
.alm-overall {
display: flex;
align-items: baseline;
gap: 6px;
padding: 6px 14px;
border-radius: 999px;
font-weight: 700;
&.on-target {
background: var(--alm-good-l);
color: var(--alm-good);
}
&.below {
background: var(--alm-low-l);
color: var(--alm-low);
}
.alm-overall-l {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
}
.alm-overall-v {
font-size: 16px;
}
.alm-overall-t {
font-size: 11px;
font-weight: 600;
opacity: 0.8;
}
}
.alm-close {
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid var(--alm-g200);
background: #fff;
font-size: 14px;
color: var(--alm-g500);
cursor: pointer;
transition: all 0.15s;
&:hover {
background: var(--alm-g100);
color: var(--alm-g900);
}
}
/* TABS */
.alm-tabs {
display: flex;
background: var(--alm-g50);
border-bottom: 1px solid var(--alm-g200);
padding: 0 22px;
overflow-x: auto;
flex-shrink: 0;
}
.alm-tab {
display: flex;
align-items: center;
padding: 12px 18px;
border: none;
background: transparent;
cursor: pointer;
font-family: inherit;
font-size: 13px;
font-weight: 600;
color: var(--alm-g500);
border-bottom: 2px solid transparent;
transition: all 0.15s;
white-space: nowrap;
&:hover {
color: var(--alm-g700);
background: rgb(255 255 255 / 50%);
}
&.active {
background: #fff;
color: var(--alm-indigo);
border-bottom-color: var(--alm-indigo);
}
}
/* BODY */
.alm-body {
padding: 18px 22px;
display: flex;
flex-direction: column;
gap: 18px;
overflow: auto;
flex: 1;
}
/* KPI STATS */
.alm-stats {
display: grid;
grid-template-columns: 220px 1fr 1fr 1fr;
gap: 14px;
}
.alm-stat-card {
background: #fff;
border: 1px solid var(--alm-g200);
border-radius: 12px;
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 4px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.alm-stat-l {
font-size: 11px;
font-weight: 600;
color: var(--alm-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.alm-stat-v {
font-size: 20px;
font-weight: 700;
color: var(--alm-g900);
line-height: 1.2;
}
.alm-stat-meta {
font-size: 11px;
color: var(--alm-g500);
margin-top: 4px;
&.pos {
color: var(--alm-good);
font-weight: 600;
}
&.neg {
color: var(--alm-low);
font-weight: 600;
}
}
.alm-stat-gauge {
align-items: center;
justify-content: center;
}
/* GAUGE */
.gauge {
width: 140px;
height: 140px;
border-radius: 50%;
background: conic-gradient(currentcolor calc(var(--pct) * 1%), var(--alm-g100) 0);
display: flex;
align-items: center;
justify-content: center;
&.on-target {
color: var(--alm-good);
}
&.below {
color: var(--alm-low);
}
}
.gauge-inner {
width: 108px;
height: 108px;
border-radius: 50%;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: inset 0 0 0 1px var(--alm-g200);
}
.gauge-pct {
font-size: 20px;
font-weight: 700;
color: var(--alm-g900);
line-height: 1;
}
.gauge-label {
font-size: 10px;
color: var(--alm-g500);
margin-top: 4px;
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
}
/* SECTIONS */
.alm-section {
background: #fff;
border: 1px solid var(--alm-g200);
border-radius: 12px;
padding: 16px 18px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.alm-sec-h {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 14px;
flex-wrap: wrap;
}
.alm-sec-t {
margin: 0;
font-size: 14px;
font-weight: 700;
color: var(--alm-g700);
}
.alm-sec-actions {
display: flex;
gap: 8px;
align-items: center;
}
.alm-sel {
height: 34px;
padding: 0 10px;
border: 1px solid var(--alm-g200);
border-radius: 6px;
background: #fff;
font-family: inherit;
font-size: 12px;
color: var(--alm-g900);
&:focus {
outline: none;
border-color: var(--alm-indigo);
box-shadow: 0 0 0 3px rgb(79 70 229 / 12%);
}
}
.alm-circular {
font-family: 'DM Mono', monospace;
font-size: 11px;
color: var(--alm-g500);
padding: 4px 10px;
background: var(--alm-g50);
border-radius: 6px;
border: 1px solid var(--alm-g200);
}
.alm-btn-xls {
font-size: 12px !important;
height: 34px !important;
color: #fff !important;
background: var(--alm-indigo) !important;
}
/* TOLERANCE BARS */
.alm-tolerance {
display: flex;
flex-direction: column;
gap: 16px;
}
.alm-tol-row {
display: flex;
flex-direction: column;
gap: 6px;
&.good .alm-tol-fill {
background: var(--alm-good);
}
&.bad .alm-tol-fill {
background: var(--alm-low);
}
&.good .alm-tol-st {
background: var(--alm-good-l);
color: var(--alm-good);
}
&.bad .alm-tol-st {
background: var(--alm-low-l);
color: var(--alm-low);
}
}
.alm-tol-h {
display: flex;
justify-content: space-between;
align-items: center;
}
.alm-tol-l {
font-size: 12px;
font-weight: 600;
color: var(--alm-g700);
fa-icon {
color: var(--alm-indigo);
}
}
.alm-tol-p {
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
strong {
font-size: 14px;
color: var(--alm-g900);
}
.alm-tol-lim {
font-size: 11px;
color: var(--alm-g500);
}
.alm-tol-st {
font-size: 10px;
font-weight: 700;
padding: 2px 8px;
border-radius: 999px;
}
}
.alm-tol-track {
position: relative;
height: 8px;
background: var(--alm-g100);
border-radius: 4px;
overflow: hidden;
}
.alm-tol-fill {
height: 100%;
border-radius: 4px;
transition: width 0.4s ease-out;
}
.alm-tol-marker {
position: absolute;
top: -2px;
bottom: -2px;
left: 50%;
width: 2px;
background: var(--alm-g500);
}
/* HEATMAP */
.alm-heatmap {
display: grid;
grid-template-columns: repeat(11, 1fr);
gap: 6px;
}
.heat-cell {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px 6px;
border-radius: 8px;
text-align: center;
cursor: default;
transition: transform 0.15s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgb(0 0 0 / 8%);
}
&.surplus {
background: var(--alm-good-l);
color: var(--alm-good);
}
&.deficit {
background: var(--alm-low-l);
color: var(--alm-low);
}
&.balanced {
background: var(--alm-g100);
color: var(--alm-g500);
}
}
.heat-code {
font-family: 'DM Mono', monospace;
font-size: 10px;
font-weight: 700;
opacity: 0.7;
}
.heat-pct {
font-size: 14px;
font-weight: 800;
margin-top: 2px;
}
.heat-lbl {
font-size: 9px;
margin-top: 4px;
opacity: 0.85;
line-height: 1.1;
}
/* INFO GRID */
.alm-info-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px 24px;
}
.alm-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.alm-info-l {
font-size: 10px;
font-weight: 600;
color: var(--alm-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.alm-info-v {
font-size: 13px;
font-weight: 600;
color: var(--alm-g900);
}
/* TABLE */
.alm-tbl-wrap {
border: 1px solid var(--alm-g200);
border-radius: 8px;
overflow: auto;
margin-bottom: 12px;
}
.alm-tbl {
width: 100%;
border-collapse: collapse;
th {
text-align: left;
padding: 10px 14px;
font-size: 11px;
font-weight: 600;
color: var(--alm-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
background: var(--alm-g50);
border-bottom: 1px solid var(--alm-g200);
white-space: nowrap;
}
td {
padding: 10px 14px;
border-bottom: 1px solid var(--alm-g100);
font-size: 13px;
color: var(--alm-g700);
vertical-align: middle;
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: var(--alm-g50);
}
.row-total td {
background: var(--alm-indigo-l);
color: var(--alm-g900);
font-weight: 700;
border-top: 2px solid var(--alm-indigo);
}
.grp-liab {
background: var(--alm-low-l) !important;
color: var(--alm-low) !important;
}
.grp-asset {
background: var(--alm-good-l) !important;
color: var(--alm-good) !important;
}
}
.status-pill {
display: inline-block;
padding: 3px 10px;
font-size: 11px;
font-weight: 600;
border-radius: 999px;
&.on-target,
&.surplus {
background: var(--alm-good-l);
color: var(--alm-good);
}
&.below,
&.deficit {
background: var(--alm-low-l);
color: var(--alm-low);
}
&.balanced {
background: var(--alm-g100);
color: var(--alm-g500);
}
}
.ta-c {
text-align: center;
}
.ta-r {
text-align: right;
}
.mono {
font-family: 'DM Mono', monospace;
font-size: 12px;
}
.pos {
color: var(--alm-good);
}
.neg {
color: var(--alm-low);
}
/* FORM ALM-1 */
.form-iii {
background: var(--alm-g50);
border: 1px solid var(--alm-g200);
border-radius: 10px;
padding: 18px;
}
.form-iii-h {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 12px;
margin-bottom: 14px;
border-bottom: 2px solid var(--alm-indigo);
flex-wrap: wrap;
gap: 10px;
}
.form-iii-bank {
font-size: 14px;
color: var(--alm-g900);
}
.form-iii-meta {
display: flex;
gap: 18px;
font-size: 12px;
color: var(--alm-g500);
flex-wrap: wrap;
fa-icon {
color: var(--alm-indigo);
}
}
.form-iii-t {
font-size: 13px;
font-weight: 700;
color: var(--alm-g700);
margin: 16px 0 8px;
padding-bottom: 4px;
border-bottom: 1px dashed var(--alm-g200);
}
.form-tbl {
background: #fff;
border: 1px solid var(--alm-g200);
border-radius: 6px;
overflow: hidden;
}
.form-total td {
background: var(--alm-indigo-l) !important;
font-weight: 700;
color: var(--alm-g900);
}
.form-iii-foot {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
margin-top: 18px;
padding-top: 16px;
border-top: 1px dashed var(--alm-g300);
}
.cert {
background: #fff;
border: 1px solid var(--alm-g200);
border-radius: 8px;
padding: 12px 14px;
}
.cert-l {
font-size: 10px;
font-weight: 600;
color: var(--alm-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.cert-v {
font-size: 14px;
font-weight: 700;
color: var(--alm-g900);
margin: 4px 0;
}
.cert-r {
font-size: 11px;
color: var(--alm-g500);
}
/* RESPONSIVE */
@media (width <= 1100px) {
.alm-stats {
grid-template-columns: 1fr 1fr;
}
.alm-info-grid {
grid-template-columns: 1fr 1fr;
}
.form-iii-foot {
grid-template-columns: 1fr;
}
.alm-heatmap {
grid-template-columns: repeat(6, 1fr);
}
}
@media (width <= 720px) {
.alm-stats,
.alm-info-grid {
grid-template-columns: 1fr;
}
.alm-head {
flex-wrap: wrap;
}
.alm-heatmap {
grid-template-columns: repeat(3, 1fr);
}
}
@@ -0,0 +1,468 @@
/**
* 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/.
*/
import { Component, inject } from '@angular/core';
import { CommonModule, DecimalPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatDialogRef, MatDialogModule } from '@angular/material/dialog';
import { MatButtonModule } from '@angular/material/button';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
interface MaturityBucket {
code: string;
label: string;
/** Outflows / Liabilities (₹ Lakhs) */
outflow: number;
/** Inflows / Assets (₹ Lakhs) */
inflow: number;
}
interface MonthSnapshot {
month: string;
reserveProv: number;
borrowings: number;
fds: number;
cashOnHand: number;
investments: number;
otherAssets: number;
}
@Component({
selector: 'mifosx-alm-report-dialog',
standalone: true,
templateUrl: './alm-report-dialog.component.html',
styleUrls: ['./alm-report-dialog.component.scss'],
imports: [
CommonModule,
DecimalPipe,
FormsModule,
MatDialogModule,
MatButtonModule,
MatTooltipModule,
FaIconComponent
]
})
export class AlmReportDialogComponent {
private dialogRef = inject(MatDialogRef<AlmReportDialogComponent>);
activeTab: 'dashboard' | 'ladder' | 'monthly' | 'returns' = 'dashboard';
bank = {
name: 'Bharat Urban Bank Ltd',
branch: 'Head Office',
asOnDate: '31-03-2026',
quarter: 'Q4 FY 2025-26',
fiscalYear: '2025-26',
rbiCircular: 'DCBR.BPD.(PCB).Cir.No.4/12.05.001/2018-19',
principalOfficer: 'Karthik Kamath',
formCode: 'Form ALM-1',
reportRefNo: '1036',
userCode: 'BUBL01',
/** Net Demand & Time Liabilities — denominator for ratios */
ndtl: 740000, // ₹7,400 Cr in Lakhs
/** RBI tolerance: cumulative negative gap up to 14 days ≤ 5%, 28 days ≤ 10% */
tolerance14d: 5,
tolerance28d: 10
};
/** Standard RBI ALM time buckets per Master Direction */
buckets: MaturityBucket[] = [
{ code: 'B01', label: 'Day 1', outflow: 1840, inflow: 2120 },
{ code: 'B02', label: '2 to 7 days', outflow: 6420, inflow: 5880 },
{ code: 'B03', label: '8 to 14 days', outflow: 4180, inflow: 4360 },
{ code: 'B04', label: '15 to 30 days', outflow: 8240, inflow: 7920 },
{ code: 'B05', label: '31 days to 2 months', outflow: 12480, inflow: 13160 },
{ code: 'B06', label: '> 2 months to 3 months', outflow: 9620, inflow: 10240 },
{ code: 'B07', label: '> 3 months to 6 months', outflow: 28640, inflow: 32180 },
{ code: 'B08', label: '> 6 months to 1 year', outflow: 64280, inflow: 68420 },
{ code: 'B09', label: '> 1 year to 3 years', outflow: 142800, inflow: 156400 },
{ code: 'B10', label: '> 3 years to 5 years', outflow: 96400, inflow: 108200 },
{ code: 'B11', label: 'Over 5 years', outflow: 124800, inflow: 138400 }
];
/** Monthly snapshot — mirrors the format of the legacy ALM Excel */
monthly: MonthSnapshot[] = [
{
month: 'APR-2025',
reserveProv: 18420,
borrowings: 24180,
fds: 462800,
cashOnHand: 8420,
investments: 184600,
otherAssets: 12480
},
{
month: 'MAY-2025',
reserveProv: 18640,
borrowings: 23980,
fds: 468200,
cashOnHand: 8680,
investments: 186400,
otherAssets: 12620
},
{
month: 'JUN-2025',
reserveProv: 18820,
borrowings: 23640,
fds: 472400,
cashOnHand: 9120,
investments: 188200,
otherAssets: 12840
},
{
month: 'JUL-2025',
reserveProv: 19080,
borrowings: 23420,
fds: 478600,
cashOnHand: 9280,
investments: 190400,
otherAssets: 13020
},
{
month: 'AUG-2025',
reserveProv: 19280,
borrowings: 23180,
fds: 482400,
cashOnHand: 9420,
investments: 192800,
otherAssets: 13280
},
{
month: 'SEP-2025',
reserveProv: 19460,
borrowings: 22940,
fds: 488200,
cashOnHand: 9640,
investments: 195200,
otherAssets: 13420
},
{
month: 'OCT-2025',
reserveProv: 19680,
borrowings: 22680,
fds: 492600,
cashOnHand: 9820,
investments: 197600,
otherAssets: 13620
},
{
month: 'NOV-2025',
reserveProv: 19880,
borrowings: 22420,
fds: 498400,
cashOnHand: 9980,
investments: 200200,
otherAssets: 13820
},
{
month: 'DEC-2025',
reserveProv: 20120,
borrowings: 22180,
fds: 504200,
cashOnHand: 10240,
investments: 202800,
otherAssets: 14040
},
{
month: 'JAN-2026',
reserveProv: 20340,
borrowings: 21940,
fds: 508800,
cashOnHand: 10420,
investments: 205200,
otherAssets: 14240
},
{
month: 'FEB-2026',
reserveProv: 20580,
borrowings: 21680,
fds: 514600,
cashOnHand: 10620,
investments: 207800,
otherAssets: 14460
},
{
month: 'MAR-2026',
reserveProv: 20840,
borrowings: 21420,
fds: 520400,
cashOnHand: 10820,
investments: 210400,
otherAssets: 14680
}
];
filterBucket = '';
// -------------------- Computed --------------------
bucketGap(b: MaturityBucket): number {
return b.inflow - b.outflow;
}
bucketGapPct(b: MaturityBucket): number {
return b.outflow === 0 ? 0 : +(((b.inflow - b.outflow) / b.outflow) * 100).toFixed(2);
}
cumulativeOutflow(idx: number): number {
return this.buckets.slice(0, idx + 1).reduce((s, b) => s + b.outflow, 0);
}
cumulativeInflow(idx: number): number {
return this.buckets.slice(0, idx + 1).reduce((s, b) => s + b.inflow, 0);
}
cumulativeGap(idx: number): number {
return this.cumulativeInflow(idx) - this.cumulativeOutflow(idx);
}
cumulativeGapPct(idx: number): number {
const out = this.cumulativeOutflow(idx);
return out === 0 ? 0 : +((this.cumulativeGap(idx) / out) * 100).toFixed(2);
}
get totalOutflow(): number {
return this.buckets.reduce((s, b) => s + b.outflow, 0);
}
get totalInflow(): number {
return this.buckets.reduce((s, b) => s + b.inflow, 0);
}
get totalGap(): number {
return this.totalInflow - this.totalOutflow;
}
get totalGapPct(): number {
return this.totalOutflow === 0 ? 0 : +((this.totalGap / this.totalOutflow) * 100).toFixed(2);
}
/** RBI tolerance test: cumulative negative gap up to bucket 3 (14 days) ≤ 5% of cumulative outflow */
get gap14dPct(): number {
return this.cumulativeGapPct(2);
}
/** Up to bucket 4 (30 days) ≤ 10% */
get gap28dPct(): number {
return this.cumulativeGapPct(3);
}
get gap14dWithinTolerance(): boolean {
return this.gap14dPct >= -this.bank.tolerance14d;
}
get gap28dWithinTolerance(): boolean {
return this.gap28dPct >= -this.bank.tolerance28d;
}
bucketStatus(b: MaturityBucket): 'surplus' | 'deficit' | 'balanced' {
const gap = this.bucketGap(b);
if (gap > 0) return 'surplus';
if (gap < 0) return 'deficit';
return 'balanced';
}
/** Monthly totals helpers */
monthTotalLiab(m: MonthSnapshot): number {
return m.reserveProv + m.borrowings + m.fds;
}
monthTotalAssets(m: MonthSnapshot): number {
return m.cashOnHand + m.investments + m.otherAssets;
}
/** Average across months */
avg<K extends keyof MonthSnapshot>(key: K): number {
if (typeof this.monthly[0][key] !== 'number') return 0;
const total = this.monthly.reduce((s, m) => s + (m[key] as number), 0);
return +(total / this.monthly.length).toFixed(2);
}
// -------------------- Filter --------------------
get filteredBuckets(): MaturityBucket[] {
if (!this.filterBucket) return this.buckets;
if (this.filterBucket === 'short') return this.buckets.slice(0, 4);
if (this.filterBucket === 'medium') return this.buckets.slice(4, 8);
if (this.filterBucket === 'long') return this.buckets.slice(8);
return this.buckets;
}
// -------------------- Excel Exports --------------------
/** Form ALM-1 — Statement of Structural Liquidity */
exportFormAlm(): void {
const rows: string[][] = [];
rows.push([
this.bank.name,
this.bank.branch
]);
rows.push([
`STATEMENT OF STRUCTURAL LIQUIDITY (${this.bank.formCode})`,
`As On ${this.bank.asOnDate}`
]);
rows.push([
`RBI Circular: ${this.bank.rbiCircular}`,
`Quarter: ${this.bank.quarter}`
]);
rows.push([
`Report Ref: ${this.bank.reportRefNo}`,
`User Code: ${this.bank.userCode}`
]);
rows.push([]);
rows.push([
'NDTL (₹ Lakhs)',
this.bank.ndtl.toString()
]);
rows.push([]);
rows.push(['Maturity Ladder (₹ in Lakhs)']);
rows.push([
'Bucket',
'Time Period',
'Outflows (Liabilities)',
'Inflows (Assets)',
'Mismatch (Gap)',
'Cum. Outflows',
'Cum. Inflows',
'Cum. Gap',
'Cum. Gap % of Outflow'
]);
this.buckets.forEach((b, i) => {
rows.push([
b.code,
b.label,
b.outflow.toFixed(2),
b.inflow.toFixed(2),
this.bucketGap(b).toFixed(2),
this.cumulativeOutflow(i).toFixed(2),
this.cumulativeInflow(i).toFixed(2),
this.cumulativeGap(i).toFixed(2),
this.cumulativeGapPct(i).toFixed(2) + '%'
]);
});
rows.push([]);
rows.push([
'TOTAL',
'',
this.totalOutflow.toFixed(2),
this.totalInflow.toFixed(2),
this.totalGap.toFixed(2)
]);
rows.push([]);
rows.push(['RBI Tolerance Compliance']);
rows.push([
'Cumulative Gap up to 14 days',
this.gap14dPct.toFixed(2) + '%',
`Limit: -${this.bank.tolerance14d}%`,
this.gap14dWithinTolerance ? 'WITHIN' : 'BREACH'
]);
rows.push([
'Cumulative Gap up to 30 days',
this.gap28dPct.toFixed(2) + '%',
`Limit: -${this.bank.tolerance28d}%`,
this.gap28dWithinTolerance ? 'WITHIN' : 'BREACH'
]);
rows.push([]);
rows.push([
'Certified by',
this.bank.principalOfficer + ' — Principal Officer'
]);
this.saveTsv(rows, `ALM-StructuralLiquidity-${this.dateTag()}.xls`);
}
/** Monthly snapshot export */
exportMonthlyXls(): void {
const rows: string[][] = [];
rows.push([
`${this.bank.name} — ${this.bank.branch}`,
`Monthly ALM Snapshot — FY ${this.bank.fiscalYear}`
]);
rows.push([]);
rows.push([
'Month',
'Reserve/Prov (Excl. OIR)',
'Borrowings',
'FDs',
'Total Liabilities',
'Cash on Hand',
'Investments incl. Bal/Repo',
'Other Assets (Excl. IRA)',
'Total Assets'
]);
for (const m of this.monthly) {
rows.push([
m.month,
m.reserveProv.toFixed(2),
m.borrowings.toFixed(2),
m.fds.toFixed(2),
this.monthTotalLiab(m).toFixed(2),
m.cashOnHand.toFixed(2),
m.investments.toFixed(2),
m.otherAssets.toFixed(2),
this.monthTotalAssets(m).toFixed(2)
]);
}
rows.push([
'AVERAGE',
this.avg('reserveProv').toFixed(2),
this.avg('borrowings').toFixed(2),
this.avg('fds').toFixed(2),
(this.avg('reserveProv') + this.avg('borrowings') + this.avg('fds')).toFixed(2),
this.avg('cashOnHand').toFixed(2),
this.avg('investments').toFixed(2),
this.avg('otherAssets').toFixed(2),
(this.avg('cashOnHand') + this.avg('investments') + this.avg('otherAssets')).toFixed(2)
]);
this.saveTsv(rows, `ALM-Monthly-${this.dateTag()}.xls`);
}
/** Maturity ladder CSV */
exportLadderCsv(): void {
const headers = [
'Bucket',
'Time Period',
'Outflow (₹ Lakh)',
'Inflow (₹ Lakh)',
'Gap',
'Gap %'
];
const rows = [headers];
for (const b of this.buckets) {
rows.push([
b.code,
b.label,
b.outflow.toString(),
b.inflow.toString(),
this.bucketGap(b).toString(),
this.bucketGapPct(b).toFixed(2) + '%'
]);
}
this.saveCsv(rows, `ALM-Ladder-${this.dateTag()}.csv`);
}
setTab(tab: 'dashboard' | 'ladder' | 'monthly' | 'returns'): void {
this.activeTab = tab;
}
close(): void {
this.dialogRef.close();
}
// ---- helpers ----
private dateTag(): string {
const d = new Date();
return `${String(d.getDate()).padStart(2, '0')}${String(d.getMonth() + 1).padStart(2, '0')}${d.getFullYear()}`;
}
private saveTsv(rows: string[][], filename: string): void {
const tsv = rows.map((r) => r.map((v) => (v || '').replace(/\t/g, ' ')).join('\t')).join('\n');
this.saveBlob(tsv, filename, 'application/vnd.ms-excel');
}
private saveCsv(rows: string[][], filename: string): void {
const escape = (v: string): string => {
const s = (v || '').replace(/"/g, '""');
if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s}"`;
return s;
};
const csv = rows.map((r) => r.map(escape).join(',')).join('\n') + '\n';
this.saveBlob(csv, filename, 'text/csv');
}
private saveBlob(content: string, filename: string, mime: string): void {
const blob = new Blob([content], { type: `${mime};charset=utf-8` });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
}
@@ -0,0 +1,135 @@
<!--
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/.
-->
<div class="cic-dlg">
<div class="cic-head">
<div class="cic-head-left">
<div class="cic-head-ic"><fa-icon icon="file-alt"></fa-icon></div>
<div>
<h2 class="cic-title">Credit Information Companies — Data Quality Report</h2>
<p class="cic-sub">
Reporting period: <strong>{{ reportingPeriod }}</strong> &middot; as per RBI Master Direction on Credit
Information Reporting
</p>
</div>
</div>
<div class="cic-head-actions">
<button
mat-stroked-button
class="cic-btn-ghost"
(click)="downloadAllExcel()"
matTooltip="Download all bureaus (CIBIL Consumer schema)"
>
<fa-icon icon="file-excel"></fa-icon>&nbsp; Download All
</button>
<button class="cic-close" (click)="close()" matTooltip="Close" aria-label="Close">✕</button>
</div>
</div>
<!-- BUREAU TABS -->
<div class="cic-tabs">
<button *ngFor="let r of reports" class="cic-tab" [class.active]="r.key === activeTab" (click)="setTab(r.key)">
<img [src]="r.logo" [alt]="r.name" class="cic-tab-logo" />
<span class="cic-tab-name">{{ r.name }}</span>
<span class="cic-tab-pct" [ngClass]="dqrBadge(r.dqrPct)">{{ r.dqrPct.toFixed(1) }}%</span>
</button>
</div>
<div class="cic-body">
<!-- TOP STATS ROW -->
<div class="cic-stats">
<div class="cic-stat-card cic-stat-gauge">
<div class="gauge" [style.--pct.%]="current.dqrPct" [ngClass]="dqrBadge(current.dqrPct)">
<div class="gauge-inner">
<div class="gauge-pct">{{ current.dqrPct.toFixed(1) }}%</div>
<div class="gauge-label">DQR Score</div>
</div>
</div>
</div>
<div class="cic-stat-card">
<div class="cic-stat-l">Status</div>
<div class="cic-stat-v">
<span class="status-pill" [ngClass]="statusClass(current.status)">{{ current.status }}</span>
</div>
<div class="cic-stat-meta">
File: <span class="mono">{{ current.fileRef }}</span>
</div>
</div>
<div class="cic-stat-card">
<div class="cic-stat-l">Last Submitted</div>
<div class="cic-stat-v">{{ current.lastSubmitted }}</div>
<div class="cic-stat-meta">Next due: {{ current.nextDue }}</div>
</div>
<div class="cic-stat-card">
<div class="cic-stat-l">Records This Cycle</div>
<div class="cic-stat-v">{{ current.totalRecords | number }}</div>
<div class="cic-stat-meta">
<span class="badge ok">{{ current.accepted | number }} accepted</span>
<span class="badge warn">{{ current.warnings | number }} warnings</span>
<span class="badge err">{{ current.rejected | number }} rejected</span>
</div>
</div>
</div>
<!-- SUB-DQR BREAKDOWN -->
<div class="cic-section">
<h3 class="cic-sec-t">Sub-DQR Breakdown</h3>
<div class="cic-bars">
<div class="cic-bar" *ngFor="let m of current.subMetrics">
<div class="cic-bar-h">
<span class="cic-bar-l">{{ m.label }}</span>
<span class="cic-bar-p" [ngClass]="dqrBadge(m.pct)">{{ m.pct.toFixed(1) }}%</span>
</div>
<div class="cic-bar-track">
<div class="cic-bar-fill" [style.width.%]="m.pct" [ngClass]="dqrBadge(m.pct)"></div>
</div>
</div>
</div>
</div>
<!-- ERRORS TABLE -->
<div class="cic-section">
<div class="cic-sec-h">
<h3 class="cic-sec-t">Error &amp; Warning Details — Last Submission</h3>
<button
mat-flat-button
color="primary"
class="cic-btn-xls"
(click)="downloadExcel()"
matTooltip="Download CIBIL Consumer schema (.xls)"
>
<fa-icon icon="file-excel"></fa-icon>&nbsp; Download Excel
</button>
</div>
<div class="cic-tbl-wrap">
<table class="cic-tbl">
<thead>
<tr>
<th>Code</th>
<th>Segment</th>
<th>Description</th>
<th class="ta-c">Severity</th>
<th class="ta-r">Records</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let e of current.errors">
<td class="mono">{{ e.code }}</td>
<td>{{ e.segment }}</td>
<td>{{ e.description }}</td>
<td class="ta-c">
<span class="sev" [ngClass]="'sev-' + e.severity.toLowerCase()">{{ e.severity }}</span>
</td>
<td class="ta-r mono">{{ e.count | number }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
@@ -0,0 +1,528 @@
/**
* 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/.
*/
:host {
--cic-good: #15803d;
--cic-good-l: #dcfce7;
--cic-mid: #d97706;
--cic-mid-l: #fef3c7;
--cic-low: #c0392b;
--cic-low-l: #fdecea;
--cic-purple: #6d28d9;
--cic-purple-l: #ede9fe;
--cic-g50: #f8fafc;
--cic-g100: #f1f5f9;
--cic-g200: #e2e8f0;
--cic-g300: #cbd5e1;
--cic-g500: #64748b;
--cic-g700: #334155;
--cic-g900: #0f172a;
display: block;
font-family:
'DM Sans',
system-ui,
-apple-system,
sans-serif;
color: var(--cic-g900);
}
.cic-dlg {
background: #fff;
border-radius: 14px;
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 90vh;
}
/* HEADER */
.cic-head {
padding: 18px 22px;
border-bottom: 1px solid var(--cic-g200);
display: flex;
align-items: center;
gap: 16px;
flex-shrink: 0;
background: linear-gradient(135deg, #f9fafb 0%, #fff 100%);
}
.cic-head-left {
display: flex;
align-items: center;
gap: 14px;
flex: 1;
}
.cic-head-ic {
width: 44px;
height: 44px;
border-radius: 11px;
background: var(--cic-purple-l);
color: var(--cic-purple);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.cic-title {
margin: 0;
font-size: 17px;
font-weight: 700;
color: var(--cic-g900);
}
.cic-sub {
margin: 2px 0 0;
font-size: 12px;
color: var(--cic-g500);
}
.cic-head-actions {
display: flex;
align-items: center;
gap: 10px;
}
.cic-btn-ghost {
font-size: 12px !important;
height: 36px !important;
}
.cic-close {
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid var(--cic-g200);
background: #fff;
font-size: 14px;
color: var(--cic-g500);
cursor: pointer;
transition: all 0.15s;
&:hover {
background: var(--cic-g100);
color: var(--cic-g900);
}
}
/* TABS */
.cic-tabs {
display: flex;
gap: 0;
background: var(--cic-g50);
border-bottom: 1px solid var(--cic-g200);
padding: 0 22px;
overflow-x: auto;
flex-shrink: 0;
}
.cic-tab {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
border: none;
background: transparent;
cursor: pointer;
font-family: inherit;
border-bottom: 2px solid transparent;
transition: all 0.15s;
white-space: nowrap;
&:hover {
background: rgb(255 255 255 / 50%);
}
&.active {
background: #fff;
border-bottom-color: var(--cic-purple);
}
}
.cic-tab-logo {
width: 28px;
height: 28px;
border-radius: 6px;
flex-shrink: 0;
}
.cic-tab-name {
font-size: 13px;
font-weight: 600;
color: var(--cic-g700);
}
.cic-tab-pct {
font-size: 11px;
font-weight: 700;
padding: 2px 8px;
border-radius: 999px;
&.good {
background: var(--cic-good-l);
color: var(--cic-good);
}
&.moderate {
background: var(--cic-mid-l);
color: var(--cic-mid);
}
&.low {
background: var(--cic-low-l);
color: var(--cic-low);
}
}
/* BODY */
.cic-body {
padding: 18px 22px;
display: flex;
flex-direction: column;
gap: 18px;
overflow: auto;
flex: 1;
}
/* TOP STAT CARDS */
.cic-stats {
display: grid;
grid-template-columns: 220px 1fr 1fr 1.4fr;
gap: 14px;
}
.cic-stat-card {
background: #fff;
border: 1px solid var(--cic-g200);
border-radius: 12px;
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 4px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.cic-stat-l {
font-size: 11px;
font-weight: 600;
color: var(--cic-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.cic-stat-v {
font-size: 18px;
font-weight: 700;
color: var(--cic-g900);
line-height: 1.2;
}
.cic-stat-meta {
font-size: 11px;
color: var(--cic-g500);
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 4px;
}
.cic-stat-gauge {
align-items: center;
justify-content: center;
}
/* GAUGE */
.gauge {
width: 140px;
height: 140px;
border-radius: 50%;
background: conic-gradient(currentcolor calc(var(--pct) * 1%), var(--cic-g100) 0);
display: flex;
align-items: center;
justify-content: center;
&.good {
color: var(--cic-good);
}
&.moderate {
color: var(--cic-mid);
}
&.low {
color: var(--cic-low);
}
}
.gauge-inner {
width: 108px;
height: 108px;
border-radius: 50%;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: inset 0 0 0 1px var(--cic-g200);
}
.gauge-pct {
font-size: 22px;
font-weight: 700;
color: var(--cic-g900);
line-height: 1;
}
.gauge-label {
font-size: 10px;
color: var(--cic-g500);
margin-top: 2px;
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
}
/* STATUS PILL */
.status-pill {
display: inline-block;
padding: 3px 10px;
font-size: 11px;
font-weight: 600;
border-radius: 999px;
&.st-good {
background: var(--cic-good-l);
color: var(--cic-good);
}
&.st-mid {
background: var(--cic-mid-l);
color: var(--cic-mid);
}
&.st-low {
background: var(--cic-low-l);
color: var(--cic-low);
}
}
/* MINI BADGES IN STAT META */
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 999px;
font-size: 10px;
font-weight: 600;
&.ok {
background: var(--cic-good-l);
color: var(--cic-good);
}
&.warn {
background: var(--cic-mid-l);
color: var(--cic-mid);
}
&.err {
background: var(--cic-low-l);
color: var(--cic-low);
}
}
/* SECTIONS */
.cic-section {
background: #fff;
border: 1px solid var(--cic-g200);
border-radius: 12px;
padding: 16px 18px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.cic-sec-h {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.cic-sec-t {
margin: 0 0 12px;
font-size: 14px;
font-weight: 700;
color: var(--cic-g700);
}
.cic-sec-h .cic-sec-t {
margin-bottom: 0;
}
.cic-btn-xls {
font-size: 12px !important;
height: 34px !important;
color: #fff !important;
background: #16a34a !important;
}
/* SUB-DQR BARS */
.cic-bars {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px 24px;
}
.cic-bar-h {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.cic-bar-l {
font-size: 12px;
color: var(--cic-g700);
font-weight: 500;
}
.cic-bar-p {
font-size: 12px;
font-weight: 700;
&.good {
color: var(--cic-good);
}
&.moderate {
color: var(--cic-mid);
}
&.low {
color: var(--cic-low);
}
}
.cic-bar-track {
height: 7px;
background: var(--cic-g100);
border-radius: 4px;
overflow: hidden;
}
.cic-bar-fill {
height: 100%;
border-radius: 4px;
transition: width 0.3s ease-out;
&.good {
background: var(--cic-good);
}
&.moderate {
background: var(--cic-mid);
}
&.low {
background: var(--cic-low);
}
}
/* ERRORS TABLE */
.cic-tbl-wrap {
border: 1px solid var(--cic-g200);
border-radius: 8px;
overflow: hidden;
}
.cic-tbl {
width: 100%;
border-collapse: collapse;
th {
text-align: left;
padding: 10px 14px;
font-size: 11px;
font-weight: 600;
color: var(--cic-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
background: var(--cic-g50);
border-bottom: 1px solid var(--cic-g200);
}
td {
padding: 10px 14px;
border-bottom: 1px solid var(--cic-g100);
font-size: 13px;
color: var(--cic-g700);
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: var(--cic-g50);
}
}
.ta-c {
text-align: center;
}
.ta-r {
text-align: right;
}
.mono {
font-family: 'DM Mono', monospace;
font-size: 12px;
}
.sev {
display: inline-block;
padding: 2px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
&.sev-critical {
background: var(--cic-low-l);
color: var(--cic-low);
}
&.sev-warning {
background: var(--cic-mid-l);
color: var(--cic-mid);
}
&.sev-info {
background: #e0e7ff;
color: #4338ca;
}
}
/* RESPONSIVE */
@media (width <= 1100px) {
.cic-stats {
grid-template-columns: 1fr 1fr;
}
.cic-bars {
grid-template-columns: 1fr;
}
}
@media (width <= 700px) {
.cic-stats {
grid-template-columns: 1fr;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,260 @@
<!--
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/.
-->
<div class="cims-dlg">
<!-- HEADER -->
<div class="cims-head">
<div class="cims-head-left">
<div class="cims-head-ic"><fa-icon icon="code"></fa-icon></div>
<div>
<h2 class="cims-title">CIMS / XBRL Returns &mdash; {{ currentForm.name }}</h2>
<p class="cims-sub" [innerHTML]="currentForm.fullName"></p>
</div>
</div>
<div class="cims-head-actions">
<span class="cims-status" [ngClass]="validation.status">
<fa-icon [icon]="validation.status === 'valid' ? 'check-double' : 'exclamation-triangle'"></fa-icon>
&nbsp;{{ validation.populated }}/{{ validation.total }} populated
</span>
<button class="cims-close" (click)="close()" matTooltip="Close" aria-label="Close">&#10005;</button>
</div>
</div>
<!-- FORM SELECTOR -->
<div class="cims-form-bar">
<span class="cims-form-bar-l">Active Form:</span>
<button
*ngFor="let code of formCodes"
class="cims-form-pill"
[class.active]="activeForm === code"
(click)="setForm(code)"
[matTooltip]="forms[code].fullName"
>
{{ forms[code].name }}
</button>
<span class="cims-form-meta mono"> <fa-icon icon="hashtag"></fa-icon>&nbsp;{{ currentForm.cimsCode }} </span>
<span class="cims-form-meta"> <fa-icon icon="calendar-alt"></fa-icon>&nbsp;{{ currentForm.periodicity }} </span>
<span class="cims-form-meta">
<fa-icon icon="clock"></fa-icon>&nbsp;Next due: <strong>{{ currentForm.nextDue }}</strong>
</span>
</div>
<!-- TABS -->
<div class="cims-tabs">
<button class="cims-tab" [class.active]="activeTab === 'filing'" (click)="setTab('filing')">
<fa-icon icon="address-card"></fa-icon>&nbsp; Filing Information
</button>
<button class="cims-tab" [class.active]="activeTab === 'data'" (click)="setTab('data')">
<fa-icon icon="layer-group"></fa-icon>&nbsp; Form Data &amp; Facts
</button>
<button class="cims-tab" [class.active]="activeTab === 'preview'" (click)="setTab('preview')">
<fa-icon icon="code"></fa-icon>&nbsp; XBRL XML Preview
</button>
</div>
<div class="cims-body">
<!-- ════════════ FILING INFO ════════════ -->
<ng-container *ngIf="activeTab === 'filing'">
<div class="cims-section">
<div class="cims-sec-h">
<h3 class="cims-sec-t">Reporting Entity</h3>
</div>
<div class="cims-info-grid">
<div class="cims-info">
<div class="cims-info-l">Bank Name</div>
<div class="cims-info-v">{{ bank.name }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">Branch</div>
<div class="cims-info-v">{{ bank.branch }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">BSR Code</div>
<div class="cims-info-v mono">{{ bank.bsrCode }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">IFSC</div>
<div class="cims-info-v mono">{{ bank.ifsc }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">Legal Entity ID</div>
<div class="cims-info-v mono">{{ bank.legalEntityId }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">PAN</div>
<div class="cims-info-v mono">{{ bank.pan }}</div>
</div>
</div>
</div>
<div class="cims-section">
<div class="cims-sec-h">
<h3 class="cims-sec-t">Return Particulars</h3>
</div>
<div class="cims-info-grid">
<div class="cims-info">
<div class="cims-info-l">CIMS Form Code</div>
<div class="cims-info-v mono">{{ currentForm.cimsCode }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">Taxonomy Version</div>
<div class="cims-info-v mono">{{ currentForm.taxonomy }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">Periodicity</div>
<div class="cims-info-v">{{ currentForm.periodicity }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">Reporting Period</div>
<div class="cims-info-v">{{ bank.quarter }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">As On Date</div>
<div class="cims-info-v">{{ bank.asOnDate }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">Last Filed</div>
<div class="cims-info-v">{{ currentForm.lastFiled }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">Next Due</div>
<div class="cims-info-v">
<strong>{{ currentForm.nextDue }}</strong>
</div>
</div>
<div class="cims-info">
<div class="cims-info-l">CIMS Portal</div>
<div class="cims-info-v mono">{{ bank.cimsPortal }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">CIMS User ID</div>
<div class="cims-info-v mono">{{ bank.cimsUserId }}</div>
</div>
</div>
<div class="cims-desc" [innerHTML]="currentForm.description"></div>
</div>
<div class="cims-section">
<div class="cims-sec-h">
<h3 class="cims-sec-t">Principal Officer</h3>
</div>
<div class="cims-info-grid">
<div class="cims-info">
<div class="cims-info-l">Name</div>
<div class="cims-info-v">{{ bank.principalOfficer }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">Designation</div>
<div class="cims-info-v">{{ bank.poDesignation }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">Email</div>
<div class="cims-info-v mono">{{ bank.poEmail }}</div>
</div>
<div class="cims-info">
<div class="cims-info-l">Mobile</div>
<div class="cims-info-v mono">{{ bank.poMobile }}</div>
</div>
</div>
</div>
</ng-container>
<!-- ════════════ FORM DATA ════════════ -->
<ng-container *ngIf="activeTab === 'data'">
<div class="cims-section" *ngFor="let sec of sections">
<div class="cims-sec-h">
<h3 class="cims-sec-t">{{ sec }}</h3>
</div>
<div class="cims-tbl-wrap">
<table class="cims-tbl">
<thead>
<tr>
<th>XBRL Element</th>
<th>Label</th>
<th class="ta-r">Value</th>
<th class="ta-c">Unit</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let f of fieldsBySection(sec)">
<td class="mono cell-ref">{{ f.ref }}</td>
<td [innerHTML]="f.label"></td>
<td class="ta-r">
<strong>{{ f.value }}</strong>
<span *ngIf="f.type === 'percent'" class="muted">&nbsp;%</span>
</td>
<td class="ta-c">
<span class="unit-pill">{{ f.unit }}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</ng-container>
<!-- ════════════ XBRL PREVIEW ════════════ -->
<ng-container *ngIf="activeTab === 'preview'">
<div class="cims-section">
<div class="cims-sec-h">
<h3 class="cims-sec-t">XBRL Instance Document Preview</h3>
<div class="cims-sec-actions">
<button mat-stroked-button class="cims-btn-ghost" (click)="copyXbrl()" matTooltip="Copy XML to clipboard">
<fa-icon icon="copy"></fa-icon>&nbsp; Copy
</button>
<button mat-stroked-button class="cims-btn-xls" (click)="exportXbrl()">
<fa-icon icon="file-export"></fa-icon>&nbsp; Download .xml
</button>
<button mat-stroked-button class="cims-btn-xls cims-btn-excel" (click)="exportExcel()">
<fa-icon icon="file-excel"></fa-icon>&nbsp; Download Excel
</button>
</div>
</div>
<div class="cims-validation" [ngClass]="validation.status">
<fa-icon [icon]="validation.status === 'valid' ? 'check-double' : 'exclamation-triangle'"></fa-icon>
<div>
<div class="cims-val-t">
<ng-container *ngIf="validation.status === 'valid'"
>XBRL Document Valid &mdash; ready to upload</ng-container
>
<ng-container *ngIf="validation.status === 'warning'"
>{{ validation.empty }} field(s) need attention</ng-container
>
</div>
<div class="cims-val-d">
{{ validation.populated }} of {{ validation.total }} elements populated &middot; Taxonomy
<span class="mono">{{ currentForm.taxonomy }}</span>
</div>
</div>
</div>
<pre class="xbrl-preview"><code>{{ generateXbrlXml() }}</code></pre>
</div>
</ng-container>
</div>
<!-- FOOTER ACTIONS -->
<div class="cims-foot">
<div class="cims-foot-l">
<fa-icon icon="shield-alt"></fa-icon>&nbsp;
<span
>Submitted to RBI CIMS Portal under <strong>{{ currentForm.cimsCode }}</strong> by
{{ bank.principalOfficer }}</span
>
</div>
<div class="cims-foot-actions">
<button mat-stroked-button class="cims-btn-ghost" (click)="exportExcel()">
<fa-icon icon="file-excel"></fa-icon>&nbsp; Excel
</button>
<button mat-stroked-button class="cims-btn-xls" (click)="exportXbrl()">
<fa-icon icon="file-export"></fa-icon>&nbsp; Generate XBRL
</button>
</div>
</div>
</div>
@@ -0,0 +1,514 @@
/**
* 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/.
*/
:host {
--cims-good: #15803d;
--cims-good-l: #dcfce7;
--cims-mid: #d97706;
--cims-mid-l: #fef3c7;
--cims-low: #c0392b;
--cims-low-l: #fdecea;
--cims-cyan: #0891b2;
--cims-cyan-l: #cffafe;
--cims-teal: #0d9488;
--cims-teal-l: #ccfbf1;
--cims-g50: #f8fafc;
--cims-g100: #f1f5f9;
--cims-g200: #e2e8f0;
--cims-g300: #cbd5e1;
--cims-g500: #64748b;
--cims-g700: #334155;
--cims-g900: #0f172a;
display: block;
font-family:
'DM Sans',
system-ui,
-apple-system,
sans-serif;
color: var(--cims-g900);
}
.cims-dlg {
background: #fff;
border-radius: 14px;
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 92vh;
}
/* HEADER */
.cims-head {
padding: 18px 22px;
border-bottom: 1px solid var(--cims-g200);
display: flex;
align-items: center;
gap: 16px;
flex-shrink: 0;
background: linear-gradient(135deg, #ecfeff 0%, #fff 100%);
}
.cims-head-left {
display: flex;
align-items: center;
gap: 14px;
flex: 1;
}
.cims-head-ic {
width: 44px;
height: 44px;
border-radius: 11px;
background: var(--cims-cyan-l);
color: var(--cims-cyan);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.cims-title {
margin: 0;
font-size: 17px;
font-weight: 700;
color: var(--cims-g900);
}
.cims-sub {
margin: 2px 0 0;
font-size: 12px;
color: var(--cims-g500);
}
.cims-head-actions {
display: flex;
align-items: center;
gap: 12px;
}
.cims-status {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
&.valid {
background: var(--cims-good-l);
color: var(--cims-good);
}
&.warning {
background: var(--cims-mid-l);
color: var(--cims-mid);
}
}
.cims-close {
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid var(--cims-g200);
background: #fff;
font-size: 14px;
color: var(--cims-g500);
cursor: pointer;
transition: all 0.15s;
&:hover {
background: var(--cims-g100);
color: var(--cims-g900);
}
}
/* FORM SELECTOR BAR */
.cims-form-bar {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 22px;
background: var(--cims-g50);
border-bottom: 1px solid var(--cims-g200);
flex-shrink: 0;
flex-wrap: wrap;
}
.cims-form-bar-l {
font-size: 11px;
font-weight: 700;
color: var(--cims-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.cims-form-pill {
padding: 6px 14px;
border-radius: 999px;
border: 1px solid var(--cims-g200);
background: #fff;
cursor: pointer;
font-family: inherit;
font-size: 12px;
font-weight: 600;
color: var(--cims-g700);
transition: all 0.15s;
&:hover {
background: var(--cims-cyan-l);
border-color: var(--cims-cyan);
color: var(--cims-cyan);
}
&.active {
background: var(--cims-cyan);
border-color: var(--cims-cyan);
color: #fff;
box-shadow: 0 2px 6px rgb(8 145 178 / 25%);
}
}
.cims-form-meta {
display: inline-flex;
align-items: center;
font-size: 11px;
color: var(--cims-g500);
padding: 4px 10px;
background: #fff;
border: 1px solid var(--cims-g200);
border-radius: 6px;
fa-icon {
color: var(--cims-cyan);
margin-right: 2px;
}
strong {
color: var(--cims-g900);
margin-left: 3px;
}
}
/* TABS */
.cims-tabs {
display: flex;
background: #fff;
border-bottom: 1px solid var(--cims-g200);
padding: 0 22px;
overflow-x: auto;
flex-shrink: 0;
}
.cims-tab {
display: flex;
align-items: center;
padding: 12px 18px;
border: none;
background: transparent;
cursor: pointer;
font-family: inherit;
font-size: 13px;
font-weight: 600;
color: var(--cims-g500);
border-bottom: 2px solid transparent;
transition: all 0.15s;
white-space: nowrap;
&:hover {
color: var(--cims-g700);
background: var(--cims-g50);
}
&.active {
background: #fff;
color: var(--cims-cyan);
border-bottom-color: var(--cims-cyan);
}
}
/* BODY */
.cims-body {
padding: 18px 22px;
display: flex;
flex-direction: column;
gap: 16px;
overflow: auto;
flex: 1;
background: var(--cims-g50);
}
.cims-section {
background: #fff;
border: 1px solid var(--cims-g200);
border-radius: 12px;
padding: 16px 18px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.cims-sec-h {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 14px;
flex-wrap: wrap;
}
.cims-sec-t {
margin: 0;
font-size: 14px;
font-weight: 700;
color: var(--cims-g700);
}
.cims-sec-actions {
display: flex;
gap: 8px;
}
.cims-btn-xls {
font-size: 12px !important;
height: 34px !important;
color: #fff !important;
background: var(--cims-cyan) !important;
}
.cims-btn-excel {
background: #16a34a !important;
}
.cims-btn-ghost {
font-size: 12px !important;
height: 34px !important;
color: var(--cims-g700) !important;
background: #fff !important;
border: 1px solid var(--cims-g200) !important;
}
/* INFO GRID */
.cims-info-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px 24px;
}
.cims-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.cims-info-l {
font-size: 10px;
font-weight: 600;
color: var(--cims-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.cims-info-v {
font-size: 13px;
font-weight: 600;
color: var(--cims-g900);
}
.cims-desc {
margin-top: 12px;
padding: 10px 14px;
background: var(--cims-g50);
border-left: 3px solid var(--cims-cyan);
border-radius: 4px;
font-size: 12px;
color: var(--cims-g700);
line-height: 1.5;
}
/* TABLE */
.cims-tbl-wrap {
border: 1px solid var(--cims-g200);
border-radius: 8px;
overflow: auto;
}
.cims-tbl {
width: 100%;
border-collapse: collapse;
th {
text-align: left;
padding: 10px 14px;
font-size: 11px;
font-weight: 600;
color: var(--cims-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
background: var(--cims-g50);
border-bottom: 1px solid var(--cims-g200);
white-space: nowrap;
}
td {
padding: 10px 14px;
border-bottom: 1px solid var(--cims-g100);
font-size: 13px;
color: var(--cims-g700);
vertical-align: middle;
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: var(--cims-g50);
}
}
.cell-ref {
color: var(--cims-cyan);
font-weight: 600;
}
.unit-pill {
display: inline-block;
padding: 2px 8px;
border-radius: 6px;
background: var(--cims-teal-l);
color: var(--cims-teal);
font-family: 'DM Mono', monospace;
font-size: 10px;
font-weight: 600;
}
.ta-c {
text-align: center;
}
.ta-r {
text-align: right;
}
.mono {
font-family: 'DM Mono', monospace;
font-size: 12px;
}
.muted {
color: var(--cims-g500);
font-weight: 400;
}
/* VALIDATION BANNER */
.cims-validation {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-radius: 8px;
margin-bottom: 12px;
fa-icon {
font-size: 20px;
}
&.valid {
background: var(--cims-good-l);
color: var(--cims-good);
}
&.warning {
background: var(--cims-mid-l);
color: var(--cims-mid);
}
}
.cims-val-t {
font-size: 13px;
font-weight: 700;
}
.cims-val-d {
font-size: 11px;
margin-top: 2px;
opacity: 0.85;
}
/* XBRL PREVIEW */
.xbrl-preview {
background: #0f172a;
color: #e2e8f0;
padding: 16px;
border-radius: 8px;
font-family: 'DM Mono', 'Cascadia Code', monospace;
font-size: 11.5px;
line-height: 1.6;
overflow: auto;
max-height: 480px;
margin: 0;
white-space: pre;
border: 1px solid var(--cims-g200);
code {
font-family: inherit;
background: transparent;
color: inherit;
padding: 0;
}
}
/* FOOTER */
.cims-foot {
padding: 12px 22px;
border-top: 1px solid var(--cims-g200);
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-shrink: 0;
flex-wrap: wrap;
}
.cims-foot-l {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--cims-g500);
fa-icon {
color: var(--cims-cyan);
}
}
.cims-foot-actions {
display: flex;
gap: 8px;
}
/* RESPONSIVE */
@media (width <= 1100px) {
.cims-info-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (width <= 720px) {
.cims-info-grid {
grid-template-columns: 1fr;
}
.cims-head,
.cims-form-bar,
.cims-foot {
flex-wrap: wrap;
}
}
@@ -0,0 +1,840 @@
/**
* 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/.
*/
import { Component, inject } from '@angular/core';
import { CommonModule, DecimalPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
import { MatButtonModule } from '@angular/material/button';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
export type CimsFormCode = 'OSS-2' | 'PSA' | 'RAL' | 'RCP-UCB';
export interface CimsXbrlDialogData {
initialForm?: CimsFormCode;
}
interface CimsField {
ref: string;
label: string;
section: string;
value: number | string;
unit?: 'INR-Lakh' | 'INR-Cr' | 'Pure' | 'Percent' | 'Number';
type?: 'monetary' | 'numeric' | 'string' | 'percent';
}
interface CimsForm {
code: CimsFormCode;
name: string;
fullName: string;
description: string;
cimsCode: string;
taxonomy: string;
periodicity: 'Daily' | 'Monthly' | 'Quarterly' | 'Half-Yearly' | 'Annual';
lastFiled: string;
nextDue: string;
fields: CimsField[];
}
@Component({
selector: 'mifosx-cims-xbrl-dialog',
standalone: true,
templateUrl: './cims-xbrl-dialog.component.html',
styleUrls: ['./cims-xbrl-dialog.component.scss'],
imports: [
CommonModule,
DecimalPipe,
FormsModule,
MatDialogModule,
MatButtonModule,
MatTooltipModule,
FaIconComponent
]
})
export class CimsXbrlDialogComponent {
private dialogRef = inject(MatDialogRef<CimsXbrlDialogComponent>);
activeForm: CimsFormCode;
activeTab: 'filing' | 'data' | 'preview' = 'filing';
bank = {
name: 'Bharat Urban Bank Ltd',
branch: 'Head Office',
bsrCode: 'BUBL0000123',
ifsc: 'BUBL0000123',
legalEntityId: 'BUBL-LEI-2025',
pan: 'AAACB1234L',
asOnDate: '31-03-2026',
quarter: 'Q4 FY 2025-26',
fiscalYear: '2025-26',
principalOfficer: 'Karthik Kamath',
poDesignation: 'Chief Compliance Officer',
poEmail: 'compliance@bharaturbanbank.in',
poMobile: '+91-9988002211',
cimsUserId: 'BUBL_CIMS_001',
cimsPortal: 'https://cims.rbi.org.in/'
};
forms: Record<CimsFormCode, CimsForm> = {
'OSS-2': {
code: 'OSS-2',
name: 'OSS-2',
fullName: 'Off-site Surveillance — Statement of Asset Quality &amp; Capital Adequacy',
description: 'Quarterly return on asset classification, NPA, capital adequacy and profitability indicators.',
cimsCode: 'CIMS_OSS_002',
taxonomy: 'rbi-cims-oss-2024-04-01',
periodicity: 'Quarterly',
lastFiled: '15-01-2026',
nextDue: '15-04-2026',
fields: [
{
ref: 'oss2:CapAdq.CRAR',
section: 'Capital Adequacy',
label: 'CRAR (Total)',
value: 14.82,
unit: 'Percent',
type: 'percent'
},
{
ref: 'oss2:CapAdq.Tier1',
section: 'Capital Adequacy',
label: 'Tier-I CRAR',
value: 12.18,
unit: 'Percent',
type: 'percent'
},
{
ref: 'oss2:CapAdq.Tier2',
section: 'Capital Adequacy',
label: 'Tier-II CRAR',
value: 2.64,
unit: 'Percent',
type: 'percent'
},
{
ref: 'oss2:Asset.Total',
section: 'Asset Classification',
label: 'Total Advances (₹ Lakh)',
value: 416265,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'oss2:Asset.Standard',
section: 'Asset Classification',
label: 'Standard Assets (₹ Lakh)',
value: 401820,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'oss2:Asset.SubStd',
section: 'Asset Classification',
label: 'Sub-Standard (₹ Lakh)',
value: 6220,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'oss2:Asset.Doubtful',
section: 'Asset Classification',
label: 'Doubtful (₹ Lakh)',
value: 5840,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'oss2:Asset.Loss',
section: 'Asset Classification',
label: 'Loss (₹ Lakh)',
value: 2385,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'oss2:Asset.GNPA',
section: 'NPA Position',
label: 'Gross NPA (₹ Lakh)',
value: 14445,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'oss2:Asset.NNPA',
section: 'NPA Position',
label: 'Net NPA (₹ Lakh)',
value: 5212,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'oss2:Asset.GNPAPct',
section: 'NPA Position',
label: 'GNPA %',
value: 3.47,
unit: 'Percent',
type: 'percent'
},
{
ref: 'oss2:Asset.NNPAPct',
section: 'NPA Position',
label: 'NNPA %',
value: 1.27,
unit: 'Percent',
type: 'percent'
},
{
ref: 'oss2:Asset.PCR',
section: 'NPA Position',
label: 'Provision Coverage Ratio',
value: 64.13,
unit: 'Percent',
type: 'percent'
},
{
ref: 'oss2:Profit.NIM',
section: 'Profitability',
label: 'Net Interest Margin',
value: 3.21,
unit: 'Percent',
type: 'percent'
},
{
ref: 'oss2:Profit.ROA',
section: 'Profitability',
label: 'Return on Assets',
value: 0.92,
unit: 'Percent',
type: 'percent'
},
{
ref: 'oss2:Profit.ROE',
section: 'Profitability',
label: 'Return on Equity',
value: 11.48,
unit: 'Percent',
type: 'percent'
},
{
ref: 'oss2:Liq.CRR',
section: 'Liquidity',
label: 'CRR Maintained',
value: 4.52,
unit: 'Percent',
type: 'percent'
},
{
ref: 'oss2:Liq.SLR',
section: 'Liquidity',
label: 'SLR Maintained',
value: 19.24,
unit: 'Percent',
type: 'percent'
}
]
},
PSA: {
code: 'PSA',
name: 'PSA',
fullName: 'Priority Sector Advances — Annual Achievement Statement',
description: 'Annual return on priority sector lending, sub-targets and weaker section coverage.',
cimsCode: 'CIMS_PSA_001',
taxonomy: 'rbi-cims-psa-2024-04-01',
periodicity: 'Annual',
lastFiled: '30-04-2025',
nextDue: '30-04-2026',
fields: [
{
ref: 'psa:Reference.ANBC',
section: 'Reference',
label: 'Adjusted Net Bank Credit (₹ Lakh)',
value: 600000,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'psa:Reference.OBE',
section: 'Reference',
label: 'Credit Equivalent of OBE (₹ Lakh)',
value: 25000,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'psa:PSL.Total',
section: 'PSL Achievement',
label: 'Total PSL Outstanding (₹ Lakh)',
value: 241700,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'psa:PSL.TotalPct',
section: 'PSL Achievement',
label: 'Total PSL % of ANBC',
value: 40.28,
unit: 'Percent',
type: 'percent'
},
{
ref: 'psa:PSL.Agri',
section: 'Sub-targets',
label: 'Agriculture (₹ Lakh)',
value: 124000,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'psa:PSL.AgriPct',
section: 'Sub-targets',
label: 'Agriculture % of ANBC',
value: 20.67,
unit: 'Percent',
type: 'percent'
},
{
ref: 'psa:PSL.SMF',
section: 'Sub-targets',
label: 'Small &amp; Marginal Farmers (₹ Lakh)',
value: 72000,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'psa:PSL.SMFPct',
section: 'Sub-targets',
label: 'SMF % of ANBC',
value: 12.0,
unit: 'Percent',
type: 'percent'
},
{
ref: 'psa:PSL.MicroEnt',
section: 'Sub-targets',
label: 'Micro Enterprises (₹ Lakh)',
value: 51000,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'psa:PSL.MicroPct',
section: 'Sub-targets',
label: 'Micro Enterprises % of ANBC',
value: 8.5,
unit: 'Percent',
type: 'percent'
},
{
ref: 'psa:PSL.Weaker',
section: 'Sub-targets',
label: 'Weaker Sections (₹ Lakh)',
value: 85500,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'psa:PSL.WeakerPct',
section: 'Sub-targets',
label: 'Weaker Sections % of ANBC',
value: 14.25,
unit: 'Percent',
type: 'percent'
},
{
ref: 'psa:PSL.Housing',
section: 'Other Categories',
label: 'Housing (₹ Lakh)',
value: 9200,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'psa:PSL.Education',
section: 'Other Categories',
label: 'Education (₹ Lakh)',
value: 4200,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'psa:PSL.Export',
section: 'Other Categories',
label: 'Export Credit (₹ Lakh)',
value: 3800,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'psa:PSL.RenEnergy',
section: 'Other Categories',
label: 'Renewable Energy (₹ Lakh)',
value: 2400,
unit: 'INR-Lakh',
type: 'monetary'
}
]
},
RAL: {
code: 'RAL',
name: 'RAL',
fullName: 'Return on Assets &amp; Liabilities (Half-Yearly)',
description: 'Half-yearly statement of bank-wide assets and liabilities composition for cooperative banks.',
cimsCode: 'CIMS_RAL_004',
taxonomy: 'rbi-cims-ral-2024-04-01',
periodicity: 'Half-Yearly',
lastFiled: '30-10-2025',
nextDue: '30-04-2026',
fields: [
{
ref: 'ral:Liab.Capital',
section: 'Liabilities',
label: 'Capital (₹ Lakh)',
value: 22000,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Liab.Reserves',
section: 'Liabilities',
label: 'Reserves &amp; Surplus (₹ Lakh)',
value: 32400,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Liab.DepDemand',
section: 'Liabilities',
label: 'Demand Deposits (₹ Lakh)',
value: 84200,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Liab.DepSavings',
section: 'Liabilities',
label: 'Savings Deposits (₹ Lakh)',
value: 218600,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Liab.DepTerm',
section: 'Liabilities',
label: 'Term Deposits (₹ Lakh)',
value: 462800,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Liab.Borrow',
section: 'Liabilities',
label: 'Borrowings (₹ Lakh)',
value: 24180,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Liab.Other',
section: 'Liabilities',
label: 'Other Liabilities &amp; Provisions (₹ Lakh)',
value: 18420,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Asset.Cash',
section: 'Assets',
label: 'Cash on Hand (₹ Lakh)',
value: 8420,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Asset.RBI',
section: 'Assets',
label: 'Balances with RBI (₹ Lakh)',
value: 38400,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Asset.Banks',
section: 'Assets',
label: 'Balances with Banks (₹ Lakh)',
value: 24800,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Asset.Money',
section: 'Assets',
label: 'Money at Call &amp; Short Notice (₹ Lakh)',
value: 12400,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Asset.Invest',
section: 'Assets',
label: 'Investments (₹ Lakh)',
value: 184600,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Asset.Adv',
section: 'Assets',
label: 'Advances (₹ Lakh)',
value: 416265,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Asset.Fixed',
section: 'Assets',
label: 'Fixed Assets (₹ Lakh)',
value: 14200,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:Asset.Other',
section: 'Assets',
label: 'Other Assets (₹ Lakh)',
value: 12480,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:OBE.Guar',
section: 'Off-Balance Sheet',
label: 'Bank Guarantees (₹ Lakh)',
value: 18420,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'ral:OBE.LC',
section: 'Off-Balance Sheet',
label: 'Letters of Credit (₹ Lakh)',
value: 6580,
unit: 'INR-Lakh',
type: 'monetary'
}
]
},
'RCP-UCB': {
code: 'RCP-UCB',
name: 'RCP (UCB)',
fullName: 'Return on Cash Position — Urban Cooperative Banks',
description: 'Fortnightly statement of CRR/SLR maintenance, NDTL composition and reserve position.',
cimsCode: 'CIMS_RCP_UCB_007',
taxonomy: 'rbi-cims-rcp-ucb-2024-04-01',
periodicity: 'Monthly',
lastFiled: '14-04-2026',
nextDue: '28-04-2026',
fields: [
{
ref: 'rcp:Liab.DemandDep',
section: 'NDTL Composition',
label: 'Demand Liabilities (₹ Lakh)',
value: 84200,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'rcp:Liab.TimeDep',
section: 'NDTL Composition',
label: 'Time Liabilities (₹ Lakh)',
value: 681400,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'rcp:Liab.NDTL',
section: 'NDTL Composition',
label: 'Net Demand &amp; Time Liabilities (NDTL)',
value: 740000,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'rcp:CRR.Required',
section: 'CRR Maintenance',
label: 'CRR Required (4.5%)',
value: 33300,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'rcp:CRR.Maintained',
section: 'CRR Maintenance',
label: 'CRR Maintained (avg)',
value: 33448,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'rcp:CRR.MaintainedPct',
section: 'CRR Maintenance',
label: 'CRR % Maintained',
value: 4.52,
unit: 'Percent',
type: 'percent'
},
{
ref: 'rcp:CRR.Surplus',
section: 'CRR Maintenance',
label: 'CRR Surplus / Shortfall (₹ Lakh)',
value: 148,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'rcp:SLR.Required',
section: 'SLR Maintenance',
label: 'SLR Required (18%)',
value: 133200,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'rcp:SLR.Maintained',
section: 'SLR Maintenance',
label: 'SLR Maintained (avg)',
value: 142376,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'rcp:SLR.MaintainedPct',
section: 'SLR Maintenance',
label: 'SLR % Maintained',
value: 19.24,
unit: 'Percent',
type: 'percent'
},
{
ref: 'rcp:SLR.Surplus',
section: 'SLR Maintenance',
label: 'SLR Surplus / Shortfall (₹ Lakh)',
value: 9176,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'rcp:Cash.OnHand',
section: 'Cash Position',
label: 'Cash on Hand (₹ Lakh)',
value: 8420,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'rcp:Cash.AtRBI',
section: 'Cash Position',
label: 'Cash at RBI (₹ Lakh)',
value: 33448,
unit: 'INR-Lakh',
type: 'monetary'
},
{
ref: 'rcp:Cash.AtBanks',
section: 'Cash Position',
label: 'Cash at other Banks (₹ Lakh)',
value: 24800,
unit: 'INR-Lakh',
type: 'monetary'
}
]
}
};
formCodes: CimsFormCode[] = [
'OSS-2',
'PSA',
'RAL',
'RCP-UCB'
];
data = inject<CimsXbrlDialogData | null>(MAT_DIALOG_DATA, { optional: true });
constructor() {
this.activeForm = this.data?.initialForm ?? 'OSS-2';
}
get currentForm(): CimsForm {
return this.forms[this.activeForm];
}
get sections(): string[] {
const s: string[] = [];
for (const f of this.currentForm.fields) {
if (!s.includes(f.section)) s.push(f.section);
}
return s;
}
fieldsBySection(section: string): CimsField[] {
return this.currentForm.fields.filter((f) => f.section === section);
}
setForm(code: CimsFormCode): void {
this.activeForm = code;
this.activeTab = 'filing';
}
setTab(tab: 'filing' | 'data' | 'preview'): void {
this.activeTab = tab;
}
close(): void {
this.dialogRef.close();
}
/** Format a number for XBRL output (no commas, 2 dp for monetary/percent) */
private fmtXbrl(f: CimsField): string {
if (typeof f.value === 'string') return f.value;
if (f.type === 'monetary' || f.type === 'percent') return f.value.toFixed(2);
return f.value.toString();
}
/** Build XBRL XML document for a form (representative; real CIMS taxonomy differs) */
generateXbrlXml(): string {
const f = this.currentForm;
const period = `${this.bank.fiscalYear} (${this.bank.quarter})`;
const ctxId = `ctx_${f.code.replace('-', '_')}`;
const startDate = '2026-01-01';
const endDate = '2026-03-31';
const lines: string[] = [];
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
lines.push('<xbrl');
lines.push(' xmlns="http://www.xbrl.org/2003/instance"');
lines.push(' xmlns:link="http://www.xbrl.org/2003/linkbase"');
lines.push(' xmlns:xbrli="http://www.xbrl.org/2003/instance"');
lines.push(' xmlns:iso4217="http://www.xbrl.org/2003/iso4217"');
lines.push(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"');
lines.push(` xmlns:${f.code.replace('-', '').toLowerCase()}="${f.taxonomy}">`);
lines.push(` <link:schemaRef xlink:type="simple" xlink:href="${f.taxonomy}.xsd"/>`);
lines.push(` <!-- Period: ${period} -->`);
lines.push(` <context id="${ctxId}">`);
lines.push(' <entity>');
lines.push(` <identifier scheme="http://www.rbi.org.in/cims">${this.bank.legalEntityId}</identifier>`);
lines.push(' </entity>');
lines.push(' <period>');
lines.push(` <startDate>${startDate}</startDate>`);
lines.push(` <endDate>${endDate}</endDate>`);
lines.push(' </period>');
lines.push(' </context>');
lines.push(' <unit id="INR_Lakh"><measure>iso4217:INR</measure></unit>');
lines.push(' <unit id="Pure"><measure>xbrli:pure</measure></unit>');
lines.push(' <!-- Filing -->');
lines.push(` <cims:bankName contextRef="${ctxId}">${this.bank.name}</cims:bankName>`);
lines.push(` <cims:bsrCode contextRef="${ctxId}">${this.bank.bsrCode}</cims:bsrCode>`);
lines.push(` <cims:cimsFormCode contextRef="${ctxId}">${f.cimsCode}</cims:cimsFormCode>`);
lines.push(' <!-- Facts -->');
for (const fld of f.fields) {
const unitRef = fld.type === 'monetary' ? 'INR_Lakh' : 'Pure';
const decimals = fld.type === 'monetary' || fld.type === 'percent' ? '2' : '0';
lines.push(
` <${fld.ref} contextRef="${ctxId}" unitRef="${unitRef}" decimals="${decimals}">${this.fmtXbrl(
fld
)}</${fld.ref}>`
);
}
lines.push('</xbrl>');
return lines.join('\n');
}
/** Validation summary — counts fields, flags any zero/missing */
get validation(): { total: number; populated: number; empty: number; status: 'valid' | 'warning' } {
const fields = this.currentForm.fields;
const populated = fields.filter((f) => f.value !== '' && f.value !== 0).length;
const empty = fields.length - populated;
return {
total: fields.length,
populated,
empty,
status: empty === 0 ? 'valid' : 'warning'
};
}
// -------------------- Exports --------------------
exportXbrl(): void {
const xml = this.generateXbrlXml();
this.saveBlob(xml, `${this.currentForm.cimsCode}-${this.dateTag()}.xml`, 'application/xml');
}
copyXbrl(): void {
const xml = this.generateXbrlXml();
if (navigator.clipboard) navigator.clipboard.writeText(xml);
}
exportExcel(): void {
const f = this.currentForm;
const rows: string[][] = [];
rows.push([
this.bank.name,
this.bank.branch
]);
rows.push([
f.fullName,
`As On ${this.bank.asOnDate}`
]);
rows.push([
`CIMS Code: ${f.cimsCode}`,
`Taxonomy: ${f.taxonomy}`
]);
rows.push([
`Periodicity: ${f.periodicity}`,
`Quarter: ${this.bank.quarter}`
]);
rows.push([
`Principal Officer: ${this.bank.principalOfficer}`,
`Email: ${this.bank.poEmail}`
]);
rows.push([]);
rows.push([
'Section',
'Element Ref',
'Label',
'Value',
'Unit'
]);
for (const fld of f.fields) {
rows.push([
fld.section,
fld.ref,
fld.label.replace(/&amp;/g, '&'),
this.fmtXbrl(fld),
fld.unit ?? ''
]);
}
rows.push([]);
rows.push([
'Certified by',
this.bank.principalOfficer + ' — ' + this.bank.poDesignation
]);
this.saveTsv(rows, `${f.cimsCode}-${this.dateTag()}.xls`);
}
// ---- helpers ----
private dateTag(): string {
const d = new Date();
return `${String(d.getDate()).padStart(2, '0')}${String(d.getMonth() + 1).padStart(2, '0')}${d.getFullYear()}`;
}
private saveTsv(rows: string[][], filename: string): void {
const tsv = rows.map((r) => r.map((v) => (v || '').replace(/\t/g, ' ')).join('\t')).join('\n');
this.saveBlob(tsv, filename, 'application/vnd.ms-excel');
}
private saveBlob(content: string, filename: string, mime: string): void {
const blob = new Blob([content], { type: `${mime};charset=utf-8` });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
}
@@ -0,0 +1,419 @@
<!--
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/.
-->
<div class="ckr-dlg">
<!-- HEADER -->
<div class="ckr-head">
<div class="ckr-head-left">
<div class="ckr-head-ic"><fa-icon icon="fingerprint"></fa-icon></div>
<div>
<h2 class="ckr-title">CKYC Compliance Reports &mdash; {{ currentReport.name }}</h2>
<p class="ckr-sub">{{ currentReport.description }}</p>
</div>
</div>
<div class="ckr-head-actions">
<span class="ckr-meta mono"> <fa-icon icon="hashtag"></fa-icon>&nbsp;Ref {{ currentReport.sourceRef }} </span>
<span class="ckr-meta">
<fa-icon icon="calendar"></fa-icon>&nbsp;As on <strong>{{ bank.asOnDate }}</strong>
</span>
<button class="ckr-close" (click)="close()" matTooltip="Close" aria-label="Close">&#10005;</button>
</div>
</div>
<!-- REPORT SELECTOR -->
<div class="ckr-sel-bar">
<span class="ckr-sel-l">Report:</span>
<button
*ngFor="let r of reports"
class="ckr-sel-pill"
[class.active]="activeReport === r.code"
(click)="setReport(r.code)"
>
<fa-icon [icon]="r.icon"></fa-icon>&nbsp;&nbsp;{{ r.name }}
</button>
</div>
<div class="ckr-body">
<!-- ════════════ RE-KYC ════════════ -->
<ng-container *ngIf="activeReport === 'REKYC'">
<!-- KPIs -->
<div class="ckr-stats">
<div class="ckr-stat-card">
<div class="ckr-stat-l">Total Customers</div>
<div class="ckr-stat-v">{{ rekyc.length | number }}</div>
<div class="ckr-stat-meta">Filtered: {{ filteredRekyc.length | number }}</div>
</div>
<div class="ckr-stat-card alert">
<div class="ckr-stat-l">KYC Overdue</div>
<div class="ckr-stat-v">{{ rekycOverdue }}</div>
<div class="ckr-stat-meta">Past expiry date</div>
</div>
<div class="ckr-stat-card warn">
<div class="ckr-stat-l">Due in 90 days</div>
<div class="ckr-stat-v">{{ rekycDueSoon }}</div>
<div class="ckr-stat-meta">Refresh window opening</div>
</div>
<div class="ckr-stat-card">
<div class="ckr-stat-l">High Risk</div>
<div class="ckr-stat-v">{{ rekycHighRisk }}</div>
<div class="ckr-stat-meta">Yearly KYC required</div>
</div>
<div class="ckr-stat-card">
<div class="ckr-stat-l">≥ ₹25 Lacs Segment</div>
<div class="ckr-stat-v">{{ rekycAbove25L }}</div>
<div class="ckr-stat-meta">Enhanced due diligence</div>
</div>
</div>
<div class="ckr-section">
<div class="ckr-sec-h">
<h3 class="ckr-sec-t">Re-KYC Pending Register &mdash; {{ filteredRekyc.length }} of {{ rekyc.length }}</h3>
<button mat-stroked-button class="ckr-btn-xls" (click)="exportRekycCsv()">
<fa-icon icon="file-csv"></fa-icon>&nbsp; Export CSV
</button>
</div>
<div class="ckr-filters">
<div class="ckr-f">
<label>Risk</label>
<select [(ngModel)]="rekycFilterRisk">
<option value="">All</option>
<option value="LOW">Low</option>
<option value="MEDIUM">Medium</option>
<option value="HIGH">High</option>
</select>
</div>
<div class="ckr-f">
<label>Branch</label>
<input type="text" placeholder="Search branch…" [(ngModel)]="rekycFilterBranch" />
</div>
<div class="ckr-f">
<label>Segment</label>
<select [(ngModel)]="rekycFilterSegment">
<option value="">All</option>
<option value="ABOVE-25-LACS">≥ ₹25 Lacs</option>
<option value="BELOW-25-LACS">&lt; ₹25 Lacs</option>
</select>
</div>
<div class="ckr-f">
<label>KYC Status</label>
<select [(ngModel)]="rekycFilterStatus">
<option value="">All</option>
<option value="OVERDUE">Overdue</option>
<option value="DUE-SOON">Due in 90 days</option>
</select>
</div>
<button class="ckr-f-reset" (click)="resetRekycFilters()" matTooltip="Reset">
<fa-icon icon="undo"></fa-icon>
</button>
</div>
<div class="ckr-tbl-wrap">
<table class="ckr-tbl">
<thead>
<tr>
<th>UCode</th>
<th>Customer Name</th>
<th>Type</th>
<th class="ta-c">Risk</th>
<th>Branch</th>
<th>KYC Expiry</th>
<th class="ta-c">Status</th>
<th class="ta-c">Segment</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let r of filteredRekyc">
<td class="mono">{{ r.ucode }}</td>
<td>{{ r.name }}</td>
<td>{{ r.customerType }}</td>
<td class="ta-c">
<span class="risk-pill" [ngClass]="riskBadgeClass(r.risk)">{{ r.risk }}</span>
</td>
<td>
{{ r.branch }} <span class="muted">({{ r.branchCode }})</span>
</td>
<td>{{ r.kycExpiry }}</td>
<td class="ta-c">
<span
class="status-pill"
[ngClass]="{
bad: r.daysOverdue > 0,
warn: r.daysOverdue <= 0 && r.daysOverdue >= -90,
good: r.daysOverdue < -90
}"
>
<ng-container *ngIf="r.daysOverdue > 0">OVERDUE {{ r.daysOverdue }}d</ng-container>
<ng-container *ngIf="r.daysOverdue <= 0 && r.daysOverdue >= -90"
>DUE IN {{ -r.daysOverdue }}d</ng-container
>
<ng-container *ngIf="r.daysOverdue < -90">WITHIN</ng-container>
</span>
</td>
<td class="ta-c">
<span *ngIf="r.segment === 'ABOVE-25-LACS'" class="seg-pill seg-high">≥ 25 L</span>
<span *ngIf="r.segment === 'BELOW-25-LACS'" class="seg-pill seg-low">&lt; 25 L</span>
</td>
</tr>
<tr *ngIf="filteredRekyc.length === 0">
<td colspan="8" class="empty">No customers match the current filters.</td>
</tr>
</tbody>
</table>
</div>
<div class="ckr-foot">
TOTAL COUNT: <strong>{{ filteredRekyc.length }}</strong> &nbsp;&middot;&nbsp; Report Ref:
<span class="mono">{{ bank.reportRefNo }}</span> &nbsp;&middot;&nbsp; User Code:
<span class="mono">{{ bank.userCode }}</span>
</div>
</div>
</ng-container>
<!-- ════════════ RISK REVIEW ════════════ -->
<ng-container *ngIf="activeReport === 'RISK-REVIEW'">
<div class="ckr-stats">
<div class="ckr-stat-card">
<div class="ckr-stat-l">Total Reviews</div>
<div class="ckr-stat-v">{{ riskReview.length | number }}</div>
<div class="ckr-stat-meta">{{ riskReviewPeriod.from }} to {{ riskReviewPeriod.to }}</div>
</div>
<div class="ckr-stat-card">
<div class="ckr-stat-l">Risk Inserted</div>
<div class="ckr-stat-v">{{ riskInsertedCount }}</div>
<div class="ckr-stat-meta">Occupation / Pancard</div>
</div>
<div class="ckr-stat-card warn">
<div class="ckr-stat-l">Risk Upgraded</div>
<div class="ckr-stat-v">{{ riskUpgradedCount }}</div>
<div class="ckr-stat-meta">LOW→MED, MED→HIGH</div>
</div>
<div class="ckr-stat-card alert">
<div class="ckr-stat-l">Accounts Frozen</div>
<div class="ckr-stat-v">{{ riskFrozenCount }}</div>
<div class="ckr-stat-meta">Suspicious / High-Risk</div>
</div>
</div>
<div class="ckr-section">
<div class="ckr-sec-h">
<h3 class="ckr-sec-t">
Risk Updating History &mdash; {{ riskReviewPeriod.from }} TO {{ riskReviewPeriod.to }}
</h3>
<button mat-stroked-button class="ckr-btn-xls" (click)="exportRiskReviewCsv()">
<fa-icon icon="file-csv"></fa-icon>&nbsp; Export CSV
</button>
</div>
<div class="ckr-filters">
<div class="ckr-f">
<label>Branch</label>
<input type="text" placeholder="Search branch…" [(ngModel)]="riskFilterBranch" />
</div>
<div class="ckr-f">
<label>New Risk</label>
<select [(ngModel)]="riskFilterNew">
<option value="">All</option>
<option value="LOW">Low</option>
<option value="MEDIUM">Medium</option>
<option value="HIGH">High</option>
</select>
</div>
<div class="ckr-f">
<label>Account Frozen</label>
<select [(ngModel)]="riskFilterFrozen">
<option value="">All</option>
<option value="Y">Yes</option>
<option value="N">No</option>
</select>
</div>
<button class="ckr-f-reset" (click)="resetRiskFilters()" matTooltip="Reset">
<fa-icon icon="undo"></fa-icon>
</button>
</div>
<div class="ckr-tbl-wrap">
<table class="ckr-tbl">
<thead>
<tr>
<th>Branch</th>
<th>A/c Type</th>
<th>Client ID</th>
<th>Customer</th>
<th class="ta-c">Old → New Risk</th>
<th>Mobile</th>
<th>Description</th>
<th class="ta-c">Frozen</th>
<th>Updated On</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let r of filteredRiskReview">
<td>
{{ r.branch }} <span class="muted">({{ r.branchCode }})</span>
</td>
<td>
<span class="actyp-pill">{{ r.acType }}</span>
</td>
<td class="mono">{{ r.clientId }}</td>
<td>{{ r.customerName }}</td>
<td class="ta-c">
<span class="risk-pill" [ngClass]="riskBadgeClass(r.oldRisk)">{{ r.oldRisk }}</span>
<fa-icon icon="long-arrow-alt-right" class="risk-arrow"></fa-icon>
<span class="risk-pill" [ngClass]="riskBadgeClass(r.newRisk)">{{ r.newRisk }}</span>
</td>
<td class="mono">{{ r.mobile }}</td>
<td>{{ r.description }}</td>
<td class="ta-c">
<span *ngIf="r.frozen" class="status-pill bad">FROZEN</span>
<span *ngIf="!r.frozen" class="muted">—</span>
</td>
<td>{{ r.updatedOn }}</td>
</tr>
<tr *ngIf="filteredRiskReview.length === 0">
<td colspan="9" class="empty">No risk-update entries match the filters.</td>
</tr>
</tbody>
</table>
</div>
<div class="ckr-foot">
Risk Category Updated Summary &mdash; Total Clients:
<strong>{{ filteredRiskReview.length }}</strong> &nbsp;&middot;&nbsp; Print Date:
<span class="mono">{{ bank.asOnDate }}</span>
</div>
</div>
</ng-container>
<!-- ════════════ CERSAI ════════════ -->
<ng-container *ngIf="activeReport === 'CERSAI'">
<div class="ckr-stats">
<div class="ckr-stat-card">
<div class="ckr-stat-l">Total Secured A/c</div>
<div class="ckr-stat-v">{{ cersaiTotalSecured | number }}</div>
<div class="ckr-stat-meta">Across all branches</div>
</div>
<div class="ckr-stat-card good">
<div class="ckr-stat-l">CERSAI Found</div>
<div class="ckr-stat-v">{{ cersaiTotalFound | number }}</div>
<div class="ckr-stat-meta">Registered with CERSAI</div>
</div>
<div class="ckr-stat-card alert">
<div class="ckr-stat-l">CERSAI Not Found</div>
<div class="ckr-stat-v">{{ cersaiTotalNotFound | number }}</div>
<div class="ckr-stat-meta">Pending registration</div>
</div>
<div class="ckr-stat-card">
<div class="ckr-stat-l">Compliance %</div>
<div class="ckr-stat-v" [ngClass]="cersaiPctClass(cersaiCompliancePct)">
{{ cersaiCompliancePct.toFixed(2) }}%
</div>
<div class="ckr-stat-meta">Target: ≥ 95%</div>
</div>
</div>
<div class="ckr-section">
<div class="ckr-sec-h">
<h3 class="ckr-sec-t">CERSAI Registration Status &mdash; Branch &amp; Account-Type Wise</h3>
<button mat-stroked-button class="ckr-btn-xls" (click)="exportCersaiCsv()">
<fa-icon icon="file-csv"></fa-icon>&nbsp; Export CSV
</button>
</div>
<div class="ckr-filters">
<div class="ckr-f">
<label>Branch</label>
<input type="text" placeholder="Search branch…" [(ngModel)]="cersaiFilterBranch" />
</div>
<div class="ckr-f">
<label>Account Type</label>
<select [(ngModel)]="cersaiFilterAcType">
<option value="">All</option>
<option value="TL">Term Loan (TL)</option>
<option value="HL">Home Loan (HL)</option>
<option value="CC">Cash Credit (CC)</option>
<option value="OD">Overdraft (OD)</option>
</select>
</div>
<button class="ckr-f-reset" (click)="resetCersaiFilters()" matTooltip="Reset">
<fa-icon icon="undo"></fa-icon>
</button>
</div>
<div class="ckr-tbl-wrap">
<table class="ckr-tbl">
<thead>
<tr>
<th>Branch Code</th>
<th>Branch</th>
<th class="ta-c">A/c Type</th>
<th class="ta-r">CERSAI Found</th>
<th class="ta-r">CERSAI Not Found</th>
<th class="ta-r">Total Secured</th>
<th class="ta-r">Compliance %</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let r of filteredCersai">
<td class="mono">{{ r.branchCode }}</td>
<td>{{ r.branch }}</td>
<td class="ta-c">
<span class="actyp-pill">{{ r.acType }}</span>
</td>
<td class="ta-r">{{ r.found | number }}</td>
<td class="ta-r" [class.bad]="r.notFound > 0">{{ r.notFound | number }}</td>
<td class="ta-r">
<strong>{{ r.totalSecured | number }}</strong>
</td>
<td class="ta-r">
<span class="pct-pill" [ngClass]="cersaiPctClass(cersaiPct(r))">{{ cersaiPct(r).toFixed(1) }}%</span>
</td>
</tr>
<tr class="row-total">
<td colspan="3"><strong>BANK TOTAL</strong></td>
<td class="ta-r">
<strong>{{ cersaiTotalFound | number }}</strong>
</td>
<td class="ta-r">
<strong>{{ cersaiTotalNotFound | number }}</strong>
</td>
<td class="ta-r">
<strong>{{ cersaiTotalSecured | number }}</strong>
</td>
<td class="ta-r">
<span class="pct-pill" [ngClass]="cersaiPctClass(cersaiCompliancePct)"
>{{ cersaiCompliancePct.toFixed(2) }}%</span
>
</td>
</tr>
<tr *ngIf="filteredCersai.length === 0">
<td colspan="7" class="empty">No CERSAI rows match the filters.</td>
</tr>
</tbody>
</table>
</div>
<div class="ckr-foot">
{{ bank.name }} &mdash; {{ bank.branch }} &middot; Certified by
<strong>{{ bank.principalOfficer }}</strong> &middot; As on
<span class="mono">{{ bank.asOnDate }}</span>
</div>
</div>
</ng-container>
</div>
<!-- FOOTER -->
<div class="ckr-footer">
<div class="ckr-footer-l">
<fa-icon icon="shield-alt"></fa-icon>&nbsp; Data classified — RBI KYC Master Direction (2016, as amended)
</div>
<button mat-stroked-button class="ckr-btn-xls" (click)="exportCurrentExcel()">
<fa-icon icon="file-export"></fa-icon>&nbsp; Export {{ currentReport.name }}
</button>
</div>
</div>
@@ -0,0 +1,610 @@
/**
* 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/.
*/
:host {
--ckr-good: #15803d;
--ckr-good-l: #dcfce7;
--ckr-mid: #d97706;
--ckr-mid-l: #fef3c7;
--ckr-low: #c0392b;
--ckr-low-l: #fdecea;
--ckr-pink: #be185d;
--ckr-pink-l: #fce7f3;
--ckr-purple: #6d28d9;
--ckr-purple-l: #ede9fe;
--ckr-blue: #2563eb;
--ckr-blue-l: #dbeafe;
--ckr-g50: #f8fafc;
--ckr-g100: #f1f5f9;
--ckr-g200: #e2e8f0;
--ckr-g300: #cbd5e1;
--ckr-g500: #64748b;
--ckr-g700: #334155;
--ckr-g900: #0f172a;
display: block;
font-family:
'DM Sans',
system-ui,
-apple-system,
sans-serif;
color: var(--ckr-g900);
}
.ckr-dlg {
background: #fff;
border-radius: 14px;
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 92vh;
}
/* HEADER */
.ckr-head {
padding: 18px 22px;
border-bottom: 1px solid var(--ckr-g200);
display: flex;
align-items: center;
gap: 16px;
flex-shrink: 0;
background: linear-gradient(135deg, #fdf2f8 0%, #fff 100%);
}
.ckr-head-left {
display: flex;
align-items: center;
gap: 14px;
flex: 1;
}
.ckr-head-ic {
width: 44px;
height: 44px;
border-radius: 11px;
background: var(--ckr-pink-l);
color: var(--ckr-pink);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.ckr-title {
margin: 0;
font-size: 17px;
font-weight: 700;
color: var(--ckr-g900);
}
.ckr-sub {
margin: 2px 0 0;
font-size: 12px;
color: var(--ckr-g500);
}
.ckr-head-actions {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.ckr-meta {
display: inline-flex;
align-items: center;
font-size: 11px;
color: var(--ckr-g500);
padding: 5px 10px;
background: #fff;
border: 1px solid var(--ckr-g200);
border-radius: 6px;
fa-icon {
color: var(--ckr-pink);
margin-right: 4px;
}
}
.ckr-close {
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid var(--ckr-g200);
background: #fff;
font-size: 14px;
color: var(--ckr-g500);
cursor: pointer;
transition: all 0.15s;
&:hover {
background: var(--ckr-g100);
color: var(--ckr-g900);
}
}
/* REPORT SELECTOR BAR */
.ckr-sel-bar {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 22px;
background: var(--ckr-g50);
border-bottom: 1px solid var(--ckr-g200);
flex-shrink: 0;
flex-wrap: wrap;
}
.ckr-sel-l {
font-size: 11px;
font-weight: 700;
color: var(--ckr-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-right: 4px;
}
.ckr-sel-pill {
padding: 7px 16px;
border-radius: 999px;
border: 1px solid var(--ckr-g200);
background: #fff;
cursor: pointer;
font-family: inherit;
font-size: 12px;
font-weight: 600;
color: var(--ckr-g700);
transition: all 0.15s;
display: inline-flex;
align-items: center;
fa-icon {
color: var(--ckr-g500);
}
&:hover {
background: var(--ckr-pink-l);
border-color: var(--ckr-pink);
color: var(--ckr-pink);
fa-icon {
color: var(--ckr-pink);
}
}
&.active {
background: var(--ckr-pink);
border-color: var(--ckr-pink);
color: #fff;
box-shadow: 0 2px 6px rgb(190 24 93 / 25%);
fa-icon {
color: #fff;
}
}
}
/* BODY */
.ckr-body {
padding: 18px 22px;
display: flex;
flex-direction: column;
gap: 16px;
overflow: auto;
flex: 1;
background: var(--ckr-g50);
}
/* KPI STATS */
.ckr-stats {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
}
.ckr-stat-card {
background: #fff;
border: 1px solid var(--ckr-g200);
border-radius: 12px;
padding: 14px 16px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
&.alert {
background: var(--ckr-low-l);
border-color: var(--ckr-low);
.ckr-stat-v {
color: var(--ckr-low);
}
}
&.warn {
background: var(--ckr-mid-l);
border-color: var(--ckr-mid);
.ckr-stat-v {
color: var(--ckr-mid);
}
}
&.good {
background: var(--ckr-good-l);
border-color: var(--ckr-good);
.ckr-stat-v {
color: var(--ckr-good);
}
}
}
.ckr-stat-l {
font-size: 11px;
font-weight: 600;
color: var(--ckr-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.ckr-stat-v {
font-size: 22px;
font-weight: 700;
color: var(--ckr-g900);
line-height: 1.2;
margin-top: 2px;
&.good {
color: var(--ckr-good);
}
&.mid {
color: var(--ckr-mid);
}
&.low {
color: var(--ckr-low);
}
}
.ckr-stat-meta {
font-size: 11px;
color: var(--ckr-g500);
margin-top: 4px;
}
/* SECTION */
.ckr-section {
background: #fff;
border: 1px solid var(--ckr-g200);
border-radius: 12px;
padding: 16px 18px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.ckr-sec-h {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.ckr-sec-t {
margin: 0;
font-size: 14px;
font-weight: 700;
color: var(--ckr-g700);
}
.ckr-btn-xls {
font-size: 12px !important;
height: 34px !important;
color: #fff !important;
background: var(--ckr-pink) !important;
}
/* FILTERS */
.ckr-filters {
display: flex;
gap: 12px;
padding: 12px 14px;
background: var(--ckr-g50);
border: 1px solid var(--ckr-g200);
border-radius: 8px;
margin-bottom: 12px;
align-items: flex-end;
flex-wrap: wrap;
}
.ckr-f {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 140px;
label {
font-size: 10px;
font-weight: 600;
color: var(--ckr-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
select,
input {
height: 32px;
padding: 0 10px;
border: 1px solid var(--ckr-g200);
border-radius: 6px;
background: #fff;
font-family: inherit;
font-size: 12px;
color: var(--ckr-g900);
&:focus {
outline: none;
border-color: var(--ckr-pink);
box-shadow: 0 0 0 3px rgb(190 24 93 / 12%);
}
}
}
.ckr-f-reset {
height: 32px;
width: 32px;
border-radius: 6px;
border: 1px solid var(--ckr-g200);
background: #fff;
color: var(--ckr-g500);
cursor: pointer;
transition: all 0.15s;
&:hover {
background: var(--ckr-low-l);
color: var(--ckr-low);
border-color: var(--ckr-low);
}
}
/* TABLE */
.ckr-tbl-wrap {
border: 1px solid var(--ckr-g200);
border-radius: 8px;
overflow: auto;
}
.ckr-tbl {
width: 100%;
border-collapse: collapse;
th {
text-align: left;
padding: 10px 14px;
font-size: 11px;
font-weight: 600;
color: var(--ckr-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
background: var(--ckr-g50);
border-bottom: 1px solid var(--ckr-g200);
white-space: nowrap;
}
td {
padding: 10px 14px;
border-bottom: 1px solid var(--ckr-g100);
font-size: 13px;
color: var(--ckr-g700);
vertical-align: middle;
&.bad {
color: var(--ckr-low);
font-weight: 600;
}
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: var(--ckr-g50);
}
.row-total td {
background: var(--ckr-pink-l);
color: var(--ckr-g900);
font-weight: 700;
border-top: 2px solid var(--ckr-pink);
}
}
/* PILLS */
.risk-pill {
display: inline-block;
padding: 2px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
&.risk-low {
background: var(--ckr-good-l);
color: var(--ckr-good);
}
&.risk-mid {
background: var(--ckr-mid-l);
color: var(--ckr-mid);
}
&.risk-high {
background: var(--ckr-low-l);
color: var(--ckr-low);
}
}
.risk-arrow {
margin: 0 6px;
color: var(--ckr-g500);
}
.actyp-pill {
display: inline-block;
padding: 2px 8px;
border-radius: 6px;
background: var(--ckr-blue-l);
color: var(--ckr-blue);
font-family: 'DM Mono', monospace;
font-size: 11px;
font-weight: 600;
}
.status-pill {
display: inline-block;
padding: 3px 10px;
font-size: 10px;
font-weight: 700;
border-radius: 999px;
letter-spacing: 0.04em;
&.bad {
background: var(--ckr-low-l);
color: var(--ckr-low);
}
&.warn {
background: var(--ckr-mid-l);
color: var(--ckr-mid);
}
&.good {
background: var(--ckr-good-l);
color: var(--ckr-good);
}
}
.seg-pill {
display: inline-block;
padding: 2px 9px;
border-radius: 6px;
font-size: 11px;
font-weight: 700;
&.seg-high {
background: var(--ckr-purple-l);
color: var(--ckr-purple);
}
&.seg-low {
background: var(--ckr-g100);
color: var(--ckr-g500);
}
}
.pct-pill {
display: inline-block;
padding: 3px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
&.good {
background: var(--ckr-good-l);
color: var(--ckr-good);
}
&.mid {
background: var(--ckr-mid-l);
color: var(--ckr-mid);
}
&.low {
background: var(--ckr-low-l);
color: var(--ckr-low);
}
}
.muted {
color: var(--ckr-g300);
font-weight: 400;
}
.ta-c {
text-align: center;
}
.ta-r {
text-align: right;
}
.mono {
font-family: 'DM Mono', monospace;
font-size: 12px;
}
.empty {
text-align: center;
color: var(--ckr-g500);
padding: 24px !important;
font-style: italic;
}
.ckr-foot {
margin-top: 12px;
padding: 10px 14px;
font-size: 12px;
color: var(--ckr-g500);
background: var(--ckr-g50);
border-radius: 6px;
text-align: right;
}
/* DIALOG FOOTER */
.ckr-footer {
padding: 12px 22px;
border-top: 1px solid var(--ckr-g200);
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-shrink: 0;
flex-wrap: wrap;
}
.ckr-footer-l {
font-size: 12px;
color: var(--ckr-g500);
display: flex;
align-items: center;
fa-icon {
color: var(--ckr-pink);
margin-right: 4px;
}
}
/* RESPONSIVE */
@media (width <= 1100px) {
.ckr-stats {
grid-template-columns: repeat(3, 1fr);
}
}
@media (width <= 720px) {
.ckr-stats {
grid-template-columns: 1fr 1fr;
}
.ckr-head,
.ckr-sel-bar,
.ckr-footer {
flex-wrap: wrap;
}
}
@@ -0,0 +1,849 @@
/**
* 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/.
*/
import { Component, inject } from '@angular/core';
import { CommonModule, DecimalPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
import { MatButtonModule } from '@angular/material/button';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
export type CkycReportCode = 'REKYC' | 'RISK-REVIEW' | 'CERSAI';
export interface CkycReportsDialogData {
initialReport?: CkycReportCode;
}
interface RekycRow {
ucode: string;
name: string;
customerType: 'INDIVIDUAL' | 'FIRM' | 'SOCIETY' | 'HUF';
risk: 'LOW' | 'MEDIUM' | 'HIGH';
branch: string;
branchCode: string;
kycExpiry: string;
daysOverdue: number;
segment: 'ABOVE-25-LACS' | 'BELOW-25-LACS';
}
interface RiskReviewRow {
branchCode: string;
branch: string;
acType: 'SB' | 'CA' | 'OD' | 'TL' | 'CC' | 'LT';
clientId: string;
customerName: string;
oldRisk: 'LOW' | 'MEDIUM' | 'HIGH';
newRisk: 'LOW' | 'MEDIUM' | 'HIGH';
mobile: string;
description: string;
frozen: boolean;
updatedOn: string;
}
interface CersaiRow {
branchCode: string;
branch: string;
acType: 'TL' | 'CC' | 'OD' | 'HL';
found: number;
notFound: number;
totalSecured: number;
}
@Component({
selector: 'mifosx-ckyc-reports-dialog',
standalone: true,
templateUrl: './ckyc-reports-dialog.component.html',
styleUrls: ['./ckyc-reports-dialog.component.scss'],
imports: [
CommonModule,
DecimalPipe,
FormsModule,
MatDialogModule,
MatButtonModule,
MatTooltipModule,
FaIconComponent
]
})
export class CkycReportsDialogComponent {
private dialogRef = inject(MatDialogRef<CkycReportsDialogComponent>);
activeReport: CkycReportCode;
bank = {
name: 'Bharat Urban Bank Ltd',
branch: 'Head Office',
asOnDate: '29-01-2026',
fiscalYear: '2025-26',
quarter: 'Q3 FY 2025-26',
principalOfficer: 'Karthik Kamath',
poDesignation: 'Principal Compliance Officer',
reportRefNo: '2075/1',
userCode: 'BUBL01',
fyStart: '01-04-2025',
fyEnd: '31-03-2026'
};
reports = [
{
code: 'REKYC' as CkycReportCode,
name: 'Re-KYC Report',
icon: 'sync-alt',
description: 'Customers due / overdue for periodic KYC refresh based on risk category.',
sourceRef: '2075/1'
},
{
code: 'RISK-REVIEW' as CkycReportCode,
name: 'Risk Review',
icon: 'exclamation-triangle',
description: 'Audit trail of risk-category changes per RBI Master Direction on KYC.',
sourceRef: '2075/2'
},
{
code: 'CERSAI' as CkycReportCode,
name: 'CERSAI Report',
icon: 'shield-alt',
description: 'Branch-wise CERSAI registration status for secured advances under SARFAESI.',
sourceRef: '2075/3'
}
];
// -------------------- Re-KYC Data --------------------
rekycFilterRisk: '' | 'LOW' | 'MEDIUM' | 'HIGH' = '';
rekycFilterBranch = '';
rekycFilterSegment: '' | 'ABOVE-25-LACS' | 'BELOW-25-LACS' = '';
rekycFilterStatus: '' | 'OVERDUE' | 'DUE-SOON' = '';
rekyc: RekycRow[] = [
{
ucode: 'BUBL000201',
name: 'Ravi Kumar Sharma',
customerType: 'INDIVIDUAL',
risk: 'LOW',
branch: 'Mumbai Main',
branchCode: '01',
kycExpiry: '12-04-2026',
daysOverdue: -73,
segment: 'BELOW-25-LACS'
},
{
ucode: 'BUBL000418',
name: 'Priya Patel',
customerType: 'INDIVIDUAL',
risk: 'MEDIUM',
branch: 'Pune FC Road',
branchCode: '02',
kycExpiry: '08-12-2025',
daysOverdue: 52,
segment: 'ABOVE-25-LACS'
},
{
ucode: 'BUBL000522',
name: 'Arjun Deshmukh',
customerType: 'INDIVIDUAL',
risk: 'LOW',
branch: 'Nagpur Dharampeth',
branchCode: '03',
kycExpiry: '01-08-2026',
daysOverdue: -184,
segment: 'BELOW-25-LACS'
},
{
ucode: 'BUBL000711',
name: 'Vikram Reddy',
customerType: 'INDIVIDUAL',
risk: 'HIGH',
branch: 'Bengaluru Jayanagar',
branchCode: '04',
kycExpiry: '15-11-2025',
daysOverdue: 75,
segment: 'ABOVE-25-LACS'
},
{
ucode: 'BUBL000814',
name: 'Lakshmi Iyer',
customerType: 'INDIVIDUAL',
risk: 'LOW',
branch: 'Bengaluru Indiranagar',
branchCode: '05',
kycExpiry: '08-10-2026',
daysOverdue: -252,
segment: 'BELOW-25-LACS'
},
{
ucode: 'BUBL001012',
name: 'Nirav Shah',
customerType: 'INDIVIDUAL',
risk: 'MEDIUM',
branch: 'Ahmedabad Satellite',
branchCode: '06',
kycExpiry: '14-02-2026',
daysOverdue: -16,
segment: 'BELOW-25-LACS'
},
{
ucode: 'BUBL001145',
name: 'Kavita Verma',
customerType: 'INDIVIDUAL',
risk: 'HIGH',
branch: 'Lucknow Gomti Nagar',
branchCode: '07',
kycExpiry: '11-11-2025',
daysOverdue: 79,
segment: 'ABOVE-25-LACS'
},
{
ucode: 'BUBL001458',
name: 'Sameer Kapoor',
customerType: 'INDIVIDUAL',
risk: 'MEDIUM',
branch: 'Delhi Greater Kailash',
branchCode: '08',
kycExpiry: '25-08-2026',
daysOverdue: -208,
segment: 'ABOVE-25-LACS'
},
{
ucode: 'BUBL001721',
name: 'Karthik Subramanian',
customerType: 'INDIVIDUAL',
risk: 'LOW',
branch: 'Chennai Valasaravakkam',
branchCode: '09',
kycExpiry: '03-05-2027',
daysOverdue: -460,
segment: 'BELOW-25-LACS'
},
{
ucode: 'BUBL002012',
name: 'Abhishek Banerjee',
customerType: 'INDIVIDUAL',
risk: 'MEDIUM',
branch: 'Kolkata Salt Lake',
branchCode: '10',
kycExpiry: '17-12-2025',
daysOverdue: 43,
segment: 'ABOVE-25-LACS'
},
{
ucode: 'BUBL002314',
name: 'Gaurav Thakur',
customerType: 'INDIVIDUAL',
risk: 'LOW',
branch: 'Shimla Sector 4',
branchCode: '11',
kycExpiry: '22-04-2027',
daysOverdue: -448,
segment: 'BELOW-25-LACS'
},
{
ucode: 'BUBL002615',
name: 'Manmohan Singh Gill',
customerType: 'INDIVIDUAL',
risk: 'HIGH',
branch: 'Amritsar Model Town',
branchCode: '12',
kycExpiry: '05-01-2026',
daysOverdue: 24,
segment: 'BELOW-25-LACS'
},
{
ucode: 'BUBL002918',
name: 'Sunita Devi',
customerType: 'INDIVIDUAL',
risk: 'LOW',
branch: 'Bhopal Arera Colony',
branchCode: '13',
kycExpiry: '18-06-2027',
daysOverdue: -505,
segment: 'BELOW-25-LACS'
},
{
ucode: 'BUBL003221',
name: 'Deepak Tiwari',
customerType: 'INDIVIDUAL',
risk: 'MEDIUM',
branch: 'Indore Vijay Nagar',
branchCode: '14',
kycExpiry: '12-03-2026',
daysOverdue: -42,
segment: 'ABOVE-25-LACS'
},
{
ucode: 'BUBL003524',
name: 'Harpreet Kaur',
customerType: 'INDIVIDUAL',
risk: 'MEDIUM',
branch: 'Ludhiana Sarabha Nagar',
branchCode: '15',
kycExpiry: '08-09-2025',
daysOverdue: 143,
segment: 'ABOVE-25-LACS'
},
{
ucode: 'BUBL003812',
name: 'Saraswati Mahila Co-op Society',
customerType: 'SOCIETY',
risk: 'MEDIUM',
branch: 'Mumbai Main',
branchCode: '01',
kycExpiry: '14-12-2025',
daysOverdue: 46,
segment: 'ABOVE-25-LACS'
},
{
ucode: 'BUBL004115',
name: 'Aditya Enterprises',
customerType: 'FIRM',
risk: 'HIGH',
branch: 'Pune FC Road',
branchCode: '02',
kycExpiry: '02-10-2025',
daysOverdue: 119,
segment: 'ABOVE-25-LACS'
},
{
ucode: 'BUBL004318',
name: 'Vasudha Trading HUF',
customerType: 'HUF',
risk: 'MEDIUM',
branch: 'Delhi Greater Kailash',
branchCode: '08',
kycExpiry: '28-11-2025',
daysOverdue: 62,
segment: 'ABOVE-25-LACS'
}
];
// -------------------- Risk Review Data --------------------
riskFilterBranch = '';
riskFilterNew: '' | 'LOW' | 'MEDIUM' | 'HIGH' = '';
riskFilterFrozen: '' | 'Y' | 'N' = '';
riskReviewPeriod = { from: '01-10-2025', to: '31-12-2025' };
riskReview: RiskReviewRow[] = [
{
branchCode: '01',
branch: 'Mumbai Main',
acType: 'SB',
clientId: 'BUBL000201',
customerName: 'Ravi Kumar Sharma',
oldRisk: 'LOW',
newRisk: 'LOW',
mobile: '+91-9988002211',
description: 'Periodic Risk Review — Individual',
frozen: false,
updatedOn: '12-10-2025'
},
{
branchCode: '02',
branch: 'Pune FC Road',
acType: 'CA',
clientId: 'BUBL000418',
customerName: 'Priya Patel',
oldRisk: 'LOW',
newRisk: 'MEDIUM',
mobile: '+91-9617086784',
description: 'Risk Updated — Account Turn Over Medium',
frozen: false,
updatedOn: '18-10-2025'
},
{
branchCode: '03',
branch: 'Nagpur Dharampeth',
acType: 'TL',
clientId: 'BUBL000522',
customerName: 'Arjun Deshmukh',
oldRisk: 'MEDIUM',
newRisk: 'LOW',
mobile: '+91-9755179366',
description: 'Risk Insert With Pancard verification',
frozen: false,
updatedOn: '02-11-2025'
},
{
branchCode: '04',
branch: 'Bengaluru Jayanagar',
acType: 'OD',
clientId: 'BUBL000711',
customerName: 'Vikram Reddy',
oldRisk: 'MEDIUM',
newRisk: 'HIGH',
mobile: '+91-9826544604',
description: 'Risk Insert With Occupation — Politically Exposed',
frozen: true,
updatedOn: '08-11-2025'
},
{
branchCode: '05',
branch: 'Bengaluru Indiranagar',
acType: 'SB',
clientId: 'BUBL000814',
customerName: 'Lakshmi Iyer',
oldRisk: 'LOW',
newRisk: 'LOW',
mobile: '+91-9893677629',
description: 'Identification — Individual Confirmed',
frozen: false,
updatedOn: '15-11-2025'
},
{
branchCode: '06',
branch: 'Ahmedabad Satellite',
acType: 'CC',
clientId: 'BUBL001012',
customerName: 'Nirav Shah',
oldRisk: 'LOW',
newRisk: 'MEDIUM',
mobile: '+91-9926362230',
description: 'Risk Updated — Account Turn Over Medium',
frozen: false,
updatedOn: '22-11-2025'
},
{
branchCode: '07',
branch: 'Lucknow Gomti Nagar',
acType: 'CA',
clientId: 'BUBL001145',
customerName: 'Kavita Verma',
oldRisk: 'MEDIUM',
newRisk: 'HIGH',
mobile: '+91-9926322804',
description: 'Suspicious Transaction Trigger — STR raised',
frozen: true,
updatedOn: '28-11-2025'
},
{
branchCode: '08',
branch: 'Delhi Greater Kailash',
acType: 'OD',
clientId: 'BUBL001458',
customerName: 'Sameer Kapoor',
oldRisk: 'MEDIUM',
newRisk: 'MEDIUM',
mobile: '+91-9827444636',
description: 'Periodic Risk Review — Individual',
frozen: false,
updatedOn: '04-12-2025'
},
{
branchCode: '09',
branch: 'Chennai Valasaravakkam',
acType: 'SB',
clientId: 'BUBL001721',
customerName: 'Karthik Subramanian',
oldRisk: 'LOW',
newRisk: 'LOW',
mobile: '+91-9827565456',
description: 'Identification — Individual Confirmed',
frozen: false,
updatedOn: '11-12-2025'
},
{
branchCode: '10',
branch: 'Kolkata Salt Lake',
acType: 'TL',
clientId: 'BUBL002012',
customerName: 'Abhishek Banerjee',
oldRisk: 'LOW',
newRisk: 'MEDIUM',
mobile: '+91-9174151366',
description: 'Risk Insert With Occupation',
frozen: false,
updatedOn: '15-12-2025'
},
{
branchCode: '11',
branch: 'Shimla Sector 4',
acType: 'SB',
clientId: 'BUBL002314',
customerName: 'Gaurav Thakur',
oldRisk: 'LOW',
newRisk: 'LOW',
mobile: '+91-9111026263',
description: 'Periodic Risk Review — Individual',
frozen: false,
updatedOn: '18-12-2025'
},
{
branchCode: '12',
branch: 'Amritsar Model Town',
acType: 'CC',
clientId: 'BUBL002615',
customerName: 'Manmohan Singh Gill',
oldRisk: 'HIGH',
newRisk: 'HIGH',
mobile: '+91-9407272122',
description: 'High-Risk Re-confirmation — PEP review',
frozen: true,
updatedOn: '22-12-2025'
},
{
branchCode: '13',
branch: 'Bhopal Arera Colony',
acType: 'SB',
clientId: 'BUBL002918',
customerName: 'Sunita Devi',
oldRisk: 'LOW',
newRisk: 'LOW',
mobile: '+91-9691683108',
description: 'Identification — Individual Confirmed',
frozen: false,
updatedOn: '24-12-2025'
},
{
branchCode: '14',
branch: 'Indore Vijay Nagar',
acType: 'OD',
clientId: 'BUBL003221',
customerName: 'Deepak Tiwari',
oldRisk: 'MEDIUM',
newRisk: 'HIGH',
mobile: '+91-9826875772',
description: 'Risk Updated — Account Turn Over High',
frozen: true,
updatedOn: '28-12-2025'
},
{
branchCode: '15',
branch: 'Ludhiana Sarabha Nagar',
acType: 'TL',
clientId: 'BUBL003524',
customerName: 'Harpreet Kaur',
oldRisk: 'MEDIUM',
newRisk: 'MEDIUM',
mobile: '+91-9407256202',
description: 'Risk Insert With Pancard',
frozen: false,
updatedOn: '30-12-2025'
}
];
// -------------------- CERSAI Data --------------------
cersai: CersaiRow[] = [
{ branchCode: '01', branch: 'Mumbai Main', acType: 'TL', found: 142, notFound: 8, totalSecured: 150 },
{ branchCode: '02', branch: 'Pune FC Road', acType: 'TL', found: 98, notFound: 4, totalSecured: 102 },
{ branchCode: '02', branch: 'Pune FC Road', acType: 'CC', found: 38, notFound: 12, totalSecured: 50 },
{ branchCode: '03', branch: 'Nagpur Dharampeth', acType: 'HL', found: 64, notFound: 6, totalSecured: 70 },
{ branchCode: '04', branch: 'Bengaluru Jayanagar', acType: 'TL', found: 124, notFound: 0, totalSecured: 124 },
{ branchCode: '04', branch: 'Bengaluru Jayanagar', acType: 'OD', found: 42, notFound: 8, totalSecured: 50 },
{ branchCode: '05', branch: 'Bengaluru Indiranagar', acType: 'HL', found: 86, notFound: 4, totalSecured: 90 },
{ branchCode: '06', branch: 'Ahmedabad Satellite', acType: 'TL', found: 110, notFound: 14, totalSecured: 124 },
{ branchCode: '07', branch: 'Lucknow Gomti Nagar', acType: 'CC', found: 28, notFound: 12, totalSecured: 40 },
{ branchCode: '08', branch: 'Delhi Greater Kailash', acType: 'TL', found: 156, notFound: 4, totalSecured: 160 },
{ branchCode: '08', branch: 'Delhi Greater Kailash', acType: 'OD', found: 64, notFound: 16, totalSecured: 80 },
{ branchCode: '09', branch: 'Chennai Valasaravakkam', acType: 'HL', found: 72, notFound: 8, totalSecured: 80 },
{ branchCode: '10', branch: 'Kolkata Salt Lake', acType: 'TL', found: 88, notFound: 12, totalSecured: 100 },
{ branchCode: '11', branch: 'Shimla Sector 4', acType: 'TL', found: 32, notFound: 8, totalSecured: 40 },
{ branchCode: '12', branch: 'Amritsar Model Town', acType: 'CC', found: 18, notFound: 22, totalSecured: 40 },
{ branchCode: '13', branch: 'Bhopal Arera Colony', acType: 'HL', found: 56, notFound: 4, totalSecured: 60 },
{ branchCode: '14', branch: 'Indore Vijay Nagar', acType: 'TL', found: 92, notFound: 18, totalSecured: 110 },
{ branchCode: '15', branch: 'Ludhiana Sarabha Nagar', acType: 'OD', found: 36, notFound: 14, totalSecured: 50 }
];
cersaiFilterBranch = '';
cersaiFilterAcType: '' | 'TL' | 'CC' | 'OD' | 'HL' = '';
data = inject<CkycReportsDialogData | null>(MAT_DIALOG_DATA, { optional: true });
constructor() {
this.activeReport = this.data?.initialReport ?? 'REKYC';
}
// -------------------- Re-KYC computed --------------------
get filteredRekyc(): RekycRow[] {
const branch = this.rekycFilterBranch.trim().toLowerCase();
return this.rekyc.filter((r) => {
if (this.rekycFilterRisk && r.risk !== this.rekycFilterRisk) return false;
if (branch && !r.branch.toLowerCase().includes(branch)) return false;
if (this.rekycFilterSegment && r.segment !== this.rekycFilterSegment) return false;
if (this.rekycFilterStatus === 'OVERDUE' && r.daysOverdue <= 0) return false;
if (this.rekycFilterStatus === 'DUE-SOON' && (r.daysOverdue > 0 || r.daysOverdue < -90)) return false;
return true;
});
}
resetRekycFilters(): void {
this.rekycFilterRisk = '';
this.rekycFilterBranch = '';
this.rekycFilterSegment = '';
this.rekycFilterStatus = '';
}
get rekycOverdue(): number {
return this.rekyc.filter((r) => r.daysOverdue > 0).length;
}
get rekycDueSoon(): number {
return this.rekyc.filter((r) => r.daysOverdue <= 0 && r.daysOverdue >= -90).length;
}
get rekycHighRisk(): number {
return this.rekyc.filter((r) => r.risk === 'HIGH').length;
}
get rekycAbove25L(): number {
return this.rekyc.filter((r) => r.segment === 'ABOVE-25-LACS').length;
}
// -------------------- Risk Review computed --------------------
get filteredRiskReview(): RiskReviewRow[] {
const branch = this.riskFilterBranch.trim().toLowerCase();
return this.riskReview.filter((r) => {
if (branch && !r.branch.toLowerCase().includes(branch)) return false;
if (this.riskFilterNew && r.newRisk !== this.riskFilterNew) return false;
if (this.riskFilterFrozen === 'Y' && !r.frozen) return false;
if (this.riskFilterFrozen === 'N' && r.frozen) return false;
return true;
});
}
resetRiskFilters(): void {
this.riskFilterBranch = '';
this.riskFilterNew = '';
this.riskFilterFrozen = '';
}
get riskInsertedCount(): number {
return this.riskReview.filter((r) => r.description.toLowerCase().includes('insert')).length;
}
get riskUpgradedCount(): number {
return this.riskReview.filter(
(r) => (r.oldRisk === 'LOW' && r.newRisk !== 'LOW') || (r.oldRisk === 'MEDIUM' && r.newRisk === 'HIGH')
).length;
}
get riskFrozenCount(): number {
return this.riskReview.filter((r) => r.frozen).length;
}
riskBadgeClass(risk: 'LOW' | 'MEDIUM' | 'HIGH'): string {
return risk === 'HIGH' ? 'risk-high' : risk === 'MEDIUM' ? 'risk-mid' : 'risk-low';
}
// -------------------- CERSAI computed --------------------
get filteredCersai(): CersaiRow[] {
const branch = this.cersaiFilterBranch.trim().toLowerCase();
return this.cersai.filter((r) => {
if (branch && !r.branch.toLowerCase().includes(branch)) return false;
if (this.cersaiFilterAcType && r.acType !== this.cersaiFilterAcType) return false;
return true;
});
}
resetCersaiFilters(): void {
this.cersaiFilterBranch = '';
this.cersaiFilterAcType = '';
}
get cersaiTotalFound(): number {
return this.cersai.reduce((s, r) => s + r.found, 0);
}
get cersaiTotalNotFound(): number {
return this.cersai.reduce((s, r) => s + r.notFound, 0);
}
get cersaiTotalSecured(): number {
return this.cersai.reduce((s, r) => s + r.totalSecured, 0);
}
get cersaiCompliancePct(): number {
return this.cersaiTotalSecured === 0 ? 0 : +((this.cersaiTotalFound / this.cersaiTotalSecured) * 100).toFixed(2);
}
cersaiPct(r: CersaiRow): number {
return r.totalSecured === 0 ? 0 : +((r.found / r.totalSecured) * 100).toFixed(1);
}
cersaiPctClass(pct: number): string {
if (pct >= 95) return 'good';
if (pct >= 80) return 'mid';
return 'low';
}
setReport(code: CkycReportCode): void {
this.activeReport = code;
}
close(): void {
this.dialogRef.close();
}
get currentReport() {
return this.reports.find((r) => r.code === this.activeReport)!;
}
// -------------------- Exports --------------------
exportRekycCsv(): void {
const headers = [
'UCode',
'Name',
'Customer Type',
'Risk',
'Branch',
'Branch Code',
'KYC Expiry',
'Days Overdue',
'Status',
'Segment'
];
const rows = [headers];
for (const r of this.filteredRekyc) {
const status = r.daysOverdue > 0 ? 'OVERDUE' : r.daysOverdue >= -90 ? 'DUE SOON' : 'WITHIN';
rows.push([
r.ucode,
r.name,
r.customerType,
r.risk,
r.branch,
r.branchCode,
r.kycExpiry,
r.daysOverdue.toString(),
status,
r.segment
]);
}
rows.push([]);
rows.push([
'TOTAL COUNT',
this.filteredRekyc.length.toString()
]);
rows.push([
'Report Ref No',
this.bank.reportRefNo,
'User Code',
this.bank.userCode
]);
this.saveCsv(rows, `Rekyc-Report-${this.dateTag()}.csv`);
}
exportRiskReviewCsv(): void {
const headers = [
'Branch Code',
'Branch',
'A/c Type',
'Client ID',
'Customer Name',
'Old Risk',
'New Risk',
'Mobile',
'Description',
'Account Frozen',
'Updated On'
];
const rows = [headers];
for (const r of this.filteredRiskReview) {
rows.push([
r.branchCode,
r.branch,
r.acType,
r.clientId,
r.customerName,
r.oldRisk,
r.newRisk,
r.mobile,
r.description,
r.frozen ? 'Y' : 'N',
r.updatedOn
]);
}
rows.push([]);
rows.push(['Risk Category Updated Summary']);
rows.push([
'Total Clients',
this.filteredRiskReview.length.toString()
]);
rows.push([
'Risk Inserted',
this.riskInsertedCount.toString()
]);
rows.push([
'Risk Upgraded',
this.riskUpgradedCount.toString()
]);
rows.push([
'Account Frozen',
this.riskFrozenCount.toString()
]);
rows.push([]);
rows.push([
'Period',
`${this.riskReviewPeriod.from} TO ${this.riskReviewPeriod.to}`
]);
rows.push([
'Print Date',
new Date().toLocaleString('en-IN')
]);
this.saveCsv(rows, `Risk-Review-${this.dateTag()}.csv`);
}
exportCersaiCsv(): void {
const headers = [
'Branch Code',
'Branch',
'A/c Type',
'CERSAI Found',
'CERSAI Not Found',
'Total Secured',
'Compliance %'
];
const rows = [headers];
for (const r of this.filteredCersai) {
rows.push([
r.branchCode,
r.branch,
r.acType,
r.found.toString(),
r.notFound.toString(),
r.totalSecured.toString(),
this.cersaiPct(r).toFixed(2) + '%'
]);
}
rows.push([]);
rows.push([
'BANK TOTAL',
'',
'',
this.cersaiTotalFound.toString(),
this.cersaiTotalNotFound.toString(),
this.cersaiTotalSecured.toString(),
this.cersaiCompliancePct.toFixed(2) + '%'
]);
rows.push([]);
rows.push([
'Bank',
this.bank.name
]);
rows.push([
'Branch',
this.bank.branch
]);
rows.push([
'As On',
this.bank.asOnDate
]);
rows.push([
'Principal Officer',
this.bank.principalOfficer
]);
this.saveCsv(rows, `CERSAI-Report-${this.dateTag()}.csv`);
}
exportCurrentExcel(): void {
if (this.activeReport === 'REKYC') return this.exportRekycCsv();
if (this.activeReport === 'RISK-REVIEW') return this.exportRiskReviewCsv();
if (this.activeReport === 'CERSAI') return this.exportCersaiCsv();
}
// ---- helpers ----
private dateTag(): string {
const d = new Date();
return `${String(d.getDate()).padStart(2, '0')}${String(d.getMonth() + 1).padStart(2, '0')}${d.getFullYear()}`;
}
private saveCsv(rows: string[][], filename: string): void {
const escape = (v: string): string => {
const s = (v || '').replace(/"/g, '""');
if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s}"`;
return s;
};
const csv = rows.map((r) => r.map(escape).join(',')).join('\n') + '\n';
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
}
@@ -22,7 +22,13 @@
<strong>Q3 FY 2025-26</strong>
<span>Reporting Quarter</span>
</div>
<a class="rbi-circular-btn" href="https://www.rbi.org.in/scripts/bs_circularindexdisplay.aspx" target="_blank" rel="noopener noreferrer" matTooltip="View RBI Circulars">
<a
class="rbi-circular-btn"
href="https://www.rbi.org.in/scripts/bs_circularindexdisplay.aspx"
target="_blank"
rel="noopener noreferrer"
matTooltip="View RBI Circulars"
>
<fa-icon icon="external-link-alt"></fa-icon>
RBI Circulars
</a>
@@ -111,13 +117,19 @@
</td>
<td class="mono">{{ item.code }}</td>
<td>
<span class="status-chip" [class.chip-cet1]="item.category === 'CET1'" [class.chip-at1]="item.category === 'AT1'">
<span
class="status-chip"
[class.chip-cet1]="item.category === 'CET1'"
[class.chip-at1]="item.category === 'AT1'"
>
{{ item.category }}
</span>
</td>
<td class="right amt amt-t1c">{{ item.prev }}</td>
<td class="right amt amt-t1c">{{ item.curr }}</td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">{{ item.change }}</td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">
{{ item.change }}
</td>
</tr>
}
</tbody>
@@ -159,7 +171,9 @@
<td class="mono">{{ item.code }}</td>
<td class="right amt amt-t2c">{{ item.prev }}</td>
<td class="right amt amt-t2c">{{ item.curr }}</td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">{{ item.change }}</td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">
{{ item.change }}
</td>
</tr>
}
</tbody>
@@ -199,7 +213,9 @@
<div class="item-sub">{{ item.sub }}</div>
</td>
<td class="mono">{{ item.code }}</td>
<td><span class="status-chip chip-rw">{{ item.rw }}</span></td>
<td>
<span class="status-chip chip-rw">{{ item.rw }}</span>
</td>
<td class="right amt amt-cr">{{ item.gross }}</td>
<td class="right amt amt-cr">{{ item.rwa }}</td>
</tr>
@@ -240,7 +256,9 @@
<div class="item-sub">{{ item.sub }}</div>
</td>
<td class="mono">{{ item.code }}</td>
<td><span class="status-chip chip-approach">{{ item.approach }}</span></td>
<td>
<span class="status-chip chip-approach">{{ item.approach }}</span>
</td>
<td class="right amt amt-mr">{{ item.cc }}</td>
<td class="right amt amt-mr">{{ item.rwa }}</td>
</tr>
@@ -276,7 +294,7 @@
<div class="item-name">Gross Income Average (3-year)</div>
<div class="item-sub">Average of last 3 years positive gross income</div>
</td>
<td class="right amt amt-or">&#8377;{{ opRiskGrossIncome | number:'1.2-2' }} Cr</td>
<td class="right amt amt-or">&#8377;{{ opRiskGrossIncome | number: '1.2-2' }} Cr</td>
</tr>
<tr>
<td class="sl">02</td>
@@ -292,7 +310,7 @@
<div class="item-name">Capital Charge</div>
<div class="item-sub">Gross Income &times; Alpha</div>
</td>
<td class="right amt amt-or">&#8377;{{ opRiskCapitalCharge | number:'1.2-2' }} Cr</td>
<td class="right amt amt-or">&#8377;{{ opRiskCapitalCharge | number: '1.2-2' }} Cr</td>
</tr>
<tr>
<td class="sl">04</td>
@@ -300,7 +318,7 @@
<div class="item-name">RWA (Capital Charge &times; 12.5)</div>
<div class="item-sub">{{ opRiskCapitalCharge }} &times; 12.5 = {{ opRiskRWA }}</div>
</td>
<td class="right amt amt-or">&#8377;{{ opRiskRWA | number:'1.2-2' }} Cr</td>
<td class="right amt amt-or">&#8377;{{ opRiskRWA | number: '1.2-2' }} Cr</td>
</tr>
</tbody>
<tfoot>
@@ -319,25 +337,25 @@
<div class="formula-row">
<div class="formula-term ft-t1c">
<div class="formula-term-label">Tier 1 Capital</div>
<div class="formula-term-val">&#8377;{{ totalTier1 | number:'1.2-2' }}</div>
<div class="formula-term-val">&#8377;{{ totalTier1 | number: '1.2-2' }}</div>
<div class="formula-term-sub">CET1 + AT1</div>
</div>
<div class="formula-op">+</div>
<div class="formula-term ft-t2c">
<div class="formula-term-label">Tier 2 Capital</div>
<div class="formula-term-val">&#8377;{{ totalTier2 | number:'1.2-2' }}</div>
<div class="formula-term-val">&#8377;{{ totalTier2 | number: '1.2-2' }}</div>
<div class="formula-term-sub">Supplementary</div>
</div>
<div class="formula-op">&divide;</div>
<div class="formula-term ft-rwa">
<div class="formula-term-label">Total RWA</div>
<div class="formula-term-val">&#8377;{{ totalRWA | number:'1.0-0' }}</div>
<div class="formula-term-val">&#8377;{{ totalRWA | number: '1.0-0' }}</div>
<div class="formula-term-sub">Credit + Market + Op</div>
</div>
<div class="formula-op">&times;</div>
<div class="formula-term" style="border-color:#e2e8f0;background:#f8fafc;">
<div class="formula-term-label" style="color:#64748b;">100</div>
<div class="formula-term-val" style="color:#334155;">100</div>
<div class="formula-term" style="border-color: #e2e8f0; background: #f8fafc">
<div class="formula-term-label" style="color: #64748b">100</div>
<div class="formula-term-val" style="color: #334155">100</div>
<div class="formula-term-sub">&nbsp;</div>
</div>
<div class="formula-op equals">=</div>
@@ -353,7 +371,10 @@
<div class="compliance-bar">
<div class="compliance-left">
<div class="compliance-title">Basel III Compliance Status</div>
<div class="compliance-desc">All thresholds met &middot; CRAR: {{ crar }}% &middot; CET1: {{ cet1Ratio }}% &middot; Tier 1: {{ tier1Ratio }}% &middot; Tier 2: {{ tier2Ratio }}%</div>
<div class="compliance-desc">
All thresholds met &middot; CRAR: {{ crar }}% &middot; CET1: {{ cet1Ratio }}% &middot; Tier 1: {{ tier1Ratio }}%
&middot; Tier 2: {{ tier2Ratio }}%
</div>
</div>
<div class="compliance-calcs">
<div class="compliance-calc">
@@ -198,12 +198,29 @@ $mono: 'IBM Plex Mono', 'Roboto Mono', monospace;
}
}
.sc-t1c::before { background: $t1c; }
.sc-t2c::before { background: $t2c; }
.sc-total-cap::before { background: linear-gradient(90deg, $t1c, $t2c); }
.sc-rwa::before { background: $rwa; }
.sc-crar::before { background: $crar; }
.sc-buffer::before { background: $green; }
.sc-t1c::before {
background: $t1c;
}
.sc-t2c::before {
background: $t2c;
}
.sc-total-cap::before {
background: linear-gradient(90deg, $t1c, $t2c);
}
.sc-rwa::before {
background: $rwa;
}
.sc-crar::before {
background: $crar;
}
.sc-buffer::before {
background: $green;
}
.sc-label {
font-size: 10px;
@@ -239,8 +256,13 @@ $mono: 'IBM Plex Mono', 'Roboto Mono', monospace;
margin-top: 4px;
}
.up { color: $green; }
.dn { color: $red; }
.up {
color: $green;
}
.dn {
color: $red;
}
// ===== Tab Navigation =====
.tab-nav {
@@ -291,11 +313,35 @@ $mono: 'IBM Plex Mono', 'Roboto Mono', monospace;
letter-spacing: 0.5px;
}
.pill-t1c { background: $t1c-bg; color: $t1c; border: 1px solid $t1c-b; }
.pill-t2c { background: $t2c-bg; color: $t2c; border: 1px solid $t2c-b; }
.pill-cr { background: $cr-bg; color: $cr; border: 1px solid $cr-b; }
.pill-mr { background: $mr-bg; color: $mr; border: 1px solid $mr-b; }
.pill-or { background: $or-bg; color: $or; border: 1px solid $or-b; }
.pill-t1c {
background: $t1c-bg;
color: $t1c;
border: 1px solid $t1c-b;
}
.pill-t2c {
background: $t2c-bg;
color: $t2c;
border: 1px solid $t2c-b;
}
.pill-cr {
background: $cr-bg;
color: $cr;
border: 1px solid $cr-b;
}
.pill-mr {
background: $mr-bg;
color: $mr;
border: 1px solid $mr-b;
}
.pill-or {
background: $or-bg;
color: $or;
border: 1px solid $or-b;
}
.section-desc {
font-size: 12px;
@@ -335,15 +381,22 @@ thead th {
text-transform: uppercase;
white-space: nowrap;
&.right { text-align: right; }
&.right {
text-align: right;
}
}
tbody tr {
border-bottom: 1px solid $border;
transition: background 0.15s;
&:last-child { border-bottom: none; }
&:hover { background: #f8fafc; }
&:last-child {
border-bottom: none;
}
&:hover {
background: #f8fafc;
}
}
td {
@@ -351,7 +404,10 @@ td {
color: $ink2;
vertical-align: middle;
&.right { text-align: right; font-family: $mono; }
&.right {
text-align: right;
font-family: $mono;
}
}
.sl {
@@ -361,7 +417,10 @@ td {
width: 36px;
}
.mono { font-family: $mono; font-size: 12px; }
.mono {
font-family: $mono;
font-size: 12px;
}
.item-name {
font-weight: 500;
@@ -379,12 +438,29 @@ td {
font-weight: 500;
}
.amt-t1c { color: $t1c; }
.amt-t2c { color: $t2c; }
.amt-cr { color: $cr; }
.amt-mr { color: $mr; }
.amt-or { color: $or; }
.amt-crar { color: $crar; }
.amt-t1c {
color: $t1c;
}
.amt-t2c {
color: $t2c;
}
.amt-cr {
color: $cr;
}
.amt-mr {
color: $mr;
}
.amt-or {
color: $or;
}
.amt-crar {
color: $crar;
}
.status-chip {
display: inline-flex;
@@ -397,17 +473,70 @@ td {
border-radius: 4px;
}
.chip-cet1 { background: $t1c-bg; color: $t1c; border: 1px solid $t1c-b; }
.chip-at1 { background: $rwa-bg; color: $rwa; border: 1px solid $rwa-b; }
.chip-rw { background: $cr-bg; color: $cr; border: 1px solid $cr-b; }
.chip-approach { background: $mr-bg; color: $mr; border: 1px solid $mr-b; }
.chip-active { background: #dcfce7; color: #15803d; }
.chip-t1c { background: $t1c-bg; color: $t1c; border: 1px solid $t1c-b; }
.chip-t2c { background: $t2c-bg; color: $t2c; border: 1px solid $t2c-b; }
.chip-cr { background: $cr-bg; color: $cr; border: 1px solid $cr-b; }
.chip-mr { background: $mr-bg; color: $mr; border: 1px solid $mr-b; }
.chip-or { background: $or-bg; color: $or; border: 1px solid $or-b; }
.chip-crar { background: $crar-bg; color: $crar; border: 1px solid $crar-b; }
.chip-cet1 {
background: $t1c-bg;
color: $t1c;
border: 1px solid $t1c-b;
}
.chip-at1 {
background: $rwa-bg;
color: $rwa;
border: 1px solid $rwa-b;
}
.chip-rw {
background: $cr-bg;
color: $cr;
border: 1px solid $cr-b;
}
.chip-approach {
background: $mr-bg;
color: $mr;
border: 1px solid $mr-b;
}
.chip-active {
background: #dcfce7;
color: #15803d;
}
.chip-t1c {
background: $t1c-bg;
color: $t1c;
border: 1px solid $t1c-b;
}
.chip-t2c {
background: $t2c-bg;
color: $t2c;
border: 1px solid $t2c-b;
}
.chip-cr {
background: $cr-bg;
color: $cr;
border: 1px solid $cr-b;
}
.chip-mr {
background: $mr-bg;
color: $mr;
border: 1px solid $mr-b;
}
.chip-or {
background: $or-bg;
color: $or;
border: 1px solid $or-b;
}
.chip-crar {
background: $crar-bg;
color: $crar;
border: 1px solid $crar-b;
}
tfoot tr {
background: $surface2;
@@ -420,15 +549,35 @@ tfoot td {
font-family: $mono;
padding: 12px 14px;
&.right { text-align: right; font-size: 14px; }
&.right {
text-align: right;
font-size: 14px;
}
}
.foot-t1c { color: $t1c; }
.foot-t2c { color: $t2c; }
.foot-cr { color: $cr; }
.foot-mr { color: $mr; }
.foot-or { color: $or; }
.foot-crar { color: $crar; }
.foot-t1c {
color: $t1c;
}
.foot-t2c {
color: $t2c;
}
.foot-cr {
color: $cr;
}
.foot-mr {
color: $mr;
}
.foot-or {
color: $or;
}
.foot-crar {
color: $crar;
}
// ===== Formula Block =====
.formula-block {
@@ -511,33 +660,58 @@ tfoot td {
padding: 0 8px;
flex-shrink: 0;
&.minus { color: $red; }
&.equals { color: $ink2; font-weight: 500; }
&.minus {
color: $red;
}
&.equals {
color: $ink2;
font-weight: 500;
}
}
.ft-t1c {
border-color: $t1c-b;
background: $t1c-bg;
.formula-term-label, .formula-term-val { color: $t1c; }
.formula-term-label,
.formula-term-val {
color: $t1c;
}
}
.ft-t2c {
border-color: $t2c-b;
background: $t2c-bg;
.formula-term-label, .formula-term-val { color: $t2c; }
.formula-term-label,
.formula-term-val {
color: $t2c;
}
}
.ft-rwa {
border-color: $rwa-b;
background: $rwa-bg;
.formula-term-label, .formula-term-val { color: $rwa; }
.formula-term-label,
.formula-term-val {
color: $rwa;
}
}
.ft-crar-result {
border-color: $crar-b;
background: linear-gradient(135deg, #f0fdf4, #ecfdf5);
.formula-term-label { color: #15803d; }
.formula-term-val { color: $crar; font-size: 20px; }
.formula-term-label {
color: #15803d;
}
.formula-term-val {
color: $crar;
font-size: 20px;
}
}
// ===== Compliance Bar =====
@@ -553,7 +727,9 @@ tfoot td {
flex-wrap: wrap;
}
.compliance-left { flex: 1; }
.compliance-left {
flex: 1;
}
.compliance-title {
font-size: 13px;
@@ -572,7 +748,9 @@ tfoot td {
gap: 24px;
}
.compliance-calc { text-align: right; }
.compliance-calc {
text-align: right;
}
.compliance-calc-label {
font-size: 10px;
@@ -587,7 +765,9 @@ tfoot td {
color: #14532d;
font-family: $mono;
&.surplus { color: $crar; }
&.surplus {
color: $crar;
}
}
// ===== Footer =====
@@ -35,9 +35,9 @@ export class CrarDialogComponent {
activeTab: 'tier1' | 'tier2' | 'creditRisk' | 'marketRisk' | 'opRisk' = 'tier1';
/** Capital Adequacy computations */
readonly totalTier1 = 8284.80;
readonly totalTier2 = 1556.00;
readonly totalCapital = 9840.80;
readonly totalTier1 = 8284.8;
readonly totalTier2 = 1556.0;
readonly totalCapital = 9840.8;
readonly totalRWA = 58420;
readonly crar = 16.84;
readonly cet1Ratio = 12.42;
@@ -46,52 +46,251 @@ export class CrarDialogComponent {
readonly capitalBuffer = 6.34;
/** Credit Risk RWA */
readonly totalCreditRWA = 47820.40;
readonly totalCreditRWA = 47820.4;
/** Market Risk RWA */
readonly totalMarketRWA = 7005.00;
readonly totalMarketRWA = 7005.0;
/** Operational Risk RWA */
readonly totalOpRWA = 3595.60;
readonly totalOpRWA = 3595.6;
tier1Capital = [
{ sl: '01', name: 'Paid-up Equity Share Capital', sub: 'Ordinary equity shares fully paid', code: '5001-ESC', category: 'CET1', prev: '1,200.00', curr: '1,200.00', change: '\u2014' },
{ sl: '02', name: 'Statutory Reserves (Sec 17)', sub: 'Reserves under Sec 17 of BR Act', code: '5002-SR', category: 'CET1', prev: '2,840.60', curr: '3,012.40', change: '+171.80', changeDir: 'up' },
{ sl: '03', name: 'Capital Reserve', sub: 'Non-distributable capital reserves', code: '5003-CR', category: 'CET1', prev: '680.20', curr: '720.80', change: '+40.60', changeDir: 'up' },
{ sl: '04', name: 'Share Premium Account', sub: 'Premium collected on equity issuance', code: '5004-SP', category: 'CET1', prev: '1,480.00', curr: '1,480.00', change: '\u2014' },
{ sl: '05', name: 'Retained Earnings (Current Year)', sub: 'Profit retained after dividend payout', code: '5005-RE', category: 'CET1', prev: '1,060.40', curr: '1,284.60', change: '+224.20', changeDir: 'up' },
{ sl: '06', name: 'PNCPS', sub: 'Perpetual Non-Cumulative Preference Shares', code: '5101-PNCPS', category: 'AT1', prev: '500.00', curr: '500.00', change: '\u2014' },
{ sl: '07', name: 'Perpetual Debt Instruments', sub: 'Basel III compliant perpetual bonds', code: '5102-PDI', category: 'AT1', prev: '800.00', curr: '800.00', change: '\u2014' },
{ sl: '08', name: 'Innovative PDI', sub: 'Innovative perpetual debt instruments', code: '5103-IPDI', category: 'AT1', prev: '200.00', curr: '200.00', change: '\u2014' }
{
sl: '01',
name: 'Paid-up Equity Share Capital',
sub: 'Ordinary equity shares fully paid',
code: '5001-ESC',
category: 'CET1',
prev: '1,200.00',
curr: '1,200.00',
change: '\u2014'
},
{
sl: '02',
name: 'Statutory Reserves (Sec 17)',
sub: 'Reserves under Sec 17 of BR Act',
code: '5002-SR',
category: 'CET1',
prev: '2,840.60',
curr: '3,012.40',
change: '+171.80',
changeDir: 'up'
},
{
sl: '03',
name: 'Capital Reserve',
sub: 'Non-distributable capital reserves',
code: '5003-CR',
category: 'CET1',
prev: '680.20',
curr: '720.80',
change: '+40.60',
changeDir: 'up'
},
{
sl: '04',
name: 'Share Premium Account',
sub: 'Premium collected on equity issuance',
code: '5004-SP',
category: 'CET1',
prev: '1,480.00',
curr: '1,480.00',
change: '\u2014'
},
{
sl: '05',
name: 'Retained Earnings (Current Year)',
sub: 'Profit retained after dividend payout',
code: '5005-RE',
category: 'CET1',
prev: '1,060.40',
curr: '1,284.60',
change: '+224.20',
changeDir: 'up'
},
{
sl: '06',
name: 'PNCPS',
sub: 'Perpetual Non-Cumulative Preference Shares',
code: '5101-PNCPS',
category: 'AT1',
prev: '500.00',
curr: '500.00',
change: '\u2014'
},
{
sl: '07',
name: 'Perpetual Debt Instruments',
sub: 'Basel III compliant perpetual bonds',
code: '5102-PDI',
category: 'AT1',
prev: '800.00',
curr: '800.00',
change: '\u2014'
},
{
sl: '08',
name: 'Innovative PDI',
sub: 'Innovative perpetual debt instruments',
code: '5103-IPDI',
category: 'AT1',
prev: '200.00',
curr: '200.00',
change: '\u2014'
}
];
tier2Capital = [
{ sl: '01', name: 'Subordinated Debt', sub: 'Long-term subordinated bonds', code: '6001-SD', prev: '620.00', curr: '620.00', change: '\u2014' },
{ sl: '02', name: 'General Provisions & Loan Loss', sub: 'General provisions for standard assets', code: '6002-GP', prev: '480.40', curr: '520.60', change: '+40.20', changeDir: 'up' },
{ sl: '03', name: 'Revaluation Reserves', sub: 'Reserves from asset revaluation (55%)', code: '6003-RR', prev: '184.20', curr: '196.80', change: '+12.60', changeDir: 'up' },
{ sl: '04', name: 'Investment Fluctuation Reserve', sub: 'IFR as per RBI guidelines', code: '6004-IFR', prev: '120.00', curr: '142.60', change: '+22.60', changeDir: 'up' },
{ sl: '05', name: 'Upper Tier 2 Bonds', sub: 'Upper Tier 2 capital instruments', code: '6005-UTB', prev: '80.00', curr: '76.00', change: '-4.00', changeDir: 'dn' }
{
sl: '01',
name: 'Subordinated Debt',
sub: 'Long-term subordinated bonds',
code: '6001-SD',
prev: '620.00',
curr: '620.00',
change: '\u2014'
},
{
sl: '02',
name: 'General Provisions & Loan Loss',
sub: 'General provisions for standard assets',
code: '6002-GP',
prev: '480.40',
curr: '520.60',
change: '+40.20',
changeDir: 'up'
},
{
sl: '03',
name: 'Revaluation Reserves',
sub: 'Reserves from asset revaluation (55%)',
code: '6003-RR',
prev: '184.20',
curr: '196.80',
change: '+12.60',
changeDir: 'up'
},
{
sl: '04',
name: 'Investment Fluctuation Reserve',
sub: 'IFR as per RBI guidelines',
code: '6004-IFR',
prev: '120.00',
curr: '142.60',
change: '+22.60',
changeDir: 'up'
},
{
sl: '05',
name: 'Upper Tier 2 Bonds',
sub: 'Upper Tier 2 capital instruments',
code: '6005-UTB',
prev: '80.00',
curr: '76.00',
change: '-4.00',
changeDir: 'dn'
}
];
creditRiskRWA = [
{ sl: '01', name: 'Claims on Sovereign', sub: 'Central & State Government exposures', code: '7001-SOV', rw: '0%', gross: '18,420.60', rwa: '0.00' },
{ sl: '02', name: 'Claims on Banks', sub: 'Inter-bank exposures — scheduled banks', code: '7002-BNK', rw: '20%', gross: '4,210.40', rwa: '842.08' },
{ sl: '03', name: 'Corporate Loans Rated', sub: 'Rated corporate term loans', code: '7003-CORP', rw: '100%', gross: '12,840.20', rwa: '12,840.20' },
{ sl: '04', name: 'Retail Portfolio', sub: 'Retail loans — personal, auto, education', code: '7004-RTL', rw: '75%', gross: '8,640.80', rwa: '6,480.60' },
{ sl: '05', name: 'Housing Loans', sub: 'Home loans — LTV based risk weight', code: '7005-HLE', rw: '35%', gross: '6,820.40', rwa: '2,387.14' }
{
sl: '01',
name: 'Claims on Sovereign',
sub: 'Central & State Government exposures',
code: '7001-SOV',
rw: '0%',
gross: '18,420.60',
rwa: '0.00'
},
{
sl: '02',
name: 'Claims on Banks',
sub: 'Inter-bank exposures — scheduled banks',
code: '7002-BNK',
rw: '20%',
gross: '4,210.40',
rwa: '842.08'
},
{
sl: '03',
name: 'Corporate Loans Rated',
sub: 'Rated corporate term loans',
code: '7003-CORP',
rw: '100%',
gross: '12,840.20',
rwa: '12,840.20'
},
{
sl: '04',
name: 'Retail Portfolio',
sub: 'Retail loans — personal, auto, education',
code: '7004-RTL',
rw: '75%',
gross: '8,640.80',
rwa: '6,480.60'
},
{
sl: '05',
name: 'Housing Loans',
sub: 'Home loans — LTV based risk weight',
code: '7005-HLE',
rw: '35%',
gross: '6,820.40',
rwa: '2,387.14'
}
];
marketRiskRWA = [
{ sl: '01', name: 'Interest Rate Risk', sub: 'Duration & modified duration approach', code: '8001-IRR', approach: 'SDA', cc: '284.60', rwa: '3,557.50' },
{ sl: '02', name: 'Forex Risk', sub: 'Net open position in foreign currencies', code: '8002-FX', approach: 'Std', cc: '148.40', rwa: '1,855.00' },
{ sl: '03', name: 'Equity Risk', sub: 'Listed & unlisted equity exposures', code: '8003-EQR', approach: 'Std', cc: '84.20', rwa: '1,052.50' },
{ sl: '04', name: 'Commodity Price Risk', sub: 'Commodity position risk charges', code: '8004-CMR', approach: 'Simplified', cc: '24.80', rwa: '310.00' },
{ sl: '05', name: 'Options Risk', sub: 'Options portfolio — scenario analysis', code: '8005-OPR', approach: 'Scenario', cc: '18.40', rwa: '230.00' }
{
sl: '01',
name: 'Interest Rate Risk',
sub: 'Duration & modified duration approach',
code: '8001-IRR',
approach: 'SDA',
cc: '284.60',
rwa: '3,557.50'
},
{
sl: '02',
name: 'Forex Risk',
sub: 'Net open position in foreign currencies',
code: '8002-FX',
approach: 'Std',
cc: '148.40',
rwa: '1,855.00'
},
{
sl: '03',
name: 'Equity Risk',
sub: 'Listed & unlisted equity exposures',
code: '8003-EQR',
approach: 'Std',
cc: '84.20',
rwa: '1,052.50'
},
{
sl: '04',
name: 'Commodity Price Risk',
sub: 'Commodity position risk charges',
code: '8004-CMR',
approach: 'Simplified',
cc: '24.80',
rwa: '310.00'
},
{
sl: '05',
name: 'Options Risk',
sub: 'Options portfolio — scenario analysis',
code: '8005-OPR',
approach: 'Scenario',
cc: '18.40',
rwa: '230.00'
}
];
/** Operational Risk — Basic Indicator Approach */
readonly opRiskGrossIncome = 2984.40;
readonly opRiskGrossIncome = 2984.4;
readonly opRiskAlpha = 15;
readonly opRiskCapitalCharge = 447.66;
readonly opRiskRWA = 3595.60; // 447.66 × 12.5 = 5595.75 — using provided value
readonly opRiskRWA = 3595.6; // 447.66 × 12.5 = 5595.75 — using provided value
close(): void {
this.dialogRef.close();
@@ -22,7 +22,13 @@
<strong>{{ reportingDateStr }}</strong>
<span>Reporting Fortnight (45-day lag)</span>
</div>
<a class="rbi-circular-btn" href="https://www.rbi.org.in/scripts/bs_circularindexdisplay.aspx" target="_blank" rel="noopener noreferrer" matTooltip="View RBI Circulars">
<a
class="rbi-circular-btn"
href="https://www.rbi.org.in/scripts/bs_circularindexdisplay.aspx"
target="_blank"
rel="noopener noreferrer"
matTooltip="View RBI Circulars"
>
<fa-icon icon="external-link-alt"></fa-icon>
RBI Circulars
</a>
@@ -56,8 +62,8 @@
</div>
<div class="summary-card sc-crr">
<div class="sc-label">CRR Required ({{ crrRate }}%)</div>
<div class="sc-value">&#8377;{{ crrRequired | number:'1.0-0' }}<span class="cr">Cr</span></div>
<div class="sc-sub">SLR Req: &#8377;{{ slrRequired | number:'1.0-0' }} Cr ({{ slrRate }}%)</div>
<div class="sc-value">&#8377;{{ crrRequired | number: '1.0-0' }}<span class="cr">Cr</span></div>
<div class="sc-sub">SLR Req: &#8377;{{ slrRequired | number: '1.0-0' }} Cr ({{ slrRate }}%)</div>
</div>
</div>
@@ -104,11 +110,15 @@
<td class="mono">{{ item.code }}</td>
<td class="right amt amt-demand">{{ item.opening }}</td>
<td class="right amt amt-demand">{{ item.current }}</td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">{{ item.change }}</td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">
{{ item.change }}
</td>
<td>
<span class="status-chip"
<span
class="status-chip"
[class.chip-active]="item.status === 'Active'"
[class.chip-overdue]="item.status === 'Overdue' || item.status === 'Unclaimed'">
[class.chip-overdue]="item.status === 'Overdue' || item.status === 'Unclaimed'"
>
{{ item.status }}
</span>
</td>
@@ -155,11 +165,15 @@
<td class="mono">{{ item.code }}</td>
<td class="right amt amt-time">{{ item.opening }}</td>
<td class="right amt amt-time">{{ item.current }}</td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">{{ item.change }}</td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">
{{ item.change }}
</td>
<td>
<span class="status-chip"
<span
class="status-chip"
[class.chip-active]="item.status === 'Active'"
[class.chip-locked]="item.status === 'Locked'">
[class.chip-locked]="item.status === 'Locked'"
>
{{ item.status }}
</span>
</td>
@@ -206,14 +220,20 @@
<td class="mono">{{ item.code }}</td>
<td class="right amt amt-asset">{{ item.opening }}</td>
<td class="right amt amt-asset">{{ item.current }}</td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">{{ item.change }}</td>
<td><span class="status-chip chip-active">{{ item.status }}</span></td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">
{{ item.change }}
</td>
<td>
<span class="status-chip chip-active">{{ item.status }}</span>
</td>
</tr>
}
</tbody>
<tfoot>
<tr>
<td colspan="3"><strong>Total Assets with Banking System</strong> <span class="deduct-tag">( Deducted from NDTL )</span></td>
<td colspan="3">
<strong>Total Assets with Banking System</strong> <span class="deduct-tag">( Deducted from NDTL )</span>
</td>
<td class="right foot-asset">7,600.70</td>
<td class="right foot-asset">8,240.20</td>
<td class="right up">+639.50</td>
@@ -226,7 +246,9 @@
<!-- SLR - Liquid Assets Table -->
@if (activeTab === 'slr') {
<div class="section-desc">SLR = (Liquid Assets / NDTL) &times; 100 &mdash; RBI Sec 24(2A) of Banking Regulation Act, 1949</div>
<div class="section-desc">
SLR = (Liquid Assets / NDTL) &times; 100 &mdash; RBI Sec 24(2A) of Banking Regulation Act, 1949
</div>
<div class="table-wrap">
<table>
<thead>
@@ -250,11 +272,17 @@
<div class="item-sub">{{ item.sub }}</div>
</td>
<td class="mono">{{ item.code }}</td>
<td><span class="status-chip chip-slr">{{ item.category }}</span></td>
<td>
<span class="status-chip chip-slr">{{ item.category }}</span>
</td>
<td class="right amt amt-slr">{{ item.opening }}</td>
<td class="right amt amt-slr">{{ item.current }}</td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">{{ item.change }}</td>
<td><span class="status-chip chip-active">{{ item.status }}</span></td>
<td class="right" [class.up]="item.changeDir === 'up'" [class.dn]="item.changeDir === 'dn'">
{{ item.change }}
</td>
<td>
<span class="status-chip chip-active">{{ item.status }}</span>
</td>
</tr>
}
</tbody>
@@ -286,9 +314,9 @@
<div class="formula-term-sub">Net Demand &amp; Time</div>
</div>
<div class="formula-op">&times;</div>
<div class="formula-term" style="border-color:#e2e8f0;background:#f8fafc;">
<div class="formula-term-label" style="color:#64748b;">100</div>
<div class="formula-term-val" style="color:#334155;">100</div>
<div class="formula-term" style="border-color: #e2e8f0; background: #f8fafc">
<div class="formula-term-label" style="color: #64748b">100</div>
<div class="formula-term-val" style="color: #334155">100</div>
<div class="formula-term-sub">&nbsp;</div>
</div>
<div class="formula-op equals">=</div>
@@ -304,20 +332,25 @@
<div class="slr-status-bar">
<div class="slr-status-left">
<div class="slr-status-title">SLR Compliance Status</div>
<div class="slr-status-desc">Actual: {{ slrActualPercent }}% &middot; Required: {{ slrRate }}.00% &middot; NDTL: &#8377;{{ ndtl | number:'1.2-2' }} Cr</div>
<div class="slr-status-desc">
Actual: {{ slrActualPercent }}% &middot; Required: {{ slrRate }}.00% &middot; NDTL: &#8377;{{
ndtl | number: '1.2-2'
}}
Cr
</div>
</div>
<div class="slr-status-calcs">
<div class="slr-calc">
<div class="slr-calc-label">SLR Required ({{ slrRate }}%)</div>
<div class="slr-calc-val">&#8377;{{ slrRequired | number:'1.2-2' }} Cr</div>
<div class="slr-calc-val">&#8377;{{ slrRequired | number: '1.2-2' }} Cr</div>
</div>
<div class="slr-calc">
<div class="slr-calc-label">Liquid Assets Held</div>
<div class="slr-calc-val">&#8377;{{ totalLiquidAssets | number:'1.2-2' }} Cr</div>
<div class="slr-calc-val">&#8377;{{ totalLiquidAssets | number: '1.2-2' }} Cr</div>
</div>
<div class="slr-calc">
<div class="slr-calc-label">Surplus</div>
<div class="slr-calc-val surplus">&#8377;{{ slrSurplus | number:'1.2-2' }} Cr</div>
<div class="slr-calc-val surplus">&#8377;{{ slrSurplus | number: '1.2-2' }} Cr</div>
</div>
</div>
</div>
@@ -325,29 +358,31 @@
<!-- NDTL Formula Block -->
<div class="formula-block">
<div class="formula-title">NDTL Computation &mdash; Formula Breakdown &middot; Data as of {{ reportingDateStr }}</div>
<div class="formula-title">
NDTL Computation &mdash; Formula Breakdown &middot; Data as of {{ reportingDateStr }}
</div>
<div class="formula-row">
<div class="formula-term ft-demand">
<div class="formula-term-label">Demand Liabilities</div>
<div class="formula-term-val">&#8377;{{ totalDemand | number:'1.2-2' }}</div>
<div class="formula-term-val">&#8377;{{ totalDemand | number: '1.2-2' }}</div>
<div class="formula-term-sub">16 line items</div>
</div>
<div class="formula-op">+</div>
<div class="formula-term ft-time">
<div class="formula-term-label">Time Liabilities</div>
<div class="formula-term-val">&#8377;{{ totalTime | number:'1.2-2' }}</div>
<div class="formula-term-val">&#8377;{{ totalTime | number: '1.2-2' }}</div>
<div class="formula-term-sub">13 line items</div>
</div>
<div class="formula-op minus">&minus;</div>
<div class="formula-term ft-asset">
<div class="formula-term-label">Assets with Banking</div>
<div class="formula-term-val">&#8377;{{ totalBankingAssets | number:'1.2-2' }}</div>
<div class="formula-term-val">&#8377;{{ totalBankingAssets | number: '1.2-2' }}</div>
<div class="formula-term-sub">3 line items</div>
</div>
<div class="formula-op equals">=</div>
<div class="formula-term ft-ndtl">
<div class="formula-term-label">NDTL</div>
<div class="formula-term-val">&#8377;{{ ndtl | number:'1.2-2' }}</div>
<div class="formula-term-val">&#8377;{{ ndtl | number: '1.2-2' }}</div>
<div class="formula-term-sub">CRR &amp; SLR base</div>
</div>
</div>
@@ -357,20 +392,22 @@
<div class="crr-bar">
<div class="crr-text">
<div class="crr-title">CRR &amp; SLR Reserve Requirements &mdash; RBI Computation on NDTL</div>
<div class="crr-desc">Based on NDTL of &#8377;{{ ndtl | number:'1.2-2' }} Cr &middot; Data as of {{ reportingDateStr }} (45-day lag)</div>
<div class="crr-desc">
Based on NDTL of &#8377;{{ ndtl | number: '1.2-2' }} Cr &middot; Data as of {{ reportingDateStr }} (45-day lag)
</div>
</div>
<div class="crr-calcs">
<div class="crr-calc">
<div class="crr-calc-label">CRR @ {{ crrRate }}.00%</div>
<div class="crr-calc-val">&#8377;{{ crrRequired | number:'1.2-2' }} Cr</div>
<div class="crr-calc-val">&#8377;{{ crrRequired | number: '1.2-2' }} Cr</div>
</div>
<div class="crr-calc">
<div class="crr-calc-label">SLR @ {{ slrRate }}.00%</div>
<div class="crr-calc-val">&#8377;{{ slrRequired | number:'1.2-2' }} Cr</div>
<div class="crr-calc-val">&#8377;{{ slrRequired | number: '1.2-2' }} Cr</div>
</div>
<div class="crr-calc">
<div class="crr-calc-label">Total Reserve</div>
<div class="crr-calc-val">&#8377;{{ totalReserve | number:'1.2-2' }} Cr</div>
<div class="crr-calc-val">&#8377;{{ totalReserve | number: '1.2-2' }} Cr</div>
</div>
</div>
</div>
@@ -186,11 +186,25 @@ $mono: 'IBM Plex Mono', 'Roboto Mono', monospace;
}
}
.sc-demand::before { background: $demand; }
.sc-time::before { background: $time; }
.sc-asset::before { background: $asset; }
.sc-ndtl::before { background: linear-gradient(90deg, $asset, $time, $demand); }
.sc-crr::before { background: $amber; }
.sc-demand::before {
background: $demand;
}
.sc-time::before {
background: $time;
}
.sc-asset::before {
background: $asset;
}
.sc-ndtl::before {
background: linear-gradient(90deg, $asset, $time, $demand);
}
.sc-crr::before {
background: $amber;
}
.sc-label {
font-size: 10px;
@@ -226,8 +240,13 @@ $mono: 'IBM Plex Mono', 'Roboto Mono', monospace;
margin-top: 4px;
}
.up { color: $green; }
.dn { color: $red; }
.up {
color: $green;
}
.dn {
color: $red;
}
// ===== Tab Navigation =====
.tab-nav {
@@ -278,9 +297,23 @@ $mono: 'IBM Plex Mono', 'Roboto Mono', monospace;
letter-spacing: 0.5px;
}
.pill-demand { background: $demand-bg; color: $demand; border: 1px solid $demand-b; }
.pill-time { background: $time-bg; color: $time; border: 1px solid $time-b; }
.pill-asset { background: $asset-bg; color: $asset; border: 1px solid $asset-b; }
.pill-demand {
background: $demand-bg;
color: $demand;
border: 1px solid $demand-b;
}
.pill-time {
background: $time-bg;
color: $time;
border: 1px solid $time-b;
}
.pill-asset {
background: $asset-bg;
color: $asset;
border: 1px solid $asset-b;
}
.section-desc {
font-size: 12px;
@@ -320,15 +353,22 @@ thead th {
text-transform: uppercase;
white-space: nowrap;
&.right { text-align: right; }
&.right {
text-align: right;
}
}
tbody tr {
border-bottom: 1px solid $border;
transition: background 0.15s;
&:last-child { border-bottom: none; }
&:hover { background: #f8fafc; }
&:last-child {
border-bottom: none;
}
&:hover {
background: #f8fafc;
}
}
td {
@@ -336,7 +376,10 @@ td {
color: $ink2;
vertical-align: middle;
&.right { text-align: right; font-family: $mono; }
&.right {
text-align: right;
font-family: $mono;
}
}
.sl {
@@ -346,7 +389,10 @@ td {
width: 36px;
}
.mono { font-family: $mono; font-size: 12px; }
.mono {
font-family: $mono;
font-size: 12px;
}
.item-name {
font-weight: 500;
@@ -364,9 +410,17 @@ td {
font-weight: 500;
}
.amt-demand { color: $demand; }
.amt-time { color: $time; }
.amt-asset { color: $asset; }
.amt-demand {
color: $demand;
}
.amt-time {
color: $time;
}
.amt-asset {
color: $asset;
}
.status-chip {
display: inline-flex;
@@ -379,9 +433,20 @@ td {
border-radius: 4px;
}
.chip-active { background: #dcfce7; color: #15803d; }
.chip-overdue { background: #fef2f2; color: $red; }
.chip-locked { background: #fefce8; color: #92400e; }
.chip-active {
background: #dcfce7;
color: #15803d;
}
.chip-overdue {
background: #fef2f2;
color: $red;
}
.chip-locked {
background: #fefce8;
color: #92400e;
}
tfoot tr {
background: $surface2;
@@ -394,34 +459,77 @@ tfoot td {
font-family: $mono;
padding: 12px 14px;
&.right { text-align: right; font-size: 14px; }
&.right {
text-align: right;
font-size: 14px;
}
}
$slr: #0369a1;
$slr-bg: #f0f9ff;
$slr-b: #bae6fd;
.foot-demand { color: $demand; }
.foot-time { color: $time; }
.foot-asset { color: $asset; }
.foot-slr { color: $slr; }
.deduct-tag { font-size: 10px; color: $asset; font-family: $mono; margin-left: 8px; }
.foot-demand {
color: $demand;
}
.amt-slr { color: $slr; }
.chip-slr { background: $slr-bg; color: $slr; border: 1px solid $slr-b; }
.pill-slr { background: $slr-bg; color: $slr; border: 1px solid $slr-b; }
.foot-time {
color: $time;
}
.foot-asset {
color: $asset;
}
.foot-slr {
color: $slr;
}
.deduct-tag {
font-size: 10px;
color: $asset;
font-family: $mono;
margin-left: 8px;
}
.amt-slr {
color: $slr;
}
.chip-slr {
background: $slr-bg;
color: $slr;
border: 1px solid $slr-b;
}
.pill-slr {
background: $slr-bg;
color: $slr;
border: 1px solid $slr-b;
}
.ft-slr {
border-color: $slr-b;
background: $slr-bg;
.formula-term-label, .formula-term-val { color: $slr; }
.formula-term-label,
.formula-term-val {
color: $slr;
}
}
.ft-slr-result {
border-color: #bbf7d0;
background: linear-gradient(135deg, #f0fdf4, #ecfdf5);
.formula-term-label { color: #15803d; }
.formula-term-val { color: $green; font-size: 20px; }
.formula-term-label {
color: #15803d;
}
.formula-term-val {
color: $green;
font-size: 20px;
}
}
.slr-formula::before {
@@ -441,7 +549,9 @@ $slr-b: #bae6fd;
flex-wrap: wrap;
}
.slr-status-left { flex: 1; }
.slr-status-left {
flex: 1;
}
.slr-status-title {
font-size: 13px;
@@ -460,7 +570,9 @@ $slr-b: #bae6fd;
gap: 24px;
}
.slr-calc { text-align: right; }
.slr-calc {
text-align: right;
}
.slr-calc-label {
font-size: 10px;
@@ -475,7 +587,9 @@ $slr-b: #bae6fd;
color: #14532d;
font-family: $mono;
&.surplus { color: $green; }
&.surplus {
color: $green;
}
}
// ===== Formula Block =====
@@ -555,19 +669,58 @@ $slr-b: #bae6fd;
padding: 0 8px;
flex-shrink: 0;
&.minus { color: $red; }
&.equals { color: $ink2; font-weight: 500; }
&.minus {
color: $red;
}
&.equals {
color: $ink2;
font-weight: 500;
}
}
.ft-demand { border-color: $demand-b; background: $demand-bg; .formula-term-label, .formula-term-val { color: $demand; } }
.ft-time { border-color: $time-b; background: $time-bg; .formula-term-label, .formula-term-val { color: $time; } }
.ft-asset { border-color: $asset-b; background: $asset-bg; .formula-term-label, .formula-term-val { color: $asset; } }
.ft-demand {
border-color: $demand-b;
background: $demand-bg;
.formula-term-label,
.formula-term-val {
color: $demand;
}
}
.ft-time {
border-color: $time-b;
background: $time-bg;
.formula-term-label,
.formula-term-val {
color: $time;
}
}
.ft-asset {
border-color: $asset-b;
background: $asset-bg;
.formula-term-label,
.formula-term-val {
color: $asset;
}
}
.ft-ndtl {
border-color: #a78bfa;
background: linear-gradient(135deg, #f5f3ff, #f0fdfa);
.formula-term-label { color: #6d28d9; }
.formula-term-val { color: $ink; font-size: 18px; }
.formula-term-label {
color: #6d28d9;
}
.formula-term-val {
color: $ink;
font-size: 18px;
}
}
// ===== CRR Bar =====
@@ -595,14 +748,18 @@ $slr-b: #bae6fd;
color: #a16207;
}
.crr-text { flex: 1; }
.crr-text {
flex: 1;
}
.crr-calcs {
display: flex;
gap: 24px;
}
.crr-calc { text-align: right; }
.crr-calc {
text-align: right;
}
.crr-calc-label {
font-size: 10px;
@@ -40,18 +40,18 @@ export class CrrSlrDialogComponent {
reportingDateStr: string;
/** NDTL & Reserve computations */
readonly totalDemand = 12843.50;
readonly totalTime = 64618.00;
readonly totalBankingAssets = 8240.20;
readonly ndtl = 69221.30; // Demand + Time - Banking Assets
readonly totalDemand = 12843.5;
readonly totalTime = 64618.0;
readonly totalBankingAssets = 8240.2;
readonly ndtl = 69221.3; // Demand + Time - Banking Assets
readonly crrRate = 3; // Current RBI CRR rate (%)
readonly slrRate = 18; // Current RBI SLR rate (%)
readonly crrRequired = 69221.30 * 0.03; // ₹2,076.64 Cr
readonly slrRequired = 69221.30 * 0.18; // ₹12,459.83 Cr
readonly totalLiquidAssets = 13238.80;
readonly slrActualPercent = +(13238.80 / 69221.30 * 100).toFixed(2); // 19.12%
readonly slrSurplus = +(13238.80 - 69221.30 * 0.18).toFixed(2); // 778.97
readonly totalReserve = +(69221.30 * 0.03 + 69221.30 * 0.18).toFixed(2); // 14,536.47
readonly crrRequired = 69221.3 * 0.03; // ₹2,076.64 Cr
readonly slrRequired = 69221.3 * 0.18; // ₹12,459.83 Cr
readonly totalLiquidAssets = 13238.8;
readonly slrActualPercent = +((13238.8 / 69221.3) * 100).toFixed(2); // 19.12%
readonly slrSurplus = +(13238.8 - 69221.3 * 0.18).toFixed(2); // 778.97
readonly totalReserve = +(69221.3 * 0.03 + 69221.3 * 0.18).toFixed(2); // 14,536.47
constructor() {
// CRR/SLR uses data from 45 days ago
@@ -59,58 +59,446 @@ export class CrrSlrDialogComponent {
this.reportingDate = new Date(this.today);
this.reportingDate.setDate(this.reportingDate.getDate() - lagDays);
this.reportingDateStr = this.reportingDate.toLocaleDateString('en-IN', {
day: '2-digit', month: 'short', year: 'numeric'
day: '2-digit',
month: 'short',
year: 'numeric'
});
}
demandLiabilities = [
{ sl: '01', name: 'Current Account Deposits', sub: 'Corporate & individual CA balances', code: '2001-CA', opening: '3,842.50', current: '4,120.75', change: '+278.25', changeDir: 'up', status: 'Active' },
{ sl: '02', name: 'Savings Bank Deposits (Withdrawable)', sub: 'Demand portion — 10% of SB balance', code: '2002-SB', opening: '1,240.00', current: '1,318.40', change: '+78.40', changeDir: 'up', status: 'Active' },
{ sl: '03', name: 'Demand Drafts Payable', sub: 'Issued DDs outstanding for payment', code: '2003-DD', opening: '284.20', current: '311.60', change: '+27.40', changeDir: 'up', status: 'Active' },
{ sl: '04', name: "Banker's Cheques / Pay Orders", sub: 'Issued pay orders pending encashment', code: '2004-PO', opening: '198.75', current: '176.30', change: '-22.45', changeDir: 'dn', status: 'Active' },
{ sl: '05', name: 'Credit Balance in Cash Credit Accounts', sub: 'CC accounts in credit position', code: '2005-CC', opening: '542.10', current: '618.90', change: '+76.80', changeDir: 'up', status: 'Active' },
{ sl: '06', name: 'Overdue Fixed Deposits', sub: 'FDs past maturity — treated as demand', code: '2006-OFD', opening: '89.40', current: '112.85', change: '+23.45', changeDir: 'dn', status: 'Overdue' },
{ sl: '07', name: 'Interest Payable on Demand Deposits', sub: 'Accrued interest on CA/SB', code: '2007-INT', opening: '34.20', current: '38.55', change: '+4.35', changeDir: 'up', status: 'Active' },
{ sl: '08', name: 'Margins held against Letters of Credit', sub: 'Withdrawable LC margin deposits', code: '2008-LCM', opening: '412.60', current: '388.20', change: '-24.40', changeDir: 'dn', status: 'Active' },
{ sl: '09', name: 'Margins held against Bank Guarantees', sub: 'Withdrawable BG margin deposits', code: '2009-BGM', opening: '318.40', current: '342.10', change: '+23.70', changeDir: 'up', status: 'Active' },
{ sl: '10', name: 'Security Deposits (Withdrawable)', sub: 'EMD / SD refundable on demand', code: '2010-SD', opening: '124.80', current: '118.60', change: '-6.20', changeDir: 'dn', status: 'Active' },
{ sl: '11', name: 'Unclaimed Deposits (Demand Portion)', sub: 'Balances unclaimed >10 years', code: '2011-UCD', opening: '56.30', current: '59.40', change: '+3.10', changeDir: 'dn', status: 'Unclaimed' },
{ sl: '12', name: 'Inter-Bank Demand Deposits', sub: 'Demand deposits from other banks', code: '2012-IBD', opening: '2,840.00', current: '3,120.50', change: '+280.50', changeDir: 'up', status: 'Active' },
{ sl: '13', name: 'Clearing Adjustments / Balances', sub: 'Net clearing settlement balances', code: '2013-CLR', opening: '186.40', current: '210.80', change: '+24.40', changeDir: 'up', status: 'Active' },
{ sl: '14', name: 'Outstanding Payment Instruments', sub: 'Cheques issued but not yet presented', code: '2014-OPI', opening: '142.60', current: '158.90', change: '+16.30', changeDir: 'up', status: 'Active' },
{ sl: '15', name: 'Demand Liabilities towards Other Banks', sub: 'Nostro / Vostro demand obligations', code: '2015-DLB', opening: '984.20', current: '1,048.60', change: '+64.40', changeDir: 'up', status: 'Active' },
{ sl: '16', name: 'Deposits Repayable on Demand', sub: 'Miscellaneous on-demand deposits', code: '2016-DRD', opening: '618.30', current: '698.05', change: '+79.75', changeDir: 'up', status: 'Active' }
{
sl: '01',
name: 'Current Account Deposits',
sub: 'Corporate & individual CA balances',
code: '2001-CA',
opening: '3,842.50',
current: '4,120.75',
change: '+278.25',
changeDir: 'up',
status: 'Active'
},
{
sl: '02',
name: 'Savings Bank Deposits (Withdrawable)',
sub: 'Demand portion — 10% of SB balance',
code: '2002-SB',
opening: '1,240.00',
current: '1,318.40',
change: '+78.40',
changeDir: 'up',
status: 'Active'
},
{
sl: '03',
name: 'Demand Drafts Payable',
sub: 'Issued DDs outstanding for payment',
code: '2003-DD',
opening: '284.20',
current: '311.60',
change: '+27.40',
changeDir: 'up',
status: 'Active'
},
{
sl: '04',
name: "Banker's Cheques / Pay Orders",
sub: 'Issued pay orders pending encashment',
code: '2004-PO',
opening: '198.75',
current: '176.30',
change: '-22.45',
changeDir: 'dn',
status: 'Active'
},
{
sl: '05',
name: 'Credit Balance in Cash Credit Accounts',
sub: 'CC accounts in credit position',
code: '2005-CC',
opening: '542.10',
current: '618.90',
change: '+76.80',
changeDir: 'up',
status: 'Active'
},
{
sl: '06',
name: 'Overdue Fixed Deposits',
sub: 'FDs past maturity — treated as demand',
code: '2006-OFD',
opening: '89.40',
current: '112.85',
change: '+23.45',
changeDir: 'dn',
status: 'Overdue'
},
{
sl: '07',
name: 'Interest Payable on Demand Deposits',
sub: 'Accrued interest on CA/SB',
code: '2007-INT',
opening: '34.20',
current: '38.55',
change: '+4.35',
changeDir: 'up',
status: 'Active'
},
{
sl: '08',
name: 'Margins held against Letters of Credit',
sub: 'Withdrawable LC margin deposits',
code: '2008-LCM',
opening: '412.60',
current: '388.20',
change: '-24.40',
changeDir: 'dn',
status: 'Active'
},
{
sl: '09',
name: 'Margins held against Bank Guarantees',
sub: 'Withdrawable BG margin deposits',
code: '2009-BGM',
opening: '318.40',
current: '342.10',
change: '+23.70',
changeDir: 'up',
status: 'Active'
},
{
sl: '10',
name: 'Security Deposits (Withdrawable)',
sub: 'EMD / SD refundable on demand',
code: '2010-SD',
opening: '124.80',
current: '118.60',
change: '-6.20',
changeDir: 'dn',
status: 'Active'
},
{
sl: '11',
name: 'Unclaimed Deposits (Demand Portion)',
sub: 'Balances unclaimed >10 years',
code: '2011-UCD',
opening: '56.30',
current: '59.40',
change: '+3.10',
changeDir: 'dn',
status: 'Unclaimed'
},
{
sl: '12',
name: 'Inter-Bank Demand Deposits',
sub: 'Demand deposits from other banks',
code: '2012-IBD',
opening: '2,840.00',
current: '3,120.50',
change: '+280.50',
changeDir: 'up',
status: 'Active'
},
{
sl: '13',
name: 'Clearing Adjustments / Balances',
sub: 'Net clearing settlement balances',
code: '2013-CLR',
opening: '186.40',
current: '210.80',
change: '+24.40',
changeDir: 'up',
status: 'Active'
},
{
sl: '14',
name: 'Outstanding Payment Instruments',
sub: 'Cheques issued but not yet presented',
code: '2014-OPI',
opening: '142.60',
current: '158.90',
change: '+16.30',
changeDir: 'up',
status: 'Active'
},
{
sl: '15',
name: 'Demand Liabilities towards Other Banks',
sub: 'Nostro / Vostro demand obligations',
code: '2015-DLB',
opening: '984.20',
current: '1,048.60',
change: '+64.40',
changeDir: 'up',
status: 'Active'
},
{
sl: '16',
name: 'Deposits Repayable on Demand',
sub: 'Miscellaneous on-demand deposits',
code: '2016-DRD',
opening: '618.30',
current: '698.05',
change: '+79.75',
changeDir: 'up',
status: 'Active'
}
];
timeLiabilities = [
{ sl: '01', name: 'Fixed Deposits (FD)', sub: '7 days to 10 years · 6.5–7.2%', code: '3001-FD', opening: '28,420.50', current: '29,184.30', change: '+763.80', changeDir: 'up', status: 'Active' },
{ sl: '02', name: 'Recurring Deposits (RD)', sub: 'Monthly instalment · 6.0–6.8%', code: '3002-RD', opening: '4,218.60', current: '4,412.80', change: '+194.20', changeDir: 'up', status: 'Active' },
{ sl: '03', name: 'Time Liability Portion of Savings', sub: '90% of SB balance treated as time', code: '3003-SBT', opening: '10,840.20', current: '11,204.60', change: '+364.40', changeDir: 'up', status: 'Active' },
{ sl: '04', name: 'Reinvestment Deposits', sub: 'Cumulative interest plans · 7.0%', code: '3004-RID', opening: '1,842.40', current: '1,918.70', change: '+76.30', changeDir: 'up', status: 'Active' },
{ sl: '05', name: 'Staff Security Deposits (Fixed Period)', sub: 'Employee security deposits — locked', code: '3005-SSD', opening: '84.60', current: '86.40', change: '+1.80', changeDir: 'up', status: 'Locked' },
{ sl: '06', name: 'Margin Money for Guarantees / LC', sub: 'LC/BG margins — fixed lock-in period', code: '3006-MGM', opening: '620.80', current: '644.20', change: '+23.40', changeDir: 'up', status: 'Locked' },
{ sl: '07', name: 'Certificates of Deposit (CD)', sub: 'Issued to corporates / FIs · 7.35%', code: '3007-CD', opening: '3,840.00', current: '3,920.50', change: '+80.50', changeDir: 'up', status: 'Active' },
{ sl: '08', name: 'Interest Accrued on Time Deposits', sub: 'Accrued but unpaid interest on FD/RD', code: '3008-IAT', opening: '842.60', current: '918.40', change: '+75.80', changeDir: 'up', status: 'Active' },
{ sl: '09', name: 'Long-term Deposits', sub: 'Tenor >5 years · 7.5%', code: '3009-LTD', opening: '5,620.40', current: '5,818.60', change: '+198.20', changeDir: 'up', status: 'Active' },
{ sl: '10', name: 'Tax Saving Deposits', sub: 'Sec 80C FDs — 5-year lock-in · 6.8%', code: '3010-TSD', opening: '1,240.80', current: '1,318.40', change: '+77.60', changeDir: 'up', status: 'Locked' },
{ sl: '11', name: 'Foreign Currency Deposits', sub: 'FCNR(B), RFC — USD/EUR/GBP', code: '3011-FCD', opening: '2,184.60', current: '2,348.20', change: '+163.60', changeDir: 'up', status: 'Active' },
{ sl: '12', name: 'Time Liabilities to Other Banks', sub: 'Inter-bank term deposits — call money', code: '3012-TLB', opening: '1,840.20', current: '1,924.40', change: '+84.20', changeDir: 'up', status: 'Active' },
{ sl: '13', name: 'Term Borrowings', sub: 'Bonds, debentures, refinance · 8.1%', code: '3013-TBR', opening: '1,018.40', current: '1,118.50', change: '+100.10', changeDir: 'up', status: 'Active' }
{
sl: '01',
name: 'Fixed Deposits (FD)',
sub: '7 days to 10 years · 6.5–7.2%',
code: '3001-FD',
opening: '28,420.50',
current: '29,184.30',
change: '+763.80',
changeDir: 'up',
status: 'Active'
},
{
sl: '02',
name: 'Recurring Deposits (RD)',
sub: 'Monthly instalment · 6.0–6.8%',
code: '3002-RD',
opening: '4,218.60',
current: '4,412.80',
change: '+194.20',
changeDir: 'up',
status: 'Active'
},
{
sl: '03',
name: 'Time Liability Portion of Savings',
sub: '90% of SB balance treated as time',
code: '3003-SBT',
opening: '10,840.20',
current: '11,204.60',
change: '+364.40',
changeDir: 'up',
status: 'Active'
},
{
sl: '04',
name: 'Reinvestment Deposits',
sub: 'Cumulative interest plans · 7.0%',
code: '3004-RID',
opening: '1,842.40',
current: '1,918.70',
change: '+76.30',
changeDir: 'up',
status: 'Active'
},
{
sl: '05',
name: 'Staff Security Deposits (Fixed Period)',
sub: 'Employee security deposits — locked',
code: '3005-SSD',
opening: '84.60',
current: '86.40',
change: '+1.80',
changeDir: 'up',
status: 'Locked'
},
{
sl: '06',
name: 'Margin Money for Guarantees / LC',
sub: 'LC/BG margins — fixed lock-in period',
code: '3006-MGM',
opening: '620.80',
current: '644.20',
change: '+23.40',
changeDir: 'up',
status: 'Locked'
},
{
sl: '07',
name: 'Certificates of Deposit (CD)',
sub: 'Issued to corporates / FIs · 7.35%',
code: '3007-CD',
opening: '3,840.00',
current: '3,920.50',
change: '+80.50',
changeDir: 'up',
status: 'Active'
},
{
sl: '08',
name: 'Interest Accrued on Time Deposits',
sub: 'Accrued but unpaid interest on FD/RD',
code: '3008-IAT',
opening: '842.60',
current: '918.40',
change: '+75.80',
changeDir: 'up',
status: 'Active'
},
{
sl: '09',
name: 'Long-term Deposits',
sub: 'Tenor >5 years · 7.5%',
code: '3009-LTD',
opening: '5,620.40',
current: '5,818.60',
change: '+198.20',
changeDir: 'up',
status: 'Active'
},
{
sl: '10',
name: 'Tax Saving Deposits',
sub: 'Sec 80C FDs — 5-year lock-in · 6.8%',
code: '3010-TSD',
opening: '1,240.80',
current: '1,318.40',
change: '+77.60',
changeDir: 'up',
status: 'Locked'
},
{
sl: '11',
name: 'Foreign Currency Deposits',
sub: 'FCNR(B), RFC — USD/EUR/GBP',
code: '3011-FCD',
opening: '2,184.60',
current: '2,348.20',
change: '+163.60',
changeDir: 'up',
status: 'Active'
},
{
sl: '12',
name: 'Time Liabilities to Other Banks',
sub: 'Inter-bank term deposits — call money',
code: '3012-TLB',
opening: '1,840.20',
current: '1,924.40',
change: '+84.20',
changeDir: 'up',
status: 'Active'
},
{
sl: '13',
name: 'Term Borrowings',
sub: 'Bonds, debentures, refinance · 8.1%',
code: '3013-TBR',
opening: '1,018.40',
current: '1,118.50',
change: '+100.10',
changeDir: 'up',
status: 'Active'
}
];
bankingAssets = [
{ sl: '01', name: 'Balances with Other Banks', sub: 'Nostro / Vostro balances — SBI, PNB, BOB', code: '4001-BWB', opening: '3,840.20', current: '4,112.40', change: '+272.20', changeDir: 'up', status: 'Active' },
{ sl: '02', name: 'Money at Call with Banks', sub: 'Overnight & short-notice call money', code: '4002-MCB', opening: '2,180.50', current: '2,418.60', change: '+238.10', changeDir: 'up', status: 'Active' },
{ sl: '03', name: 'Loans to Banks (Inter-Bank Lending)', sub: 'Term loans & credit lines to SCBs', code: '4003-LTB', opening: '1,580.00', current: '1,709.20', change: '+129.20', changeDir: 'up', status: 'Active' }
{
sl: '01',
name: 'Balances with Other Banks',
sub: 'Nostro / Vostro balances — SBI, PNB, BOB',
code: '4001-BWB',
opening: '3,840.20',
current: '4,112.40',
change: '+272.20',
changeDir: 'up',
status: 'Active'
},
{
sl: '02',
name: 'Money at Call with Banks',
sub: 'Overnight & short-notice call money',
code: '4002-MCB',
opening: '2,180.50',
current: '2,418.60',
change: '+238.10',
changeDir: 'up',
status: 'Active'
},
{
sl: '03',
name: 'Loans to Banks (Inter-Bank Lending)',
sub: 'Term loans & credit lines to SCBs',
code: '4003-LTB',
opening: '1,580.00',
current: '1,709.20',
change: '+129.20',
changeDir: 'up',
status: 'Active'
}
];
liquidAssets = [
{ sl: '01', name: 'Central Government Securities', sub: 'G-Secs held in HTM, AFS & HFT portfolios', code: '5001-CGS', opening: '6,840.50', current: '7,218.40', change: '+377.90', changeDir: 'up', status: 'Active', category: 'G-Sec' },
{ sl: '02', name: 'State Development Loans (SDLs)', sub: 'State govt bonds — various maturities', code: '5002-SDL', opening: '2,420.30', current: '2,584.60', change: '+164.30', changeDir: 'up', status: 'Active', category: 'SDL' },
{ sl: '03', name: 'Treasury Bills (91/182/364 day)', sub: 'Short-term govt securities — discounted', code: '5003-TB', opening: '1,840.20', current: '1,962.80', change: '+122.60', changeDir: 'up', status: 'Active', category: 'T-Bill' },
{ sl: '04', name: 'Gold (valued at current market)', sub: 'Gold reserves held per RBI valuation norms', code: '5004-GLD', opening: '680.40', current: '724.60', change: '+44.20', changeDir: 'up', status: 'Active', category: 'Gold' },
{ sl: '05', name: 'Special Deposit Scheme with RBI', sub: 'Deposits placed under SDS — SLR eligible', code: '5005-SDS', opening: '420.00', current: '420.00', change: '0.00', changeDir: 'up', status: 'Active', category: 'RBI Dep' },
{ sl: '06', name: 'NABARD / SIDBI / NHB Bonds', sub: 'PSE bonds approved as SLR-eligible', code: '5006-PSE', opening: '310.80', current: '328.40', change: '+17.60', changeDir: 'up', status: 'Active', category: 'PSE Bond' }
{
sl: '01',
name: 'Central Government Securities',
sub: 'G-Secs held in HTM, AFS & HFT portfolios',
code: '5001-CGS',
opening: '6,840.50',
current: '7,218.40',
change: '+377.90',
changeDir: 'up',
status: 'Active',
category: 'G-Sec'
},
{
sl: '02',
name: 'State Development Loans (SDLs)',
sub: 'State govt bonds — various maturities',
code: '5002-SDL',
opening: '2,420.30',
current: '2,584.60',
change: '+164.30',
changeDir: 'up',
status: 'Active',
category: 'SDL'
},
{
sl: '03',
name: 'Treasury Bills (91/182/364 day)',
sub: 'Short-term govt securities — discounted',
code: '5003-TB',
opening: '1,840.20',
current: '1,962.80',
change: '+122.60',
changeDir: 'up',
status: 'Active',
category: 'T-Bill'
},
{
sl: '04',
name: 'Gold (valued at current market)',
sub: 'Gold reserves held per RBI valuation norms',
code: '5004-GLD',
opening: '680.40',
current: '724.60',
change: '+44.20',
changeDir: 'up',
status: 'Active',
category: 'Gold'
},
{
sl: '05',
name: 'Special Deposit Scheme with RBI',
sub: 'Deposits placed under SDS — SLR eligible',
code: '5005-SDS',
opening: '420.00',
current: '420.00',
change: '0.00',
changeDir: 'up',
status: 'Active',
category: 'RBI Dep'
},
{
sl: '06',
name: 'NABARD / SIDBI / NHB Bonds',
sub: 'PSE bonds approved as SLR-eligible',
code: '5006-PSE',
opening: '310.80',
current: '328.40',
change: '+17.60',
changeDir: 'up',
status: 'Active',
category: 'PSE Bond'
}
];
close(): void {
@@ -177,10 +177,18 @@
</div>
<!-- PSL -->
<div class="dash-card">
<div class="dash-card psl-card">
<div class="card-top">
<div class="card-icon-wrap green"><fa-icon icon="flag"></fa-icon></div>
<div class="card-label">Priority Sector Lending (PSL)</div>
<button
class="psl-open-btn"
(click)="openPslReportDialog()"
matTooltip="View PSL Returns &amp; Account Register"
aria-label="Open PSL report"
>
<fa-icon icon="external-link-alt"></fa-icon>
</button>
</div>
<div class="card-big-value">65%</div>
<div class="psl-breakdown">
@@ -206,6 +214,14 @@
<div class="card-top">
<div class="card-icon-wrap purple"><fa-icon icon="file-alt"></fa-icon></div>
<div class="card-label">Credit Information Companies</div>
<button
class="cic-open-btn"
(click)="openCicDqrDialog()"
matTooltip="View detailed DQR report"
aria-label="Open detailed report"
>
<fa-icon icon="external-link-alt"></fa-icon>
</button>
</div>
<div class="cic-subtitle">Data Quality Report (DQR) — as per RBI Guidelines</div>
<div class="quality-grid">
@@ -330,6 +346,5 @@
<fa-icon icon="info-circle"></fa-icon> RBI circular pending review — compliance action due by month end.
</div>
</div>
</div>
</div>
@@ -576,10 +576,64 @@ $purple: #7c3aed;
text-align: right;
}
/* ─── Priority Sector Lending — Open button ─── */
.psl-card {
.card-top {
align-items: center;
}
}
.psl-open-btn {
margin-left: auto;
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid #e2e8f0;
background: #fff;
color: #16a34a;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 13px;
transition: all 0.15s;
&:hover {
background: #dcfce7;
border-color: #16a34a;
transform: translateY(-1px);
box-shadow: 0 2px 6px rgb(22 163 74 / 20%);
}
}
/* ─── Credit Information Companies — DQR ─── */
.cic-card {
.card-top {
margin-bottom: 0;
align-items: center;
}
}
.cic-open-btn {
margin-left: auto;
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid #e2e8f0;
background: #fff;
color: #6d28d9;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 13px;
transition: all 0.15s;
&:hover {
background: #ede9fe;
border-color: #6d28d9;
transform: translateY(-1px);
box-shadow: 0 2px 6px rgb(109 40 217 / 20%);
}
}
@@ -833,5 +887,4 @@ $purple: #7c3aed;
gap: 4px;
text-align: center;
}
}
@@ -170,4 +170,32 @@ export class DashboardComponent implements OnInit {
autoFocus: false
});
}
async openCicDqrDialog(): Promise<void> {
const { CicDqrDialogComponent } = await import('./cic-dqr-dialog/cic-dqr-dialog.component');
this.dialog.open(CicDqrDialogComponent, {
width: '88vw',
maxWidth: '1200px',
maxHeight: '92vh',
panelClass: 'crr-slr-dialog-panel',
autoFocus: false,
disableClose: false,
hasBackdrop: true,
backdropClass: 'cic-dlg-backdrop'
});
}
async openPslReportDialog(): Promise<void> {
const { PslReportDialogComponent } = await import('./psl-report-dialog/psl-report-dialog.component');
this.dialog.open(PslReportDialogComponent, {
width: '92vw',
maxWidth: '1280px',
maxHeight: '92vh',
panelClass: 'crr-slr-dialog-panel',
autoFocus: false,
disableClose: false,
hasBackdrop: true,
backdropClass: 'psl-dlg-backdrop'
});
}
}
@@ -0,0 +1,501 @@
<!--
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/.
-->
<div class="psl-dlg">
<!-- HEADER -->
<div class="psl-head">
<div class="psl-head-left">
<div class="psl-head-ic"><fa-icon icon="flag"></fa-icon></div>
<div>
<h2 class="psl-title">Priority Sector Lending — Returns &amp; Register</h2>
<p class="psl-sub">
{{ bank.name }} &middot; <strong>{{ bank.formCode }}</strong> &middot; Quarter
<strong>{{ bank.quarter }}</strong> &middot; As on <strong>{{ bank.asOnDate }}</strong>
</p>
</div>
</div>
<div class="psl-head-actions">
<span class="psl-overall" [ngClass]="totalPslAchievedPct >= TOTAL_PSL_TARGET_PCT ? 'on-target' : 'below'">
<span class="psl-overall-l">Overall PSL</span>
<span class="psl-overall-v">{{ totalPslAchievedPct.toFixed(2) }}%</span>
<span class="psl-overall-t">/ {{ TOTAL_PSL_TARGET_PCT }}%</span>
</span>
<button class="psl-close" (click)="close()" matTooltip="Close" aria-label="Close">&#10005;</button>
</div>
</div>
<!-- TABS -->
<div class="psl-tabs">
<button class="psl-tab" [class.active]="activeTab === 'dashboard'" (click)="setTab('dashboard')">
<fa-icon icon="chart-pie"></fa-icon>&nbsp; Dashboard
</button>
<button class="psl-tab" [class.active]="activeTab === 'categories'" (click)="setTab('categories')">
<fa-icon icon="layer-group"></fa-icon>&nbsp; Categories
</button>
<button class="psl-tab" [class.active]="activeTab === 'register'" (click)="setTab('register')">
<fa-icon icon="list-alt"></fa-icon>&nbsp; Account Register
</button>
<button class="psl-tab" [class.active]="activeTab === 'returns'" (click)="setTab('returns')">
<fa-icon icon="file-invoice"></fa-icon>&nbsp; Returns &amp; Form III
</button>
</div>
<div class="psl-body">
<!-- ════════════ DASHBOARD ════════════ -->
<ng-container *ngIf="activeTab === 'dashboard'">
<!-- KPI ROW -->
<div class="psl-stats">
<div class="psl-stat-card psl-stat-gauge">
<div
class="gauge"
[style.--pct.%]="totalPslAchievedPct > 100 ? 100 : totalPslAchievedPct"
[ngClass]="totalPslAchievedPct >= TOTAL_PSL_TARGET_PCT ? 'on-target' : 'below'"
>
<div class="gauge-inner">
<div class="gauge-pct">{{ totalPslAchievedPct.toFixed(2) }}%</div>
<div class="gauge-label">PSL of ANBC</div>
</div>
</div>
</div>
<div class="psl-stat-card">
<div class="psl-stat-l">ANBC</div>
<div class="psl-stat-v">&#8377; {{ bank.anbc / 10000000 | number: '1.2-2' }} Cr</div>
<div class="psl-stat-meta">Adjusted Net Bank Credit</div>
</div>
<div class="psl-stat-card">
<div class="psl-stat-l">PSL Outstanding</div>
<div class="psl-stat-v">&#8377; {{ totalPslOutstanding / 10000000 | number: '1.2-2' }} Cr</div>
<div class="psl-stat-meta">{{ totalPslAccounts | number }} accounts</div>
</div>
<div class="psl-stat-card">
<div class="psl-stat-l">Gap / Surplus</div>
<div class="psl-stat-v" [ngClass]="totalPslGapPct >= 0 ? 'pos' : 'neg'">
{{ totalPslGapPct >= 0 ? '+' : '' }}{{ totalPslGapPct.toFixed(2) }} pp
</div>
<div class="psl-stat-meta">Vs. {{ TOTAL_PSL_TARGET_PCT }}% target</div>
</div>
</div>
<!-- SUB-TARGETS -->
<div class="psl-section">
<div class="psl-sec-h">
<h3 class="psl-sec-t">RBI Sub-Target Compliance</h3>
<span class="psl-circular">RBI MD: {{ bank.rbiCircular }}</span>
</div>
<div class="psl-bars">
<div class="psl-bar-row">
<div class="psl-bar-h">
<span class="psl-bar-l"><fa-icon icon="hand-holding-usd"></fa-icon>&nbsp; Agriculture (18% of ANBC)</span>
<span class="psl-bar-p" [ngClass]="agriAchievedPct >= AGRI_TARGET_PCT ? 'good' : 'low'"
>{{ agriAchievedPct.toFixed(2) }}%</span
>
</div>
<div class="psl-bar-track">
<div
class="psl-bar-fill"
[class.good]="agriAchievedPct >= AGRI_TARGET_PCT"
[class.low]="agriAchievedPct < AGRI_TARGET_PCT"
[style.width.%]="
(agriAchievedPct / AGRI_TARGET_PCT) * 100 > 100 ? 100 : (agriAchievedPct / AGRI_TARGET_PCT) * 100
"
></div>
</div>
</div>
<div class="psl-bar-row">
<div class="psl-bar-h">
<span class="psl-bar-l"><fa-icon icon="users"></fa-icon>&nbsp; Small &amp; Marginal Farmers (10%)</span>
<span class="psl-bar-p" [ngClass]="smfAchievedPct >= SMALL_MARGINAL_FARMER_TARGET_PCT ? 'good' : 'low'"
>{{ smfAchievedPct.toFixed(2) }}%</span
>
</div>
<div class="psl-bar-track">
<div
class="psl-bar-fill"
[class.good]="smfAchievedPct >= SMALL_MARGINAL_FARMER_TARGET_PCT"
[class.low]="smfAchievedPct < SMALL_MARGINAL_FARMER_TARGET_PCT"
[style.width.%]="
(smfAchievedPct / SMALL_MARGINAL_FARMER_TARGET_PCT) * 100 > 100
? 100
: (smfAchievedPct / SMALL_MARGINAL_FARMER_TARGET_PCT) * 100
"
></div>
</div>
</div>
<div class="psl-bar-row">
<div class="psl-bar-h">
<span class="psl-bar-l"><fa-icon icon="cogs"></fa-icon>&nbsp; Micro Enterprises (7.5%)</span>
<span class="psl-bar-p" [ngClass]="microAchievedPct >= MICRO_ENTERPRISES_TARGET_PCT ? 'good' : 'low'"
>{{ microAchievedPct.toFixed(2) }}%</span
>
</div>
<div class="psl-bar-track">
<div
class="psl-bar-fill"
[class.good]="microAchievedPct >= MICRO_ENTERPRISES_TARGET_PCT"
[class.low]="microAchievedPct < MICRO_ENTERPRISES_TARGET_PCT"
[style.width.%]="
(microAchievedPct / MICRO_ENTERPRISES_TARGET_PCT) * 100 > 100
? 100
: (microAchievedPct / MICRO_ENTERPRISES_TARGET_PCT) * 100
"
></div>
</div>
</div>
<div class="psl-bar-row">
<div class="psl-bar-h">
<span class="psl-bar-l"><fa-icon icon="heart"></fa-icon>&nbsp; Weaker Sections (12%)</span>
<span class="psl-bar-p" [ngClass]="weakerAchievedPct >= WEAKER_SECTIONS_TARGET_PCT ? 'good' : 'low'"
>{{ weakerAchievedPct.toFixed(2) }}%</span
>
</div>
<div class="psl-bar-track">
<div
class="psl-bar-fill"
[class.good]="weakerAchievedPct >= WEAKER_SECTIONS_TARGET_PCT"
[class.low]="weakerAchievedPct < WEAKER_SECTIONS_TARGET_PCT"
[style.width.%]="
(weakerAchievedPct / WEAKER_SECTIONS_TARGET_PCT) * 100 > 100
? 100
: (weakerAchievedPct / WEAKER_SECTIONS_TARGET_PCT) * 100
"
></div>
</div>
</div>
</div>
</div>
<!-- SNAPSHOT -->
<div class="psl-section">
<div class="psl-sec-h">
<h3 class="psl-sec-t">Bank &amp; Reporting Snapshot</h3>
</div>
<div class="psl-info-grid">
<div class="psl-info">
<div class="psl-info-l">Reporting Bank</div>
<div class="psl-info-v">{{ bank.name }}</div>
</div>
<div class="psl-info">
<div class="psl-info-l">Branch</div>
<div class="psl-info-v">{{ bank.branch }}</div>
</div>
<div class="psl-info">
<div class="psl-info-l">Principal Officer</div>
<div class="psl-info-v">{{ bank.principalOfficer }}</div>
</div>
<div class="psl-info">
<div class="psl-info-l">Form Code</div>
<div class="psl-info-v">{{ bank.formCode }}</div>
</div>
<div class="psl-info">
<div class="psl-info-l">Credit Equivalent (OBE)</div>
<div class="psl-info-v">&#8377; {{ bank.creditEquivalentOBE / 10000000 | number: '1.2-2' }} Cr</div>
</div>
<div class="psl-info">
<div class="psl-info-l">RBI Master Direction</div>
<div class="psl-info-v mono">{{ bank.rbiCircular }}</div>
</div>
</div>
</div>
</ng-container>
<!-- ════════════ CATEGORIES ════════════ -->
<ng-container *ngIf="activeTab === 'categories'">
<div class="psl-section">
<div class="psl-sec-h">
<h3 class="psl-sec-t">Category-wise Outstanding &amp; Achievement</h3>
<button mat-stroked-button class="psl-btn-xls" (click)="exportCategoriesXls()">
<fa-icon icon="file-excel"></fa-icon>&nbsp; Export Excel
</button>
</div>
<div class="psl-tbl-wrap">
<table class="psl-tbl">
<thead>
<tr>
<th>Code</th>
<th>Category</th>
<th class="ta-r">Outstanding</th>
<th class="ta-c">#A/c</th>
<th class="ta-r">Achieved %</th>
<th class="ta-r">Sub-Target %</th>
<th class="ta-c">Status</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let c of categories">
<td class="mono">{{ c.code }}</td>
<td>
<div class="cat-name">{{ c.name }}</div>
<div class="cat-desc">{{ c.description }}</div>
</td>
<td class="ta-r">&#8377; {{ c.outstanding / 10000000 | number: '1.2-2' }} Cr</td>
<td class="ta-c">{{ c.accounts | number }}</td>
<td class="ta-r">
<strong>{{ c.achievedPct.toFixed(2) }}%</strong>
</td>
<td class="ta-r">
<span *ngIf="c.targetPct != null">{{ c.targetPct }}%</span>
<span *ngIf="c.targetPct == null" class="muted">—</span>
</td>
<td class="ta-c">
<span class="status-pill" [ngClass]="achieveStatus(c.achievedPct, c.targetPct)">
<ng-container *ngIf="achieveStatus(c.achievedPct, c.targetPct) === 'on-target'"
>ACHIEVED</ng-container
>
<ng-container *ngIf="achieveStatus(c.achievedPct, c.targetPct) === 'below'">BELOW</ng-container>
<ng-container *ngIf="achieveStatus(c.achievedPct, c.targetPct) === 'na'">NA</ng-container>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</ng-container>
<!-- ════════════ ACCOUNT REGISTER ════════════ -->
<ng-container *ngIf="activeTab === 'register'">
<div class="psl-section">
<div class="psl-sec-h">
<h3 class="psl-sec-t">PSL Account Register — {{ filteredAccounts.length }} of {{ accounts.length }}</h3>
<button mat-stroked-button class="psl-btn-xls" (click)="exportRegisterCsv()">
<fa-icon icon="file-csv"></fa-icon>&nbsp; Export CSV
</button>
</div>
<!-- Filters -->
<div class="psl-filters">
<div class="psl-f">
<label>Category</label>
<select [(ngModel)]="filterCategory">
<option value="">All</option>
<option *ngFor="let c of categories" [value]="c.code">{{ c.code }} — {{ c.name }}</option>
</select>
</div>
<div class="psl-f">
<label>Branch</label>
<input type="text" placeholder="Search branch…" [(ngModel)]="filterBranch" />
</div>
<div class="psl-f">
<label>Weaker Section</label>
<select [(ngModel)]="filterWeaker">
<option value="">All</option>
<option value="Y">Yes</option>
<option value="N">No</option>
</select>
</div>
<div class="psl-f">
<label>Asset Class</label>
<select [(ngModel)]="filterNpa">
<option value="">All</option>
<option value="STANDARD">Standard</option>
<option value="SMA">SMA (Stress)</option>
<option value="NPA">NPA</option>
</select>
</div>
<button class="psl-f-reset" (click)="resetFilters()" matTooltip="Reset filters">
<fa-icon icon="undo"></fa-icon>
</button>
</div>
<div class="psl-tbl-wrap">
<table class="psl-tbl">
<thead>
<tr>
<th>A/c No</th>
<th>Customer</th>
<th>Category</th>
<th>Sub-Category</th>
<th>Branch</th>
<th class="ta-r">Sanctioned</th>
<th class="ta-r">Outstanding</th>
<th class="ta-c">Asset Class</th>
<th class="ta-c">Weaker</th>
<th class="ta-c">SMF</th>
<th>Sanctioned</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let a of filteredAccounts">
<td class="mono">{{ a.acNo }}</td>
<td>{{ a.customerName }}</td>
<td>
<span class="cat-pill">{{ a.category }}</span>
</td>
<td>{{ a.subCategory }}</td>
<td>{{ a.branch }}</td>
<td class="ta-r">&#8377; {{ a.sanctioned | number }}</td>
<td class="ta-r">&#8377; {{ a.outstanding | number }}</td>
<td class="ta-c">
<span
class="npa-pill"
[ngClass]="{
ok: a.npaFlag === 'STANDARD',
warn: a.npaFlag === 'SMA-1' || a.npaFlag === 'SMA-2',
err: a.npaFlag === 'SUB-STD' || a.npaFlag === 'DOUBTFUL' || a.npaFlag === 'LOSS'
}"
>{{ a.npaFlag }}</span
>
</td>
<td class="ta-c">
<fa-icon *ngIf="a.weakerSection" icon="check" class="ico-yes"></fa-icon>
<span *ngIf="!a.weakerSection" class="muted">—</span>
</td>
<td class="ta-c">
<fa-icon *ngIf="a.smallMarginalFarmer" icon="check" class="ico-yes"></fa-icon>
<span *ngIf="!a.smallMarginalFarmer" class="muted">—</span>
</td>
<td>{{ a.sanctionedDate }}</td>
</tr>
<tr *ngIf="filteredAccounts.length === 0">
<td colspan="11" class="empty">No accounts match the current filters.</td>
</tr>
</tbody>
</table>
</div>
</div>
</ng-container>
<!-- ════════════ RETURNS / FORM III ════════════ -->
<ng-container *ngIf="activeTab === 'returns'">
<div class="psl-section">
<div class="psl-sec-h">
<h3 class="psl-sec-t">{{ bank.formCode }} — Priority Sector Achievement Return</h3>
<button mat-stroked-button class="psl-btn-xls" (click)="exportFormIII()">
<fa-icon icon="file-excel"></fa-icon>&nbsp; Generate {{ bank.formCode }}
</button>
</div>
<div class="form-iii">
<div class="form-iii-h">
<div class="form-iii-bank">
<strong>{{ bank.name }}</strong> &mdash; {{ bank.branch }}
</div>
<div class="form-iii-meta">
<span><fa-icon icon="calendar"></fa-icon>&nbsp; As On {{ bank.asOnDate }}</span>
<span><fa-icon icon="university"></fa-icon>&nbsp; {{ bank.quarter }}</span>
</div>
</div>
<h4 class="form-iii-t">A. ANBC Computation (₹ in Lakhs)</h4>
<table class="psl-tbl form-tbl">
<tbody>
<tr>
<td>Adjusted Net Bank Credit (ANBC)</td>
<td class="ta-r">
<strong>{{ bank.anbc / 100000 | number: '1.2-2' }}</strong>
</td>
</tr>
<tr>
<td>Credit Equivalent of Off-Balance Sheet Exposures</td>
<td class="ta-r">
<strong>{{ bank.creditEquivalentOBE / 100000 | number: '1.2-2' }}</strong>
</td>
</tr>
<tr class="form-total">
<td>Total Reference for PSL %</td>
<td class="ta-r">
<strong>{{ (bank.anbc + bank.creditEquivalentOBE) / 100000 | number: '1.2-2' }}</strong>
</td>
</tr>
</tbody>
</table>
<h4 class="form-iii-t">B. Category-wise Achievement</h4>
<table class="psl-tbl form-tbl">
<thead>
<tr>
<th>#</th>
<th>Category</th>
<th class="ta-r">Outstanding (₹ Lakh)</th>
<th class="ta-c">#A/c</th>
<th class="ta-r">Achieved %</th>
<th class="ta-r">Sub-Target %</th>
<th class="ta-c">Status</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let c of categories; let i = index">
<td>{{ i + 1 }}</td>
<td class="mono">{{ c.code }} — {{ c.name }}</td>
<td class="ta-r">{{ c.outstanding / 100000 | number: '1.2-2' }}</td>
<td class="ta-c">{{ c.accounts | number }}</td>
<td class="ta-r">
<strong>{{ c.achievedPct.toFixed(2) }}%</strong>
</td>
<td class="ta-r">
<span *ngIf="c.targetPct != null">{{ c.targetPct }}%</span>
<span *ngIf="c.targetPct == null" class="muted">—</span>
</td>
<td class="ta-c">
<span class="status-pill" [ngClass]="achieveStatus(c.achievedPct, c.targetPct)">
<ng-container *ngIf="achieveStatus(c.achievedPct, c.targetPct) === 'on-target'"
>ACHIEVED</ng-container
>
<ng-container *ngIf="achieveStatus(c.achievedPct, c.targetPct) === 'below'">BELOW</ng-container>
<ng-container *ngIf="achieveStatus(c.achievedPct, c.targetPct) === 'na'">NA</ng-container>
</span>
</td>
</tr>
</tbody>
</table>
<h4 class="form-iii-t">C. Overall PSL Position</h4>
<table class="psl-tbl form-tbl">
<tbody>
<tr>
<td>Total PSL Outstanding (₹ Lakh)</td>
<td class="ta-r">
<strong>{{ totalPslOutstanding / 100000 | number: '1.2-2' }}</strong>
</td>
</tr>
<tr>
<td>Achieved % of ANBC</td>
<td class="ta-r">
<strong>{{ totalPslAchievedPct.toFixed(2) }}%</strong>
</td>
</tr>
<tr>
<td>RBI Target</td>
<td class="ta-r">{{ TOTAL_PSL_TARGET_PCT }}%</td>
</tr>
<tr class="form-total">
<td>Gap / Surplus (Percentage Points)</td>
<td class="ta-r" [ngClass]="totalPslGapPct >= 0 ? 'pos' : 'neg'">
<strong>{{ totalPslGapPct >= 0 ? '+' : '' }}{{ totalPslGapPct.toFixed(2) }}</strong>
</td>
</tr>
<tr>
<td>Compliance Status</td>
<td class="ta-r">
<span
class="status-pill"
[ngClass]="totalPslAchievedPct >= TOTAL_PSL_TARGET_PCT ? 'on-target' : 'below'"
>{{ totalPslAchievedPct >= TOTAL_PSL_TARGET_PCT ? 'ACHIEVED' : 'BELOW TARGET' }}</span
>
</td>
</tr>
</tbody>
</table>
<div class="form-iii-foot">
<div class="cert">
<div class="cert-l">Certified by</div>
<div class="cert-v">{{ bank.principalOfficer }}</div>
<div class="cert-r">Principal Officer — {{ bank.name }}</div>
</div>
<div class="cert">
<div class="cert-l">Submitted under</div>
<div class="cert-v mono">{{ bank.rbiCircular }}</div>
<div class="cert-r">RBI Master Direction on Priority Sector Lending, 2020</div>
</div>
</div>
</div>
</div>
</ng-container>
</div>
</div>
@@ -0,0 +1,753 @@
/**
* 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/.
*/
:host {
--psl-good: #15803d;
--psl-good-l: #dcfce7;
--psl-mid: #d97706;
--psl-mid-l: #fef3c7;
--psl-low: #c0392b;
--psl-low-l: #fdecea;
--psl-green: #16a34a;
--psl-green-l: #dcfce7;
--psl-blue: #2563eb;
--psl-blue-l: #dbeafe;
--psl-g50: #f8fafc;
--psl-g100: #f1f5f9;
--psl-g200: #e2e8f0;
--psl-g300: #cbd5e1;
--psl-g500: #64748b;
--psl-g700: #334155;
--psl-g900: #0f172a;
display: block;
font-family:
'DM Sans',
system-ui,
-apple-system,
sans-serif;
color: var(--psl-g900);
}
.psl-dlg {
background: #fff;
border-radius: 14px;
display: flex;
flex-direction: column;
overflow: hidden;
max-height: 90vh;
}
/* HEADER */
.psl-head {
padding: 18px 22px;
border-bottom: 1px solid var(--psl-g200);
display: flex;
align-items: center;
gap: 16px;
flex-shrink: 0;
background: linear-gradient(135deg, #f0fdf4 0%, #fff 100%);
}
.psl-head-left {
display: flex;
align-items: center;
gap: 14px;
flex: 1;
}
.psl-head-ic {
width: 44px;
height: 44px;
border-radius: 11px;
background: var(--psl-green-l);
color: var(--psl-green);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.psl-title {
margin: 0;
font-size: 17px;
font-weight: 700;
color: var(--psl-g900);
}
.psl-sub {
margin: 2px 0 0;
font-size: 12px;
color: var(--psl-g500);
}
.psl-head-actions {
display: flex;
align-items: center;
gap: 12px;
}
.psl-overall {
display: flex;
align-items: baseline;
gap: 6px;
padding: 6px 14px;
border-radius: 999px;
font-weight: 700;
&.on-target {
background: var(--psl-good-l);
color: var(--psl-good);
}
&.below {
background: var(--psl-low-l);
color: var(--psl-low);
}
.psl-overall-l {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
}
.psl-overall-v {
font-size: 16px;
}
.psl-overall-t {
font-size: 11px;
font-weight: 600;
opacity: 0.8;
}
}
.psl-close {
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid var(--psl-g200);
background: #fff;
font-size: 14px;
color: var(--psl-g500);
cursor: pointer;
transition: all 0.15s;
&:hover {
background: var(--psl-g100);
color: var(--psl-g900);
}
}
/* TABS */
.psl-tabs {
display: flex;
background: var(--psl-g50);
border-bottom: 1px solid var(--psl-g200);
padding: 0 22px;
overflow-x: auto;
flex-shrink: 0;
}
.psl-tab {
display: flex;
align-items: center;
padding: 12px 18px;
border: none;
background: transparent;
cursor: pointer;
font-family: inherit;
font-size: 13px;
font-weight: 600;
color: var(--psl-g500);
border-bottom: 2px solid transparent;
transition: all 0.15s;
white-space: nowrap;
&:hover {
color: var(--psl-g700);
background: rgb(255 255 255 / 50%);
}
&.active {
background: #fff;
color: var(--psl-green);
border-bottom-color: var(--psl-green);
}
}
/* BODY */
.psl-body {
padding: 18px 22px;
display: flex;
flex-direction: column;
gap: 18px;
overflow: auto;
flex: 1;
}
/* KPI STATS */
.psl-stats {
display: grid;
grid-template-columns: 220px 1fr 1fr 1fr;
gap: 14px;
}
.psl-stat-card {
background: #fff;
border: 1px solid var(--psl-g200);
border-radius: 12px;
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 4px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.psl-stat-l {
font-size: 11px;
font-weight: 600;
color: var(--psl-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.psl-stat-v {
font-size: 20px;
font-weight: 700;
color: var(--psl-g900);
line-height: 1.2;
&.pos {
color: var(--psl-good);
}
&.neg {
color: var(--psl-low);
}
}
.psl-stat-meta {
font-size: 11px;
color: var(--psl-g500);
margin-top: 4px;
}
.psl-stat-gauge {
align-items: center;
justify-content: center;
}
/* GAUGE */
.gauge {
width: 140px;
height: 140px;
border-radius: 50%;
background: conic-gradient(currentcolor calc(var(--pct) * 1%), var(--psl-g100) 0);
display: flex;
align-items: center;
justify-content: center;
&.on-target {
color: var(--psl-good);
}
&.below {
color: var(--psl-low);
}
}
.gauge-inner {
width: 108px;
height: 108px;
border-radius: 50%;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: inset 0 0 0 1px var(--psl-g200);
}
.gauge-pct {
font-size: 20px;
font-weight: 700;
color: var(--psl-g900);
line-height: 1;
}
.gauge-label {
font-size: 10px;
color: var(--psl-g500);
margin-top: 4px;
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
}
/* SECTIONS */
.psl-section {
background: #fff;
border: 1px solid var(--psl-g200);
border-radius: 12px;
padding: 16px 18px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.psl-sec-h {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 14px;
}
.psl-sec-t {
margin: 0;
font-size: 14px;
font-weight: 700;
color: var(--psl-g700);
}
.psl-circular {
font-family: 'DM Mono', monospace;
font-size: 11px;
color: var(--psl-g500);
padding: 4px 10px;
background: var(--psl-g50);
border-radius: 6px;
border: 1px solid var(--psl-g200);
}
.psl-btn-xls {
font-size: 12px !important;
height: 34px !important;
color: #fff !important;
background: var(--psl-green) !important;
}
/* SUB-TARGET BARS */
.psl-bars {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px 28px;
}
.psl-bar-row {
display: flex;
flex-direction: column;
gap: 6px;
}
.psl-bar-h {
display: flex;
justify-content: space-between;
align-items: center;
}
.psl-bar-l {
font-size: 12px;
font-weight: 600;
color: var(--psl-g700);
fa-icon {
color: var(--psl-green);
}
}
.psl-bar-p {
font-size: 13px;
font-weight: 700;
&.good {
color: var(--psl-good);
}
&.low {
color: var(--psl-low);
}
}
.psl-bar-track {
height: 8px;
background: var(--psl-g100);
border-radius: 4px;
overflow: hidden;
}
.psl-bar-fill {
height: 100%;
border-radius: 4px;
transition: width 0.4s ease-out;
&.good {
background: var(--psl-good);
}
&.low {
background: var(--psl-low);
}
}
/* INFO GRID */
.psl-info-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px 24px;
}
.psl-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.psl-info-l {
font-size: 10px;
font-weight: 600;
color: var(--psl-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.psl-info-v {
font-size: 13px;
font-weight: 600;
color: var(--psl-g900);
}
/* TABLE */
.psl-tbl-wrap {
border: 1px solid var(--psl-g200);
border-radius: 8px;
overflow: auto;
}
.psl-tbl {
width: 100%;
border-collapse: collapse;
th {
text-align: left;
padding: 10px 14px;
font-size: 11px;
font-weight: 600;
color: var(--psl-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
background: var(--psl-g50);
border-bottom: 1px solid var(--psl-g200);
white-space: nowrap;
}
td {
padding: 10px 14px;
border-bottom: 1px solid var(--psl-g100);
font-size: 13px;
color: var(--psl-g700);
vertical-align: middle;
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: var(--psl-g50);
}
}
.cat-name {
font-weight: 600;
color: var(--psl-g900);
}
.cat-desc {
font-size: 11px;
color: var(--psl-g500);
margin-top: 2px;
}
.cat-pill {
display: inline-block;
padding: 2px 8px;
border-radius: 6px;
background: var(--psl-blue-l);
color: var(--psl-blue);
font-family: 'DM Mono', monospace;
font-size: 11px;
font-weight: 600;
}
.npa-pill {
display: inline-block;
padding: 2px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
&.ok {
background: var(--psl-good-l);
color: var(--psl-good);
}
&.warn {
background: var(--psl-mid-l);
color: var(--psl-mid);
}
&.err {
background: var(--psl-low-l);
color: var(--psl-low);
}
}
.status-pill {
display: inline-block;
padding: 3px 10px;
font-size: 11px;
font-weight: 600;
border-radius: 999px;
&.on-target {
background: var(--psl-good-l);
color: var(--psl-good);
}
&.below {
background: var(--psl-low-l);
color: var(--psl-low);
}
&.na {
background: var(--psl-g100);
color: var(--psl-g500);
}
}
.ico-yes {
color: var(--psl-good);
}
.muted {
color: var(--psl-g300);
}
.ta-c {
text-align: center;
}
.ta-r {
text-align: right;
}
.mono {
font-family: 'DM Mono', monospace;
font-size: 12px;
}
.empty {
text-align: center;
color: var(--psl-g500);
padding: 24px !important;
font-style: italic;
}
/* FILTERS */
.psl-filters {
display: flex;
gap: 12px;
padding: 12px 14px;
background: var(--psl-g50);
border: 1px solid var(--psl-g200);
border-radius: 8px;
margin-bottom: 12px;
align-items: flex-end;
flex-wrap: wrap;
}
.psl-f {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 140px;
label {
font-size: 10px;
font-weight: 600;
color: var(--psl-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
select,
input {
height: 32px;
padding: 0 10px;
border: 1px solid var(--psl-g200);
border-radius: 6px;
background: #fff;
font-family: inherit;
font-size: 12px;
color: var(--psl-g900);
&:focus {
outline: none;
border-color: var(--psl-green);
box-shadow: 0 0 0 3px rgb(22 163 74 / 12%);
}
}
}
.psl-f-reset {
height: 32px;
width: 32px;
border-radius: 6px;
border: 1px solid var(--psl-g200);
background: #fff;
color: var(--psl-g500);
cursor: pointer;
transition: all 0.15s;
&:hover {
background: var(--psl-low-l);
color: var(--psl-low);
border-color: var(--psl-low);
}
}
/* FORM III */
.form-iii {
background: var(--psl-g50);
border: 1px solid var(--psl-g200);
border-radius: 10px;
padding: 18px;
}
.form-iii-h {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 12px;
margin-bottom: 14px;
border-bottom: 2px solid var(--psl-green);
}
.form-iii-bank {
font-size: 14px;
color: var(--psl-g900);
}
.form-iii-meta {
display: flex;
gap: 18px;
font-size: 12px;
color: var(--psl-g500);
fa-icon {
color: var(--psl-green);
}
}
.form-iii-t {
font-size: 13px;
font-weight: 700;
color: var(--psl-g700);
margin: 16px 0 8px;
padding-bottom: 4px;
border-bottom: 1px dashed var(--psl-g200);
}
.form-tbl {
background: #fff;
border: 1px solid var(--psl-g200);
border-radius: 6px;
overflow: hidden;
}
.form-total td {
background: var(--psl-green-l) !important;
font-weight: 700;
color: var(--psl-g900);
}
.pos {
color: var(--psl-good);
}
.neg {
color: var(--psl-low);
}
.form-iii-foot {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
margin-top: 18px;
padding-top: 16px;
border-top: 1px dashed var(--psl-g300);
}
.cert {
background: #fff;
border: 1px solid var(--psl-g200);
border-radius: 8px;
padding: 12px 14px;
}
.cert-l {
font-size: 10px;
font-weight: 600;
color: var(--psl-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.cert-v {
font-size: 14px;
font-weight: 700;
color: var(--psl-g900);
margin: 4px 0;
}
.cert-r {
font-size: 11px;
color: var(--psl-g500);
}
/* RESPONSIVE */
@media (width <= 1100px) {
.psl-stats {
grid-template-columns: 1fr 1fr;
}
.psl-bars {
grid-template-columns: 1fr;
}
.psl-info-grid {
grid-template-columns: 1fr 1fr;
}
.form-iii-foot {
grid-template-columns: 1fr;
}
}
@media (width <= 720px) {
.psl-stats,
.psl-info-grid {
grid-template-columns: 1fr;
}
.psl-head {
flex-wrap: wrap;
}
}
@@ -0,0 +1,661 @@
/**
* 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/.
*/
import { Component, inject } from '@angular/core';
import { CommonModule, DecimalPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatDialogRef, MatDialogModule } from '@angular/material/dialog';
import { MatButtonModule } from '@angular/material/button';
import { MatTooltipModule } from '@angular/material/tooltip';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
interface PslCategory {
code: string;
name: string;
description: string;
/** RBI sub-target as % of ANBC (null = no specific sub-target, only counts toward 40% overall) */
targetPct: number | null;
/** Outstanding amount in ₹ */
outstanding: number;
/** Number of accounts */
accounts: number;
/** Achievement % of ANBC */
achievedPct: number;
}
interface PslAccount {
acNo: string;
customerName: string;
category: string;
subCategory: string;
branch: string;
sanctioned: number;
outstanding: number;
npaFlag: 'STANDARD' | 'SMA-1' | 'SMA-2' | 'SUB-STD' | 'DOUBTFUL' | 'LOSS';
weakerSection: boolean;
smallMarginalFarmer: boolean;
sanctionedDate: string;
}
@Component({
selector: 'mifosx-psl-report-dialog',
standalone: true,
templateUrl: './psl-report-dialog.component.html',
styleUrls: ['./psl-report-dialog.component.scss'],
imports: [
CommonModule,
DecimalPipe,
FormsModule,
MatDialogModule,
MatButtonModule,
MatTooltipModule,
FaIconComponent
]
})
export class PslReportDialogComponent {
private dialogRef = inject(MatDialogRef<PslReportDialogComponent>);
activeTab: 'dashboard' | 'categories' | 'register' | 'returns' = 'dashboard';
bank = {
name: 'Bharat Urban Bank Ltd',
branch: 'Head Office',
quarter: 'Q3 FY 2025-26',
asOnDate: '29-01-2026',
rbiCircular: 'FIDD.CO.PSD.BC.16/04.09.001/2020-21',
principalOfficer: 'Karthik Kamath',
formCode: 'Form III',
/** Adjusted Net Bank Credit — basis for all PSL % calculations */
anbc: 6000000000, // ₹600 Cr
psBalanceCash: 0,
creditEquivalentOBE: 250000000 // ₹25 Cr
};
/** PSL targets per RBI Master Direction (2020) */
readonly TOTAL_PSL_TARGET_PCT = 40;
readonly AGRI_TARGET_PCT = 18;
readonly SMALL_MARGINAL_FARMER_TARGET_PCT = 10;
readonly MICRO_ENTERPRISES_TARGET_PCT = 7.5;
readonly WEAKER_SECTIONS_TARGET_PCT = 12;
categories: PslCategory[] = [
{
code: 'AGRI',
name: 'Agriculture',
description: 'Farm credit, agri-infrastructure, ancillary activities',
targetPct: 18,
outstanding: 1240000000, // ₹124 Cr
accounts: 4824,
achievedPct: 20.67
},
{
code: 'AGRI-SMF',
name: '— Small & Marginal Farmers',
description: 'Sub-target: 10% of ANBC for SMF (within Agri)',
targetPct: 10,
outstanding: 720000000, // ₹72 Cr
accounts: 3110,
achievedPct: 12.0
},
{
code: 'MSME-MICRO',
name: 'Micro Enterprises',
description: 'Investment ≤₹1 Cr, turnover ≤₹5 Cr',
targetPct: 7.5,
outstanding: 510000000, // ₹51 Cr
accounts: 1842,
achievedPct: 8.5
},
{
code: 'MSME-SMALL',
name: 'Small Enterprises',
description: 'Investment ≤₹10 Cr, turnover ≤₹50 Cr',
targetPct: null,
outstanding: 285000000, // ₹28.5 Cr
accounts: 412,
achievedPct: 4.75
},
{
code: 'MSME-MED',
name: 'Medium Enterprises',
description: 'Investment ≤₹50 Cr, turnover ≤₹250 Cr',
targetPct: null,
outstanding: 168000000, // ₹16.8 Cr
accounts: 87,
achievedPct: 2.8
},
{
code: 'EDU',
name: 'Education',
description: 'Loans ≤ ₹20 L (India) / ₹50 L (foreign)',
targetPct: null,
outstanding: 42000000, // ₹4.2 Cr
accounts: 224,
achievedPct: 0.7
},
{
code: 'HOUSE',
name: 'Housing',
description: 'Up to ₹35 L (metro) / ₹25 L (non-metro)',
targetPct: null,
outstanding: 92000000, // ₹9.2 Cr
accounts: 318,
achievedPct: 1.53
},
{
code: 'SOC-INFRA',
name: 'Social Infrastructure',
description: 'Schools, hospitals, drinking water in Tier-II–VI centres',
targetPct: null,
outstanding: 18000000, // ₹1.8 Cr
accounts: 38,
achievedPct: 0.3
},
{
code: 'REN-ENERGY',
name: 'Renewable Energy',
description: 'Up to ₹30 Cr per borrower (solar, wind, biomass)',
targetPct: null,
outstanding: 24000000, // ₹2.4 Cr
accounts: 12,
achievedPct: 0.4
},
{
code: 'EXPORT',
name: 'Export Credit',
description: 'Pre/post-shipment to MSME exporters',
targetPct: null,
outstanding: 38000000, // ₹3.8 Cr
accounts: 19,
achievedPct: 0.63
},
{
code: 'WEAKER',
name: 'Weaker Sections',
description: 'SC/ST, women, minorities, persons with disabilities',
targetPct: 12,
outstanding: 855000000, // ₹85.5 Cr (overlaps with other categories)
accounts: 4218,
achievedPct: 14.25
}
];
/** Sample dummy PSL accounts */
accounts: PslAccount[] = [
{
acNo: '110002101000211',
customerName: 'RAVI KUMAR SHARMA',
category: 'AGRI',
subCategory: 'Crop Loan',
branch: 'Mumbai Main',
sanctioned: 500000,
outstanding: 425000,
npaFlag: 'STANDARD',
weakerSection: false,
smallMarginalFarmer: true,
sanctionedDate: '12-04-2024'
},
{
acNo: '110002102000418',
customerName: 'PRIYA PATEL',
category: 'MSME-MICRO',
subCategory: 'Micro Manufacturing',
branch: 'Pune FC Road',
sanctioned: 800000,
outstanding: 654000,
npaFlag: 'STANDARD',
weakerSection: true,
smallMarginalFarmer: false,
sanctionedDate: '20-09-2024'
},
{
acNo: '110002103000522',
customerName: 'ARJUN DESHMUKH',
category: 'AGRI',
subCategory: 'Allied — Dairy',
branch: 'Nagpur Dharampeth',
sanctioned: 1500000,
outstanding: 1148000,
npaFlag: 'STANDARD',
weakerSection: false,
smallMarginalFarmer: false,
sanctionedDate: '01-06-2024'
},
{
acNo: '110002104000711',
customerName: 'VIKRAM REDDY',
category: 'EDU',
subCategory: 'Higher Education',
branch: 'Bengaluru Jayanagar',
sanctioned: 1500000,
outstanding: 1280000,
npaFlag: 'STANDARD',
weakerSection: false,
smallMarginalFarmer: false,
sanctionedDate: '15-07-2023'
},
{
acNo: '110002105000814',
customerName: 'LAKSHMI IYER',
category: 'AGRI-SMF',
subCategory: 'Marginal Farmer',
branch: 'Bengaluru Indiranagar',
sanctioned: 250000,
outstanding: 210000,
npaFlag: 'STANDARD',
weakerSection: true,
smallMarginalFarmer: true,
sanctionedDate: '08-10-2024'
},
{
acNo: '110002106001012',
customerName: 'NIRAV SHAH',
category: 'HOUSE',
subCategory: 'Housing — Non-Metro',
branch: 'Ahmedabad Satellite',
sanctioned: 2200000,
outstanding: 1840000,
npaFlag: 'STANDARD',
weakerSection: false,
smallMarginalFarmer: false,
sanctionedDate: '14-02-2023'
},
{
acNo: '110002107001145',
customerName: 'KAVITA VERMA',
category: 'MSME-MICRO',
subCategory: 'Service Sector',
branch: 'Lucknow Gomti Nagar',
sanctioned: 600000,
outstanding: 482000,
npaFlag: 'SMA-1',
weakerSection: true,
smallMarginalFarmer: false,
sanctionedDate: '11-11-2024'
},
{
acNo: '110002108001458',
customerName: 'SAMEER KAPOOR',
category: 'MSME-SMALL',
subCategory: 'Small Manufacturing',
branch: 'Delhi Greater Kailash',
sanctioned: 4500000,
outstanding: 3980000,
npaFlag: 'STANDARD',
weakerSection: false,
smallMarginalFarmer: false,
sanctionedDate: '25-08-2024'
},
{
acNo: '110002109001721',
customerName: 'KARTHIK SUBRAMANIAN',
category: 'REN-ENERGY',
subCategory: 'Solar Rooftop',
branch: 'Chennai Valasaravakkam',
sanctioned: 1800000,
outstanding: 1620000,
npaFlag: 'STANDARD',
weakerSection: false,
smallMarginalFarmer: false,
sanctionedDate: '03-05-2025'
},
{
acNo: '110002110002012',
customerName: 'ABHISHEK BANERJEE',
category: 'EXPORT',
subCategory: 'Pre-shipment',
branch: 'Kolkata Salt Lake',
sanctioned: 3500000,
outstanding: 2950000,
npaFlag: 'STANDARD',
weakerSection: false,
smallMarginalFarmer: false,
sanctionedDate: '17-12-2024'
},
{
acNo: '110002111002314',
customerName: 'GAURAV THAKUR',
category: 'AGRI',
subCategory: 'Kisan Credit Card',
branch: 'Shimla Sector 4',
sanctioned: 350000,
outstanding: 295000,
npaFlag: 'STANDARD',
weakerSection: false,
smallMarginalFarmer: true,
sanctionedDate: '22-04-2024'
},
{
acNo: '110002112002615',
customerName: 'MANMOHAN SINGH GILL',
category: 'WEAKER',
subCategory: 'SC/ST — Mudra',
branch: 'Amritsar Model Town',
sanctioned: 200000,
outstanding: 165000,
npaFlag: 'STANDARD',
weakerSection: true,
smallMarginalFarmer: false,
sanctionedDate: '05-01-2025'
},
{
acNo: '110002113002918',
customerName: 'SUNITA DEVI',
category: 'WEAKER',
subCategory: 'Women Self-Help Group',
branch: 'Bhopal Arera Colony',
sanctioned: 150000,
outstanding: 125000,
npaFlag: 'STANDARD',
weakerSection: true,
smallMarginalFarmer: false,
sanctionedDate: '18-06-2024'
},
{
acNo: '110002114003221',
customerName: 'DEEPAK TIWARI',
category: 'SOC-INFRA',
subCategory: 'Drinking Water Project',
branch: 'Indore Vijay Nagar',
sanctioned: 5000000,
outstanding: 4280000,
npaFlag: 'STANDARD',
weakerSection: false,
smallMarginalFarmer: false,
sanctionedDate: '12-03-2025'
},
{
acNo: '110002115003524',
customerName: 'HARPREET KAUR',
category: 'MSME-MICRO',
subCategory: 'Women — Self Employed',
branch: 'Ludhiana Sarabha Nagar',
sanctioned: 750000,
outstanding: 632000,
npaFlag: 'SMA-2',
weakerSection: true,
smallMarginalFarmer: false,
sanctionedDate: '08-09-2024'
}
];
/** Filters for register tab */
filterCategory = '';
filterBranch = '';
filterWeaker: '' | 'Y' | 'N' = '';
filterNpa: '' | 'STANDARD' | 'SMA' | 'NPA' = '';
// -------------------- Computed --------------------
get totalPslOutstanding(): number {
// Sum excluding sub-categories that double-count (AGRI-SMF, WEAKER overlap)
const main = [
'AGRI',
'MSME-MICRO',
'MSME-SMALL',
'MSME-MED',
'EDU',
'HOUSE',
'SOC-INFRA',
'REN-ENERGY',
'EXPORT'
];
return this.categories.filter((c) => main.includes(c.code)).reduce((s, c) => s + c.outstanding, 0);
}
get totalPslAccounts(): number {
const main = [
'AGRI',
'MSME-MICRO',
'MSME-SMALL',
'MSME-MED',
'EDU',
'HOUSE',
'SOC-INFRA',
'REN-ENERGY',
'EXPORT'
];
return this.categories.filter((c) => main.includes(c.code)).reduce((s, c) => s + c.accounts, 0);
}
get totalPslAchievedPct(): number {
return +((this.totalPslOutstanding / this.bank.anbc) * 100).toFixed(2);
}
get totalPslGapPct(): number {
return +(this.totalPslAchievedPct - this.TOTAL_PSL_TARGET_PCT).toFixed(2);
}
cat(code: string): PslCategory | undefined {
return this.categories.find((c) => c.code === code);
}
get agriAchievedPct(): number {
return this.cat('AGRI')?.achievedPct ?? 0;
}
get smfAchievedPct(): number {
return this.cat('AGRI-SMF')?.achievedPct ?? 0;
}
get microAchievedPct(): number {
return this.cat('MSME-MICRO')?.achievedPct ?? 0;
}
get weakerAchievedPct(): number {
return this.cat('WEAKER')?.achievedPct ?? 0;
}
achieveStatus(achieved: number, target: number | null): 'on-target' | 'below' | 'na' {
if (target == null) return 'na';
return achieved >= target ? 'on-target' : 'below';
}
// -------------------- Filtered Register --------------------
get filteredAccounts(): PslAccount[] {
const branch = this.filterBranch.trim().toLowerCase();
return this.accounts.filter((a) => {
if (this.filterCategory && a.category !== this.filterCategory) return false;
if (branch && !a.branch.toLowerCase().includes(branch)) return false;
if (this.filterWeaker === 'Y' && !a.weakerSection) return false;
if (this.filterWeaker === 'N' && a.weakerSection) return false;
if (this.filterNpa === 'STANDARD' && a.npaFlag !== 'STANDARD') return false;
if (this.filterNpa === 'SMA' && !a.npaFlag.startsWith('SMA')) return false;
if (this.filterNpa === 'NPA' && (a.npaFlag === 'STANDARD' || a.npaFlag.startsWith('SMA'))) return false;
return true;
});
}
resetFilters(): void {
this.filterCategory = '';
this.filterBranch = '';
this.filterWeaker = '';
this.filterNpa = '';
}
// -------------------- Excel Exports --------------------
/** Form III — Quarterly PSL Achievement Statement */
exportFormIII(): void {
const rows: string[][] = [];
rows.push([
this.bank.name,
this.bank.branch
]);
rows.push([
`PRIORITY REGISTER STATEMENT (${this.bank.formCode})`,
`As On ${this.bank.asOnDate}`
]);
rows.push([
`RBI Circular: ${this.bank.rbiCircular}`,
`Quarter: ${this.bank.quarter}`
]);
rows.push([]);
rows.push(['ANBC Computation (₹ in Lakhs)']);
rows.push([
'Adjusted Net Bank Credit (ANBC)',
(this.bank.anbc / 100000).toFixed(2)
]);
rows.push([
'Credit Equivalent of OBE',
(this.bank.creditEquivalentOBE / 100000).toFixed(2)
]);
rows.push([
'Total Reference for PSL %',
((this.bank.anbc + this.bank.creditEquivalentOBE) / 100000).toFixed(2)
]);
rows.push([]);
rows.push(['Category-wise Achievement (₹ in Lakhs)']);
rows.push([
'Sr.No',
'Category Code',
'Category Description',
'Outstanding (₹ Lakh)',
'#A/c',
'Achieved % of ANBC',
'Sub-Target %',
'Status',
'Gap (PP)'
]);
let sl = 1;
for (const c of this.categories) {
const status = this.achieveStatus(c.achievedPct, c.targetPct);
const gap = c.targetPct == null ? '' : (c.achievedPct - c.targetPct).toFixed(2);
rows.push([
(sl++).toString(),
c.code,
c.name + ' — ' + c.description,
(c.outstanding / 100000).toFixed(2),
c.accounts.toString(),
c.achievedPct.toFixed(2),
c.targetPct == null ? '—' : c.targetPct.toString(),
status === 'on-target' ? 'ACHIEVED' : status === 'below' ? 'BELOW TARGET' : 'NA',
gap
]);
}
rows.push([]);
rows.push(['Overall PSL Achievement']);
rows.push([
'Total PSL Outstanding (₹ Lakh)',
(this.totalPslOutstanding / 100000).toFixed(2)
]);
rows.push([
'Achieved % of ANBC',
this.totalPslAchievedPct.toFixed(2)
]);
rows.push([
'Target',
this.TOTAL_PSL_TARGET_PCT.toString() + '%'
]);
rows.push([
'Gap / Surplus (PP)',
this.totalPslGapPct.toFixed(2)
]);
rows.push([
'Status',
this.totalPslAchievedPct >= this.TOTAL_PSL_TARGET_PCT ? 'ACHIEVED' : 'BELOW TARGET'
]);
rows.push([]);
rows.push([
'Certified by',
this.bank.principalOfficer + ' — Principal Officer'
]);
this.saveTsv(rows, `PSL-FormIII-${this.dateTag()}.xls`);
}
/** Account Register CSV — every PSL-tagged loan in the filtered set */
exportRegisterCsv(): void {
const headers = [
'A/c No',
'Customer Name',
'Category',
'Sub-Category',
'Branch',
'Sanctioned (₹)',
'Outstanding (₹)',
'NPA Flag',
'Weaker Section',
'Small/Marginal Farmer',
'Sanctioned Date'
];
const rows = [headers];
for (const a of this.filteredAccounts) {
rows.push([
a.acNo,
a.customerName,
a.category,
a.subCategory,
a.branch,
a.sanctioned.toString(),
a.outstanding.toString(),
a.npaFlag,
a.weakerSection ? 'Y' : 'N',
a.smallMarginalFarmer ? 'Y' : 'N',
a.sanctionedDate
]);
}
this.saveCsv(rows, `PSL-Register-${this.dateTag()}.csv`);
}
/** Category Outstanding TSV */
exportCategoriesXls(): void {
const rows: string[][] = [];
rows.push([
`PSL Category-wise Outstanding — ${this.bank.name}`,
this.bank.asOnDate
]);
rows.push([]);
rows.push([
'Code',
'Category',
'Description',
'Outstanding (₹)',
'#A/c',
'Achieved %',
'Target %',
'Status'
]);
for (const c of this.categories) {
rows.push([
c.code,
c.name,
c.description,
c.outstanding.toString(),
c.accounts.toString(),
c.achievedPct.toFixed(2),
c.targetPct == null ? '' : c.targetPct.toString(),
this.achieveStatus(c.achievedPct, c.targetPct)
]);
}
this.saveTsv(rows, `PSL-Categories-${this.dateTag()}.xls`);
}
setTab(tab: 'dashboard' | 'categories' | 'register' | 'returns'): void {
this.activeTab = tab;
}
close(): void {
this.dialogRef.close();
}
// ---- helpers ----
private dateTag(): string {
const d = new Date();
return `${String(d.getDate()).padStart(2, '0')}${String(d.getMonth() + 1).padStart(2, '0')}${d.getFullYear()}`;
}
private saveTsv(rows: string[][], filename: string): void {
const tsv = rows.map((r) => r.map((v) => (v || '').replace(/\t/g, ' ')).join('\t')).join('\n');
this.saveBlob(tsv, filename, 'application/vnd.ms-excel');
}
private saveCsv(rows: string[][], filename: string): void {
const escape = (v: string): string => {
const s = (v || '').replace(/"/g, '""');
if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s}"`;
return s;
};
const csv = rows.map((r) => r.map(escape).join(',')).join('\n') + '\n';
this.saveBlob(csv, filename, 'text/csv');
}
private saveBlob(content: string, filename: string, mime: string): void {
const blob = new Blob([content], { type: `${mime};charset=utf-8` });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
}
+654
View File
@@ -0,0 +1,654 @@
/**
* 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/.
*/
/**
* SFT — Statement of Financial Transactions, filed under
* Section 285BA of the Income Tax Act 1961 + Rule 114E,
* with the Income Tax Reporting Portal (report.insight.gov.in)
* by 31 May for the previous FY.
*
* Schema reproduces the actual SFT CSV templates; bank/customer names are dummy.
*
* SFT-001 Cash purchase of bank drafts / pay orders / banker's cheques (≥ ₹10 L cash)
* SFT-002 Time-deposit (FD/RD) opening (≥ ₹10 L cash)
* SFT-003 Cash deposits / withdrawals — current account (≥ ₹50 L per FY)
* SFT-004 Cash deposits — savings account (≥ ₹10 L per FY)
* SFT-015 Dividend payments
* SFT-016 Interest paid on deposits
*/
export type SftCode = 'SFT-001' | 'SFT-002' | 'SFT-003' | 'SFT-004' | 'SFT-015' | 'SFT-016';
/** Person-wise schema (SFT-001 / 002 / 004) */
export interface SftPersonRecord {
reportSerialNumber: number;
originalReportSerialNumber: number;
personName: string;
personType: string; // I=Individual, F=Firm, P=Partnership, etc.
customerIdentity: string;
gender: 'M' | 'F' | 'O' | '';
fatherName: string;
pan: string;
aadhaar: string;
form60Ack: string;
identificationType: string;
identificationNumber: string;
dobOrIncorporation: string;
nationality: string;
addressType: string;
fixAddress: string;
city: string;
postCode: string;
state: string;
countryCode: string;
businessOrOccupation: string;
primaryStdCode: string;
primaryPhoneNo: string;
primaryMobileNo: string;
email: string;
remarks: string;
productType: string; // SFT-001/002/004 — DD / Pay Order / Time Deposit / SB
aggGrossReceived: number;
aggGrossReceivedCash: number;
aggGrossPaid: number;
productIdentifier: string;
lastDateOfTransaction: string;
amountReceivedFromPerson: number;
cashAmountReceivedFromPerson: number;
amountPaidToPerson: number;
relatedAccountNumber: string;
relatedInstituteName: string;
relatedInstituteRefNumber: string;
/** Which SFT code this row belongs to */
sftCode: SftCode;
}
/** Account-wise schema with embedded Person Card (SFT-003) */
export interface SftAccountRecord {
reportSerialNumber: number;
originalReportSerialNumber: number;
accountType: string;
accountNumber: string;
accountHolderName: string;
accountStatus: 'A' | 'C' | 'D';
branchReferenceNumber: string;
branchName: string;
branchAddress: string;
branchCity: string;
branchPostalCode: string;
branchState: string;
branchCountry: string;
branchStdCode: string;
branchPhone: string;
branchMobile: string;
branchFaxStd: string;
branchFaxPhone: string;
branchEmail: string;
aggGrossCashCredited: number;
aggGrossCashDebited: number;
amountCreditedBeforeDemonetization: number;
amountCreditedAfterDemonetization: number;
// Person Card (PC)
pcAccountRelationship: string;
pcPersonName: string;
pcPersonType: string;
pcCustomerIdentity: string;
pcGender: 'M' | 'F' | 'O' | '';
pcFatherName: string;
pcPan: string;
pcAadhaar: string;
pcForm60Ack: string;
pcIdentificationType: string;
pcIdentificationNumber: string;
pcDob: string;
pcNationality: string;
pcBusiness: string;
pcAddressType: string;
pcAddress: string;
pcCity: string;
pcPin: string;
pcState: string;
pcCountry: string;
pcPrimaryStd: string;
pcPrimaryPhone: string;
pcPrimaryMobile: string;
pcEmail: string;
}
/** Compact pipe-delimited schema (SFT-015 dividend / SFT-016 interest) */
export interface SftInterestDividendRecord {
rsn: number;
fy: string;
name: string;
pan: string;
aadhaar: string;
mobile: string;
email: string;
/** Address only present in SFT-015 */
address?: string;
pincode?: string;
/** Account fields only present in SFT-016 */
accountNumber?: string;
typeOfAccount?: string;
/** SFT-015 = dividend amount; SFT-016 = interest paid */
amount: number;
sftCode: 'SFT-015' | 'SFT-016';
}
export const SFT_BANK = {
name: 'Bharat Urban Bank Ltd',
branch: 'Head Office',
filerPan: 'AABCB1100M',
filerTan: 'MUMR99001E',
itdReportingPortalId: 'BHARATURBAN-285BA',
fy: '2025-26',
filingDueDate: '31-05-2026',
principalOfficer: 'Karthik Kamath',
poDesignation: 'Chief Compliance Officer',
branchRef: 'YESB0BHRT',
branchAddress: '12 MG Road, Worli',
branchCity: 'MUMBAI',
branchState: 'MH',
branchPostalCode: '400018',
branchCountry: 'IN',
branchStdCode: '022',
branchPhone: '24938000',
branchMobile: '9892000100',
branchEmail: 'compliance@bharaturbanbank.example'
};
/** ── SFT-001 / 002 / 004 — Person-wise records ── */
export const SFT_PERSON_RECORDS: SftPersonRecord[] = [
// SFT-001 — Cash purchase of DD ≥ ₹10 L
{
reportSerialNumber: 1,
originalReportSerialNumber: 0,
personName: 'RAVI KUMAR SHARMA',
personType: 'I',
customerIdentity: '10078',
gender: 'M',
fatherName: 'RAJESH KUMAR SHARMA',
pan: 'AAQPS8560J',
aadhaar: '',
form60Ack: '',
identificationType: 'C',
identificationNumber: 'AAQPS8560J',
dobOrIncorporation: '10-08-1985',
nationality: 'IN',
addressType: '2',
fixAddress: 'FLAT 402 SUNRISE TOWERS MAROL ANDHERI EAST',
city: 'MUMBAI',
postCode: '400069',
state: 'MH',
countryCode: 'IN',
businessOrOccupation: 'SERVICE',
primaryStdCode: '',
primaryPhoneNo: '',
primaryMobileNo: '9892011001',
email: 'ravi.sharma@example.com',
remarks: '',
productType: 'BANK_DRAFT',
aggGrossReceived: 1500000,
aggGrossReceivedCash: 1500000,
aggGrossPaid: 0,
productIdentifier: 'DD24304',
lastDateOfTransaction: '20-01-2026',
amountReceivedFromPerson: 1500000,
cashAmountReceivedFromPerson: 1500000,
amountPaidToPerson: 0,
relatedAccountNumber: '110001101000838',
relatedInstituteName: 'BHARAT URBAN BANK',
relatedInstituteRefNumber: 'YESB0BHRT',
sftCode: 'SFT-001'
},
{
reportSerialNumber: 2,
originalReportSerialNumber: 0,
personName: 'PRIYA PATEL',
personType: 'I',
customerIdentity: '10079',
gender: 'F',
fatherName: 'HARISH PATEL',
pan: 'AKLPS9933P',
aadhaar: '',
form60Ack: '',
identificationType: 'C',
identificationNumber: 'AKLPS9933P',
dobOrIncorporation: '22-03-1990',
nationality: 'IN',
addressType: '2',
fixAddress: 'B-12 SHIVAJI NAGAR FC ROAD',
city: 'PUNE',
postCode: '411005',
state: 'MH',
countryCode: 'IN',
businessOrOccupation: 'BUSINESS',
primaryStdCode: '',
primaryPhoneNo: '',
primaryMobileNo: '9822055002',
email: 'priya.patel@example.com',
remarks: '',
productType: 'BANKERS_CHEQUE',
aggGrossReceived: 1100000,
aggGrossReceivedCash: 1100000,
aggGrossPaid: 0,
productIdentifier: 'BC100039',
lastDateOfTransaction: '02-01-2026',
amountReceivedFromPerson: 1100000,
cashAmountReceivedFromPerson: 1100000,
amountPaidToPerson: 0,
relatedAccountNumber: '110001352000357',
relatedInstituteName: 'BHARAT URBAN BANK',
relatedInstituteRefNumber: 'YESB0BHRT',
sftCode: 'SFT-001'
},
// SFT-002 — Time deposit ≥ ₹10 L cash
{
reportSerialNumber: 3,
originalReportSerialNumber: 0,
personName: 'VIKRAM REDDY',
personType: 'I',
customerIdentity: '20101',
gender: 'M',
fatherName: 'SURESH REDDY',
pan: 'AAEPR9352L',
aadhaar: '',
form60Ack: '',
identificationType: 'C',
identificationNumber: 'AAEPR9352L',
dobOrIncorporation: '07-07-1982',
nationality: 'IN',
addressType: '2',
fixAddress: 'NO 23 4TH CROSS JAYANAGAR 5TH BLOCK',
city: 'BENGALURU',
postCode: '560041',
state: 'KA',
countryCode: 'IN',
businessOrOccupation: 'PROFESSION',
primaryStdCode: '',
primaryPhoneNo: '',
primaryMobileNo: '9845022004',
email: '',
remarks: '',
productType: 'TIME_DEPOSIT',
aggGrossReceived: 2500000,
aggGrossReceivedCash: 2500000,
aggGrossPaid: 0,
productIdentifier: 'FDR-2026-001',
lastDateOfTransaction: '15-09-2025',
amountReceivedFromPerson: 2500000,
cashAmountReceivedFromPerson: 2500000,
amountPaidToPerson: 0,
relatedAccountNumber: '110001352000420',
relatedInstituteName: 'BHARAT URBAN BANK',
relatedInstituteRefNumber: 'YESB0BHRT',
sftCode: 'SFT-002'
},
{
reportSerialNumber: 4,
originalReportSerialNumber: 0,
personName: 'NIRAV SHAH',
personType: 'I',
customerIdentity: '40301',
gender: 'M',
fatherName: 'JAYESH SHAH',
pan: 'AHKPS5544Z',
aadhaar: '',
form60Ack: '',
identificationType: 'C',
identificationNumber: 'AHKPS5544Z',
dobOrIncorporation: '18-05-1992',
nationality: 'IN',
addressType: '2',
fixAddress: 'A-401 ATLANTIS APARTMENTS SATELLITE ROAD',
city: 'AHMEDABAD',
postCode: '380015',
state: 'GJ',
countryCode: 'IN',
businessOrOccupation: 'SERVICE',
primaryStdCode: '',
primaryPhoneNo: '',
primaryMobileNo: '9824012010',
email: 'nirav.shah@example.com',
remarks: '',
productType: 'TIME_DEPOSIT',
aggGrossReceived: 1800000,
aggGrossReceivedCash: 1800000,
aggGrossPaid: 0,
productIdentifier: 'FDR-2026-002',
lastDateOfTransaction: '12-12-2025',
amountReceivedFromPerson: 1800000,
cashAmountReceivedFromPerson: 1800000,
amountPaidToPerson: 0,
relatedAccountNumber: '110001352000511',
relatedInstituteName: 'BHARAT URBAN BANK',
relatedInstituteRefNumber: 'YESB0BHRT',
sftCode: 'SFT-002'
},
// SFT-004 — Cash deposits in SB ≥ ₹10 L per FY
{
reportSerialNumber: 5,
originalReportSerialNumber: 0,
personName: 'KAVITA VERMA',
personType: 'I',
customerIdentity: '50402',
gender: 'F',
fatherName: 'DEEPAK VERMA',
pan: 'AKHPV2244R',
aadhaar: '',
form60Ack: '',
identificationType: 'C',
identificationNumber: 'AKHPV2244R',
dobOrIncorporation: '01-04-1988',
nationality: 'IN',
addressType: '2',
fixAddress: '45 CIVIL LINES MALL ROAD',
city: 'KANPUR',
postCode: '208001',
state: 'UP',
countryCode: 'IN',
businessOrOccupation: 'SERVICE',
primaryStdCode: '',
primaryPhoneNo: '',
primaryMobileNo: '9415055014',
email: '',
remarks: '',
productType: 'SAVINGS_DEPOSIT',
aggGrossReceived: 1250000,
aggGrossReceivedCash: 1250000,
aggGrossPaid: 0,
productIdentifier: 'SB-50402',
lastDateOfTransaction: '28-01-2026',
amountReceivedFromPerson: 1250000,
cashAmountReceivedFromPerson: 1250000,
amountPaidToPerson: 0,
relatedAccountNumber: '110001101001145',
relatedInstituteName: 'BHARAT URBAN BANK',
relatedInstituteRefNumber: 'YESB0BHRT',
sftCode: 'SFT-004'
},
{
reportSerialNumber: 6,
originalReportSerialNumber: 0,
personName: 'SAMEER KAPOOR',
personType: 'I',
customerIdentity: '70601',
gender: 'M',
fatherName: 'RAJINDER KAPOOR',
pan: 'AHKPK7788Q',
aadhaar: '',
form60Ack: '',
identificationType: 'C',
identificationNumber: 'AHKPK7788Q',
dobOrIncorporation: '03-02-1987',
nationality: 'IN',
addressType: '2',
fixAddress: 'B-148 GREATER KAILASH GK PART 1',
city: 'NEW DELHI',
postCode: '110048',
state: 'DL',
countryCode: 'IN',
businessOrOccupation: 'SERVICE',
primaryStdCode: '',
primaryPhoneNo: '',
primaryMobileNo: '9811022019',
email: 'sameer.kapoor@example.com',
remarks: '',
productType: 'SAVINGS_DEPOSIT',
aggGrossReceived: 2150000,
aggGrossReceivedCash: 2150000,
aggGrossPaid: 0,
productIdentifier: 'SB-70601',
lastDateOfTransaction: '15-01-2026',
amountReceivedFromPerson: 2150000,
cashAmountReceivedFromPerson: 2150000,
amountPaidToPerson: 0,
relatedAccountNumber: '110001101001458',
relatedInstituteName: 'BHARAT URBAN BANK',
relatedInstituteRefNumber: 'YESB0BHRT',
sftCode: 'SFT-004'
}
];
/** ── SFT-003 — Account-wise (current account) cash deposits ≥ ₹50 L per FY ── */
export const SFT_ACCOUNT_RECORDS: SftAccountRecord[] = [
{
reportSerialNumber: 1,
originalReportSerialNumber: 0,
accountType: 'BC',
accountNumber: '110001101000685',
accountHolderName: 'NEXTGEN TECH PVT LTD',
accountStatus: 'A',
branchReferenceNumber: 'YESB0BHRT',
branchName: 'HEAD OFFICE',
branchAddress: '12 MG ROAD WORLI MUMBAI 400018',
branchCity: 'MUMBAI',
branchPostalCode: '400018',
branchState: 'MH',
branchCountry: 'IN',
branchStdCode: '022',
branchPhone: '24938000',
branchMobile: '9892000100',
branchFaxStd: '022',
branchFaxPhone: '24938011',
branchEmail: 'compliance@bharaturbanbank.example',
aggGrossCashCredited: 6500000,
aggGrossCashDebited: 4200000,
amountCreditedBeforeDemonetization: 0,
amountCreditedAfterDemonetization: 0,
pcAccountRelationship: 'F',
pcPersonName: 'NEXTGEN TECH PVT LTD',
pcPersonType: 'PF',
pcCustomerIdentity: 'E10102',
pcGender: '',
pcFatherName: '',
pcPan: 'AABCN5566K',
pcAadhaar: '',
pcForm60Ack: '',
pcIdentificationType: 'C',
pcIdentificationNumber: 'AABCN5566K',
pcDob: '18-09-2015',
pcNationality: 'IN',
pcBusiness: 'IT_SERVICES',
pcAddressType: '2',
pcAddress: 'TOWER B SUNRISE BUSINESS PARK ANDHERI EAST',
pcCity: 'MUMBAI',
pcPin: '400093',
pcState: 'MH',
pcCountry: 'IN',
pcPrimaryStd: '',
pcPrimaryPhone: '',
pcPrimaryMobile: '9824012011',
pcEmail: ''
},
{
reportSerialNumber: 2,
originalReportSerialNumber: 0,
accountType: 'BC',
accountNumber: '110001101000922',
accountHolderName: 'BHARAT TRADING COMPANY (PROP. RAVI KUMAR SHARMA)',
accountStatus: 'A',
branchReferenceNumber: 'YESB0BHRT',
branchName: 'HEAD OFFICE',
branchAddress: '12 MG ROAD WORLI MUMBAI 400018',
branchCity: 'MUMBAI',
branchPostalCode: '400018',
branchState: 'MH',
branchCountry: 'IN',
branchStdCode: '022',
branchPhone: '24938000',
branchMobile: '9892000100',
branchFaxStd: '022',
branchFaxPhone: '24938011',
branchEmail: 'compliance@bharaturbanbank.example',
aggGrossCashCredited: 5800000,
aggGrossCashDebited: 3450000,
amountCreditedBeforeDemonetization: 0,
amountCreditedAfterDemonetization: 0,
pcAccountRelationship: 'F',
pcPersonName: 'BHARAT TRADING COMPANY',
pcPersonType: 'PF',
pcCustomerIdentity: 'E10101',
pcGender: '',
pcFatherName: '',
pcPan: 'ABCFB1234N',
pcAadhaar: '',
pcForm60Ack: '',
pcIdentificationType: 'C',
pcIdentificationNumber: 'ABCFB1234N',
pcDob: '24-03-2008',
pcNationality: 'IN',
pcBusiness: 'TRADING',
pcAddressType: '2',
pcAddress: 'GALA 22 LOWER PAREL',
pcCity: 'MUMBAI',
pcPin: '400013',
pcState: 'MH',
pcCountry: 'IN',
pcPrimaryStd: '',
pcPrimaryPhone: '',
pcPrimaryMobile: '9892011010',
pcEmail: ''
}
];
/** ── SFT-015 / SFT-016 — Pipe-delimited interest & dividend ── */
export const SFT_INT_DIV_RECORDS: SftInterestDividendRecord[] = [
// SFT-016 — Interest paid to depositors
{
rsn: 1,
fy: '2025-26',
name: 'RAVI KUMAR SHARMA',
pan: 'AAQPS8560J',
aadhaar: 'XXXXXXXX1234',
mobile: '9892011001',
email: 'ravi.sharma@example.com',
accountNumber: '110001302000312',
typeOfAccount: 'FD',
amount: 56250,
sftCode: 'SFT-016'
},
{
rsn: 2,
fy: '2025-26',
name: 'PRIYA PATEL',
pan: 'AKLPS9933P',
aadhaar: 'XXXXXXXX2210',
mobile: '9822055002',
email: 'priya.patel@example.com',
accountNumber: '110001302000418',
typeOfAccount: 'FD',
amount: 41280,
sftCode: 'SFT-016'
},
{
rsn: 3,
fy: '2025-26',
name: 'VIKRAM REDDY',
pan: 'AAEPR9352L',
aadhaar: 'XXXXXXXX6622',
mobile: '9845022004',
email: '',
accountNumber: '110001302000711',
typeOfAccount: 'FD',
amount: 73500,
sftCode: 'SFT-016'
},
{
rsn: 4,
fy: '2025-26',
name: 'LAKSHMI IYER',
pan: '',
aadhaar: 'XXXXXXXX5511',
mobile: '9845066005',
email: '',
accountNumber: '110001301000814',
typeOfAccount: 'SB',
amount: 18420,
sftCode: 'SFT-016'
},
{
rsn: 5,
fy: '2025-26',
name: 'NIRAV SHAH',
pan: 'AHKPS5544Z',
aadhaar: 'XXXXXXXX9933',
mobile: '9824012010',
email: 'nirav.shah@example.com',
accountNumber: '110001352000511',
typeOfAccount: 'FD',
amount: 124200,
sftCode: 'SFT-016'
},
{
rsn: 6,
fy: '2025-26',
name: 'KAVITA VERMA',
pan: 'AKHPV2244R',
aadhaar: 'XXXXXXXX1144',
mobile: '9415055014',
email: '',
accountNumber: '110001101001145',
typeOfAccount: 'SB',
amount: 9850,
sftCode: 'SFT-016'
},
{
rsn: 7,
fy: '2025-26',
name: 'SAMEER KAPOOR',
pan: 'AHKPK7788Q',
aadhaar: 'XXXXXXXX8877',
mobile: '9811022019',
email: 'sameer.kapoor@example.com',
accountNumber: '110001304001458',
typeOfAccount: 'TL',
amount: 285600,
sftCode: 'SFT-016'
},
// SFT-015 — Dividend payments (bank as paying entity, e.g. on shares held)
{
rsn: 8,
fy: '2025-26',
name: 'ABHISHEK BANERJEE',
pan: 'AHKPK7788Q',
aadhaar: 'XXXXXXXX3344',
mobile: '9831022021',
email: 'abhishek.b@example.com',
address: '14/B SALT LAKE SECTOR V BIDHANNAGAR',
pincode: '700091',
amount: 24500,
sftCode: 'SFT-015'
},
{
rsn: 9,
fy: '2025-26',
name: 'GAURAV THAKUR',
pan: 'AILPT4455G',
aadhaar: 'XXXXXXXX5566',
mobile: '9418022023',
email: '',
address: 'HOUSE 7 NEW SHIMLA SECTOR 4',
pincode: '171002',
amount: 18750,
sftCode: 'SFT-015'
},
{
rsn: 10,
fy: '2025-26',
name: 'MANMOHAN SINGH GILL',
pan: 'ANBPG2233J',
aadhaar: '',
mobile: '9876022027',
email: 'manmohan.gill@example.com',
address: 'H 32 MODEL TOWN MAJITHA ROAD AMRITSAR',
pincode: '143001',
amount: 35200,
sftCode: 'SFT-015'
}
];
+29
View File
@@ -0,0 +1,29 @@
/**
* 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/.
*/
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Route } from '../core/route/route.service';
import { SftComponent } from './sft.component';
const routes: Routes = [
Route.withShell([
{
path: 'sft',
component: SftComponent,
data: { title: 'Income Tax — SFT', breadcrumb: 'SFT Reports' }
}
])
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class SftRoutingModule {}
+397
View File
@@ -0,0 +1,397 @@
<!--
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/.
-->
<div class="sft-root">
<!-- HEADER -->
<div class="sft-head">
<div class="sft-head-left">
<fa-icon icon="file-invoice" class="sft-head-ic"></fa-icon>
<div>
<h2 class="sft-head-t">Income Tax — Statement of Financial Transactions (SFT)</h2>
<div class="sft-head-s">
{{ bank.name }} &middot; {{ bank.branch }} &middot; FY <strong>{{ bank.fy }}</strong> &middot; IT Act §285BA /
Rule 114E &middot; Due <strong>{{ bank.filingDueDate }}</strong>
</div>
</div>
</div>
<div class="sft-head-meta">
<div>
<span class="sft-meta-l">Filer PAN</span><span class="sft-meta-v mono">{{ bank.filerPan }}</span>
</div>
<div>
<span class="sft-meta-l">TAN</span><span class="sft-meta-v mono">{{ bank.filerTan }}</span>
</div>
<div>
<span class="sft-meta-l">Principal Officer</span><span class="sft-meta-v">{{ bank.principalOfficer }}</span>
</div>
</div>
</div>
<!-- TABS -->
<div class="sft-tabs">
<button class="sft-tab" [class.active]="activeTab === 'dashboard'" (click)="showTab('dashboard')">
<fa-icon icon="chart-pie"></fa-icon> Dashboard
</button>
<button class="sft-tab" [class.active]="activeTab === 'person'" (click)="showTab('person')">
<fa-icon icon="users"></fa-icon> Person-wise (001 / 002 / 004)
</button>
<button class="sft-tab" [class.active]="activeTab === 'account'" (click)="showTab('account')">
<fa-icon icon="university"></fa-icon> Account-wise (003)
</button>
<button class="sft-tab" [class.active]="activeTab === 'intdiv'" (click)="showTab('intdiv')">
<fa-icon icon="coins"></fa-icon> Interest &amp; Dividend (015 / 016)
</button>
</div>
<!-- DASHBOARD -->
@if (activeTab === 'dashboard') {
<div class="sft-section">
<div class="sft-kpis">
<div class="sft-kpi sft-kpi--blue">
<div class="sft-kpi-ic"><fa-icon icon="file-alt"></fa-icon></div>
<div>
<div class="sft-kpi-n">{{ totalRecords | number }}</div>
<div class="sft-kpi-l">Total Records</div>
<div class="sft-kpi-sub">all SFT codes combined</div>
</div>
</div>
<div class="sft-kpi sft-kpi--teal">
<div class="sft-kpi-ic"><fa-icon icon="coins"></fa-icon></div>
<div>
<div class="sft-kpi-n">&#8377;{{ totalReportableAmount | number: '1.0-0' }}</div>
<div class="sft-kpi-l">Reportable Amount</div>
<div class="sft-kpi-sub">aggregate per FY {{ bank.fy }}</div>
</div>
</div>
<div class="sft-kpi sft-kpi--amber">
<div class="sft-kpi-ic"><fa-icon icon="users"></fa-icon></div>
<div>
<div class="sft-kpi-n">{{ sft001Count + sft002Count + sft004Count | number }}</div>
<div class="sft-kpi-l">Person-wise Records</div>
<div class="sft-kpi-sub">
{{ sft001Count }} ·001 &middot; {{ sft002Count }} ·002 &middot; {{ sft004Count }} ·004
</div>
</div>
</div>
<div class="sft-kpi sft-kpi--purple">
<div class="sft-kpi-ic"><fa-icon icon="university"></fa-icon></div>
<div>
<div class="sft-kpi-n">{{ sft003Count | number }}</div>
<div class="sft-kpi-l">SFT-003 Accounts</div>
<div class="sft-kpi-sub">cash CA ≥ &#8377;50 L per FY</div>
</div>
</div>
<div class="sft-kpi sft-kpi--green">
<div class="sft-kpi-ic"><fa-icon icon="coins"></fa-icon></div>
<div>
<div class="sft-kpi-n">{{ sft015Count + sft016Count | number }}</div>
<div class="sft-kpi-l">Int / Dividend</div>
<div class="sft-kpi-sub">{{ sft015Count }} ·015 &middot; {{ sft016Count }} ·016</div>
</div>
</div>
</div>
<!-- SFT code summary cards -->
<div class="sft-codes">
<div class="sft-code-card">
<div class="sft-code-h">
<span class="sft-code-tag t-001">SFT-001</span><span>Cash Drafts / Pay Orders</span>
</div>
<p>
Cash purchase of bank drafts, pay orders or banker's cheques aggregating ≥ <strong>&#8377;10 L</strong> per
person/FY
</p>
<div class="sft-code-meta">{{ sft001Count }} record{{ sft001Count === 1 ? '' : 's' }}</div>
</div>
<div class="sft-code-card">
<div class="sft-code-h">
<span class="sft-code-tag t-002">SFT-002</span><span>Time Deposits (FD/RD)</span>
</div>
<p>Time-deposit opening with cash ≥ <strong>&#8377;10 L</strong> per person/FY (excluding renewals)</p>
<div class="sft-code-meta">{{ sft002Count }} record{{ sft002Count === 1 ? '' : 's' }}</div>
</div>
<div class="sft-code-card">
<div class="sft-code-h"><span class="sft-code-tag t-003">SFT-003</span><span>Current Account Cash</span></div>
<p>
Cash deposits / withdrawals — <strong>current account</strong> aggregating ≥
<strong>&#8377;50 L</strong> per FY
</p>
<div class="sft-code-meta">{{ sft003Count }} account{{ sft003Count === 1 ? '' : 's' }}</div>
</div>
<div class="sft-code-card">
<div class="sft-code-h"><span class="sft-code-tag t-004">SFT-004</span><span>Savings Bank Cash</span></div>
<p>Cash deposits — <strong>savings account</strong> aggregating ≥ <strong>&#8377;10 L</strong> per FY</p>
<div class="sft-code-meta">{{ sft004Count }} record{{ sft004Count === 1 ? '' : 's' }}</div>
</div>
<div class="sft-code-card">
<div class="sft-code-h"><span class="sft-code-tag t-015">SFT-015</span><span>Dividend Payments</span></div>
<p>Dividend payments to shareholders — any amount, aggregated per PAN/FY</p>
<div class="sft-code-meta">{{ sft015Count }} record{{ sft015Count === 1 ? '' : 's' }}</div>
</div>
<div class="sft-code-card">
<div class="sft-code-h">
<span class="sft-code-tag t-016">SFT-016</span><span>Interest Paid on Deposits</span>
</div>
<p>Interest paid on deposits — any amount, aggregated per PAN/FY</p>
<div class="sft-code-meta">{{ sft016Count }} record{{ sft016Count === 1 ? '' : 's' }}</div>
</div>
</div>
<div class="sft-toolbar">
<button class="sft-btn sft-btn-xls" (click)="exportAll()">
<fa-icon icon="file-csv"></fa-icon> Download All SFT Files
</button>
</div>
</div>
}
<!-- PERSON-WISE -->
@if (activeTab === 'person') {
<div class="sft-section">
<div class="sft-form-meta">
<div class="sft-form-title">
<h3>Person-wise Records — SFT-001 / 002 / 004</h3>
<p>One record per person per SFT code per FY (aggregate)</p>
</div>
<div class="sft-action-group">
<select class="sft-inp sft-inp-narrow" [(ngModel)]="personSftFilter">
<option value="ALL">All Codes</option>
<option value="SFT-001">SFT-001 only</option>
<option value="SFT-002">SFT-002 only</option>
<option value="SFT-004">SFT-004 only</option>
</select>
<button class="sft-btn sft-btn-csv" (click)="exportPersonCsv('SFT-001')">
<fa-icon icon="file-csv"></fa-icon> SFT_001.csv
</button>
<button class="sft-btn sft-btn-csv" (click)="exportPersonCsv('SFT-002')">
<fa-icon icon="file-csv"></fa-icon> SFT_002.csv
</button>
<button class="sft-btn sft-btn-csv" (click)="exportPersonCsv('SFT-004')">
<fa-icon icon="file-csv"></fa-icon> SFT_004.csv
</button>
</div>
</div>
<div class="sft-tbl-wrap">
<table class="sft-tbl">
<thead>
<tr>
<th class="ta-c">RSN</th>
<th>Code</th>
<th>Person Name</th>
<th class="ta-c">Type</th>
<th>PAN</th>
<th>DOB</th>
<th>Mobile</th>
<th>City</th>
<th>Product</th>
<th class="ta-r">Aggregate</th>
<th class="ta-r">Cash</th>
<th>Last Txn</th>
<th>Account</th>
</tr>
</thead>
<tbody>
@for (r of filteredPersons; track r.reportSerialNumber + '-' + r.sftCode) {
<tr>
<td class="ta-c">{{ r.reportSerialNumber }}</td>
<td>
<span class="sft-code-tag" [class]="'t-' + r.sftCode.replace('SFT-', '')">{{ r.sftCode }}</span>
</td>
<td>{{ r.personName }}</td>
<td class="ta-c">{{ r.personType }}</td>
<td class="mono">{{ r.pan }}</td>
<td>{{ r.dobOrIncorporation }}</td>
<td class="mono">{{ r.primaryMobileNo }}</td>
<td>{{ r.city }}, {{ r.state }}</td>
<td>
<span class="muted">{{ productLabel(r.productType) }}</span>
</td>
<td class="ta-r mono">&#8377;{{ r.aggGrossReceived | number: '1.0-0' }}</td>
<td class="ta-r mono">&#8377;{{ r.aggGrossReceivedCash | number: '1.0-0' }}</td>
<td>{{ r.lastDateOfTransaction }}</td>
<td class="mono">{{ r.relatedAccountNumber }}</td>
</tr>
} @empty {
<tr>
<td colspan="13" class="sft-empty">No records for the selected SFT code.</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
<!-- ACCOUNT-WISE (SFT-003) -->
@if (activeTab === 'account') {
<div class="sft-section">
<div class="sft-form-meta">
<div class="sft-form-title">
<h3>SFT-003 — Cash Deposits/Withdrawals in Current Accounts</h3>
<p>Account-wise schema with embedded Person Card; aggregate ≥ &#8377;50 L cash per FY</p>
</div>
<button class="sft-btn sft-btn-csv" (click)="exportAccountCsv()">
<fa-icon icon="file-csv"></fa-icon> Download SFT_003.csv
</button>
</div>
<div class="sft-acc-list">
@for (r of accountRecords; track r.reportSerialNumber) {
<div class="acc-card">
<div class="acc-card-h">
<div class="acc-card-id">
<span class="acc-rsn">RSN-{{ r.reportSerialNumber }}</span>
<span class="acc-type-pill">{{ r.accountType }}</span>
<span class="acc-status-pill" [class.act]="r.accountStatus === 'A'">{{
r.accountStatus === 'A' ? 'Active' : r.accountStatus === 'C' ? 'Closed' : 'Dormant'
}}</span>
</div>
<div class="acc-card-amt">
<span class="muted">Cash credited</span>
<strong>&#8377;{{ r.aggGrossCashCredited | number: '1.0-0' }}</strong>
<span class="muted">/ debited</span>
<strong>&#8377;{{ r.aggGrossCashDebited | number: '1.0-0' }}</strong>
</div>
</div>
<div class="acc-card-body">
<div class="acc-meta">
<div>
<label>Account No.</label><span class="mono">{{ r.accountNumber }}</span>
</div>
<div>
<label>Holder</label><span>{{ r.accountHolderName }}</span>
</div>
<div>
<label>Branch</label><span>{{ r.branchName }} ({{ r.branchReferenceNumber }})</span>
</div>
<div>
<label>Branch Address</label><span>{{ r.branchAddress }}</span>
</div>
<div>
<label>Branch Phone</label><span class="mono">{{ r.branchStdCode }}-{{ r.branchPhone }}</span>
</div>
<div>
<label>Branch Email</label><span>{{ r.branchEmail }}</span>
</div>
</div>
<div class="acc-pc-block">
<div class="acc-pc-h"><fa-icon icon="user"></fa-icon> Person Card</div>
<div class="acc-meta">
<div>
<label>Name</label><span>{{ r.pcPersonName }}</span>
</div>
<div>
<label>Type</label><span>{{ r.pcPersonType }}</span>
</div>
<div>
<label>PAN</label><span class="mono">{{ r.pcPan }}</span>
</div>
<div>
<label>DOB / Incorp.</label><span>{{ r.pcDob }}</span>
</div>
<div>
<label>Business</label><span>{{ productLabel(r.pcBusiness) }}</span>
</div>
<div>
<label>Mobile</label><span class="mono">{{ r.pcPrimaryMobile }}</span>
</div>
<div class="acc-pc-addr">
<label>Address</label><span>{{ r.pcAddress }}, {{ r.pcCity }}, {{ r.pcState }} {{ r.pcPin }}</span>
</div>
</div>
</div>
</div>
</div>
} @empty {
<div class="sft-empty-card">No SFT-003 records this FY.</div>
}
</div>
</div>
}
<!-- INTEREST & DIVIDEND -->
@if (activeTab === 'intdiv') {
<div class="sft-section">
<div class="sft-form-meta">
<div class="sft-form-title">
<h3>SFT-015 (Dividend) &amp; SFT-016 (Interest Paid)</h3>
<p>Pipe-delimited compact format submitted to Income Tax Reporting Portal</p>
</div>
<div class="sft-action-group">
<select class="sft-inp sft-inp-narrow" [(ngModel)]="intDivFilter">
<option value="ALL">Both</option>
<option value="SFT-015">SFT-015 (Dividend)</option>
<option value="SFT-016">SFT-016 (Interest)</option>
</select>
<button class="sft-btn sft-btn-csv" (click)="exportIntDivCsv('SFT-015')">
<fa-icon icon="file-csv"></fa-icon> SFT15.csv
</button>
<button class="sft-btn sft-btn-csv" (click)="exportIntDivCsv('SFT-016')">
<fa-icon icon="file-csv"></fa-icon> SFT16.csv
</button>
</div>
</div>
<div class="sft-tbl-wrap">
<table class="sft-tbl">
<thead>
<tr>
<th class="ta-c">RSN</th>
<th>Code</th>
<th>FY</th>
<th>Name</th>
<th>PAN</th>
<th>Aadhaar (mask)</th>
<th>Mobile</th>
<th>Account</th>
<th class="ta-c">Type</th>
<th class="ta-r">Amount</th>
</tr>
</thead>
<tbody>
@for (r of filteredIntDiv; track r.rsn + '-' + r.sftCode) {
<tr>
<td class="ta-c">{{ r.rsn }}</td>
<td>
<span class="sft-code-tag" [class]="'t-' + r.sftCode.replace('SFT-', '')">{{ r.sftCode }}</span>
</td>
<td>{{ r.fy }}</td>
<td>{{ r.name }}</td>
<td class="mono">{{ r.pan || '—' }}</td>
<td class="mono">{{ r.aadhaar || '—' }}</td>
<td class="mono">{{ r.mobile }}</td>
<td class="mono">{{ r.accountNumber || '—' }}</td>
<td class="ta-c">{{ r.typeOfAccount || (r.sftCode === 'SFT-015' ? 'DIV' : '—') }}</td>
<td class="ta-r mono">
<strong>&#8377;{{ r.amount | number: '1.0-0' }}</strong>
</td>
</tr>
} @empty {
<tr>
<td colspan="10" class="sft-empty">No records for the selected SFT code.</td>
</tr>
}
</tbody>
</table>
</div>
<div class="sft-note">
<fa-icon icon="info-circle"></fa-icon>
<span
><strong>Note:</strong> SFT-015 (Dividend) and SFT-016 (Interest) are pipe-delimited (`|`) files, one row per
PAN per FY with aggregated amounts. Submit via the IT Reporting Portal under {{ bank.principalOfficer }}'s
digital signature.</span
>
</div>
</div>
}
<!-- TOAST -->
<div class="sft-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
</div>
+689
View File
@@ -0,0 +1,689 @@
/**
* 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/.
*/
:host {
--s-navy: #0b1e3d;
--s-blue: #1d4ed8;
--s-blue-l: #eff6ff;
--s-teal: #0e7e6b;
--s-teal-l: #e0f4f0;
--s-green: #15803d;
--s-green-l: #dcfce7;
--s-amber: #d97706;
--s-amber-l: #fef3c7;
--s-red: #c0392b;
--s-red-l: #fdecea;
--s-purple: #6d28d9;
--s-purple-l: #ede9fe;
--s-pink: #db2777;
--s-pink-l: #fce7f3;
--s-g50: #f8fafc;
--s-g100: #f1f5f9;
--s-g200: #e2e8f0;
--s-g300: #cbd5e1;
--s-g500: #64748b;
--s-g700: #334155;
--s-g900: #0f172a;
display: block;
font-family:
'DM Sans',
system-ui,
-apple-system,
sans-serif;
background: var(--s-g50);
color: var(--s-g900);
font-size: 14px;
line-height: 1.5;
}
.sft-root {
padding: 18px 22px;
max-width: 1500px;
margin: 0 auto;
}
.sft-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.sft-head-left {
display: flex;
align-items: center;
gap: 14px;
}
.sft-head-ic {
font-size: 28px;
color: var(--s-blue);
}
.sft-head-t {
margin: 0;
font-size: 18px;
font-weight: 700;
color: var(--s-g900);
}
.sft-head-s {
font-size: 12px;
color: var(--s-g500);
}
.sft-head-meta {
display: flex;
gap: 22px;
> div {
display: flex;
flex-direction: column;
.sft-meta-l {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--s-g500);
}
.sft-meta-v {
font-size: 14px;
font-weight: 600;
color: var(--s-g900);
}
}
}
.sft-tabs {
display: flex;
gap: 4px;
background: #fff;
border: 1px solid var(--s-g200);
border-radius: 10px;
padding: 4px;
margin-bottom: 16px;
overflow-x: auto;
}
.sft-tab {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 9px 14px;
border: none;
background: transparent;
font-size: 13px;
font-weight: 500;
color: var(--s-g500);
cursor: pointer;
border-radius: 7px;
white-space: nowrap;
transition: all 0.15s;
font-family: inherit;
&:hover {
color: var(--s-g900);
}
&.active {
background: var(--s-blue);
color: #fff;
box-shadow: 0 2px 6px rgb(29 78 216 / 25%);
}
}
.sft-section {
display: flex;
flex-direction: column;
gap: 16px;
}
/* KPIs */
.sft-kpis {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
}
.sft-kpi {
background: #fff;
border: 1px solid var(--s-g200);
border-radius: 12px;
padding: 14px 16px;
display: flex;
align-items: center;
gap: 12px;
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 14px rgb(0 0 0 / 10%);
}
}
.sft-kpi-ic {
width: 42px;
height: 42px;
border-radius: 11px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.sft-kpi--blue .sft-kpi-ic {
background: var(--s-blue-l);
color: var(--s-blue);
}
.sft-kpi--teal .sft-kpi-ic {
background: var(--s-teal-l);
color: var(--s-teal);
}
.sft-kpi--amber .sft-kpi-ic {
background: var(--s-amber-l);
color: var(--s-amber);
}
.sft-kpi--purple .sft-kpi-ic {
background: var(--s-purple-l);
color: var(--s-purple);
}
.sft-kpi--green .sft-kpi-ic {
background: var(--s-green-l);
color: var(--s-green);
}
.sft-kpi-n {
font-size: 18px;
font-weight: 700;
color: var(--s-g900);
line-height: 1.1;
}
.sft-kpi-l {
font-size: 12px;
color: var(--s-g700);
font-weight: 500;
margin-top: 2px;
}
.sft-kpi-sub {
font-size: 11px;
color: var(--s-g500);
margin-top: 1px;
}
/* SFT code summary cards */
.sft-codes {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.sft-code-card {
background: #fff;
border: 1px solid var(--s-g200);
border-radius: 12px;
padding: 14px 16px;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.sft-code-h {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
color: var(--s-g900);
}
.sft-code-tag {
display: inline-block;
padding: 3px 10px;
border-radius: 6px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
font-family: 'DM Mono', monospace;
background: var(--s-g100);
color: var(--s-g700);
&.t-001 {
background: var(--s-blue-l);
color: var(--s-blue);
}
&.t-002 {
background: var(--s-teal-l);
color: var(--s-teal);
}
&.t-003 {
background: var(--s-purple-l);
color: var(--s-purple);
}
&.t-004 {
background: var(--s-amber-l);
color: var(--s-amber);
}
&.t-015 {
background: var(--s-pink-l);
color: var(--s-pink);
}
&.t-016 {
background: var(--s-green-l);
color: var(--s-green);
}
}
.sft-code-card p {
margin: 0 0 10px;
font-size: 12px;
color: var(--s-g500);
line-height: 1.5;
}
.sft-code-meta {
font-size: 12px;
color: var(--s-g700);
font-weight: 600;
}
/* Toolbar */
.sft-toolbar {
display: flex;
justify-content: flex-end;
gap: 8px;
}
/* Form header */
.sft-form-meta {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 14px;
flex-wrap: wrap;
}
.sft-form-title {
h3 {
margin: 0;
font-size: 16px;
font-weight: 700;
color: var(--s-g900);
}
p {
margin: 2px 0 0;
font-size: 12px;
color: var(--s-g500);
}
}
.sft-action-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
/* Buttons */
.sft-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: 1px solid var(--s-g300);
background: #fff;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
color: var(--s-g700);
cursor: pointer;
font-family: inherit;
transition: all 0.15s;
&:hover {
background: var(--s-g50);
border-color: var(--s-g500);
}
}
.sft-btn-csv {
background: #1d4ed8;
border-color: #1d4ed8;
color: #fff;
&:hover {
background: #1e40af;
border-color: #1e40af;
color: #fff;
}
}
.sft-btn-xls {
background: #16a34a;
border-color: #16a34a;
color: #fff;
&:hover {
background: #15803d;
border-color: #15803d;
color: #fff;
}
}
/* Inputs */
.sft-inp {
padding: 8px 12px;
border: 1px solid var(--s-g200);
border-radius: 8px;
font-size: 13px;
background: #fff;
font-family: inherit;
&:focus {
outline: none;
border-color: var(--s-blue);
box-shadow: 0 0 0 3px rgb(29 78 216 / 15%);
}
}
.sft-inp-narrow {
width: 200px;
}
/* Table */
.sft-tbl-wrap {
background: #fff;
border: 1px solid var(--s-g200);
border-radius: 12px;
overflow: auto;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.sft-tbl {
width: 100%;
border-collapse: collapse;
th {
text-align: left;
padding: 10px 12px;
font-size: 11px;
font-weight: 600;
color: var(--s-g500);
text-transform: uppercase;
letter-spacing: 0.04em;
background: var(--s-g50);
border-bottom: 1px solid var(--s-g200);
}
td {
padding: 10px 12px;
border-bottom: 1px solid var(--s-g100);
font-size: 12px;
color: var(--s-g700);
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background: var(--s-g50);
}
}
.ta-c {
text-align: center;
}
.ta-r {
text-align: right;
}
.mono {
font-family: 'DM Mono', monospace;
font-size: 11px;
letter-spacing: 0.02em;
}
.muted {
color: var(--s-g500);
font-size: 11px;
}
.sft-empty {
text-align: center;
padding: 30px;
color: var(--s-g500);
font-style: italic;
}
.sft-empty-card {
background: #fff;
border: 1px dashed var(--s-g300);
border-radius: 12px;
padding: 50px;
text-align: center;
color: var(--s-g500);
font-size: 13px;
}
/* Account-wise SFT-003 cards */
.sft-acc-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.acc-card {
background: #fff;
border: 1px solid var(--s-g200);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
}
.acc-card-h {
padding: 12px 16px;
background: var(--s-g50);
border-bottom: 1px solid var(--s-g200);
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.acc-card-id {
display: flex;
align-items: center;
gap: 10px;
}
.acc-rsn {
font-family: 'DM Mono', monospace;
font-size: 13px;
font-weight: 700;
color: var(--s-g900);
}
.acc-type-pill {
display: inline-block;
padding: 2px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
background: var(--s-purple-l);
color: var(--s-purple);
}
.acc-status-pill {
display: inline-block;
padding: 2px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
background: var(--s-g100);
color: var(--s-g500);
&.act {
background: var(--s-green-l);
color: var(--s-green);
}
}
.acc-card-amt {
display: flex;
align-items: baseline;
gap: 6px;
strong {
font-size: 14px;
color: var(--s-g900);
font-family: 'DM Mono', monospace;
}
}
.acc-card-body {
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.acc-meta {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px 18px;
> div {
display: flex;
flex-direction: column;
label {
font-size: 10px;
font-weight: 600;
color: var(--s-g500);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 1px;
}
span {
font-size: 12px;
color: var(--s-g900);
}
}
}
.acc-pc-block {
background: var(--s-blue-l);
border-radius: 8px;
padding: 12px 14px;
}
.acc-pc-h {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
font-weight: 700;
color: var(--s-blue);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 8px;
}
.acc-pc-addr {
grid-column: 1 / -1;
}
/* Note */
.sft-note {
background: var(--s-amber-l);
border: 1px solid #fed7aa;
border-radius: 10px;
padding: 12px 16px;
display: flex;
gap: 10px;
font-size: 12px;
color: #92400e;
line-height: 1.5;
fa-icon {
font-size: 14px;
flex-shrink: 0;
margin-top: 2px;
}
}
/* Toast */
.sft-toast {
position: fixed;
bottom: 32px;
left: 50%;
transform: translateX(-50%) translateY(20px);
background: var(--s-navy);
color: #fff;
padding: 12px 22px;
border-radius: 10px;
font-size: 13px;
font-weight: 500;
box-shadow: 0 6px 20px rgb(0 0 0 / 25%);
opacity: 0;
pointer-events: none;
transition: all 0.25s;
z-index: 1500;
&.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
@media (width <= 1100px) {
.sft-kpis {
grid-template-columns: repeat(3, 1fr);
}
.sft-codes {
grid-template-columns: 1fr 1fr;
}
.acc-meta {
grid-template-columns: 1fr 1fr;
}
}
@media (width <= 720px) {
.sft-kpis {
grid-template-columns: 1fr 1fr;
}
.sft-codes {
grid-template-columns: 1fr;
}
.acc-meta {
grid-template-columns: 1fr;
}
}
+466
View File
@@ -0,0 +1,466 @@
/**
* 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/.
*/
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import {
SFT_ACCOUNT_RECORDS,
SFT_BANK,
SFT_INT_DIV_RECORDS,
SFT_PERSON_RECORDS,
SftAccountRecord,
SftCode,
SftInterestDividendRecord,
SftPersonRecord
} from './sft-data';
@Component({
selector: 'mifosx-sft',
templateUrl: './sft.component.html',
styleUrls: ['./sft.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
FormsModule
]
})
export class SftComponent {
/** Active tab */
activeTab: 'dashboard' | 'person' | 'account' | 'intdiv' = 'dashboard';
bank = SFT_BANK;
personRecords: SftPersonRecord[] = SFT_PERSON_RECORDS;
accountRecords: SftAccountRecord[] = SFT_ACCOUNT_RECORDS;
intDivRecords: SftInterestDividendRecord[] = SFT_INT_DIV_RECORDS;
/** Person tab — switch which SFT code is being shown */
personSftFilter: 'ALL' | 'SFT-001' | 'SFT-002' | 'SFT-004' = 'ALL';
/** Int/Div tab — switch interest vs dividend */
intDivFilter: 'ALL' | 'SFT-015' | 'SFT-016' = 'ALL';
toastMsg = '';
toastVisible = false;
private toastTimer: any;
showTab(tab: 'dashboard' | 'person' | 'account' | 'intdiv'): void {
this.activeTab = tab;
}
// -------------------- KPIs --------------------
get totalRecords(): number {
return this.personRecords.length + this.accountRecords.length + this.intDivRecords.length;
}
get sft001Count(): number {
return this.personRecords.filter((r) => r.sftCode === 'SFT-001').length;
}
get sft002Count(): number {
return this.personRecords.filter((r) => r.sftCode === 'SFT-002').length;
}
get sft003Count(): number {
return this.accountRecords.length;
}
get sft004Count(): number {
return this.personRecords.filter((r) => r.sftCode === 'SFT-004').length;
}
get sft015Count(): number {
return this.intDivRecords.filter((r) => r.sftCode === 'SFT-015').length;
}
get sft016Count(): number {
return this.intDivRecords.filter((r) => r.sftCode === 'SFT-016').length;
}
get totalReportableAmount(): number {
return (
this.personRecords.reduce((s, r) => s + r.aggGrossReceived, 0) +
this.accountRecords.reduce((s, r) => s + r.aggGrossCashCredited, 0) +
this.intDivRecords.reduce((s, r) => s + r.amount, 0)
);
}
// -------------------- Filters --------------------
get filteredPersons(): SftPersonRecord[] {
if (this.personSftFilter === 'ALL') return this.personRecords;
return this.personRecords.filter((r) => r.sftCode === this.personSftFilter);
}
get filteredIntDiv(): SftInterestDividendRecord[] {
if (this.intDivFilter === 'ALL') return this.intDivRecords;
return this.intDivRecords.filter((r) => r.sftCode === this.intDivFilter);
}
// -------------------- Helpers --------------------
productLabel(t: string): string {
return t.replace(/_/g, ' ');
}
sftLabel(c: SftCode): string {
const map: Record<SftCode, string> = {
'SFT-001': 'Cash Drafts/PO',
'SFT-002': 'Time Deposits',
'SFT-003': 'Cash CA',
'SFT-004': 'Cash SB',
'SFT-015': 'Dividend',
'SFT-016': 'Interest Paid'
};
return map[c];
}
// -------------------- CSV exports --------------------
/** SFT-001/002/004 — Person-wise CSV (comma-separated, all 43 columns) */
exportPersonCsv(filter?: SftCode): void {
const list = filter ? this.personRecords.filter((r) => r.sftCode === filter) : this.filteredPersons;
const headers = [
'Report Serial Number',
'Original Report Serial Number',
'Person Name',
'Person Type',
'Customer Identity',
'Gender',
'Father\u2019s Name',
'PAN',
'Aadhaar Number',
'Form 60 Acknowledgment',
'Identification Type',
'Identification Number',
'Date of birth/Incorporation',
'Nationality/Country of Incorporation',
'AddressType',
'Fix Address',
'City/Town',
'PostCode',
'State',
'CountryCode',
'Business or occupation',
'Primary STD Code',
'Primary Phone No',
'Primary Mobile No',
'Secondary STD Code',
'Secondary Phone No',
'Secondary Mobile No',
'Email',
'Remarks',
'Product Type',
'Aggregate gross amount received from the person',
'Aggregate gross amount received from the person in cash',
'Aggregate gross amount paid to the person',
'TransactionSummry Remarks',
'Product Identifier',
'Last Date of Transaction',
'Amount Received From Person',
'Cash Amount Received From Person',
'Amount Paid To Person',
'Related Account Number',
'Related Institute Name',
'Related Institute Ref Number',
'Financial TransactionDtls Remarks'
];
const rows: string[][] = [headers];
if (list.length === 0) {
rows.push(['NIL Report']);
} else {
for (const r of list) {
rows.push([
r.reportSerialNumber.toString(),
r.originalReportSerialNumber.toString(),
r.personName,
r.personType,
r.customerIdentity,
r.gender,
r.fatherName,
r.pan,
r.aadhaar,
r.form60Ack,
r.identificationType,
r.identificationNumber,
r.dobOrIncorporation.replace(/-/g, '-'),
r.nationality,
r.addressType,
r.fixAddress,
r.city,
r.postCode,
r.state,
r.countryCode,
r.businessOrOccupation,
r.primaryStdCode,
r.primaryPhoneNo,
r.primaryMobileNo,
'',
'',
'',
r.email,
r.remarks,
r.productType,
r.aggGrossReceived.toString(),
r.aggGrossReceivedCash.toString(),
r.aggGrossPaid.toString(),
'',
r.productIdentifier,
r.lastDateOfTransaction,
r.amountReceivedFromPerson.toString(),
r.cashAmountReceivedFromPerson.toString(),
r.amountPaidToPerson.toString(),
r.relatedAccountNumber,
r.relatedInstituteName,
r.relatedInstituteRefNumber,
''
]);
}
}
const fileTag = filter || 'ALL';
this.saveCsv(rows, `${fileTag.replace('-', '_')}.csv`);
this.toast(`${fileTag} exported (${list.length} rows)`);
}
/** SFT-003 — Account-wise CSV with embedded Person Card */
exportAccountCsv(): void {
const headers = [
'Report Serial Number',
'Original Report Serial Number',
'Account Type',
'Account Number',
'Account Holder Name',
'Account Status',
'Branch Reference Number',
'Branch Name',
'Branch Address',
'Branch City/Town',
'Branch Postal Code',
'Branch State',
'Branch Country',
'Branch STD Code',
'Branch Phone Number',
'Branch Mobile Number',
'Branch Fax STD Code',
'Branch Fax Phone No',
'Branch Email',
'Branch Remarks',
'Aggregate gross amount credited to the account in cash',
'Aggregate gross amount debited to the account in cash',
'Amount credited to the account before demonetization',
'Amount credited to the account after demonetization',
'Account Remarks',
'Account Relationship (PC)',
'Person Name (PC)',
'Person Type (PC)',
'Customer Identity (PC)',
'Gender (PC)',
'Father\u2019s Name (PC)',
'PAN (PC)',
'Aadhaar Number (PC)',
'Form 60 Acknowledgement (PC)',
'Identification Type (PC)',
'Identification Number (PC)',
'Date of Birth/ Incorporation (PC)',
'Nationality/Country of Incorporation (PC)',
'Business or Occupation (PC)',
'Address Type (PC-L)',
'Address (PC-L)',
'City/Town (PC-L)',
'Pin Code (PC-L)',
'State (PC-L)',
'Country (PC-L)',
'Primary STD Code (PC)',
'Primary Telephone Number (PC)',
'Primary Mobile Number (PC)',
'Secondary STD Code (PC)',
'Secondary Telephone Number (PC)',
'Secondary Mobile Number (PC)',
'Email (PC)',
'Remarks (PC)'
];
const rows: string[][] = [headers];
if (this.accountRecords.length === 0) {
rows.push(['NIL Report']);
} else {
for (const r of this.accountRecords) {
rows.push([
r.reportSerialNumber.toString(),
r.originalReportSerialNumber.toString(),
r.accountType,
r.accountNumber,
r.accountHolderName,
r.accountStatus,
r.branchReferenceNumber,
r.branchName,
r.branchAddress,
r.branchCity,
r.branchPostalCode,
r.branchState,
r.branchCountry,
r.branchStdCode,
r.branchPhone,
r.branchMobile,
r.branchFaxStd,
r.branchFaxPhone,
r.branchEmail,
'',
r.aggGrossCashCredited.toString(),
r.aggGrossCashDebited.toString(),
r.amountCreditedBeforeDemonetization.toString(),
r.amountCreditedAfterDemonetization.toString(),
'',
r.pcAccountRelationship,
r.pcPersonName,
r.pcPersonType,
r.pcCustomerIdentity,
r.pcGender,
r.pcFatherName,
r.pcPan,
r.pcAadhaar,
r.pcForm60Ack,
r.pcIdentificationType,
r.pcIdentificationNumber,
r.pcDob,
r.pcNationality,
r.pcBusiness,
r.pcAddressType,
r.pcAddress,
r.pcCity,
r.pcPin,
r.pcState,
r.pcCountry,
r.pcPrimaryStd,
r.pcPrimaryPhone,
r.pcPrimaryMobile,
'',
'',
'',
r.pcEmail,
''
]);
}
}
this.saveCsv(rows, 'SFT_003.csv');
this.toast(`SFT-003 exported (${this.accountRecords.length} accounts)`);
}
/** SFT-015 / SFT-016 — Pipe-delimited */
exportIntDivCsv(filter?: 'SFT-015' | 'SFT-016'): void {
const list = filter ? this.intDivRecords.filter((r) => r.sftCode === filter) : this.filteredIntDiv;
const fileTag = filter || 'BOTH';
const lines: string[] = [];
if (filter === 'SFT-015' || (!filter && list.some((r) => r.sftCode === 'SFT-015'))) {
// Dividend file header
const headers15 = [
'RSN',
'FY',
'Name',
'PAN',
'Aadhaar',
'Address',
'Pincode',
'Mobile',
'Email',
'Dividend',
''
];
const div15 = list.filter((r) => r.sftCode === 'SFT-015');
if (filter === 'SFT-015' || div15.length > 0) {
if (filter !== 'SFT-016') {
lines.push(headers15.join('|'));
for (const r of div15) {
lines.push(
[
r.rsn.toString(),
r.fy,
r.name,
r.pan,
r.aadhaar,
r.address || '',
r.pincode || '',
r.mobile,
r.email,
r.amount.toString(),
''
].join('|')
);
}
}
}
}
if (filter === 'SFT-016' || (!filter && list.some((r) => r.sftCode === 'SFT-016'))) {
const headers16 = [
'RSN',
'FY',
'Name',
'PAN',
'Aadhaar',
'Mobile',
'Email',
'Account Number',
'Type of Account',
'Interest',
''
];
const int16 = list.filter((r) => r.sftCode === 'SFT-016');
if (filter !== 'SFT-015' && int16.length > 0) {
if (lines.length > 0) lines.push('');
lines.push(headers16.join('|'));
for (const r of int16) {
lines.push(
[
r.rsn.toString(),
r.fy,
r.name,
r.pan,
r.aadhaar,
r.mobile,
r.email,
r.accountNumber || '',
r.typeOfAccount || '',
r.amount.toString(),
''
].join('|')
);
}
}
}
const filename = filter === 'SFT-015' ? 'SFT15.csv' : filter === 'SFT-016' ? 'SFT16.csv' : 'SFT15_SFT16.csv';
this.saveText(lines.join('\n') + '\n', filename, 'text/csv');
this.toast(`${fileTag} exported (${list.length} rows)`);
}
/** Bundle: all SFT files (one per SFT code) */
exportAll(): void {
setTimeout(() => this.exportPersonCsv('SFT-001'), 0);
setTimeout(() => this.exportPersonCsv('SFT-002'), 200);
setTimeout(() => this.exportAccountCsv(), 400);
setTimeout(() => this.exportPersonCsv('SFT-004'), 600);
setTimeout(() => this.exportIntDivCsv('SFT-015'), 800);
setTimeout(() => this.exportIntDivCsv('SFT-016'), 1000);
}
private saveCsv(rows: string[][], filename: string): void {
const escape = (v: string): string => {
const s = (v || '').replace(/"/g, '""');
if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s}"`;
return s;
};
const csv = rows.map((r) => r.map(escape).join(',')).join('\n') + '\n';
this.saveText(csv, filename, 'text/csv');
}
private saveText(content: string, filename: string, mime: string): void {
const blob = new Blob([content], { type: `${mime};charset=utf-8` });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
toast(msg: string): void {
this.toastMsg = msg;
this.toastVisible = true;
if (this.toastTimer) clearTimeout(this.toastTimer);
this.toastTimer = setTimeout(() => (this.toastVisible = false), 2800);
}
}
+22
View File
@@ -0,0 +1,22 @@
/**
* 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/.
*/
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { SftRoutingModule } from './sft-routing.module';
import { SftComponent } from './sft.component';
@NgModule({
imports: [
SharedModule,
SftRoutingModule,
SftComponent
]
})
export class SftModule {}
+36
View File
@@ -957,3 +957,39 @@ mat-card {
border-radius: 16px !important;
}
}
/* ─── CIC DQR Dialog Backdrop — ensure outside clicks register ─── */
.cic-dlg-backdrop {
background: rgb(15 23 42 / 45%);
pointer-events: auto;
}
/* ─── PSL Report Dialog Backdrop ─── */
.psl-dlg-backdrop {
background: rgb(15 23 42 / 45%);
pointer-events: auto;
}
/* ─── ALM Report Dialog Backdrop ─── */
.alm-dlg-backdrop {
background: rgb(15 23 42 / 45%);
pointer-events: auto;
}
/* ─── CIMS / XBRL Returns Dialog Backdrop ─── */
.cims-dlg-backdrop {
background: rgb(15 23 42 / 45%);
pointer-events: auto;
}
/* ─── CKYC Compliance Reports Dialog Backdrop ─── */
.ckyc-rpt-dlg-backdrop {
background: rgb(15 23 42 / 45%);
pointer-events: auto;
}
/* ─── Accounting Reports Dialog Backdrop ─── */
.acct-rpt-dlg-backdrop {
background: rgb(15 23 42 / 45%);
pointer-events: auto;
}