Merge branch 'dev-latest' into feature/advanceLoanProduct
This commit is contained in:
@@ -18,9 +18,16 @@
|
||||
<p class="dialog-sub">NDTL · CRR/SLR Computation Base · As per RBI Master Circular</p>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<!-- The close being reported, and under it the close the ratios are charged on. Two dates,
|
||||
because they are not the same day and the requirement belongs to the earlier one. -->
|
||||
<div class="as-of-box">
|
||||
<strong>{{ reportingDateStr }}</strong>
|
||||
<span>Reporting Fortnight (45-day lag)</span>
|
||||
<strong>{{ reportingDateStr || '—' }}</strong>
|
||||
<span>
|
||||
Fortnight close
|
||||
@if (baseDateStr) {
|
||||
· charged on {{ baseDateStr }}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<a
|
||||
class="rbi-circular-btn"
|
||||
@@ -42,31 +49,42 @@
|
||||
<div class="summary-row">
|
||||
<div class="summary-card sc-demand">
|
||||
<div class="sc-label">Total Demand Liabilities</div>
|
||||
<div class="sc-value">₹12,843<span class="cr">Cr</span></div>
|
||||
<div class="sc-change up">↑ 3.2% vs prev fortnight</div>
|
||||
<div class="sc-value">{{ crore(totalDemand) }}<span class="cr" *ngIf="totalDemand !== null">Cr</span></div>
|
||||
<div class="sc-sub">Payable on demand</div>
|
||||
</div>
|
||||
<div class="summary-card sc-time">
|
||||
<div class="sc-label">Total Time Liabilities</div>
|
||||
<div class="sc-value">₹64,618<span class="cr">Cr</span></div>
|
||||
<div class="sc-change up">↑ 1.8% vs prev fortnight</div>
|
||||
<div class="sc-value">{{ crore(totalTime) }}<span class="cr" *ngIf="totalTime !== null">Cr</span></div>
|
||||
<div class="sc-sub">Payable on maturity</div>
|
||||
</div>
|
||||
<div class="summary-card sc-asset">
|
||||
<div class="sc-label">Assets with Banking System</div>
|
||||
<div class="sc-value">₹8,240<span class="cr">Cr</span></div>
|
||||
<div class="sc-value">
|
||||
{{ crore(totalBankingAssets) }}<span class="cr" *ngIf="totalBankingAssets !== null">Cr</span>
|
||||
</div>
|
||||
<div class="sc-change dn">↓ Deducted from NDTL</div>
|
||||
</div>
|
||||
<div class="summary-card sc-ndtl">
|
||||
<div class="sc-label">Net Demand & Time Liabilities (NDTL)</div>
|
||||
<div class="sc-value">₹69,221<span class="cr">Cr</span></div>
|
||||
<div class="sc-value">{{ crore(ndtl) }}<span class="cr" *ngIf="ndtl !== null">Cr</span></div>
|
||||
<div class="sc-sub">After deducting banking assets</div>
|
||||
</div>
|
||||
<div class="summary-card sc-crr">
|
||||
<div class="sc-label">CRR Required ({{ crrRate }}%)</div>
|
||||
<div class="sc-value">₹{{ crrRequired | number: '1.0-0' }}<span class="cr">Cr</span></div>
|
||||
<div class="sc-sub">SLR Req: ₹{{ slrRequired | number: '1.0-0' }} Cr ({{ slrRate }}%)</div>
|
||||
<!-- The rate is named from the configuration, not fixed here: a card headed "CRR Required
|
||||
(3%)" while Admin holds 4% states a rate the bank is not on. -->
|
||||
<div class="sc-label">CRR Required{{ crrRate === null ? '' : ' (' + crrRate + '%)' }}</div>
|
||||
<div class="sc-value">{{ crore(crrRequired) }}<span class="cr" *ngIf="crrRequired !== null">Cr</span></div>
|
||||
<div class="sc-sub">SLR Req: {{ crore(slrRequired) }}{{ slrRate === null ? '' : ' Cr (' + slrRate + '%)' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Why the figures are not there, in the return's own words. A dashboard that simply shows
|
||||
dashes sends an administrator looking for a fault in the dashboard. -->
|
||||
<div class="unavailable-note" *ngIf="unavailable">
|
||||
<fa-icon icon="exclamation-triangle"></fa-icon>
|
||||
{{ unavailable }}
|
||||
</div>
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="tab-nav">
|
||||
<button class="tab-btn" [class.active]="activeTab === 'demand'" (click)="activeTab = 'demand'">
|
||||
@@ -123,14 +141,23 @@
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr class="empty-row">
|
||||
<td colspan="7">
|
||||
No accounts are mapped to this head, so the return reports nothing under it. Map them in Admin →
|
||||
RBI Reserve Configuration.
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3"><strong>Total Demand Liabilities (All 16 Items)</strong></td>
|
||||
<td class="right foot-demand">12,114.75</td>
|
||||
<td class="right foot-demand">12,843.50</td>
|
||||
<td class="right up">+728.75</td>
|
||||
<td colspan="3">
|
||||
<strong>Total Demand Liabilities ({{ demandLiabilities.length }} accounts)</strong>
|
||||
</td>
|
||||
<td class="right foot-demand">{{ totalOf(demandLiabilities, 'openingValue') }}</td>
|
||||
<td class="right foot-demand">{{ totalOf(demandLiabilities, 'currentValue') }}</td>
|
||||
<td class="right">{{ totalChange(demandLiabilities) }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
@@ -178,14 +205,23 @@
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr class="empty-row">
|
||||
<td colspan="7">
|
||||
No accounts are mapped to this head, so the return reports nothing under it. Map them in Admin →
|
||||
RBI Reserve Configuration.
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3"><strong>Total Time Liabilities (All 13 Items)</strong></td>
|
||||
<td class="right foot-time">62,613.10</td>
|
||||
<td class="right foot-time">64,618.00</td>
|
||||
<td class="right up">+2,004.90</td>
|
||||
<td colspan="3">
|
||||
<strong>Total Time Liabilities ({{ timeLiabilities.length }} accounts)</strong>
|
||||
</td>
|
||||
<td class="right foot-time">{{ totalOf(timeLiabilities, 'openingValue') }}</td>
|
||||
<td class="right foot-time">{{ totalOf(timeLiabilities, 'currentValue') }}</td>
|
||||
<td class="right">{{ totalChange(timeLiabilities) }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
@@ -227,6 +263,13 @@
|
||||
<span class="status-chip chip-active">{{ item.status }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr class="empty-row">
|
||||
<td colspan="7">
|
||||
No accounts are mapped to this head, so the return reports nothing under it. Map them in Admin →
|
||||
RBI Reserve Configuration.
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
@@ -234,9 +277,9 @@
|
||||
<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>
|
||||
<td class="right foot-asset">{{ totalOf(bankingAssets, 'openingValue') }}</td>
|
||||
<td class="right foot-asset">{{ totalOf(bankingAssets, 'currentValue') }}</td>
|
||||
<td class="right">{{ totalChange(bankingAssets) }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
@@ -284,14 +327,21 @@
|
||||
<span class="status-chip chip-active">{{ item.status }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr class="empty-row">
|
||||
<td colspan="7">
|
||||
No accounts are mapped to this head, so the return reports nothing under it. Map them in Admin →
|
||||
RBI Reserve Configuration.
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="4"><strong>Total Liquid Assets (SLR Eligible)</strong></td>
|
||||
<td class="right foot-slr">12,511.70</td>
|
||||
<td class="right foot-slr">13,238.80</td>
|
||||
<td class="right up">+727.10</td>
|
||||
<td class="right foot-slr">{{ totalOf(liquidAssets, 'openingValue') }}</td>
|
||||
<td class="right foot-slr">{{ totalOf(liquidAssets, 'currentValue') }}</td>
|
||||
<td class="right">{{ totalChange(liquidAssets) }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
||||
@@ -842,3 +842,30 @@ $slr-b: #bae6fd;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Why the figures are missing, in the return's own words — an administrator reading only dashes
|
||||
goes looking for a fault in this screen rather than in the configuration that actually caused it. */
|
||||
.unavailable-note {
|
||||
margin: 0 0 16px;
|
||||
padding: 10px 14px;
|
||||
background: #fffbeb;
|
||||
color: #92400e;
|
||||
border-radius: 8px;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.6;
|
||||
|
||||
fa-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* A head with nothing mapped. Said in the table rather than left as an empty body, and it names the
|
||||
screen where the mapping is made — an empty table otherwise reads as a fault in this one. */
|
||||
.empty-row td {
|
||||
padding: 22px 16px;
|
||||
text-align: center;
|
||||
color: #6b7280;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.6;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,30 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { CommonModule, DecimalPipe } from '@angular/common';
|
||||
import { MatDialogRef, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { RbiCircularsService } from '../../../rbi-circulars/rbi-circulars.service';
|
||||
|
||||
/** One account under a head, at the base date and the reporting date. */
|
||||
interface CrrSlrRow {
|
||||
sl: string;
|
||||
name: string;
|
||||
sub: string;
|
||||
code: string;
|
||||
opening: string;
|
||||
current: string;
|
||||
/** The figures themselves, kept so totals add from values rather than from formatted text. */
|
||||
openingValue: number | null;
|
||||
currentValue: number | null;
|
||||
change: string;
|
||||
changeDir: 'up' | 'dn' | 'flat';
|
||||
status: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-crr-slr-dialog',
|
||||
@@ -29,477 +46,228 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
FaIconComponent
|
||||
]
|
||||
})
|
||||
export class CrrSlrDialogComponent {
|
||||
export class CrrSlrDialogComponent implements OnInit {
|
||||
private dialogRef = inject(MatDialogRef<CrrSlrDialogComponent>);
|
||||
private rbi = inject(RbiCircularsService);
|
||||
|
||||
activeTab: 'demand' | 'time' | 'assets' | 'slr' = 'demand';
|
||||
|
||||
/** Reporting fortnight date — 45 days before current date */
|
||||
today = new Date();
|
||||
reportingDate: Date;
|
||||
reportingDateStr: string;
|
||||
/** The date the figures were struck at, as the return itself reports it. */
|
||||
reportingDateStr = '';
|
||||
|
||||
/** NDTL & Reserve computations */
|
||||
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.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
|
||||
/**
|
||||
* The close the ratios are charged on — the second preceding fortnight, not the one reported.
|
||||
*
|
||||
* Named on screen because the gap between the two is the whole of how the rules work: a bank
|
||||
* cannot hold a reserve against liabilities it does not yet know, so the requirement is fixed on
|
||||
* a position already struck and reported. A screen showing one date invites a reader to think the
|
||||
* requirement was worked out on the day it is shown against.
|
||||
*/
|
||||
baseDateStr = '';
|
||||
|
||||
constructor() {
|
||||
// CRR/SLR uses data from 45 days ago
|
||||
const lagDays = 45;
|
||||
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'
|
||||
loading = true;
|
||||
/** Set when the return could not be computed — and why, in the return's own words. */
|
||||
unavailable = '';
|
||||
|
||||
/**
|
||||
* The NDTL computation, read from the return rather than stated here.
|
||||
*
|
||||
* These were fixed numbers until now — a demonstration screen that showed ₹69,221 Cr whatever the
|
||||
* ledger held. The figures are the bank's own regulatory position, so a plausible-looking number
|
||||
* is worse than no number: it reads as a reported figure and cannot be told apart from one.
|
||||
*
|
||||
* Null until the return answers, and left null where it cannot compute. The template shows a dash
|
||||
* for a null, which says "not reported" where a zero would say "reported as nil".
|
||||
*/
|
||||
totalDemand: number | null = null;
|
||||
totalTime: number | null = null;
|
||||
totalBankingAssets: number | null = null;
|
||||
ndtl: number | null = null;
|
||||
crrRate: number | null = null;
|
||||
slrRate: number | null = null;
|
||||
crrRequired: number | null = null;
|
||||
slrRequired: number | null = null;
|
||||
totalLiquidAssets: number | null = null;
|
||||
slrActualPercent: number | null = null;
|
||||
slrSurplus: number | null = null;
|
||||
totalReserve: number | null = null;
|
||||
|
||||
/**
|
||||
* A figure for display, or a dash where the return did not carry one.
|
||||
*
|
||||
* A dash and a zero say different things — "not reported" against "reported as nil" — and on a
|
||||
* regulatory screen the difference decides whether anyone goes looking for the missing mapping.
|
||||
*/
|
||||
crore(value: number | null): string {
|
||||
if (value === null) {
|
||||
return '—';
|
||||
}
|
||||
return `₹${value.toLocaleString('en-IN', { maximumFractionDigits: 0 })}`;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// The return is reported in rupees; this screen reads in crore, as a dashboard tile does.
|
||||
const toCrore = (v: number | null | undefined): number | null =>
|
||||
v === null || v === undefined ? null : +(v / 1e7).toFixed(2);
|
||||
|
||||
// The statutory Form 1, not the working report. Form 1 is filed twice a month — at the 15th and
|
||||
// at the month end — and this screen should show the close the bank last filed, not a position
|
||||
// struck 45 days back by a lag rule that belongs to the other report. Those two disagree by
|
||||
// weeks, and the 45-day one lands mid-fortnight on a date the bank never reported.
|
||||
this.rbi.getForm1Statutory().subscribe({
|
||||
next: (data: any) => {
|
||||
this.loading = false;
|
||||
|
||||
// The later of the two closes the return reports — the one the bank most recently filed.
|
||||
const columns: string[] = data?.columns ?? [];
|
||||
const latest = columns.length - 1;
|
||||
this.reportingDateStr = columns[latest] ?? '';
|
||||
this.baseDateStr = data?.baseColumns?.[latest] ?? '';
|
||||
this.crrRate = data?.crrPercentage ?? null;
|
||||
this.slrRate = data?.slrPercentage ?? null;
|
||||
|
||||
if (!columns.length) {
|
||||
this.unavailable =
|
||||
data?.unmetRequirements?.[0] ?? 'Form 1 could not be computed. Check the CRR/SLR configuration in Admin.';
|
||||
return;
|
||||
}
|
||||
// The return computes but says something would make it wrong to file — a rate never set, a
|
||||
// base date the ledger cannot answer. Shown rather than swallowed: the reason is the thing
|
||||
// an administrator needs in order to fix it.
|
||||
this.unavailable = data?.unmetRequirements?.[0] ?? '';
|
||||
|
||||
// Part A's own lines, read by the Roman numeral each carries. Read from the return rather
|
||||
// than recomputed, so this screen and the filed form cannot come to disagree.
|
||||
const lines: any[] = data?.partA?.lines ?? [];
|
||||
const at = (startsWith: string): number | null => {
|
||||
const line = lines.find((l: any) => String(l?.label ?? '').startsWith(startsWith));
|
||||
return line?.values?.[latest] ?? null;
|
||||
};
|
||||
|
||||
this.totalDemand = toCrore(at('(A) Demand liabilities'));
|
||||
this.totalTime = toCrore(at('(B) Time liabilities'));
|
||||
this.totalBankingAssets = toCrore(at('TOTAL OF III'));
|
||||
// IV(b) is the base the ratios are actually charged on — the figure behind the requirement,
|
||||
// not the bank's liabilities on the day being reported.
|
||||
this.ndtl = toCrore(at('IV. b)'));
|
||||
this.crrRequired = toCrore(data?.partB?.required?.[latest]);
|
||||
this.slrRequired = toCrore(data?.partC?.required?.[latest]);
|
||||
this.totalLiquidAssets = toCrore(data?.partC?.maintained?.[latest]);
|
||||
|
||||
if (this.slrRequired !== null && this.totalLiquidAssets !== null) {
|
||||
this.slrSurplus = +(this.totalLiquidAssets - this.slrRequired).toFixed(2);
|
||||
}
|
||||
if (this.ndtl) {
|
||||
this.slrActualPercent =
|
||||
this.totalLiquidAssets === null ? null : +((this.totalLiquidAssets / this.ndtl) * 100).toFixed(2);
|
||||
}
|
||||
if (this.crrRequired !== null && this.slrRequired !== null) {
|
||||
this.totalReserve = +(this.crrRequired + this.slrRequired).toFixed(2);
|
||||
}
|
||||
|
||||
this.fillTabs(lines, latest);
|
||||
},
|
||||
error: () => {
|
||||
this.loading = false;
|
||||
this.unavailable = 'The CRR/SLR position could not be read. The return service did not respond.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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'
|
||||
/**
|
||||
* The four tabs, filled from the return's own lines.
|
||||
*
|
||||
* These were four fixed lists until now — 435 lines of invented accounts with invented balances,
|
||||
* which a reader had no way to tell apart from the bank's real position. They are now built from
|
||||
* the accounts the return actually read, so a tab is empty exactly when the bank has nothing
|
||||
* mapped under that head, and says so.
|
||||
*/
|
||||
demandLiabilities: CrrSlrRow[] = [];
|
||||
timeLiabilities: CrrSlrRow[] = [];
|
||||
bankingAssets: CrrSlrRow[] = [];
|
||||
liquidAssets: CrrSlrRow[] = [];
|
||||
/**
|
||||
* Turns Part A's lines into the rows a tab shows.
|
||||
*
|
||||
* The form reports each head at both of the closes it carries, so the earlier column is the
|
||||
* opening and the later one the current — the same pair these tables have always shown, now taken
|
||||
* from the form rather than invented. Headings and totals are skipped: a heading carries no
|
||||
* figure, and a total would be added a second time by the footer below.
|
||||
*/
|
||||
private rowsFrom(lines: any[], latest: number, match: (label: string) => boolean): CrrSlrRow[] {
|
||||
const rows: CrrSlrRow[] = [];
|
||||
for (const line of lines ?? []) {
|
||||
const label = String(line?.label ?? '');
|
||||
if (line?.heading || line?.total || !line?.values || !match(label)) {
|
||||
continue;
|
||||
}
|
||||
const opening = latest > 0 ? (line.values[latest - 1] ?? null) : null;
|
||||
const current = line.values[latest] ?? null;
|
||||
const change = opening === null || current === null ? null : current - opening;
|
||||
rows.push({
|
||||
sl: String(rows.length + 1).padStart(2, '0'),
|
||||
// The form's own wording, with its numbering trimmed off the front — the table has a column
|
||||
// of its own for that.
|
||||
name: label.replace(/^\s*\(?[a-zA-Z0-9]+\)?[.)]?\s*/, '').trim() || label.trim(),
|
||||
sub: line.configKey ? String(line.configKey).replace(/_/g, ' ') : 'Derived from the form',
|
||||
code: line.configKey ?? '—',
|
||||
opening: this.crore(opening),
|
||||
current: this.crore(current),
|
||||
openingValue: opening,
|
||||
currentValue: current,
|
||||
change: change === null ? '—' : `${change >= 0 ? '+' : ''}${this.crore(change).replace('₹', '')}`,
|
||||
changeDir: change === null ? 'flat' : change >= 0 ? 'up' : 'dn',
|
||||
// A head with no GL mapped reports nothing, and the form says so rather than reporting nil.
|
||||
status: line.configured === false ? 'Not mapped' : 'Active'
|
||||
});
|
||||
}
|
||||
];
|
||||
return rows;
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
];
|
||||
/**
|
||||
* A tab's column total, added from the rows on screen.
|
||||
*
|
||||
* Added from the rows rather than stated separately, so the foot of a table always agrees with
|
||||
* what is above it. The footers used to be fixed text and agreed with nothing.
|
||||
*
|
||||
* Summed from each row's own figure, not from the text it displays: a total parsed back out of
|
||||
* formatted strings would quietly lose the paise the display rounds away.
|
||||
*/
|
||||
totalOf(rows: CrrSlrRow[], column: 'openingValue' | 'currentValue'): string {
|
||||
const values = rows.map((r) => r[column]).filter((v): v is number => v !== null);
|
||||
return values.length ? this.crore(values.reduce((a, b) => a + b, 0)) : '—';
|
||||
}
|
||||
|
||||
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'
|
||||
/** The movement across a tab, between the two closes the form reports. */
|
||||
totalChange(rows: CrrSlrRow[]): string {
|
||||
const opening = rows.map((r) => r.openingValue).filter((v): v is number => v !== null);
|
||||
const current = rows.map((r) => r.currentValue).filter((v): v is number => v !== null);
|
||||
if (!opening.length || !current.length) {
|
||||
return '—';
|
||||
}
|
||||
];
|
||||
const change = current.reduce((a, b) => a + b, 0) - opening.reduce((a, b) => a + b, 0);
|
||||
return `${change >= 0 ? '+' : ''}${this.crore(change).replace('₹', '')}`;
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
];
|
||||
/** Fills the four tabs from one reading of the form. */
|
||||
private fillTabs(lines: any[], latest: number): void {
|
||||
const has = (label: string, ...words: string[]): boolean => {
|
||||
const l = label.toLowerCase();
|
||||
return words.some((w) => l.includes(w));
|
||||
};
|
||||
// Matched on how the form labels each head, not on a fixed list of account names: a bank that
|
||||
// maps a new account to a head gets it on the right tab without this screen being changed.
|
||||
this.demandLiabilities = this.rowsFrom(lines, latest, (l) => has(l, 'demand liabilit'));
|
||||
this.timeLiabilities = this.rowsFrom(lines, latest, (l) => has(l, 'time liabilit') && !has(l, 'demand and time'));
|
||||
// III — what the banking system owes this bank, which nets off the liability base.
|
||||
this.bankingAssets = this.rowsFrom(lines, latest, (l) =>
|
||||
has(l, 'current account balances with sbi', 'balances in all accounts', 'money at call', 'advances and any other')
|
||||
);
|
||||
// V, VI and VII — the cash, balances and securities the two ratios are met in.
|
||||
this.liquidAssets = this.rowsFrom(lines, latest, (l) =>
|
||||
has(l, 'cash on hand', 'reserve bank of india', 'co-operative bank', 'government securities')
|
||||
);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.dialogRef.close();
|
||||
|
||||
@@ -28,17 +28,34 @@
|
||||
<fa-icon icon="external-link-alt"></fa-icon> View
|
||||
</button>
|
||||
</div>
|
||||
<!-- Read from the return, not stated here. Both the rates and the amounts were fixed text
|
||||
until now, so the card reported ₹2,077 Cr at 3% whatever the ledger held and whatever
|
||||
rate Admin was on. -->
|
||||
<div class="card-metrics">
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">CRR Required (3%)</span>
|
||||
<span class="metric-value">₹2,077 Cr</span>
|
||||
<span class="metric-label"
|
||||
>CRR Required{{ crrSlr.crrRate === null ? '' : ' (' + crrSlr.crrRate + '%)' }}</span
|
||||
>
|
||||
<span class="metric-value">{{ crrSlr.crrRequired }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">SLR Required (18%)</span>
|
||||
<span class="metric-value">₹12,460 Cr</span>
|
||||
<span class="metric-label"
|
||||
>SLR Required{{ crrSlr.slrRate === null ? '' : ' (' + crrSlr.slrRate + '%)' }}</span
|
||||
>
|
||||
<span class="metric-value">{{ crrSlr.slrRequired }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mini-sub"><span class="tag-success">Within Limits</span></div>
|
||||
<!-- "Within Limits" is a compliance statement, so it is made only where the return actually
|
||||
says so. Where it cannot compute, the card says that instead of passing the bank. -->
|
||||
<div class="mini-sub">
|
||||
@if (crrSlr.status === 'ok') {
|
||||
<span class="tag-success">Within Limits</span>
|
||||
} @else if (crrSlr.status === 'short') {
|
||||
<span class="tag-danger">Shortfall</span>
|
||||
} @else {
|
||||
<span class="tag-muted">Not computed</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CRAR -->
|
||||
|
||||
@@ -444,6 +444,17 @@ $purple: #7c3aed;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* A figure that could not be computed. Deliberately not green: a tile that cannot read the ledger
|
||||
has not found the bank compliant, and a success tag on an unknown position is a false all-clear. */
|
||||
.tag-muted {
|
||||
background: #f3f4f6;
|
||||
color: #4b5563;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ─── Compact Detail Cards ─── */
|
||||
.dash-card.compact {
|
||||
padding: 12px 16px;
|
||||
|
||||
@@ -19,6 +19,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { AsyncPipe, DatePipe } from '@angular/common';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { RbiCircularsService } from '../../rbi-circulars/rbi-circulars.service';
|
||||
/**
|
||||
* Dashboard component.
|
||||
*/
|
||||
@@ -40,9 +41,29 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
export class DashboardComponent implements OnInit {
|
||||
private router = inject(Router);
|
||||
private dialog = inject(MatDialog);
|
||||
private rbi = inject(RbiCircularsService);
|
||||
|
||||
today = new Date();
|
||||
|
||||
/**
|
||||
* The CRR/SLR tile, read from the return.
|
||||
*
|
||||
* The amounts and the rates were both fixed text on the template until now, so the tile reported
|
||||
* ₹2,077 Cr at 3% whatever the ledger held and whatever rate Admin was on. A dashboard figure is
|
||||
* read as the bank's position, and one that cannot move is worse than none: nothing on the tile
|
||||
* says it is a placeholder.
|
||||
*
|
||||
* Dashes until the return answers. 'status' drives the compliance tag, which is only claimed
|
||||
* where the return states it.
|
||||
*/
|
||||
crrSlr: {
|
||||
crrRate: number | null;
|
||||
slrRate: number | null;
|
||||
crrRequired: string;
|
||||
slrRequired: string;
|
||||
status: 'ok' | 'short' | 'unknown';
|
||||
} = { crrRate: null, slrRate: null, crrRequired: '—', slrRequired: '—', status: 'unknown' };
|
||||
|
||||
/** Array of all user activities */
|
||||
userActivity: string[];
|
||||
/** Array of most recent user activities */
|
||||
@@ -67,6 +88,45 @@ export class DashboardComponent implements OnInit {
|
||||
this.recentActivities = this.getRecentActivities();
|
||||
this.frequentActivities = this.getFrequentActivities();
|
||||
this.setFilteredActivities();
|
||||
this.loadCrrSlr();
|
||||
}
|
||||
|
||||
/** Fills the CRR/SLR tile from the return, leaving it dashed where the return cannot compute. */
|
||||
private loadCrrSlr(): void {
|
||||
const crore = (v: number | null | undefined): string =>
|
||||
v === null || v === undefined ? '—' : `₹${Math.round(v / 1e7).toLocaleString('en-IN')} Cr`;
|
||||
|
||||
// The statutory Form 1, which is the return this bank files — twice a month, at the 15th and at
|
||||
// the month end. The working report's 45-day lag lands mid-fortnight, on a date the bank never
|
||||
// reported, so a tile built on it shows a position that appears on no filing.
|
||||
this.rbi.getForm1Statutory().subscribe({
|
||||
next: (data: any) => {
|
||||
const latest = (data?.columns?.length ?? 0) - 1;
|
||||
if (latest < 0) {
|
||||
return;
|
||||
}
|
||||
this.crrSlr.crrRate = data?.crrPercentage ?? null;
|
||||
this.crrSlr.slrRate = data?.slrPercentage ?? null;
|
||||
this.crrSlr.crrRequired = crore(data?.partB?.required?.[latest]);
|
||||
this.crrSlr.slrRequired = crore(data?.partC?.required?.[latest]);
|
||||
|
||||
// Compliant only where BOTH ratios are met: the tile carries one tag for the pair, and a
|
||||
// bank short on SLR is not within limits because its cash reserve happens to be ample.
|
||||
const crrHeld = data?.partB?.maintained?.[latest];
|
||||
const crrNeed = data?.partB?.required?.[latest];
|
||||
const slrHeld = data?.partC?.maintained?.[latest];
|
||||
const slrNeed = data?.partC?.required?.[latest];
|
||||
if ([
|
||||
crrHeld,
|
||||
crrNeed,
|
||||
slrHeld,
|
||||
slrNeed
|
||||
].every((v) => v !== null && v !== undefined)) {
|
||||
this.crrSlr.status = crrHeld >= crrNeed && slrHeld >= slrNeed ? 'ok' : 'short';
|
||||
}
|
||||
},
|
||||
error: () => undefined
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user