oss file updated, profitl loss as per the day month and also with date select
This commit is contained in:
@@ -45,6 +45,12 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Income / Occupation Limit Column -->
|
||||
<ng-container matColumnDef="incomeLimit">
|
||||
<th mat-header-cell *matHeaderCellDef>Income / Occupation Limit</th>
|
||||
<td mat-cell *matCellDef="let element" class="income-limit-cell">{{ getIncomeLimitLabel(element) }}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Reason Column -->
|
||||
<ng-container matColumnDef="reason">
|
||||
<th mat-header-cell *matHeaderCellDef>Rule Reason / Criteria</th>
|
||||
|
||||
@@ -114,6 +114,15 @@
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.income-limit-cell {
|
||||
color: #334155;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
white-space: normal;
|
||||
max-width: 200px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
/* Badges for Risk Category */
|
||||
.risk-badge {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -42,6 +42,7 @@ export class AmlRulesComponent implements OnInit {
|
||||
'orderPosition',
|
||||
'ruleName',
|
||||
'riskCategory',
|
||||
'incomeLimit',
|
||||
'reason',
|
||||
'amlCheckStatus',
|
||||
'bannedEntityMatch'
|
||||
@@ -84,4 +85,39 @@ export class AmlRulesComponent implements OnInit {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/** Formats whichever income/turnover/occupation criterion drives this rule's risk mark. */
|
||||
getIncomeLimitLabel(rule: any): string {
|
||||
const inr = (n: number) => '₹' + Number(n).toLocaleString('en-IN');
|
||||
if (rule.minMonthlyIncome != null && rule.minMonthlyIncome > 0) {
|
||||
return rule.maxMonthlyIncome != null && rule.maxMonthlyIncome > 0
|
||||
? `Monthly Income ${inr(rule.minMonthlyIncome)} - ${inr(rule.maxMonthlyIncome)}`
|
||||
: `Monthly Income > ${inr(rule.minMonthlyIncome)}`;
|
||||
}
|
||||
if (rule.minAnnualIncome != null && rule.minAnnualIncome > 0) {
|
||||
return rule.maxAnnualIncome != null && rule.maxAnnualIncome > 0
|
||||
? `Annual Income ${inr(rule.minAnnualIncome)} - ${inr(rule.maxAnnualIncome)}`
|
||||
: `Annual Income > ${inr(rule.minAnnualIncome)}`;
|
||||
}
|
||||
if (rule.minAnnualTurnover != null && rule.minAnnualTurnover > 0) {
|
||||
return rule.maxAnnualTurnover != null && rule.maxAnnualTurnover > 0
|
||||
? `Annual Turnover ${inr(rule.minAnnualTurnover)} - ${inr(rule.maxAnnualTurnover)}`
|
||||
: `Annual Turnover > ${inr(rule.minAnnualTurnover)}`;
|
||||
}
|
||||
if (rule.occupationName) return `Occupation: ${rule.occupationName}`;
|
||||
if (rule.occupationCategoryName) return `Occupation Category: ${rule.occupationCategoryName}`;
|
||||
if (rule.txnThresholdAmountMediumRisk != null || rule.txnThresholdAmountHighRisk != null) {
|
||||
const parts: string[] = [];
|
||||
if (rule.txnThresholdAmountMediumRisk != null) parts.push(`${inr(rule.txnThresholdAmountMediumRisk)} (Medium Risk)`);
|
||||
if (rule.txnThresholdAmountHighRisk != null) parts.push(`${inr(rule.txnThresholdAmountHighRisk)} (High Risk)`);
|
||||
return `Txn Amount ≥ ${parts.join(' / ')}`;
|
||||
}
|
||||
if (rule.txnThresholdAmount != null) {
|
||||
const amt = inr(rule.txnThresholdAmount);
|
||||
if (rule.txnRuleType === 'THRESHOLD') return `Txn Amount ≥ ${amt}`;
|
||||
if (rule.txnWindowDays != null) return `${amt} within ${rule.txnWindowDays} days`;
|
||||
return `Txn Amount ≥ ${amt}`;
|
||||
}
|
||||
return 'Behavioral / Transaction Pattern';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shapes common to every RBI CIMS off-site surveillance return (OSS 1..7).
|
||||
* Kept free of any ExcelJS import so data files stay dependency-light.
|
||||
*/
|
||||
|
||||
/** One labelled row of a return. */
|
||||
export interface ReturnLine {
|
||||
label: string;
|
||||
/** 0 = numbered head, 1 = roman sub-item, 2 = a/b/c leaf. Drives indentation. */
|
||||
level: number;
|
||||
/** null renders as an empty cell, never as 0 — RBI treats blank and zero differently. */
|
||||
value: number | null;
|
||||
/** Derived/aggregate row — shaded and bold, like the blue cells in the workbook. */
|
||||
total?: boolean;
|
||||
/** Row is a headcount (depositors / borrowers / employees), not a rupee amount. */
|
||||
count?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* One row of a multi-column statement. Returns from OSS 3 onwards are matrices
|
||||
* (risk category x asset class) rather than the single-value lists of OSS 1/2.
|
||||
*/
|
||||
export interface MatrixRow {
|
||||
label: string;
|
||||
/** 0 = numbered head, 1..n = progressively indented sub-items. */
|
||||
level: number;
|
||||
/** One entry per data column; null renders blank, never 0. */
|
||||
values: (number | null)[];
|
||||
/** Derived/aggregate row — shaded and bold. */
|
||||
total?: boolean;
|
||||
/** Caption-only row that spans the table with no figures of its own. */
|
||||
header?: boolean;
|
||||
/** Render values as percentages rather than amounts. */
|
||||
percent?: boolean;
|
||||
}
|
||||
|
||||
/** A labelled statement made of a header band plus matrix rows. */
|
||||
export interface MatrixTable {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
/** Column captions excluding the leading label column. */
|
||||
columns: string[];
|
||||
rows: MatrixRow[];
|
||||
notes?: string[];
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
/** A signatory block as it appears on the Signatory sheet of every OSS return. */
|
||||
export interface ReturnSignatory {
|
||||
heading: string;
|
||||
signature: string;
|
||||
name: string;
|
||||
designation: string;
|
||||
email: string;
|
||||
telephoneOffice: string;
|
||||
telephoneResidence: string;
|
||||
place: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
/** A `label -> value` row on the General Information sheet. */
|
||||
export interface ReturnGeneralInfoRow {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** Field order shared by the Signatory sheet of every OSS return. */
|
||||
export const SIGNATORY_FIELDS: { key: keyof ReturnSignatory; caption: string }[] = [
|
||||
{ key: 'signature', caption: 'Signature' },
|
||||
{ key: 'name', caption: 'Name' },
|
||||
{ key: 'designation', caption: 'Designation' },
|
||||
{ key: 'email', caption: 'e-mail ID' },
|
||||
{ key: 'telephoneOffice', caption: 'Tele. No. (O)' },
|
||||
{ key: 'telephoneResidence', caption: 'Tele. No. (R)' },
|
||||
{ key: 'place', caption: 'Place' },
|
||||
{ key: 'date', caption: 'Date' }
|
||||
];
|
||||
|
||||
/**
|
||||
* Identity of the reporting bank, shared by every OSS return so the General
|
||||
* Information sheets can never drift apart between OSS 1 and OSS 2.
|
||||
*/
|
||||
export const OSS_BANK = {
|
||||
name: 'CBSNEXORA BANK LTD., HEAD OFFICE',
|
||||
bankCode: '91007',
|
||||
roName: 'MUMBAI',
|
||||
institutionType: 'Urban Cooperative Banks',
|
||||
reportingFrequency: 'Quarterly',
|
||||
quarterEndDate: '30-Jun-2026',
|
||||
auditFlag: 'Un-Audited',
|
||||
dateOfAudit: '',
|
||||
returnVersion: 'V1.0',
|
||||
startDate: '01-Apr-2026',
|
||||
/** OSS 3 prints dates as DD-MM-YYYY rather than DD-Mon-YYYY. */
|
||||
startDateNumeric: '01-04-2026',
|
||||
quarterEndDateNumeric: '30-06-2026'
|
||||
};
|
||||
|
||||
/**
|
||||
* Signatories for the demo bank. The `.example` domain is reserved for
|
||||
* documentation, so these addresses can never reach a real inbox.
|
||||
*/
|
||||
export const OSS_SIGNATORIES: ReturnSignatory[] = [
|
||||
{
|
||||
heading: 'Authorised Reporting Official',
|
||||
signature: 'ROHAN DESHPANDE',
|
||||
name: 'ROHAN DESHPANDE',
|
||||
designation: 'MANAGER — REGULATORY REPORTING',
|
||||
email: 'rohan.deshpande@cbsnexorabank.example',
|
||||
telephoneOffice: '022-40015520',
|
||||
telephoneResidence: '022-40015588',
|
||||
place: 'MUMBAI',
|
||||
date: '14/07/2026'
|
||||
},
|
||||
{
|
||||
heading: 'Countersigned By',
|
||||
signature: 'ANJALI MENON',
|
||||
name: 'ANJALI MENON',
|
||||
designation: 'CHIEF EXECUTIVE OFFICER',
|
||||
email: 'ceo@cbsnexorabank.example',
|
||||
telephoneOffice: '022-40015500',
|
||||
telephoneResidence: '022-40015501',
|
||||
place: 'MUMBAI',
|
||||
date: '14/07/2026'
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,417 @@
|
||||
/**
|
||||
* 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 {
|
||||
/* Mirrors the RBI CIMS workbook palette so the page and the export read alike. */
|
||||
--o-navy: #002f5f;
|
||||
--o-blue: #1d4ed8;
|
||||
--o-blue-l: #eff6ff;
|
||||
--o-derived: #cfe2f8;
|
||||
--o-green: #15803d;
|
||||
--o-green-l: #dcfce7;
|
||||
--o-amber-l: #ffc;
|
||||
--o-amber-d: #92400e;
|
||||
--o-red: #c0392b;
|
||||
--o-red-l: #fdecea;
|
||||
--o-g50: #f8fafc;
|
||||
--o-g100: #f1f5f9;
|
||||
--o-g200: #e2e8f0;
|
||||
--o-g300: #cbd5e1;
|
||||
--o-g500: #64748b;
|
||||
--o-g700: #334155;
|
||||
--o-g900: #0f172a;
|
||||
|
||||
display: block;
|
||||
font-family:
|
||||
'DM Sans',
|
||||
system-ui,
|
||||
-apple-system,
|
||||
sans-serif;
|
||||
background: var(--o-g50);
|
||||
color: var(--o-g900);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.oss-root {
|
||||
padding: 18px 22px;
|
||||
max-width: 1500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
.oss-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.oss-head-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.oss-head-ic {
|
||||
font-size: 28px;
|
||||
color: var(--o-navy);
|
||||
}
|
||||
|
||||
.oss-head-t {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.oss-head-s {
|
||||
font-size: 12px;
|
||||
color: var(--o-g500);
|
||||
}
|
||||
|
||||
.oss-head-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.oss-audit-pill {
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
background: var(--o-amber-l);
|
||||
color: var(--o-amber-d);
|
||||
border: 1px solid #fcd34d;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* BUTTONS */
|
||||
.oss-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 9px 16px;
|
||||
border: 1px solid var(--o-g300);
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--o-g700);
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.oss-btn-xls {
|
||||
background: #16a34a;
|
||||
border-color: #16a34a;
|
||||
color: #fff;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #15803d;
|
||||
border-color: #15803d;
|
||||
}
|
||||
}
|
||||
|
||||
/* BALANCE CHECK */
|
||||
.oss-balance {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 10px;
|
||||
background: var(--o-green-l);
|
||||
border: 1px solid #86efac;
|
||||
color: #14532d;
|
||||
font-size: 13px;
|
||||
margin-bottom: 14px;
|
||||
|
||||
&--bad {
|
||||
background: var(--o-red-l);
|
||||
border-color: #fca5a5;
|
||||
color: #7f1d1d;
|
||||
}
|
||||
}
|
||||
|
||||
/* TABS */
|
||||
.oss-tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--o-g200);
|
||||
border-radius: 10px;
|
||||
padding: 4px;
|
||||
margin-bottom: 16px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.oss-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 9px 14px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--o-g500);
|
||||
cursor: pointer;
|
||||
border-radius: 7px;
|
||||
white-space: nowrap;
|
||||
transition: all 0.15s;
|
||||
font-family: inherit;
|
||||
|
||||
&:hover {
|
||||
color: var(--o-g900);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--o-navy);
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 6px rgb(0 47 95 / 25%);
|
||||
}
|
||||
}
|
||||
|
||||
.oss-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* Full-width banner, matching the dark blue header band in the workbook. */
|
||||
.oss-sheet-banner {
|
||||
background: var(--o-navy);
|
||||
color: #fff;
|
||||
padding: 10px 18px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* TABLES */
|
||||
.oss-tbl-wrap {
|
||||
background: #fff;
|
||||
border: 1px solid var(--o-g200);
|
||||
border-radius: 10px;
|
||||
overflow: auto;
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
|
||||
}
|
||||
|
||||
.oss-tbl-h {
|
||||
background: var(--o-blue-l);
|
||||
color: var(--o-navy);
|
||||
padding: 10px 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
border-bottom: 1px solid var(--o-g200);
|
||||
}
|
||||
|
||||
.oss-tbl {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding: 9px 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--o-g700);
|
||||
background: var(--o-g100);
|
||||
border-bottom: 1px solid var(--o-g200);
|
||||
border-right: 1px solid var(--o-g200);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--o-g100);
|
||||
border-right: 1px solid var(--o-g100);
|
||||
font-size: 13px;
|
||||
color: var(--o-g700);
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
tbody tr:hover td {
|
||||
background: var(--o-g50);
|
||||
}
|
||||
}
|
||||
|
||||
/* Key/value layout used by General Information and Signatory. */
|
||||
.oss-tbl--kv {
|
||||
th {
|
||||
width: 40%;
|
||||
background: #d9d9d9;
|
||||
color: var(--o-g900);
|
||||
}
|
||||
|
||||
td.filled {
|
||||
background: var(--o-derived);
|
||||
}
|
||||
|
||||
td.audit {
|
||||
background: var(--o-amber-l);
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
/* Derived/aggregate rows — the blue cells in the original sheet. */
|
||||
.total-row td {
|
||||
font-weight: 700;
|
||||
color: var(--o-g900);
|
||||
}
|
||||
|
||||
td.derived {
|
||||
background: var(--o-derived);
|
||||
font-weight: 700;
|
||||
color: var(--o-navy);
|
||||
}
|
||||
|
||||
/* Indentation matching the return's numbered / roman / lettered hierarchy. */
|
||||
.lvl-0 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lvl-1 {
|
||||
padding-left: 30px !important;
|
||||
}
|
||||
|
||||
.lvl-2 {
|
||||
padding-left: 52px !important;
|
||||
}
|
||||
|
||||
.lvl-3 {
|
||||
padding-left: 74px !important;
|
||||
}
|
||||
|
||||
/* Caption-only row spanning a statement (e.g. "PRIORITY SECTOR"). */
|
||||
.section-row td {
|
||||
background: var(--o-g100);
|
||||
font-weight: 700;
|
||||
color: var(--o-g900);
|
||||
}
|
||||
|
||||
/* Wide matrices keep their label column readable while the figures scroll. */
|
||||
.oss-col-label {
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
.oss-tbl-unit {
|
||||
float: right;
|
||||
font-weight: 600;
|
||||
color: var(--o-g500);
|
||||
}
|
||||
|
||||
.oss-tbl-sub {
|
||||
padding: 8px 16px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--o-g700);
|
||||
background: var(--o-g100);
|
||||
border-bottom: 1px solid var(--o-g200);
|
||||
}
|
||||
|
||||
.ta-c {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ta-r {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* NOTES */
|
||||
.oss-notes {
|
||||
background: var(--o-g100);
|
||||
border: 1px solid var(--o-g200);
|
||||
border-radius: 8px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
.oss-note-line {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--o-g700);
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.oss-foot-note {
|
||||
margin: -6px 2px 0;
|
||||
font-size: 11px;
|
||||
color: var(--o-g500);
|
||||
}
|
||||
|
||||
/* Tie-up status pills (cross-return reconciliation checks). */
|
||||
.report-pill {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
|
||||
&.report-ok {
|
||||
background: var(--o-green-l);
|
||||
color: var(--o-green);
|
||||
}
|
||||
|
||||
&.report-bad {
|
||||
background: var(--o-red-l);
|
||||
color: var(--o-red);
|
||||
}
|
||||
}
|
||||
|
||||
.oss-sign-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* TOAST */
|
||||
.oss-toast {
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(20px);
|
||||
background: var(--o-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 <= 1000px) {
|
||||
.oss-sign-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* 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 * as ExcelJS from 'exceljs';
|
||||
import {
|
||||
MatrixTable,
|
||||
ReturnGeneralInfoRow,
|
||||
ReturnLine,
|
||||
ReturnSignatory,
|
||||
SIGNATORY_FIELDS
|
||||
} from './oss-return.model';
|
||||
|
||||
/** Palette lifted from the RBI CIMS workbooks so exports look like the originals. */
|
||||
export const XL = {
|
||||
banner: 'FF002F5F',
|
||||
bannerText: 'FFFFFFFF',
|
||||
headerGrey: 'FFC0C0C0',
|
||||
derivedBlue: 'FF9CC3F5',
|
||||
auditYellow: 'FFFFFFCC',
|
||||
keyCell: 'FFD9D9D9',
|
||||
border: 'FF000000'
|
||||
};
|
||||
|
||||
const AMOUNT_FMT = '#,##0.00';
|
||||
const COUNT_FMT = '#,##0';
|
||||
|
||||
export function solidFill(argb: string): ExcelJS.Fill {
|
||||
return { type: 'pattern', pattern: 'solid', fgColor: { argb } };
|
||||
}
|
||||
|
||||
/** Dark-blue full-width banner that heads every sheet in the RBI templates. */
|
||||
export function addBanner(sheet: ExcelJS.Worksheet, title: string, lastCol: string): void {
|
||||
const row = sheet.addRow([title]);
|
||||
sheet.mergeCells(`A${row.number}:${lastCol}${row.number}`);
|
||||
row.height = 26;
|
||||
const cell = row.getCell(1);
|
||||
cell.fill = solidFill(XL.banner);
|
||||
cell.font = { bold: true, size: 13, color: { argb: XL.bannerText } };
|
||||
cell.alignment = { vertical: 'middle', horizontal: 'center' };
|
||||
sheet.addRow([]);
|
||||
}
|
||||
|
||||
export function boxAllCells(
|
||||
sheet: ExcelJS.Worksheet,
|
||||
from: number,
|
||||
to: number,
|
||||
lastColIndex: number
|
||||
): void {
|
||||
for (let r = from; r <= to; r++) {
|
||||
for (let c = 1; c <= lastColIndex; c++) {
|
||||
sheet.getRow(r).getCell(c).border = {
|
||||
top: { style: 'thin', color: { argb: XL.border } },
|
||||
left: { style: 'thin', color: { argb: XL.border } },
|
||||
bottom: { style: 'thin', color: { argb: XL.border } },
|
||||
right: { style: 'thin', color: { argb: XL.border } }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addSectionHeader(sheet: ExcelJS.Worksheet, label: string, unitLabel?: string): void {
|
||||
const row = sheet.addRow([label, unitLabel ?? '']);
|
||||
row.font = { bold: true };
|
||||
row.eachCell((cell) => (cell.fill = solidFill(XL.headerGrey)));
|
||||
row.getCell(2).alignment = { horizontal: 'right' };
|
||||
}
|
||||
|
||||
/** Renders a labelled block; indents by level and shades derived totals. */
|
||||
export function addLineRows(sheet: ExcelJS.Worksheet, lines: ReturnLine[]): void {
|
||||
for (const line of lines) {
|
||||
const row = sheet.addRow([line.label, line.value]);
|
||||
const labelCell = row.getCell(1);
|
||||
const valueCell = row.getCell(2);
|
||||
|
||||
labelCell.alignment = { wrapText: true, vertical: 'middle', indent: line.level };
|
||||
if (line.level === 0) {
|
||||
labelCell.font = { bold: true };
|
||||
}
|
||||
valueCell.numFmt = line.count ? COUNT_FMT : AMOUNT_FMT;
|
||||
valueCell.alignment = { horizontal: 'right' };
|
||||
if (line.total) {
|
||||
valueCell.fill = solidFill(XL.derivedBlue);
|
||||
valueCell.font = { bold: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a multi-column statement: title band, column header row, then the matrix
|
||||
* rows. Returns the row number after the block so callers can keep stacking.
|
||||
*/
|
||||
export function addMatrixTable(sheet: ExcelJS.Worksheet, table: MatrixTable): void {
|
||||
const colCount = table.columns.length + 1;
|
||||
const start = sheet.rowCount + 1;
|
||||
|
||||
const titleRow = sheet.addRow([table.title, ...(table.unit ? [table.unit] : [])]);
|
||||
if (!table.unit) {
|
||||
sheet.mergeCells(titleRow.number, 1, titleRow.number, colCount);
|
||||
}
|
||||
titleRow.font = { bold: true };
|
||||
for (let c = 1; c <= colCount; c++) {
|
||||
titleRow.getCell(c).fill = solidFill(XL.headerGrey);
|
||||
}
|
||||
|
||||
if (table.subtitle) {
|
||||
const sub = sheet.addRow([table.subtitle]);
|
||||
sheet.mergeCells(sub.number, 1, sub.number, colCount);
|
||||
sub.font = { bold: true, size: 10 };
|
||||
sub.alignment = { wrapText: true };
|
||||
sub.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
}
|
||||
|
||||
const head = sheet.addRow(['Particulars', ...table.columns]);
|
||||
head.font = { bold: true };
|
||||
head.alignment = { wrapText: true, horizontal: 'center', vertical: 'middle' };
|
||||
head.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
|
||||
for (const row of table.rows) {
|
||||
const excelRow = sheet.addRow([row.label, ...row.values]);
|
||||
const labelCell = excelRow.getCell(1);
|
||||
labelCell.alignment = { wrapText: true, vertical: 'middle', indent: row.level };
|
||||
if (row.level === 0 || row.header) {
|
||||
labelCell.font = { bold: true };
|
||||
}
|
||||
if (row.header) {
|
||||
labelCell.fill = solidFill(XL.headerGrey);
|
||||
continue;
|
||||
}
|
||||
for (let c = 2; c <= colCount; c++) {
|
||||
const cell = excelRow.getCell(c);
|
||||
cell.numFmt = row.percent ? '0.00"%"' : AMOUNT_FMT;
|
||||
cell.alignment = { horizontal: 'right' };
|
||||
if (row.total) {
|
||||
cell.fill = solidFill(XL.derivedBlue);
|
||||
cell.font = { bold: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
boxAllCells(sheet, start, sheet.rowCount, colCount);
|
||||
|
||||
for (const note of table.notes ?? []) {
|
||||
const noteRow = sheet.addRow([note]);
|
||||
sheet.mergeCells(noteRow.number, 1, noteRow.number, colCount);
|
||||
noteRow.font = { bold: true, size: 9 };
|
||||
noteRow.alignment = { wrapText: true };
|
||||
noteRow.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
}
|
||||
sheet.addRow([]);
|
||||
}
|
||||
|
||||
/** The General Information sheet is identical in shape across every OSS return. */
|
||||
export function buildGeneralInfoSheet(wb: ExcelJS.Workbook, rows: ReturnGeneralInfoRow[]): void {
|
||||
const sheet = wb.addWorksheet('General Information');
|
||||
sheet.columns = [
|
||||
{ width: 26 },
|
||||
{ width: 58 }
|
||||
];
|
||||
addBanner(sheet, 'General Information', 'B');
|
||||
|
||||
const first = sheet.rowCount + 1;
|
||||
for (const item of rows) {
|
||||
const row = sheet.addRow([item.label, item.value]);
|
||||
row.getCell(1).fill = solidFill(XL.keyCell);
|
||||
// The workbook tints the audit flag yellow and leaves unfilled cells grey.
|
||||
const fill = item.label === 'Audit Flag' ? XL.auditYellow : item.value ? XL.derivedBlue : XL.headerGrey;
|
||||
row.getCell(2).fill = solidFill(fill);
|
||||
}
|
||||
boxAllCells(sheet, first, sheet.rowCount, 2);
|
||||
}
|
||||
|
||||
/** The Signatory sheet is likewise identical across returns. */
|
||||
export function buildSignatorySheet(
|
||||
wb: ExcelJS.Workbook,
|
||||
signatories: ReturnSignatory[],
|
||||
sheetName = 'Signatory'
|
||||
): void {
|
||||
const sheet = wb.addWorksheet(sheetName);
|
||||
sheet.columns = [
|
||||
{ width: 44 },
|
||||
{ width: 24 }
|
||||
];
|
||||
addBanner(sheet, sheetName, 'B');
|
||||
|
||||
for (const person of signatories) {
|
||||
const start = sheet.rowCount + 1;
|
||||
const head = sheet.addRow([person.heading, '']);
|
||||
head.font = { bold: true };
|
||||
head.alignment = { horizontal: 'center' };
|
||||
head.getCell(1).fill = solidFill(XL.keyCell);
|
||||
|
||||
for (const f of SIGNATORY_FIELDS) {
|
||||
const row = sheet.addRow([person[f.key], f.caption]);
|
||||
row.getCell(2).font = { bold: true };
|
||||
row.getCell(2).alignment = { horizontal: 'center' };
|
||||
row.getCell(2).fill = solidFill(XL.keyCell);
|
||||
}
|
||||
boxAllCells(sheet, start, sheet.rowCount, 2);
|
||||
sheet.addRow([]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Serialises the workbook and triggers a browser download. */
|
||||
export async function downloadWorkbook(wb: ExcelJS.Workbook, filename: string): Promise<void> {
|
||||
const buffer = await wb.xlsx.writeBuffer();
|
||||
const blob = new Blob([buffer], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export function newWorkbook(): ExcelJS.Workbook {
|
||||
const wb = new ExcelJS.Workbook();
|
||||
wb.creator = 'CBSNexora';
|
||||
wb.created = new Date();
|
||||
return wb;
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* OSS 1 — Return on Assets and Liabilities (RAL), return code R070.
|
||||
*
|
||||
* Mirrors the RBI CIMS "Form RAL_CIMS Template1 OSS 1" workbook sheet-for-sheet:
|
||||
* General Information / Assets & Liabilities / Annexure 1 / Annexure 2 / Signatory
|
||||
*
|
||||
* All monetary values are Rs in Thousands, exactly as the return prescribes.
|
||||
* A `null` value means the source return leaves that cell blank — blank and zero
|
||||
* are different to RBI, so they must not be collapsed into one another.
|
||||
*/
|
||||
|
||||
import { OSS_BANK, OSS_SIGNATORIES, ReturnGeneralInfoRow, ReturnLine, ReturnSignatory } from '../oss-return.model';
|
||||
|
||||
/** One labelled row of the return. */
|
||||
export type RalLine = ReturnLine;
|
||||
|
||||
export interface RalTopDeposit {
|
||||
srNo: number;
|
||||
institution: string;
|
||||
amount: number | null;
|
||||
rateOfInterest: number | null;
|
||||
dateOfReceipt: string;
|
||||
days: number | null;
|
||||
months: number | null;
|
||||
years: number | null;
|
||||
}
|
||||
|
||||
export interface RalAgeingRow {
|
||||
period: string;
|
||||
noOfDebitEntries: number | null;
|
||||
amtOfDebitEntries: number | null;
|
||||
noOfCreditEntries: number | null;
|
||||
amtOfCreditEntries: number | null;
|
||||
}
|
||||
|
||||
export interface RalUnsecuredGuarantee {
|
||||
srNo: number;
|
||||
borrower: string;
|
||||
beneficiary: string;
|
||||
amountOutstanding: number | null;
|
||||
}
|
||||
|
||||
export interface RalSundryDebtor {
|
||||
srNo: number;
|
||||
dueFromName: string;
|
||||
amountDue: number | null;
|
||||
provisionMade: number | null;
|
||||
}
|
||||
|
||||
export type RalSignatory = ReturnSignatory;
|
||||
|
||||
/**
|
||||
* Sheet 1 — General Information.
|
||||
* Return Name / Return Code are fixed by RBI; everything else is this bank's own
|
||||
* identity and the quarter being reported.
|
||||
*/
|
||||
export const RAL_GENERAL_INFO: ReturnGeneralInfoRow[] = [
|
||||
{ label: 'Return Name', value: 'Return on Assets and Liabilities (RAL)' },
|
||||
{ label: 'Return Code', value: 'R070' },
|
||||
{ label: 'Bank Name', value: OSS_BANK.name },
|
||||
{ label: 'Bank Code', value: OSS_BANK.bankCode },
|
||||
{ label: 'RO Name', value: OSS_BANK.roName },
|
||||
{ label: 'Reporting Frequency', value: OSS_BANK.reportingFrequency },
|
||||
{ label: 'Quarter End Date', value: OSS_BANK.quarterEndDate },
|
||||
{ label: 'Audit Flag', value: OSS_BANK.auditFlag },
|
||||
{ label: 'Date of Audit', value: OSS_BANK.dateOfAudit },
|
||||
{ label: 'Return Version', value: OSS_BANK.returnVersion },
|
||||
{ label: 'Start Date', value: OSS_BANK.startDate }
|
||||
];
|
||||
|
||||
/** Sheet 2 — Assets & Liabilities :: Liabilities */
|
||||
export const RAL_LIABILITIES: RalLine[] = [
|
||||
{ label: '1. Paid-up Capital', level: 0, value: 27278, total: true },
|
||||
{ label: 'i. Individuals', level: 1, value: 27278 },
|
||||
{ label: 'ii. State Government', level: 1, value: null },
|
||||
{ label: 'iii. Others', level: 1, value: null },
|
||||
|
||||
{ label: '2. Reserve Fund and other Reserves', level: 0, value: 60863, total: true },
|
||||
{ label: 'i. Statutory Reserve Fund', level: 1, value: 31149 },
|
||||
{ label: 'ii. Building Fund', level: 1, value: 13642 },
|
||||
{ label: 'iii. Dividend Equalization Fund', level: 1, value: 36 },
|
||||
{ label: 'iv. Revaluation Reserve', level: 1, value: null },
|
||||
{ label: 'v. Provision for Standard Assets', level: 1, value: 3750 },
|
||||
{ label: 'vi. General Provisions (other than provision for standard advances)', level: 1, value: 282 },
|
||||
{
|
||||
label: 'vii. Other Funds and Reserves not in the nature of outside liability-(Refer Annexure I)',
|
||||
level: 1,
|
||||
value: 12004
|
||||
},
|
||||
|
||||
{ label: '3. Profit (+) / Loss (-)', level: 0, value: 10397, total: true },
|
||||
{ label: 'i. Surplus Unallocated and Carried over from Previous years', level: 1, value: 5592 },
|
||||
{ label: 'ii. Accumulated Losses of Previous years', level: 1, value: null },
|
||||
{ label: 'iii. Operating Surplus in Current year', level: 1, value: 4805 },
|
||||
{ label: 'iv. Operating Deficit in Current year', level: 1, value: null },
|
||||
|
||||
{ label: '4. Total Capital And Reserves', level: 0, value: 98538, total: true },
|
||||
{ label: '5. Subordinated Debt (Tier II Capital) #', level: 0, value: null },
|
||||
|
||||
{ label: '6. Deposits and other Accounts', level: 0, value: 634309, total: true },
|
||||
{ label: 'Customer Deposits -', level: 1, value: 634309, total: true },
|
||||
{ label: 'Total No of Depositors', level: 1, value: 8156, total: true, count: true },
|
||||
{ label: 'A. Resident Deposits', level: 1, value: 634309, total: true },
|
||||
{
|
||||
label:
|
||||
'i. Term Deposits (including recurring deposits, cash certificates, staff provident funds,security deposits and matured term deposits)',
|
||||
level: 2,
|
||||
value: 406601
|
||||
},
|
||||
{ label: 'No. of Term Depositors', level: 2, value: 3462, count: true },
|
||||
{ label: 'ii. Savings Bank Deposits', level: 2, value: 208116 },
|
||||
{ label: 'No of Savings Depositors', level: 2, value: 4455, count: true },
|
||||
{ label: 'iii. Current Deposits', level: 2, value: 19592 },
|
||||
{ label: 'No of Current Depositors', level: 2, value: 239, count: true },
|
||||
{ label: 'B. Non Resident Deposits', level: 1, value: 0, total: true },
|
||||
{ label: '1. NRI Deposits (Other than deposits in NRO Accounts)', level: 2, value: 0, total: true },
|
||||
{ label: 'i. FCNR Deposits', level: 2, value: null },
|
||||
{ label: 'No of FCNR Depositors', level: 2, value: null, count: true },
|
||||
{ label: 'ii. NRE Deposits', level: 2, value: null },
|
||||
{ label: 'No of NRE Depositors', level: 2, value: null, count: true },
|
||||
{ label: '2. NRO Deposits', level: 2, value: null, total: true },
|
||||
{ label: 'No of NRO Depositors', level: 2, value: null, count: true },
|
||||
{ label: 'C. Deposit of Banks', level: 1, value: 0, total: true },
|
||||
{ label: 'i. Commercial Banks', level: 2, value: null },
|
||||
{ label: 'ii. Scheduled Cooperative Banks', level: 2, value: null },
|
||||
{ label: 'iii. Other Cooperative Banks', level: 2, value: null },
|
||||
|
||||
{ label: '7. Borrowings from', level: 0, value: 0, total: true },
|
||||
{ label: 'i. RBI / NABARD / SIDBI / NHB', level: 1, value: null },
|
||||
{ label: 'ii. State/Central Coop. Bank of the State/ District', level: 1, value: 0, total: true },
|
||||
{ label: 'a. Short-term loans', level: 2, value: null },
|
||||
{ label: 'b. Medium-term loans', level: 2, value: null },
|
||||
{ label: 'c. Cash credits and overdrafts', level: 2, value: null },
|
||||
{ label: 'iii. Other banks (exclude borrowings from call money market)', level: 1, value: null },
|
||||
{ label: 'iv. Call money market', level: 1, value: null },
|
||||
{ label: 'v. Long Term Deposits', level: 1, value: null },
|
||||
{ label: 'vi. Others', level: 1, value: null },
|
||||
|
||||
{ label: '8. Bills for collection being bills receivable as per contra', level: 0, value: null },
|
||||
{ label: '9. Branch Adjustments *', level: 0, value: null },
|
||||
{ label: '10. Overdue Interest Reserve', level: 0, value: 32096 },
|
||||
|
||||
{ label: '11. Interest Payable on', level: 0, value: 731, total: true },
|
||||
{ label: 'i. Deposits', level: 1, value: 731 },
|
||||
{ label: 'ii. Borrowings', level: 1, value: null },
|
||||
|
||||
{
|
||||
label:
|
||||
'12. Risk Provisions Other than netted off assets (BDDR, Spl BDDR, Depreciation on fixed assets etc.,)',
|
||||
level: 0,
|
||||
value: 39440,
|
||||
total: true
|
||||
},
|
||||
{ label: 'i. For investments (Investment Depreciation Reserve)', level: 1, value: 3750 },
|
||||
{ label: 'ii. BDDR, Spl BDDR', level: 1, value: 24341 },
|
||||
{ label: 'iii. Depreciation on fixed assets', level: 1, value: null },
|
||||
{ label: 'iv. For other impaired assets', level: 1, value: 11349 },
|
||||
{ label: 'v. For contingent / non-funded exposures', level: 1, value: null },
|
||||
|
||||
{ label: '13. Other Liabilities', level: 0, value: 5806, total: true },
|
||||
{ label: 'i. Bills payable', level: 1, value: 291 },
|
||||
{ label: 'ii. Unclaimed dividends', level: 1, value: null },
|
||||
{ label: 'iii.Provident fund payable to Regional Provident Fund Commissioner', level: 1, value: null },
|
||||
{ label: 'iv.Others', level: 1, value: 5515 },
|
||||
|
||||
{ label: '14. Total Outside Liabilities', level: 0, value: 712382, total: true },
|
||||
{ label: '15. Total Liabilities', level: 0, value: 810920, total: true },
|
||||
|
||||
{ label: '16. Contingent Liabilities', level: 0, value: 2638, total: true },
|
||||
{ label: 'i. Claims against the bank not acknowledged as debt', level: 1, value: null },
|
||||
{ label: 'ii. Liabilities for partly paid investments', level: 1, value: null },
|
||||
{ label: 'iii. Letter of Credit Documentary', level: 1, value: null },
|
||||
{ label: 'iv. Guarantees - Financial', level: 1, value: null },
|
||||
{ label: 'v. Guarantees- Others (Indemnity/ Tender/ Bid Bonds)', level: 1, value: null },
|
||||
{ label: 'vi. Sale and Repurchase Agreement /Asset sales with recourse', level: 1, value: null },
|
||||
{ label: 'vii. Liability on account of outstanding forward exchange contracts', level: 1, value: null },
|
||||
{ label: 'viii. Acceptances, endorsements and other obligations', level: 1, value: null },
|
||||
{ label: 'ix. Others', level: 1, value: 2638 }
|
||||
];
|
||||
|
||||
/** Sheet 2 — Assets & Liabilities :: Assets */
|
||||
export const RAL_ASSETS: RalLine[] = [
|
||||
{ label: '1. Cash in Hand', level: 0, value: 9996 },
|
||||
|
||||
{ label: '2. I. Due from Banks', level: 0, value: 57600, total: true },
|
||||
{ label: 'A. Balances with Banks', level: 1, value: 57600, total: true },
|
||||
{ label: 'i. State/ Central Cooperative Bank of the State/District concerned in', level: 2, value: 32268, total: true },
|
||||
{ label: 'a. Current', level: 2, value: 5980 },
|
||||
{ label: 'b. Savings', level: 2, value: null },
|
||||
{ label: 'c. Fixed', level: 2, value: 26288 },
|
||||
{ label: 'ii. RBI :', level: 2, value: 0, total: true },
|
||||
{ label: 'a. Current', level: 2, value: null },
|
||||
{ label: 'b. Others', level: 2, value: null },
|
||||
{ label: 'iii. SBI and other notified banks :', level: 2, value: 25332, total: true },
|
||||
{ label: 'a. Current', level: 2, value: 13667 },
|
||||
{ label: 'b. Savings', level: 2, value: null },
|
||||
{ label: 'c. Fixed', level: 2, value: 11665 },
|
||||
{ label: 'B. Loans and Advances to Banks (including bills)', level: 1, value: 0, total: true },
|
||||
{ label: 'i. Call and Short Notice Money to Cooperative Banks', level: 2, value: null },
|
||||
{ label: 'ii. Call and Short Notice Money to Other Banks', level: 2, value: null },
|
||||
{ label: 'iii. Loans and Advances to Scheduled Cooperative Banks', level: 2, value: null },
|
||||
{ label: 'iv.Loans and advances to Non scheduled Cooperative Banks', level: 2, value: null },
|
||||
{ label: 'v. Loans and Advances to Other Banks', level: 2, value: null },
|
||||
|
||||
{
|
||||
label: '2. II. Due from Other institutions (including post office / treasuries / others)',
|
||||
level: 0,
|
||||
value: 77353,
|
||||
total: true
|
||||
},
|
||||
{ label: 'a. Current', level: 1, value: 9954 },
|
||||
{ label: 'b. Fixed', level: 1, value: 67399 },
|
||||
{ label: 'c. Others', level: 1, value: null },
|
||||
|
||||
{ label: '3. Investments', level: 0, value: 234297, total: true },
|
||||
{
|
||||
label: 'A. SLR Investments in Government and Other Approved Central and State Govt Securities',
|
||||
level: 1,
|
||||
value: 211734,
|
||||
total: true
|
||||
},
|
||||
{ label: 'i. GOI T. Bills', level: 2, value: null },
|
||||
{ label: 'ii. GOI (dated) Securities', level: 2, value: 63839 },
|
||||
{ label: 'iii. State Government Securities', level: 2, value: 147895 },
|
||||
{
|
||||
label: 'iv. Other Trustee/approved securities including Land Development Bank debentures',
|
||||
level: 2,
|
||||
value: null
|
||||
},
|
||||
{ label: 'B. Non - SLR Investments', level: 1, value: 22563, total: true },
|
||||
{ label: 'B1. Other Debt Securities', level: 2, value: 0, total: true },
|
||||
{ label: 'i. Other State Government Securities', level: 2, value: null },
|
||||
{ label: 'ii. Bonds of PSUs / All India Financial Institutions', level: 2, value: null },
|
||||
{ label: 'B2. Equities / Units of Mutual Funds', level: 2, value: 63, total: true },
|
||||
{ label: 'i. Shares in Cooperative Institutions', level: 2, value: 63 },
|
||||
{ label: 'ii. Shares of All India Financial Institutions', level: 2, value: null },
|
||||
{ label: 'iii. Units of Mutual Funds (UTI,etc)', level: 2, value: null },
|
||||
{ label: 'B3. Investment in Subsidiaries / Equity Participations', level: 2, value: null },
|
||||
{ label: 'B4. Other Investments', level: 2, value: 22500 },
|
||||
|
||||
{ label: '4. Loans and Advances', level: 0, value: 391288, total: true },
|
||||
{ label: 'A. Gross Loans and Advances: (Other than banks)', level: 1, value: 391288, total: true },
|
||||
{ label: 'Total No of Borrowers', level: 1, value: 363, total: true, count: true },
|
||||
{ label: 'i. Short term loans', level: 2, value: 125082, total: true },
|
||||
{ label: 'No of Borrowers', level: 2, value: 137, count: true },
|
||||
{ label: 'a. Secured', level: 2, value: 125082 },
|
||||
{ label: 'b. Unsecured', level: 2, value: null },
|
||||
{ label: 'ii. Cash credits and overdrafts', level: 2, value: 166614, total: true },
|
||||
{ label: 'No of Borrowers', level: 2, value: 140, count: true },
|
||||
{ label: 'a. Secured', level: 2, value: 166614 },
|
||||
{ label: 'b. Unsecured', level: 2, value: null },
|
||||
{ label: 'iii. Medium - term loans', level: 2, value: 70236, total: true },
|
||||
{ label: 'No of Borrowers', level: 2, value: 66, count: true },
|
||||
{ label: 'a. Secured', level: 2, value: 70070 },
|
||||
{ label: 'b. Unsecured', level: 2, value: 166 },
|
||||
{ label: 'iv. Long - term loans', level: 2, value: 29356, total: true },
|
||||
{ label: 'No of Borrowers', level: 2, value: 20, count: true },
|
||||
{ label: 'a. Secured', level: 2, value: 29356 },
|
||||
{ label: 'b. Unsecured', level: 2, value: null },
|
||||
{ label: 'v. Bills/hundies purchased/discounted', level: 2, value: 0, total: true },
|
||||
{ label: 'No of Borrowers', level: 2, value: null, count: true },
|
||||
{ label: 'a. Documentary', level: 2, value: null },
|
||||
{ label: 'b. Clean', level: 2, value: null },
|
||||
{ label: 'B. Netting items on Loans and Advances', level: 1, value: 0, total: true },
|
||||
{ label: 'i. Unrealised Interest in Suspense', level: 2, value: null },
|
||||
{ label: 'ii. Credit Recoveries in Suspense', level: 2, value: null },
|
||||
{ label: 'iii. Provisions for Credit Losses ( funded credits)', level: 2, value: null },
|
||||
{ label: 'C. Loans and Advances (Net)', level: 1, value: 391288, total: true },
|
||||
|
||||
{ label: '5. Interest Receivable on', level: 0, value: 33541, total: true },
|
||||
{ label: 'i. Investments', level: 1, value: 1445 },
|
||||
{ label: 'ii.Loans and Advances', level: 1, value: 32096 },
|
||||
{ label: 'Of 5 (ii) above, overdues', level: 1, value: 32096 },
|
||||
|
||||
{ label: '6. Bills Receivable being bills for collection as per contra', level: 0, value: null },
|
||||
{ label: '7. Premises (less depreciation)', level: 0, value: 3185 },
|
||||
{ label: '8. Furniture and Fixtures (less depreciation)', level: 0, value: 1763 },
|
||||
{ label: '9. Branch Adjustments **', level: 0, value: null },
|
||||
|
||||
{ label: '10. Other Assets', level: 0, value: 1897, total: true },
|
||||
{ label: 'i. Advance Tax Paid and TDS (net)', level: 1, value: null },
|
||||
{ label: 'ii. Amounts due from RBI', level: 1, value: null },
|
||||
{ label: 'iii. Sundry Debtors', level: 1, value: null },
|
||||
{ label: 'iv. Intangible Assets', level: 1, value: null },
|
||||
{ label: 'v. All Other', level: 1, value: 1897 },
|
||||
|
||||
{ label: '11. Non-banking Assets acquired in satisfaction of claims', level: 0, value: null },
|
||||
{ label: '12. Total Assets (1 to 11)', level: 0, value: 810920, total: true }
|
||||
];
|
||||
|
||||
/** Sheet 2 — Memo block */
|
||||
export const RAL_MEMO: RalLine[] = [
|
||||
{ label: '1.Capital Infusion during Apr to Date', level: 0, value: 0, total: true },
|
||||
{ label: '1.1 Tier 1 Capital Raised during April to Date', level: 1, value: null },
|
||||
{ label: '1.2 Tier 2 Capital Raised during April to Date', level: 1, value: null },
|
||||
{ label: '2. Capital Instruments due for redemption in next twelve months', level: 0, value: null }
|
||||
];
|
||||
|
||||
export const RAL_BALANCE_SHEET_NOTES: string[] = [
|
||||
'Note : * To be furnished as per the books of the bank.',
|
||||
'Note : ** Branch adjustments: Net position to be shown as an item of liability / assets, as the case may be.',
|
||||
'Note: # Capital raised by way of RCPS / RNCPS / PNCPS to be reported under this item'
|
||||
];
|
||||
|
||||
/** Sheet 3 — Annexure 1 :: 1. Break-up of Other Funds and Reserves */
|
||||
export const RAL_OTHER_FUNDS: RalLine[] = [
|
||||
{ label: 'i. Gratuity Fund', level: 1, value: null },
|
||||
{ label: 'ii. Staff Welfare Fund', level: 1, value: 1310 },
|
||||
{ label: 'iii. Silver Jubilee Fund', level: 1, value: null },
|
||||
{ label: 'iv. Development Fund', level: 1, value: 36 },
|
||||
{ label: 'v. Charity Fund', level: 1, value: 1718 },
|
||||
{ label: 'vi. Any Other Fund/ Disclosed reserves', level: 1, value: 8940 },
|
||||
{
|
||||
label:
|
||||
'vii. Undisclosed Reserves created out of PAT and included in B/S under heads like other liabilities and provisions',
|
||||
level: 1,
|
||||
value: null
|
||||
},
|
||||
{ label: 'Total', level: 0, value: 12004, total: true }
|
||||
];
|
||||
|
||||
/** Sheet 3 — Annexure 1 :: 2. Top 20 deposits from other banks / institutions */
|
||||
export const RAL_TOP_20_DEPOSITS: RalTopDeposit[] = Array.from(
|
||||
{ length: 20 },
|
||||
(_, i): RalTopDeposit => ({
|
||||
srNo: i + 1,
|
||||
institution: '',
|
||||
amount: null,
|
||||
rateOfInterest: null,
|
||||
dateOfReceipt: '',
|
||||
days: null,
|
||||
months: null,
|
||||
years: null
|
||||
})
|
||||
);
|
||||
|
||||
/** Column captions shared by the two age-wise analyses in Annexure 1. */
|
||||
export const RAL_AGEING_HEADERS: string[] = [
|
||||
'Time period',
|
||||
'No. of debit entries',
|
||||
'Amt. of debit entries',
|
||||
'No. of credit entries',
|
||||
'Amt. of credit entries'
|
||||
];
|
||||
|
||||
/** Sheet 3 — Annexure 1 :: 3. Age-wise Inter Branch Adjustment Account */
|
||||
export const RAL_INTER_BRANCH_AGEING: RalAgeingRow[] = [
|
||||
{ period: '3 months to 6 months', noOfDebitEntries: null, amtOfDebitEntries: null, noOfCreditEntries: null, amtOfCreditEntries: null },
|
||||
{ period: 'Above 6 months to 1 year', noOfDebitEntries: null, amtOfDebitEntries: null, noOfCreditEntries: null, amtOfCreditEntries: null },
|
||||
{ period: 'Above 1 year to 2 years', noOfDebitEntries: null, amtOfDebitEntries: null, noOfCreditEntries: null, amtOfCreditEntries: null },
|
||||
{ period: 'Above 2 years', noOfDebitEntries: null, amtOfDebitEntries: null, noOfCreditEntries: null, amtOfCreditEntries: null },
|
||||
{ period: 'Total', noOfDebitEntries: 0, amtOfDebitEntries: 0, noOfCreditEntries: 0, amtOfCreditEntries: 0 }
|
||||
];
|
||||
|
||||
/** Sheet 3 — Annexure 1 :: 4. Age-wise Inter Bank Reconciliation Account */
|
||||
export const RAL_INTER_BANK_AGEING: RalAgeingRow[] = [
|
||||
{ period: '3 months to 6 months', noOfDebitEntries: null, amtOfDebitEntries: null, noOfCreditEntries: null, amtOfCreditEntries: null },
|
||||
{ period: 'Above 6 months to 1 year', noOfDebitEntries: null, amtOfDebitEntries: null, noOfCreditEntries: null, amtOfCreditEntries: null },
|
||||
{ period: 'Above 1 year to 2 years', noOfDebitEntries: null, amtOfDebitEntries: null, noOfCreditEntries: null, amtOfCreditEntries: null },
|
||||
{ period: 'Above 2 years', noOfDebitEntries: null, amtOfDebitEntries: null, noOfCreditEntries: null, amtOfCreditEntries: null },
|
||||
{ period: 'Total', noOfDebitEntries: 0, amtOfDebitEntries: 0, noOfCreditEntries: 0, amtOfCreditEntries: 0 }
|
||||
];
|
||||
|
||||
/** Sheet 3 — Annexure 1 :: 5. Unsecured guarantees issued by the bank */
|
||||
export const RAL_UNSECURED_GUARANTEES: RalUnsecuredGuarantee[] = [
|
||||
{ srNo: 1, borrower: '', beneficiary: '', amountOutstanding: null }
|
||||
];
|
||||
export const RAL_UNSECURED_GUARANTEES_TOTAL = 0;
|
||||
|
||||
/** Sheet 4 — Annexure 2 :: Sundry Debtors ageing buckets */
|
||||
export const RAL_SUNDRY_DEBTORS: { title: string; subtitle: string; rows: RalSundryDebtor[] }[] = [
|
||||
{
|
||||
title: 'Sundry Debtors Account -1',
|
||||
subtitle: 'Long Outstanding Large Amounts - Outstanding for over 6 months but less than 1 year*',
|
||||
rows: [{ srNo: 1, dueFromName: '', amountDue: null, provisionMade: null }]
|
||||
},
|
||||
{
|
||||
title: 'Sundry Debtors Account -2',
|
||||
subtitle: 'Long Outstanding Large Amounts - Outstanding for over 1 year but less than 3 years*',
|
||||
rows: [{ srNo: 1, dueFromName: '', amountDue: null, provisionMade: null }]
|
||||
},
|
||||
{
|
||||
title: 'Sundry Debtors Account -3',
|
||||
subtitle: 'Long Outstanding Large Amounts - Outstanding for over 3 years*',
|
||||
rows: [{ srNo: 1, dueFromName: '', amountDue: null, provisionMade: null }]
|
||||
}
|
||||
];
|
||||
|
||||
export const RAL_SUNDRY_DEBTORS_NOTE =
|
||||
'* Large Amounts: Rs. 2 lakhs for banks having total assets of Rs. 100 lakhs and over Rs. 50,000 for other banks';
|
||||
|
||||
/** Sheet 4 — Annexure 2 :: Details of Depositors & Borrowers */
|
||||
export const RAL_DEPOSITORS_BORROWERS: RalLine[] = [
|
||||
{ label: '1.Total No of Depositors', level: 0, value: 8156, total: true, count: true },
|
||||
{ label: 'Total Customer Deposits', level: 0, value: 634309, total: true },
|
||||
{ label: 'a. i) Of which Total No. of Depositors with Deposits less than Rs. 1 Lakh', level: 1, value: 6568, count: true },
|
||||
{ label: 'ii) Of which total Deposit Amount', level: 1, value: 109992 },
|
||||
{ label: 'b. i) Of which Total No. of Depositors with Deposits Between Rs 1 Lakh to Rs. 2 Lakh', level: 1, value: 739, count: true },
|
||||
{ label: 'ii) Of which total Deposit Amount', level: 1, value: 102475 },
|
||||
{ label: 'c. i) Of which Total No. of Depositors with Deposits Between Rs. 2 Lakh to Rs 5 Lakh', level: 1, value: 580, count: true },
|
||||
{ label: 'ii) Of which total Deposit Amount', level: 1, value: 176820 },
|
||||
|
||||
{ label: '2.Total No of Borrowers', level: 0, value: 363, total: true, count: true },
|
||||
{ label: 'Total Loans and Advances (Other than Banks)', level: 0, value: 391288, total: true },
|
||||
{ label: 'a. i) Of which Total No. of Borrowers with Loans and Advances less than Rs. 1 Lakh', level: 1, value: 55, count: true },
|
||||
{ label: 'ii) Of which total Loans and Advances Amount', level: 1, value: 2061 },
|
||||
{ label: 'b. i) Of which Total No. of Borrowers with Loans and Advances Between Rs 1 Lakh to Rs. 2 Lakh', level: 1, value: 18, count: true },
|
||||
{ label: 'ii) Of which total Loans and Advances Amount', level: 1, value: 2837 },
|
||||
{ label: 'c. i) Of which Total No. of Borrowers with Loans and Advances Between Rs. 2 Lakh to Rs 5 Lakh', level: 1, value: 55, count: true },
|
||||
{ label: 'ii) Of which total Loans and Advances Amount', level: 1, value: 21260 }
|
||||
];
|
||||
|
||||
/** Sheet 4 — Annexure 2 :: Complaints Received from public */
|
||||
export const RAL_COMPLAINTS: RalLine[] = [
|
||||
{ label: 'a) No. Of complaints outstanding at the beginning of the quarter', level: 1, value: 0, count: true },
|
||||
{ label: 'b) No. Of complaints received during the quarter', level: 1, value: 0, count: true },
|
||||
{ label: 'c) No. of complaints addressed during the quarter', level: 1, value: 0, count: true },
|
||||
{ label: 'd) No. of complaints outstanding at the end of the quarter', level: 1, value: 0, count: true },
|
||||
{ label: 'e) No. of complaints outstanding for over 2 quarters', level: 1, value: 0, count: true }
|
||||
];
|
||||
|
||||
/** Sheet 5 — Signatory. Shared across returns so OSS 1 and OSS 2 always agree. */
|
||||
export const RAL_SIGNATORIES: RalSignatory[] = OSS_SIGNATORIES;
|
||||
@@ -0,0 +1,422 @@
|
||||
<!--
|
||||
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="oss-root">
|
||||
<!-- HEADER -->
|
||||
<div class="oss-head">
|
||||
<div class="oss-head-left">
|
||||
<fa-icon icon="university" class="oss-head-ic"></fa-icon>
|
||||
<div>
|
||||
<h2 class="oss-head-t">OSS 1 — {{ returnName }}</h2>
|
||||
<div class="oss-head-s">
|
||||
{{ bankName }} · Bank Code <strong>{{ bankCode }}</strong> · Return Code
|
||||
<strong>{{ returnCode }}</strong> · Quarter ending <strong>{{ quarterEndDate }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oss-head-right">
|
||||
<span class="oss-audit-pill">{{ auditFlag }}</span>
|
||||
<button class="oss-btn oss-btn-xls" [disabled]="generating" (click)="generateExcel()">
|
||||
<fa-icon icon="file-csv"></fa-icon>
|
||||
{{ generating ? 'Generating…' : 'Generate Excel' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BALANCE CHECK -->
|
||||
<div class="oss-balance" [class.oss-balance--bad]="!isBalanced">
|
||||
<fa-icon [icon]="isBalanced ? 'check-circle' : 'exclamation-triangle'"></fa-icon>
|
||||
<span>
|
||||
Total Liabilities <strong>₹{{ totalLiabilities | number: '1.2-2' }}</strong> · Total Assets
|
||||
<strong>₹{{ totalAssets | number: '1.2-2' }}</strong> (Rs in Thousands) —
|
||||
{{ isBalanced ? 'balance sheet tallies' : 'MISMATCH — resolve before filing with RBI' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- TABS -->
|
||||
<div class="oss-tabs">
|
||||
<button class="oss-tab" [class.active]="activeTab === 'general'" (click)="showTab('general')">
|
||||
<fa-icon icon="info-circle"></fa-icon> General Information
|
||||
</button>
|
||||
<button class="oss-tab" [class.active]="activeTab === 'balance-sheet'" (click)="showTab('balance-sheet')">
|
||||
<fa-icon icon="university"></fa-icon> Assets & Liabilities
|
||||
</button>
|
||||
<button class="oss-tab" [class.active]="activeTab === 'annexure1'" (click)="showTab('annexure1')">
|
||||
<fa-icon icon="file-alt"></fa-icon> Annexure 1
|
||||
</button>
|
||||
<button class="oss-tab" [class.active]="activeTab === 'annexure2'" (click)="showTab('annexure2')">
|
||||
<fa-icon icon="file-alt"></fa-icon> Annexure 2
|
||||
</button>
|
||||
<button class="oss-tab" [class.active]="activeTab === 'signatory'" (click)="showTab('signatory')">
|
||||
<fa-icon icon="check-circle"></fa-icon> Signatory
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- GENERAL INFORMATION -->
|
||||
@if (activeTab === 'general') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">General Information</div>
|
||||
<div class="oss-tbl-wrap">
|
||||
<table class="oss-tbl oss-tbl--kv">
|
||||
<tbody>
|
||||
@for (item of generalInfo; track item.label) {
|
||||
<tr>
|
||||
<th>{{ item.label }}</th>
|
||||
<td [class.filled]="item.value" [class.audit]="item.label === 'Audit Flag'">{{ item.value }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ASSETS & LIABILITIES -->
|
||||
@if (activeTab === 'balance-sheet') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Assets & Liabilities</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Liabilities</th>
|
||||
<th class="ta-r">Rs in Thousands</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (line of liabilities; track $index) {
|
||||
<tr [class.total-row]="line.total">
|
||||
<td [class]="'lvl-' + line.level">{{ line.label }}</td>
|
||||
<td class="ta-r mono" [class.derived]="line.total">
|
||||
{{ line.value === null ? '' : (line.value | number: line.count ? '1.0-0' : '1.2-2') }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Assets</th>
|
||||
<th class="ta-r">Rs in Thousands</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (line of assets; track $index) {
|
||||
<tr [class.total-row]="line.total">
|
||||
<td [class]="'lvl-' + line.level">{{ line.label }}</td>
|
||||
<td class="ta-r mono" [class.derived]="line.total">
|
||||
{{ line.value === null ? '' : (line.value | number: line.count ? '1.0-0' : '1.2-2') }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="oss-notes">
|
||||
@for (note of balanceSheetNotes; track note) {
|
||||
<div class="oss-note-line">{{ note }}</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Memo</th>
|
||||
<th class="ta-r">Rs in Thousands</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (line of memo; track $index) {
|
||||
<tr [class.total-row]="line.total">
|
||||
<td [class]="'lvl-' + line.level">{{ line.label }}</td>
|
||||
<td class="ta-r mono" [class.derived]="line.total">
|
||||
{{ line.value === null ? '' : (line.value | number: '1.2-2') }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ANNEXURE 1 -->
|
||||
@if (activeTab === 'annexure1') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Annexure 1</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">1. Break-up of Other Funds and Reserves — Rs in Thousands</div>
|
||||
<table class="oss-tbl">
|
||||
<tbody>
|
||||
@for (line of otherFunds; track line.label) {
|
||||
<tr [class.total-row]="line.total">
|
||||
<td>{{ line.label }}</td>
|
||||
<td class="ta-r mono" [class.derived]="line.total">
|
||||
{{ line.value === null ? '' : (line.value | number: '1.2-2') }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">
|
||||
2. Particulars of top 20 deposits accepted from other banks / and institutions as on the date of review
|
||||
</div>
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="ta-c">Sr. No</th>
|
||||
<th>Name of Bank / Institution</th>
|
||||
<th class="ta-r">Amount of deposit</th>
|
||||
<th class="ta-r">Rate of interest (%)</th>
|
||||
<th>Date of receipt</th>
|
||||
<th class="ta-c">Days</th>
|
||||
<th class="ta-c">Months</th>
|
||||
<th class="ta-c">Years</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (d of topDeposits; track d.srNo) {
|
||||
<tr>
|
||||
<td class="ta-c">{{ d.srNo }}</td>
|
||||
<td>{{ d.institution }}</td>
|
||||
<td class="ta-r mono">{{ d.amount === null ? '' : (d.amount | number: '1.2-2') }}</td>
|
||||
<td class="ta-r mono">{{ d.rateOfInterest }}</td>
|
||||
<td>{{ d.dateOfReceipt }}</td>
|
||||
<td class="ta-c mono">{{ d.days }}</td>
|
||||
<td class="ta-c mono">{{ d.months }}</td>
|
||||
<td class="ta-c mono">{{ d.years }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">
|
||||
3. Age-wise analysis of Inter Branch Adjustment Account — Position as on: {{ quarterEndDate }}
|
||||
</div>
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
@for (h of ageingHeaders; track h) {
|
||||
<th [class.ta-r]="!$first">{{ h }}</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (r of interBranchAgeing; track r.period) {
|
||||
<tr [class.total-row]="r.period === 'Total'">
|
||||
<td>{{ r.period }}</td>
|
||||
<td class="ta-r mono">{{ r.noOfDebitEntries }}</td>
|
||||
<td class="ta-r mono">
|
||||
{{ r.amtOfDebitEntries === null ? '' : (r.amtOfDebitEntries | number: '1.2-2') }}
|
||||
</td>
|
||||
<td class="ta-r mono">{{ r.noOfCreditEntries }}</td>
|
||||
<td class="ta-r mono">
|
||||
{{ r.amtOfCreditEntries === null ? '' : (r.amtOfCreditEntries | number: '1.2-2') }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">
|
||||
4. Age-wise analysis of Inter Bank Reconciliation Account — Position as on: {{ quarterEndDate }}
|
||||
</div>
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
@for (h of ageingHeaders; track h) {
|
||||
<th [class.ta-r]="!$first">{{ h }}</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (r of interBankAgeing; track r.period) {
|
||||
<tr [class.total-row]="r.period === 'Total'">
|
||||
<td>{{ r.period }}</td>
|
||||
<td class="ta-r mono">{{ r.noOfDebitEntries }}</td>
|
||||
<td class="ta-r mono">
|
||||
{{ r.amtOfDebitEntries === null ? '' : (r.amtOfDebitEntries | number: '1.2-2') }}
|
||||
</td>
|
||||
<td class="ta-r mono">{{ r.noOfCreditEntries }}</td>
|
||||
<td class="ta-r mono">
|
||||
{{ r.amtOfCreditEntries === null ? '' : (r.amtOfCreditEntries | number: '1.2-2') }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">5. Unsecured guarantees issued by the bank</div>
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="ta-c">Sr. No</th>
|
||||
<th>Name of Borrower</th>
|
||||
<th>Name of Beneficiary</th>
|
||||
<th class="ta-r">Amount Outstanding as on: {{ quarterEndDate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (g of unsecuredGuarantees; track g.srNo) {
|
||||
<tr>
|
||||
<td class="ta-c">{{ g.srNo }}</td>
|
||||
<td>{{ g.borrower }}</td>
|
||||
<td>{{ g.beneficiary }}</td>
|
||||
<td class="ta-r mono">
|
||||
{{ g.amountOutstanding === null ? '' : (g.amountOutstanding | number: '1.2-2') }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="total-row">
|
||||
<td colspan="3">Total</td>
|
||||
<td class="ta-r mono derived">{{ unsecuredGuaranteesTotal | number: '1.2-2' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="oss-foot-note">
|
||||
Note: * Give name if borrower/beneficiary is director/top management/related entity. Other guarantees may be
|
||||
consolidated and reported against others.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ANNEXURE 2 -->
|
||||
@if (activeTab === 'annexure2') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Annexure 2</div>
|
||||
|
||||
@for (block of sundryDebtors; track block.title) {
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">{{ block.title }} — {{ block.subtitle }}</div>
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="ta-c">Sr. No</th>
|
||||
<th>Due from Name</th>
|
||||
<th class="ta-r">Amount due</th>
|
||||
<th class="ta-r">Provision made</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (r of block.rows; track r.srNo) {
|
||||
<tr>
|
||||
<td class="ta-c">{{ r.srNo }}</td>
|
||||
<td>{{ r.dueFromName }}</td>
|
||||
<td class="ta-r mono">{{ r.amountDue === null ? '' : (r.amountDue | number: '1.2-2') }}</td>
|
||||
<td class="ta-r mono">{{ r.provisionMade === null ? '' : (r.provisionMade | number: '1.2-2') }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
<p class="oss-foot-note">{{ sundryDebtorsNote }}</p>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">Details of Depositors & Borrowers — Rs in Thousands</div>
|
||||
<table class="oss-tbl">
|
||||
<tbody>
|
||||
@for (line of depositorsBorrowers; track $index) {
|
||||
<tr [class.total-row]="line.total">
|
||||
<td [class]="'lvl-' + line.level">{{ line.label }}</td>
|
||||
<td class="ta-r mono" [class.derived]="line.total">
|
||||
{{ line.value === null ? '' : (line.value | number: line.count ? '1.0-0' : '1.2-2') }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">Complaints Received from public</div>
|
||||
<table class="oss-tbl">
|
||||
<tbody>
|
||||
@for (line of complaints; track line.label) {
|
||||
<tr>
|
||||
<td>{{ line.label }}</td>
|
||||
<td class="ta-r mono">{{ line.value }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- SIGNATORY -->
|
||||
@if (activeTab === 'signatory') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Signatory</div>
|
||||
<div class="oss-sign-grid">
|
||||
@for (p of signatories; track p.heading) {
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">{{ p.heading }}</div>
|
||||
<table class="oss-tbl oss-tbl--kv">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ p.signature }}</td>
|
||||
<th>Signature</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.name }}</td>
|
||||
<th>Name</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.designation }}</td>
|
||||
<th>Designation</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.email }}</td>
|
||||
<th>e-mail ID</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono">{{ p.telephoneOffice }}</td>
|
||||
<th>Tele. No. (O)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono">{{ p.telephoneResidence }}</td>
|
||||
<th>Tele. No. (R)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.place }}</td>
|
||||
<th>Place</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.date }}</td>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- TOAST -->
|
||||
<div class="oss-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|
||||
</div>
|
||||
@@ -0,0 +1,423 @@
|
||||
/**
|
||||
* 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 * as ExcelJS from 'exceljs';
|
||||
import {
|
||||
addBanner,
|
||||
addLineRows,
|
||||
addSectionHeader,
|
||||
boxAllCells,
|
||||
buildGeneralInfoSheet,
|
||||
buildSignatorySheet,
|
||||
downloadWorkbook,
|
||||
newWorkbook,
|
||||
solidFill,
|
||||
XL
|
||||
} from '../oss-workbook';
|
||||
import {
|
||||
RAL_AGEING_HEADERS,
|
||||
RAL_ASSETS,
|
||||
RAL_BALANCE_SHEET_NOTES,
|
||||
RAL_COMPLAINTS,
|
||||
RAL_DEPOSITORS_BORROWERS,
|
||||
RAL_GENERAL_INFO,
|
||||
RAL_INTER_BANK_AGEING,
|
||||
RAL_INTER_BRANCH_AGEING,
|
||||
RAL_LIABILITIES,
|
||||
RAL_MEMO,
|
||||
RAL_OTHER_FUNDS,
|
||||
RAL_SIGNATORIES,
|
||||
RAL_SUNDRY_DEBTORS,
|
||||
RAL_SUNDRY_DEBTORS_NOTE,
|
||||
RAL_TOP_20_DEPOSITS,
|
||||
RAL_UNSECURED_GUARANTEES,
|
||||
RAL_UNSECURED_GUARANTEES_TOTAL
|
||||
} from './oss1-ral-data';
|
||||
|
||||
type Oss1Tab = 'general' | 'balance-sheet' | 'annexure1' | 'annexure2' | 'signatory';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-oss1-ral',
|
||||
templateUrl: './oss1-ral.component.html',
|
||||
styleUrls: ['../oss-return.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent,
|
||||
FormsModule
|
||||
]
|
||||
})
|
||||
export class Oss1RalComponent {
|
||||
activeTab: Oss1Tab = 'general';
|
||||
|
||||
generalInfo = RAL_GENERAL_INFO;
|
||||
liabilities = RAL_LIABILITIES;
|
||||
assets = RAL_ASSETS;
|
||||
memo = RAL_MEMO;
|
||||
balanceSheetNotes = RAL_BALANCE_SHEET_NOTES;
|
||||
otherFunds = RAL_OTHER_FUNDS;
|
||||
topDeposits = RAL_TOP_20_DEPOSITS;
|
||||
interBranchAgeing = RAL_INTER_BRANCH_AGEING;
|
||||
interBankAgeing = RAL_INTER_BANK_AGEING;
|
||||
ageingHeaders = RAL_AGEING_HEADERS;
|
||||
unsecuredGuarantees = RAL_UNSECURED_GUARANTEES;
|
||||
unsecuredGuaranteesTotal = RAL_UNSECURED_GUARANTEES_TOTAL;
|
||||
sundryDebtors = RAL_SUNDRY_DEBTORS;
|
||||
sundryDebtorsNote = RAL_SUNDRY_DEBTORS_NOTE;
|
||||
depositorsBorrowers = RAL_DEPOSITORS_BORROWERS;
|
||||
complaints = RAL_COMPLAINTS;
|
||||
signatories = RAL_SIGNATORIES;
|
||||
|
||||
generating = false;
|
||||
toastMsg = '';
|
||||
toastVisible = false;
|
||||
private toastTimer: any;
|
||||
|
||||
showTab(tab: Oss1Tab): void {
|
||||
this.activeTab = tab;
|
||||
}
|
||||
|
||||
private info(label: string): string {
|
||||
return this.generalInfo.find((g) => g.label === label)?.value ?? '';
|
||||
}
|
||||
|
||||
get returnName(): string {
|
||||
return this.info('Return Name');
|
||||
}
|
||||
get returnCode(): string {
|
||||
return this.info('Return Code');
|
||||
}
|
||||
get bankName(): string {
|
||||
return this.info('Bank Name');
|
||||
}
|
||||
get bankCode(): string {
|
||||
return this.info('Bank Code');
|
||||
}
|
||||
get quarterEndDate(): string {
|
||||
return this.info('Quarter End Date');
|
||||
}
|
||||
get auditFlag(): string {
|
||||
return this.info('Audit Flag');
|
||||
}
|
||||
|
||||
/** Total Liabilities must equal Total Assets — surfaced so a mismatch can't be filed unnoticed. */
|
||||
get totalLiabilities(): number {
|
||||
return this.liabilities.find((l) => l.label.startsWith('15.'))?.value ?? 0;
|
||||
}
|
||||
get totalAssets(): number {
|
||||
return this.assets.find((a) => a.label.startsWith('12.'))?.value ?? 0;
|
||||
}
|
||||
get isBalanced(): boolean {
|
||||
return this.totalLiabilities === this.totalAssets;
|
||||
}
|
||||
|
||||
// ══════════════ Excel generation ══════════════
|
||||
|
||||
/** Rebuilds the RBI workbook: one sheet per section, original layout and styling. */
|
||||
async generateExcel(): Promise<void> {
|
||||
this.generating = true;
|
||||
try {
|
||||
const wb = newWorkbook();
|
||||
|
||||
buildGeneralInfoSheet(wb, this.generalInfo);
|
||||
this.buildBalanceSheet(wb);
|
||||
this.buildAnnexure1(wb);
|
||||
this.buildAnnexure2(wb);
|
||||
buildSignatorySheet(wb, this.signatories);
|
||||
|
||||
await downloadWorkbook(wb, `Form RAL_CIMS ${this.returnCode} OSS 1 - ${this.quarterEndDate}.xlsx`);
|
||||
this.toast(`${this.returnCode} workbook generated for quarter ending ${this.quarterEndDate}`);
|
||||
} catch {
|
||||
this.toast('Could not generate the workbook — please retry');
|
||||
} finally {
|
||||
this.generating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private buildBalanceSheet(wb: ExcelJS.Workbook): void {
|
||||
const sheet = wb.addWorksheet('Assets & Liabilities');
|
||||
sheet.columns = [
|
||||
{ width: 78 },
|
||||
{ width: 20 }
|
||||
];
|
||||
addBanner(sheet, 'Assets & Liabilities', 'B');
|
||||
|
||||
const first = sheet.rowCount + 1;
|
||||
addSectionHeader(sheet, 'Liabilities', 'Rs in Thousands');
|
||||
addLineRows(sheet, this.liabilities);
|
||||
|
||||
addSectionHeader(sheet, 'Assets', 'Rs in Thousands');
|
||||
addLineRows(sheet, this.assets);
|
||||
boxAllCells(sheet, first, sheet.rowCount, 2);
|
||||
|
||||
sheet.addRow([]);
|
||||
const notesStart = sheet.rowCount + 1;
|
||||
for (const note of this.balanceSheetNotes) {
|
||||
const row = sheet.addRow([note]);
|
||||
sheet.mergeCells(`A${row.number}:B${row.number}`);
|
||||
row.font = { bold: true, size: 10 };
|
||||
row.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
}
|
||||
boxAllCells(sheet, notesStart, sheet.rowCount, 2);
|
||||
|
||||
sheet.addRow([]);
|
||||
const memoStart = sheet.rowCount + 1;
|
||||
addSectionHeader(sheet, 'Memo', 'Rs in Thousands');
|
||||
addLineRows(sheet, this.memo);
|
||||
boxAllCells(sheet, memoStart, sheet.rowCount, 2);
|
||||
|
||||
sheet.views = [{ state: 'frozen', ySplit: 3 }];
|
||||
}
|
||||
|
||||
private buildAnnexure1(wb: ExcelJS.Workbook): void {
|
||||
const sheet = wb.addWorksheet('Annexure 1');
|
||||
sheet.columns = [
|
||||
{ width: 10 },
|
||||
{ width: 46 },
|
||||
{ width: 20 },
|
||||
{ width: 18 },
|
||||
{ width: 16 },
|
||||
{ width: 12 },
|
||||
{ width: 12 },
|
||||
{ width: 12 }
|
||||
];
|
||||
addBanner(sheet, 'Annexure 1', 'H');
|
||||
|
||||
// 1. Break-up of Other Funds and Reserves
|
||||
let start = sheet.rowCount + 1;
|
||||
const fundsTitle = sheet.addRow(['1. Break-up of Other Funds and Reserves', '', 'Rs in Thousands']);
|
||||
sheet.mergeCells(`A${fundsTitle.number}:B${fundsTitle.number}`);
|
||||
fundsTitle.font = { bold: true };
|
||||
fundsTitle.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
for (const line of this.otherFunds) {
|
||||
const row = sheet.addRow([line.label, '', line.value]);
|
||||
sheet.mergeCells(`A${row.number}:B${row.number}`);
|
||||
row.getCell(1).alignment = { wrapText: true, vertical: 'middle' };
|
||||
row.getCell(3).numFmt = '#,##0.00';
|
||||
if (line.total) {
|
||||
row.font = { bold: true };
|
||||
row.getCell(3).fill = solidFill(XL.derivedBlue);
|
||||
row.getCell(1).alignment = { horizontal: 'center' };
|
||||
}
|
||||
}
|
||||
boxAllCells(sheet, start, sheet.rowCount, 3);
|
||||
sheet.addRow([]);
|
||||
|
||||
// 2. Top 20 deposits from other banks / institutions
|
||||
start = sheet.rowCount + 1;
|
||||
const depTitle = sheet.addRow([
|
||||
`2. Particulars of top 20 deposits accepted from other banks / and institutions as on the date of review`
|
||||
]);
|
||||
sheet.mergeCells(`A${depTitle.number}:H${depTitle.number}`);
|
||||
depTitle.font = { bold: true };
|
||||
depTitle.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
|
||||
const depHead = sheet.addRow([
|
||||
'Sr. No',
|
||||
'Name of Bank / Institution',
|
||||
'Amount of deposit',
|
||||
'Rate of interest (%)',
|
||||
'Date of receipt',
|
||||
'Period in Days',
|
||||
'Period in Months',
|
||||
'Period in Years'
|
||||
]);
|
||||
depHead.font = { bold: true };
|
||||
depHead.alignment = { wrapText: true, horizontal: 'center', vertical: 'middle' };
|
||||
depHead.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
for (const d of this.topDeposits) {
|
||||
const row = sheet.addRow([
|
||||
d.srNo,
|
||||
d.institution,
|
||||
d.amount,
|
||||
d.rateOfInterest,
|
||||
d.dateOfReceipt,
|
||||
d.days,
|
||||
d.months,
|
||||
d.years
|
||||
]);
|
||||
row.getCell(3).numFmt = '#,##0.00';
|
||||
}
|
||||
boxAllCells(sheet, start, sheet.rowCount, 8);
|
||||
sheet.addRow([]);
|
||||
|
||||
// 3 & 4 — ageing analyses
|
||||
start = sheet.rowCount + 1;
|
||||
this.addAgeingBlock(
|
||||
sheet,
|
||||
`3. Age-wise analysis of Inter Branch Adjustment Account - Position as on:${this.quarterEndDate}`,
|
||||
this.interBranchAgeing
|
||||
);
|
||||
sheet.addRow([]);
|
||||
this.addAgeingBlock(
|
||||
sheet,
|
||||
`4. Age-wise analysis of Inter Bank Reconciliation Account - Position as on:${this.quarterEndDate}`,
|
||||
this.interBankAgeing
|
||||
);
|
||||
sheet.addRow([]);
|
||||
|
||||
// 5. Unsecured guarantees
|
||||
start = sheet.rowCount + 1;
|
||||
const gTitle = sheet.addRow(['5. Unsecured guarantees issued by the bank']);
|
||||
sheet.mergeCells(`A${gTitle.number}:D${gTitle.number}`);
|
||||
gTitle.font = { bold: true };
|
||||
gTitle.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
|
||||
const gHead = sheet.addRow([
|
||||
'Sr. No',
|
||||
'Name of Borrower',
|
||||
'Name of Beneficiary',
|
||||
`Amount Outstanding as on:${this.quarterEndDate}`
|
||||
]);
|
||||
gHead.font = { bold: true };
|
||||
gHead.alignment = { wrapText: true, horizontal: 'center', vertical: 'middle' };
|
||||
gHead.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
for (const g of this.unsecuredGuarantees) {
|
||||
const row = sheet.addRow([g.srNo, g.borrower, g.beneficiary, g.amountOutstanding]);
|
||||
row.getCell(4).numFmt = '#,##0.00';
|
||||
}
|
||||
const gTotal = sheet.addRow(['Total', '', '', this.unsecuredGuaranteesTotal]);
|
||||
gTotal.font = { bold: true };
|
||||
gTotal.getCell(4).numFmt = '#,##0.00';
|
||||
gTotal.getCell(4).fill = solidFill(XL.derivedBlue);
|
||||
|
||||
const gNote = sheet.addRow([
|
||||
'Note : * Give name if borrower/beneficiary is director/top management/related entity. Other guarantees may be consolidated and reported against others.'
|
||||
]);
|
||||
sheet.mergeCells(`A${gNote.number}:D${gNote.number}`);
|
||||
gNote.font = { bold: true, size: 10 };
|
||||
gNote.alignment = { wrapText: true };
|
||||
boxAllCells(sheet, start, sheet.rowCount, 4);
|
||||
}
|
||||
|
||||
private addAgeingBlock(sheet: ExcelJS.Worksheet, title: string, rows: typeof RAL_INTER_BRANCH_AGEING): void {
|
||||
const start = sheet.rowCount + 1;
|
||||
const titleRow = sheet.addRow([title]);
|
||||
sheet.mergeCells(`A${titleRow.number}:E${titleRow.number}`);
|
||||
titleRow.font = { bold: true };
|
||||
titleRow.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
|
||||
const head = sheet.addRow([...this.ageingHeaders]);
|
||||
head.font = { bold: true };
|
||||
head.alignment = { wrapText: true, horizontal: 'center', vertical: 'middle' };
|
||||
head.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
|
||||
for (const r of rows) {
|
||||
const row = sheet.addRow([
|
||||
r.period,
|
||||
r.noOfDebitEntries,
|
||||
r.amtOfDebitEntries,
|
||||
r.noOfCreditEntries,
|
||||
r.amtOfCreditEntries
|
||||
]);
|
||||
row.getCell(3).numFmt = '#,##0.00';
|
||||
row.getCell(5).numFmt = '#,##0.00';
|
||||
if (r.period === 'Total') {
|
||||
row.font = { bold: true };
|
||||
[
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
].forEach(
|
||||
(c) => (row.getCell(c).fill = solidFill(XL.derivedBlue))
|
||||
);
|
||||
}
|
||||
}
|
||||
boxAllCells(sheet, start, sheet.rowCount, 5);
|
||||
}
|
||||
|
||||
private buildAnnexure2(wb: ExcelJS.Workbook): void {
|
||||
const sheet = wb.addWorksheet('Annexure 2');
|
||||
sheet.columns = [
|
||||
{ width: 10 },
|
||||
{ width: 62 },
|
||||
{ width: 20 },
|
||||
{ width: 20 }
|
||||
];
|
||||
addBanner(sheet, 'Annexure 2', 'D');
|
||||
|
||||
for (const block of this.sundryDebtors) {
|
||||
const start = sheet.rowCount + 1;
|
||||
const t = sheet.addRow([block.title]);
|
||||
sheet.mergeCells(`A${t.number}:D${t.number}`);
|
||||
t.font = { bold: true };
|
||||
t.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
|
||||
const s = sheet.addRow([block.subtitle]);
|
||||
sheet.mergeCells(`A${s.number}:D${s.number}`);
|
||||
s.font = { bold: true, size: 10 };
|
||||
s.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
|
||||
const head = sheet.addRow(['Sr. No', 'Due from Name', 'Amount due', 'Provision made']);
|
||||
head.font = { bold: true };
|
||||
head.alignment = { horizontal: 'center' };
|
||||
head.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
|
||||
for (const r of block.rows) {
|
||||
const row = sheet.addRow([r.srNo, r.dueFromName, r.amountDue, r.provisionMade]);
|
||||
row.getCell(3).numFmt = '#,##0.00';
|
||||
row.getCell(4).numFmt = '#,##0.00';
|
||||
}
|
||||
|
||||
const note = sheet.addRow([this.sundryDebtorsNote]);
|
||||
sheet.mergeCells(`A${note.number}:D${note.number}`);
|
||||
note.font = { bold: true, size: 10 };
|
||||
note.alignment = { wrapText: true };
|
||||
boxAllCells(sheet, start, sheet.rowCount, 4);
|
||||
sheet.addRow([]);
|
||||
}
|
||||
|
||||
// Details of Depositors & Borrowers
|
||||
let start = sheet.rowCount + 1;
|
||||
const dbTitle = sheet.addRow(['Details of Depositors & Borrowers', '', 'Rs. In Thousands']);
|
||||
sheet.mergeCells(`A${dbTitle.number}:B${dbTitle.number}`);
|
||||
dbTitle.font = { bold: true };
|
||||
dbTitle.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
dbTitle.getCell(3).alignment = { horizontal: 'right' };
|
||||
|
||||
for (const line of this.depositorsBorrowers) {
|
||||
const row = sheet.addRow([line.label, '', line.value]);
|
||||
sheet.mergeCells(`A${row.number}:B${row.number}`);
|
||||
row.getCell(1).alignment = { wrapText: true, vertical: 'middle', indent: line.level };
|
||||
row.getCell(3).numFmt = line.count ? '#,##0' : '#,##0.00';
|
||||
if (line.total) {
|
||||
row.font = { bold: true };
|
||||
row.getCell(3).fill = solidFill(XL.derivedBlue);
|
||||
}
|
||||
}
|
||||
boxAllCells(sheet, start, sheet.rowCount, 3);
|
||||
sheet.addRow([]);
|
||||
|
||||
// Complaints
|
||||
start = sheet.rowCount + 1;
|
||||
const cTitle = sheet.addRow(['Complaints Received from public']);
|
||||
sheet.mergeCells(`A${cTitle.number}:B${cTitle.number}`);
|
||||
cTitle.font = { bold: true };
|
||||
cTitle.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
for (const line of this.complaints) {
|
||||
const row = sheet.addRow([line.label, '', line.value]);
|
||||
sheet.mergeCells(`A${row.number}:B${row.number}`);
|
||||
row.getCell(1).alignment = { wrapText: true, vertical: 'middle' };
|
||||
row.getCell(3).numFmt = '#,##0';
|
||||
}
|
||||
boxAllCells(sheet, start, sheet.rowCount, 3);
|
||||
}
|
||||
|
||||
private toast(msg: string): void {
|
||||
this.toastMsg = msg;
|
||||
this.toastVisible = true;
|
||||
if (this.toastTimer) {
|
||||
clearTimeout(this.toastTimer);
|
||||
}
|
||||
this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* OSS 2 — Return on Earnings (RoE), return code R071.
|
||||
*
|
||||
* Mirrors the RBI CIMS "Form OSS-2_CIMS Template1" workbook sheet-for-sheet:
|
||||
* General Information / Earnings / Interest Receivable Not Recognized /
|
||||
* Other Operating Expenses / Signatory Information
|
||||
*
|
||||
* All monetary values are Rs in Thousands. A `null` value means the source return
|
||||
* leaves that cell blank — RBI treats blank and zero differently, so the two are
|
||||
* never collapsed into one another.
|
||||
*/
|
||||
|
||||
import { OSS_BANK, OSS_SIGNATORIES, ReturnGeneralInfoRow, ReturnLine, ReturnSignatory } from '../oss-return.model';
|
||||
|
||||
export type RoeLine = ReturnLine;
|
||||
export type RoeSignatory = ReturnSignatory;
|
||||
|
||||
/** A row on the Interest Receivable Not Recognized sheet. */
|
||||
export interface RoeInterestNotRecognizedRow {
|
||||
label: string;
|
||||
value: number | null;
|
||||
total?: boolean;
|
||||
}
|
||||
|
||||
/** A line item on the Other Operating Expenses detail sheet. */
|
||||
export interface RoeOtherExpense {
|
||||
srNo: number;
|
||||
particulars: string;
|
||||
amount: number | null;
|
||||
}
|
||||
|
||||
/** Sheet 1 — General Information */
|
||||
export const ROE_GENERAL_INFO: ReturnGeneralInfoRow[] = [
|
||||
{ label: 'Return Name', value: 'Return on Earnings (RoE)' },
|
||||
{ label: 'Return Code', value: 'R071' },
|
||||
{ label: 'Name Of Bank', value: OSS_BANK.name },
|
||||
{ label: 'Bank Code', value: OSS_BANK.bankCode },
|
||||
{ label: 'RO Name', value: OSS_BANK.roName },
|
||||
{ label: 'Quarter End Date', value: OSS_BANK.quarterEndDate },
|
||||
{ label: 'Reporting Frequency', value: OSS_BANK.reportingFrequency },
|
||||
{ label: 'Audit Flag', value: OSS_BANK.auditFlag },
|
||||
{ label: 'Date of Audit', value: OSS_BANK.dateOfAudit },
|
||||
{ label: 'Return Version', value: OSS_BANK.returnVersion },
|
||||
{ label: 'Start Date', value: OSS_BANK.startDate }
|
||||
];
|
||||
|
||||
/** Sheet 2 — Earnings (items 1 to 33) */
|
||||
export const ROE_EARNINGS: RoeLine[] = [
|
||||
{ label: '1. Interest/discount received (2+3+4+5)', level: 0, value: 15069, total: true },
|
||||
{
|
||||
label: '2. Interest/discount received on loans and advances (other than from banks)',
|
||||
level: 0,
|
||||
value: 12070,
|
||||
total: true
|
||||
},
|
||||
{ label: 'i) Actually received', level: 1, value: 12070 },
|
||||
{ label: 'ii) Accrued interest on Standard advances', level: 1, value: null },
|
||||
{ label: '3. Interest on market lending ( to banks, if any)', level: 0, value: null },
|
||||
{ label: '4. Interest on investments', level: 0, value: 2999 },
|
||||
{ label: 'i) Interest received /accrued on Standard Investments', level: 1, value: null },
|
||||
{
|
||||
label: '5. Interest on additional balance with RBI, inter-bank deposits, placements and credits',
|
||||
level: 0,
|
||||
value: null
|
||||
},
|
||||
|
||||
{ label: '6. Other Operating income', level: 0, value: 285, total: true },
|
||||
{ label: 'i) Fee, Commission, exchange and brokerage', level: 1, value: 51 },
|
||||
{ label: 'ii) Profit (+) / Loss (-) on forex operations', level: 1, value: null },
|
||||
{ label: 'iii) Profit (+) / Loss (-) on trading and sale of securities', level: 1, value: null },
|
||||
{ label: 'iv) Dividend Income', level: 1, value: null },
|
||||
{ label: 'v) Miscellaneous Income', level: 1, value: 234 },
|
||||
|
||||
{ label: '7. Total Operating Income [1+6]', level: 0, value: 15354, total: true },
|
||||
|
||||
{ label: '8. Non Operating Income [Capital gains / losses (+)/ (-)]', level: 0, value: 0, total: true },
|
||||
{ label: 'i) Profit / (Loss) on sale of fixed assets (+) / (-)', level: 1, value: null },
|
||||
{ label: 'ii) Profit / (Loss) on sale of other assets (+) / (-)', level: 1, value: null },
|
||||
{ label: 'iii) Other Non-operating Income / (Expenses) (+) / (-)', level: 1, value: null },
|
||||
|
||||
{ label: '9. Total income[7+8]', level: 0, value: 15354, total: true },
|
||||
|
||||
{ label: '10. Interest paid', level: 0, value: 5659, total: true },
|
||||
{ label: 'i) On Customer deposits', level: 1, value: 5659 },
|
||||
{ label: 'ii) On Inter-bank deposits', level: 1, value: null },
|
||||
{ label: 'iii) On Inter-bank borrowings (including from RBI/FIs)', level: 1, value: null },
|
||||
{ label: 'iv) Others', level: 1, value: null },
|
||||
|
||||
{ label: '11. Operating Expenses', level: 0, value: 3352, total: true },
|
||||
{ label: 'i) Staff expenses', level: 1, value: 3244 },
|
||||
{ label: 'ii) Directors fees', level: 1, value: 45 },
|
||||
{ label: "iii) Auditors' fees", level: 1, value: 63 },
|
||||
|
||||
{ label: '12. Other Operating expenses', level: 0, value: 1038, total: true },
|
||||
{ label: 'i) Rent, taxes and lighting', level: 1, value: null },
|
||||
{ label: 'ii) Insurance', level: 1, value: 4 },
|
||||
{ label: 'iii) Law charges', level: 1, value: 0 },
|
||||
{ label: 'iv) Postage, telegrams and stamps', level: 1, value: 34 },
|
||||
{ label: 'v) Stationery and printing', level: 1, value: 23 },
|
||||
{ label: 'vi) Advertisement and publicity', level: 1, value: null },
|
||||
{ label: "vii) Depreciation on bank's property", level: 1, value: null },
|
||||
{ label: 'viii) Repairs and maintenance', level: 1, value: null },
|
||||
{ label: 'ix) Others', level: 1, value: 977, total: true },
|
||||
|
||||
{ label: '13. Total operating expenses [10 + 11+ 12]', level: 0, value: 10049, total: true },
|
||||
{ label: '14. Operating Profit/Loss [7 - 13]', level: 0, value: 5305, total: true },
|
||||
|
||||
{
|
||||
label:
|
||||
'15. Value adjustment of Securities (marking to market of securities in AFS & HFT) [Only loss is to be reported]',
|
||||
level: 0,
|
||||
value: null
|
||||
},
|
||||
|
||||
{ label: '16. Write off', level: 0, value: 0, total: true },
|
||||
{ label: 'i) Bad Debts written off', level: 1, value: null },
|
||||
{ label: 'ii) Other assets written off', level: 1, value: null },
|
||||
{ label: 'iii) Capitalised expenditure written off', level: 1, value: null },
|
||||
|
||||
{ label: '17. Provision against risks/ contingencies', level: 0, value: 0, total: true },
|
||||
{ label: 'i) Provision for loan losses (BDDR, Spl. BDDR, etc.) *', level: 1, value: null },
|
||||
{ label: 'ii) Provision for depreciation in investments including AFS #', level: 1, value: null },
|
||||
{
|
||||
label:
|
||||
'iii) Other risk provisions towards losses on account of frauds, misappropriations, robberies, claims against the bank, etc.',
|
||||
level: 1,
|
||||
value: null
|
||||
},
|
||||
{ label: 'iv) Other provisions', level: 1, value: null },
|
||||
|
||||
{ label: '18. Net Operating Profit (+)/Loss (-) [14 - 15 - 16 - 17]', level: 0, value: 5305, total: true },
|
||||
{ label: '19. Non Operating Income [Capital gains]', level: 0, value: 0, total: true },
|
||||
{ label: '20. Non Operating Income [Capital losses]', level: 0, value: 0, total: true },
|
||||
{ label: '21. Net Profit (+)/ Loss (-) before taxes - (EBPT) [18 + 19 - 20]', level: 0, value: 5305, total: true },
|
||||
{ label: '22. Provisions for taxes (if any)', level: 0, value: 500 },
|
||||
{ label: '23. Net Profit (+)/ Loss (-) after taxes [21 - 22]', level: 0, value: 4805, total: true },
|
||||
{ label: '24. Balance of previous year’s profit (+) / Accumulated losses (-)', level: 0, value: 5592 },
|
||||
{ label: '25. Net distributable profit (+) / Accumulated losses (-) [23 + 24]', level: 0, value: 10397, total: true },
|
||||
|
||||
{ label: '26. Appropriation of profits', level: 0, value: 10397, total: true },
|
||||
{ label: 'i) Appropriation towards Statutory Reserves', level: 1, value: 3638 },
|
||||
{ label: 'ii) Appropriation towards Building Fund', level: 1, value: 519 },
|
||||
{ label: 'iii) Appropriation towards Dividend Equalisation Fund', level: 1, value: null },
|
||||
{ label: 'iv) Appropriation towards Other Reserves/ Provisions', level: 1, value: 4058 },
|
||||
{ label: 'v) Dividend', level: 1, value: 2182 },
|
||||
|
||||
{ label: '27. Net interest income', level: 0, value: 9410, total: true },
|
||||
{ label: '28. Non-interest income', level: 0, value: 285, total: true },
|
||||
{ label: '29. Non-interest expenditure', level: 0, value: 4390, total: true },
|
||||
{ label: '30. Average Total Assets', level: 0, value: 796295 },
|
||||
{ label: '31. Average Earning Assets', level: 0, value: 716407 },
|
||||
{ label: '32. Working Fund', level: 0, value: 778829 },
|
||||
{ label: '33. Number of Employee', level: 0, value: 11, count: true }
|
||||
];
|
||||
|
||||
/**
|
||||
* The two cross-return tie-ups printed at the foot of the Earnings sheet. Both
|
||||
* reference line items on OSS 1, which is why the page checks them live.
|
||||
*/
|
||||
export const ROE_EARNINGS_NOTES: string[] = [
|
||||
'Note: * The value of {17 (i) Provision for loan losses (BDDR, Spl. BDDR, etc.)} should tally with {12(ii) {BDDR, spl. BDDR} of liabilities in OSS-1.',
|
||||
'Note: # The value of {17 (ii) Provision for Depreciation in investments including AFS} should tally with {i. For investments (Investment Depreciation Reserve)} of liabilities in OSS-1 .'
|
||||
];
|
||||
|
||||
/** Sheet 3 — Interest Receivable Not Recognized */
|
||||
export const ROE_INTEREST_NOT_RECOGNIZED: RoeInterestNotRecognizedRow[] = [
|
||||
{ label: 'i. Interest receivable not recognized during the quarter', value: 2614 },
|
||||
{ label: 'ii. Balance at the beginning of the quarter', value: 35101 },
|
||||
{
|
||||
label:
|
||||
'iii. Interest received and reversed to income from (i) in the quarter on NPAs upgraded to Performing / accrual basis',
|
||||
value: 5620
|
||||
},
|
||||
{ label: 'Balance at the end of the quarter (i)+(ii)-(iii)', value: 32095, total: true }
|
||||
];
|
||||
|
||||
/** Sheet 4 — Details of Other Operating Expenses (feeds Earnings line 12 ix) */
|
||||
export const ROE_OTHER_OPERATING_EXPENSES: RoeOtherExpense[] = [
|
||||
{ srNo: 1, particulars: 'Miscelaneous expenses', amount: 55 },
|
||||
{ srNo: 2, particulars: 'Entertainment Exps', amount: 24 },
|
||||
{ srNo: 3, particulars: 'Commission On Mini Deposits', amount: 317 },
|
||||
{ srNo: 4, particulars: 'AGM expenses', amount: 19 },
|
||||
{ srNo: 5, particulars: 'Computer Exps', amount: 441 },
|
||||
{ srNo: 6, particulars: 'Govt sec premium', amount: 0 },
|
||||
{ srNo: 7, particulars: 'CIC Member Ship', amount: 0 },
|
||||
{ srNo: 8, particulars: 'transaction expenses', amount: 7 },
|
||||
{ srNo: 9, particulars: 'Digital Banking', amount: 11 },
|
||||
{ srNo: 10, particulars: 'GST', amount: 26 },
|
||||
{ srNo: 11, particulars: 'inventory write off', amount: 0 },
|
||||
{ srNo: 12, particulars: 'Electricity Expenses', amount: 77 },
|
||||
{ srNo: 13, particulars: 'Donation to Jila Sahakari', amount: 0 }
|
||||
];
|
||||
|
||||
/** Sheet 5 — Signatory Information. Shared so OSS 1 and OSS 2 always agree. */
|
||||
export const ROE_SIGNATORIES: RoeSignatory[] = OSS_SIGNATORIES;
|
||||
@@ -0,0 +1,278 @@
|
||||
<!--
|
||||
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="oss-root">
|
||||
<!-- HEADER -->
|
||||
<div class="oss-head">
|
||||
<div class="oss-head-left">
|
||||
<fa-icon icon="university" class="oss-head-ic"></fa-icon>
|
||||
<div>
|
||||
<h2 class="oss-head-t">OSS 2 — {{ returnName }}</h2>
|
||||
<div class="oss-head-s">
|
||||
{{ bankName }} · Bank Code <strong>{{ bankCode }}</strong> · Return Code
|
||||
<strong>{{ returnCode }}</strong> · Quarter ending <strong>{{ quarterEndDate }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oss-head-right">
|
||||
<span class="oss-audit-pill">{{ auditFlag }}</span>
|
||||
<button class="oss-btn oss-btn-xls" [disabled]="generating" (click)="generateExcel()">
|
||||
<fa-icon icon="file-csv"></fa-icon>
|
||||
{{ generating ? 'Generating…' : 'Generate Excel' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HEADLINE RESULT -->
|
||||
<div class="oss-balance">
|
||||
<fa-icon icon="check-circle"></fa-icon>
|
||||
<span>
|
||||
Operating Profit <strong>₹{{ operatingProfit | number: '1.2-2' }}</strong> · Net Profit after tax
|
||||
<strong>₹{{ netProfitAfterTax | number: '1.2-2' }}</strong> (Rs in Thousands)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- TABS -->
|
||||
<div class="oss-tabs">
|
||||
<button class="oss-tab" [class.active]="activeTab === 'general'" (click)="showTab('general')">
|
||||
<fa-icon icon="info-circle"></fa-icon> General Information
|
||||
</button>
|
||||
<button class="oss-tab" [class.active]="activeTab === 'earnings'" (click)="showTab('earnings')">
|
||||
<fa-icon icon="university"></fa-icon> Earnings
|
||||
</button>
|
||||
<button class="oss-tab" [class.active]="activeTab === 'interest'" (click)="showTab('interest')">
|
||||
<fa-icon icon="exclamation-triangle"></fa-icon> Interest Not Recognized
|
||||
</button>
|
||||
<button class="oss-tab" [class.active]="activeTab === 'other-expenses'" (click)="showTab('other-expenses')">
|
||||
<fa-icon icon="file-alt"></fa-icon> Other Operating Expenses
|
||||
</button>
|
||||
<button class="oss-tab" [class.active]="activeTab === 'signatory'" (click)="showTab('signatory')">
|
||||
<fa-icon icon="check-circle"></fa-icon> Signatory
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- GENERAL INFORMATION -->
|
||||
@if (activeTab === 'general') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">General Information</div>
|
||||
<div class="oss-tbl-wrap">
|
||||
<table class="oss-tbl oss-tbl--kv">
|
||||
<tbody>
|
||||
@for (item of generalInfo; track item.label) {
|
||||
<tr>
|
||||
<th>{{ item.label }}</th>
|
||||
<td [class.filled]="item.value" [class.audit]="item.label === 'Audit Flag'">{{ item.value }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- EARNINGS -->
|
||||
@if (activeTab === 'earnings') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Earnings</div>
|
||||
|
||||
<!-- Cross-return tie-ups the footnotes demand -->
|
||||
<div class="oss-balance" [class.oss-balance--bad]="!allTieUpsMatch">
|
||||
<fa-icon [icon]="allTieUpsMatch ? 'check-circle' : 'exclamation-triangle'"></fa-icon>
|
||||
<span>
|
||||
{{
|
||||
allTieUpsMatch
|
||||
? 'Item 17 tallies with OSS-1 liabilities as the footnotes require.'
|
||||
: 'Item 17 does not tally with OSS-1 liabilities — see the tie-up check below.'
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">OSS-1 tie-up check</div>
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>OSS-2 item</th>
|
||||
<th class="ta-r">OSS-2 value</th>
|
||||
<th>Must tally with</th>
|
||||
<th class="ta-r">OSS-1 value</th>
|
||||
<th class="ta-c">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (t of oss1TieUps; track t.label) {
|
||||
<tr>
|
||||
<td>{{ t.label }}</td>
|
||||
<td class="ta-r mono">{{ t.oss2Value === null ? '—' : (t.oss2Value | number: '1.2-2') }}</td>
|
||||
<td>{{ t.oss1Label }}</td>
|
||||
<td class="ta-r mono">{{ t.oss1Value === null ? '—' : (t.oss1Value | number: '1.2-2') }}</td>
|
||||
<td class="ta-c">
|
||||
<span [class]="t.tallies ? 'report-pill report-ok' : 'report-pill report-bad'">
|
||||
{{ t.tallies ? 'Tallies' : 'Mismatch' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Particulars</th>
|
||||
<th class="ta-r">Rs in Thousand</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (line of earnings; track $index) {
|
||||
<tr [class.total-row]="line.total">
|
||||
<td [class]="'lvl-' + line.level">{{ line.label }}</td>
|
||||
<td class="ta-r mono" [class.derived]="line.total">
|
||||
{{ line.value === null ? '' : (line.value | number: line.count ? '1.0-0' : '1.2-2') }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="oss-notes">
|
||||
@for (note of earningsNotes; track note) {
|
||||
<div class="oss-note-line">{{ note }}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- INTEREST RECEIVABLE NOT RECOGNIZED -->
|
||||
@if (activeTab === 'interest') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Interest Receivable Not Recognized</div>
|
||||
<div class="oss-tbl-wrap">
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Interest receivable on NPAs not recognized as income</th>
|
||||
<th class="ta-r">{{ quarterEndDate }} (Rs in Thousand)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (r of interestNotRecognized; track r.label) {
|
||||
<tr [class.total-row]="r.total">
|
||||
<td>{{ r.label }}</td>
|
||||
<td class="ta-r mono" [class.derived]="r.total">
|
||||
{{ r.value === null ? '' : (r.value | number: '1.2-2') }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- OTHER OPERATING EXPENSES -->
|
||||
@if (activeTab === 'other-expenses') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Other Operating Expenses</div>
|
||||
|
||||
<div class="oss-balance" [class.oss-balance--bad]="!otherExpensesTally">
|
||||
<fa-icon [icon]="otherExpensesTally ? 'check-circle' : 'exclamation-triangle'"></fa-icon>
|
||||
<span>
|
||||
Detail total <strong>₹{{ otherExpensesTotal | number: '1.2-2' }}</strong> vs Earnings line 12 ix)
|
||||
Others
|
||||
<strong>₹{{ otherExpensesLineValue === null ? '—' : (otherExpensesLineValue | number: '1.2-2') }}</strong>
|
||||
— {{ otherExpensesTally ? 'tallies' : 'MISMATCH — resolve before filing' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">Details of Other Operating Expenses — Rs in Thousand</div>
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="ta-c">Sr No</th>
|
||||
<th>Particulars</th>
|
||||
<th class="ta-r">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (e of otherExpenses; track e.srNo) {
|
||||
<tr>
|
||||
<td class="ta-c">{{ e.srNo }}</td>
|
||||
<td>{{ e.particulars }}</td>
|
||||
<td class="ta-r mono">{{ e.amount === null ? '' : (e.amount | number: '1.2-2') }}</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="total-row">
|
||||
<td class="ta-c"></td>
|
||||
<td>Total</td>
|
||||
<td class="ta-r mono derived">{{ otherExpensesTotal | number: '1.2-2' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="oss-foot-note">Feeds Earnings sheet line 12 ix) Others.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- SIGNATORY -->
|
||||
@if (activeTab === 'signatory') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Signatory Information</div>
|
||||
<div class="oss-sign-grid">
|
||||
@for (p of signatories; track p.heading) {
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">{{ p.heading }}</div>
|
||||
<table class="oss-tbl oss-tbl--kv">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ p.signature }}</td>
|
||||
<th>Signature</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.name }}</td>
|
||||
<th>Name</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.designation }}</td>
|
||||
<th>Designation</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.email }}</td>
|
||||
<th>e-mail ID</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono">{{ p.telephoneOffice }}</td>
|
||||
<th>Tel. No. (O)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono">{{ p.telephoneResidence }}</td>
|
||||
<th>Tel. No. (R)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.place }}</td>
|
||||
<th>Place</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.date }}</td>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- TOAST -->
|
||||
<div class="oss-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|
||||
</div>
|
||||
@@ -0,0 +1,283 @@
|
||||
/**
|
||||
* 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 * as ExcelJS from 'exceljs';
|
||||
import {
|
||||
addBanner,
|
||||
addLineRows,
|
||||
addSectionHeader,
|
||||
boxAllCells,
|
||||
buildGeneralInfoSheet,
|
||||
buildSignatorySheet,
|
||||
downloadWorkbook,
|
||||
newWorkbook,
|
||||
solidFill,
|
||||
XL
|
||||
} from '../oss-workbook';
|
||||
import { RAL_LIABILITIES } from '../oss1-ral/oss1-ral-data';
|
||||
import {
|
||||
ROE_EARNINGS,
|
||||
ROE_EARNINGS_NOTES,
|
||||
ROE_GENERAL_INFO,
|
||||
ROE_INTEREST_NOT_RECOGNIZED,
|
||||
ROE_OTHER_OPERATING_EXPENSES,
|
||||
ROE_SIGNATORIES
|
||||
} from './oss2-roe-data';
|
||||
|
||||
type Oss2Tab = 'general' | 'earnings' | 'interest' | 'other-expenses' | 'signatory';
|
||||
|
||||
/** One of the two OSS-1 tie-ups printed at the foot of the Earnings sheet. */
|
||||
interface TieUp {
|
||||
label: string;
|
||||
oss2Value: number | null;
|
||||
oss1Label: string;
|
||||
oss1Value: number | null;
|
||||
tallies: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-oss2-roe',
|
||||
templateUrl: './oss2-roe.component.html',
|
||||
styleUrls: ['../oss-return.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent,
|
||||
FormsModule
|
||||
]
|
||||
})
|
||||
export class Oss2RoeComponent {
|
||||
activeTab: Oss2Tab = 'general';
|
||||
|
||||
generalInfo = ROE_GENERAL_INFO;
|
||||
earnings = ROE_EARNINGS;
|
||||
earningsNotes = ROE_EARNINGS_NOTES;
|
||||
interestNotRecognized = ROE_INTEREST_NOT_RECOGNIZED;
|
||||
otherExpenses = ROE_OTHER_OPERATING_EXPENSES;
|
||||
signatories = ROE_SIGNATORIES;
|
||||
|
||||
generating = false;
|
||||
toastMsg = '';
|
||||
toastVisible = false;
|
||||
private toastTimer: any;
|
||||
|
||||
showTab(tab: Oss2Tab): void {
|
||||
this.activeTab = tab;
|
||||
}
|
||||
|
||||
private info(label: string): string {
|
||||
return this.generalInfo.find((g) => g.label === label)?.value ?? '';
|
||||
}
|
||||
|
||||
get returnName(): string {
|
||||
return this.info('Return Name');
|
||||
}
|
||||
get returnCode(): string {
|
||||
return this.info('Return Code');
|
||||
}
|
||||
get bankName(): string {
|
||||
return this.info('Name Of Bank');
|
||||
}
|
||||
get bankCode(): string {
|
||||
return this.info('Bank Code');
|
||||
}
|
||||
get quarterEndDate(): string {
|
||||
return this.info('Quarter End Date');
|
||||
}
|
||||
get auditFlag(): string {
|
||||
return this.info('Audit Flag');
|
||||
}
|
||||
|
||||
private earningsValue(prefix: string): number | null {
|
||||
return this.earnings.find((l) => l.label.startsWith(prefix))?.value ?? null;
|
||||
}
|
||||
|
||||
private liabilityValue(prefix: string): number | null {
|
||||
return RAL_LIABILITIES.find((l) => l.label.startsWith(prefix))?.value ?? null;
|
||||
}
|
||||
|
||||
get netProfitAfterTax(): number {
|
||||
return this.earningsValue('23.') ?? 0;
|
||||
}
|
||||
get operatingProfit(): number {
|
||||
return this.earningsValue('14.') ?? 0;
|
||||
}
|
||||
|
||||
/** Sum of the detail sheet — must equal Earnings line 12 ix) Others. */
|
||||
get otherExpensesTotal(): number {
|
||||
return this.otherExpenses.reduce((sum, e) => sum + (e.amount ?? 0), 0);
|
||||
}
|
||||
get otherExpensesLineValue(): number | null {
|
||||
return this.earningsValue('ix) Others');
|
||||
}
|
||||
get otherExpensesTally(): boolean {
|
||||
return this.otherExpensesTotal === (this.otherExpensesLineValue ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Earnings sheet carries two footnotes requiring 17(i) and 17(ii) to tally
|
||||
* with OSS-1 liabilities 12(ii) and 12(i). Checked live rather than left to a
|
||||
* reader spotting it in the fine print.
|
||||
*/
|
||||
get oss1TieUps(): TieUp[] {
|
||||
const rows: TieUp[] = [
|
||||
{
|
||||
label: '17 (i) Provision for loan losses (BDDR, Spl. BDDR, etc.)',
|
||||
oss2Value: this.earningsValue('i) Provision for loan losses'),
|
||||
oss1Label: 'OSS-1 liabilities 12 (ii) BDDR, Spl BDDR',
|
||||
oss1Value: this.liabilityValue('ii. BDDR, Spl BDDR'),
|
||||
tallies: false
|
||||
},
|
||||
{
|
||||
label: '17 (ii) Provision for depreciation in investments including AFS',
|
||||
oss2Value: this.earningsValue('ii) Provision for depreciation in investments'),
|
||||
oss1Label: 'OSS-1 liabilities 12 (i) Investment Depreciation Reserve',
|
||||
oss1Value: this.liabilityValue('i. For investments (Investment Depreciation Reserve)'),
|
||||
tallies: false
|
||||
}
|
||||
];
|
||||
return rows.map((r) => ({ ...r, tallies: (r.oss2Value ?? 0) === (r.oss1Value ?? 0) }));
|
||||
}
|
||||
|
||||
get allTieUpsMatch(): boolean {
|
||||
return this.oss1TieUps.every((t) => t.tallies);
|
||||
}
|
||||
|
||||
// ══════════════ Excel generation ══════════════
|
||||
|
||||
/** Rebuilds the RBI workbook: one sheet per section, original layout and styling. */
|
||||
async generateExcel(): Promise<void> {
|
||||
this.generating = true;
|
||||
try {
|
||||
const wb = newWorkbook();
|
||||
|
||||
buildGeneralInfoSheet(wb, this.generalInfo);
|
||||
this.buildEarningsSheet(wb);
|
||||
this.buildInterestSheet(wb);
|
||||
this.buildOtherExpensesSheet(wb);
|
||||
buildSignatorySheet(wb, this.signatories, 'Signatory Information');
|
||||
|
||||
await downloadWorkbook(wb, `Form OSS-2_CIMS ${this.returnCode} - ${this.quarterEndDate}.xlsx`);
|
||||
this.toast(`${this.returnCode} workbook generated for quarter ending ${this.quarterEndDate}`);
|
||||
} catch {
|
||||
this.toast('Could not generate the workbook — please retry');
|
||||
} finally {
|
||||
this.generating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private buildEarningsSheet(wb: ExcelJS.Workbook): void {
|
||||
const sheet = wb.addWorksheet('Earnings');
|
||||
sheet.columns = [
|
||||
{ width: 90 },
|
||||
{ width: 20 }
|
||||
];
|
||||
addBanner(sheet, 'Earnings', 'B');
|
||||
|
||||
const first = sheet.rowCount + 1;
|
||||
addSectionHeader(sheet, '', 'Rs in Thousand');
|
||||
addLineRows(sheet, this.earnings);
|
||||
boxAllCells(sheet, first, sheet.rowCount, 2);
|
||||
|
||||
sheet.addRow([]);
|
||||
const notesStart = sheet.rowCount + 1;
|
||||
for (const note of this.earningsNotes) {
|
||||
const row = sheet.addRow([note]);
|
||||
sheet.mergeCells(`A${row.number}:B${row.number}`);
|
||||
row.font = { bold: true, italic: true, size: 10 };
|
||||
row.alignment = { wrapText: true };
|
||||
row.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
}
|
||||
boxAllCells(sheet, notesStart, sheet.rowCount, 2);
|
||||
|
||||
sheet.views = [{ state: 'frozen', ySplit: 3 }];
|
||||
}
|
||||
|
||||
private buildInterestSheet(wb: ExcelJS.Workbook): void {
|
||||
const sheet = wb.addWorksheet('Interest Receivable Not Recog');
|
||||
sheet.columns = [
|
||||
{ width: 72 },
|
||||
{ width: 22 }
|
||||
];
|
||||
addBanner(sheet, 'Interest Receivable Not Recognized', 'B');
|
||||
|
||||
const start = sheet.rowCount + 1;
|
||||
const unit = sheet.addRow(['', 'Rs in Thousand']);
|
||||
unit.font = { bold: true };
|
||||
unit.alignment = { horizontal: 'center' };
|
||||
unit.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
|
||||
const head = sheet.addRow(['Interest receivable on NPAs not recognized as income', this.quarterEndDate]);
|
||||
head.font = { bold: true };
|
||||
head.getCell(2).alignment = { horizontal: 'center' };
|
||||
head.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
|
||||
for (const r of this.interestNotRecognized) {
|
||||
const row = sheet.addRow([r.label, r.value]);
|
||||
row.getCell(1).alignment = { wrapText: true, vertical: 'middle' };
|
||||
row.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
row.getCell(2).numFmt = '#,##0.00';
|
||||
if (r.total) {
|
||||
row.getCell(2).fill = solidFill(XL.derivedBlue);
|
||||
row.getCell(2).font = { bold: true };
|
||||
}
|
||||
}
|
||||
boxAllCells(sheet, start, sheet.rowCount, 2);
|
||||
}
|
||||
|
||||
private buildOtherExpensesSheet(wb: ExcelJS.Workbook): void {
|
||||
const sheet = wb.addWorksheet('Other Operating Expenses');
|
||||
sheet.columns = [
|
||||
{ width: 10 },
|
||||
{ width: 56 },
|
||||
{ width: 20 }
|
||||
];
|
||||
addBanner(sheet, 'Other Operating Expenses', 'C');
|
||||
|
||||
const start = sheet.rowCount + 1;
|
||||
const title = sheet.addRow(['Details of Other Operating Expenses', '', 'Rs in Thousand']);
|
||||
sheet.mergeCells(`A${title.number}:B${title.number}`);
|
||||
title.font = { bold: true };
|
||||
title.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
title.getCell(3).alignment = { horizontal: 'center' };
|
||||
|
||||
const head = sheet.addRow(['Sr No', 'Particulars', 'Amount']);
|
||||
head.font = { bold: true };
|
||||
head.alignment = { horizontal: 'center' };
|
||||
head.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
|
||||
for (const e of this.otherExpenses) {
|
||||
const row = sheet.addRow([e.srNo, e.particulars, e.amount]);
|
||||
row.getCell(3).numFmt = '#,##0.00';
|
||||
}
|
||||
|
||||
const total = sheet.addRow(['Total', '', this.otherExpensesTotal]);
|
||||
sheet.mergeCells(`A${total.number}:B${total.number}`);
|
||||
total.font = { bold: true };
|
||||
total.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
total.getCell(3).numFmt = '#,##0.00';
|
||||
total.getCell(3).fill = solidFill(XL.derivedBlue);
|
||||
|
||||
boxAllCells(sheet, start, sheet.rowCount, 3);
|
||||
|
||||
const ref = sheet.addRow(['', '', 'Refer Earnings Sheet — line 12 ix) Others']);
|
||||
ref.getCell(3).font = { italic: true, size: 10 };
|
||||
}
|
||||
|
||||
private toast(msg: string): void {
|
||||
this.toastMsg = msg;
|
||||
this.toastVisible = true;
|
||||
if (this.toastTimer) {
|
||||
clearTimeout(this.toastTimer);
|
||||
}
|
||||
this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,372 @@
|
||||
<!--
|
||||
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="oss-root">
|
||||
<!-- HEADER -->
|
||||
<div class="oss-head">
|
||||
<div class="oss-head-left">
|
||||
<fa-icon icon="university" class="oss-head-ic"></fa-icon>
|
||||
<div>
|
||||
<h2 class="oss-head-t">OSS 3 — {{ returnName }}</h2>
|
||||
<div class="oss-head-s">
|
||||
{{ bankName }} · Bank Code <strong>{{ bankCode }}</strong> · Return Code
|
||||
<strong>{{ returnCode }}</strong> · Quarter ending <strong>{{ reportingEndDate }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oss-head-right">
|
||||
<span class="oss-audit-pill">{{ reportStatus }}</span>
|
||||
<button class="oss-btn oss-btn-xls" [disabled]="generating" (click)="generateExcel()">
|
||||
<fa-icon icon="file-csv"></fa-icon>
|
||||
{{ generating ? 'Generating…' : 'Generate Excel' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HEADLINE ASSET QUALITY -->
|
||||
<div class="oss-balance" [class.oss-balance--bad]="!totalsAgree">
|
||||
<fa-icon [icon]="totalsAgree ? 'check-circle' : 'exclamation-triangle'"></fa-icon>
|
||||
<span>
|
||||
Gross NPA <strong>₹{{ grossNpa | number: '1.2-2' }}</strong> ({{ grossNpaRatio | number: '1.2-2' }}%)
|
||||
· Net NPA <strong>₹{{ netNpa | number: '1.2-2' }}</strong> ({{ netNpaRatio | number: '1.2-2' }}%)
|
||||
·
|
||||
{{
|
||||
totalsAgree
|
||||
? 'Portfolio Analysis and Classification totals agree'
|
||||
: 'MISMATCH between Portfolio Analysis and Classification totals — resolve before filing'
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- TABS -->
|
||||
<div class="oss-tabs">
|
||||
@for (t of tabs; track t.id) {
|
||||
<button class="oss-tab" [class.active]="activeTab === t.id" (click)="showTab(t.id)">
|
||||
<fa-icon [icon]="t.icon"></fa-icon> {{ t.label }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- FILING INFORMATION -->
|
||||
@if (activeTab === 'filing') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Filing Information</div>
|
||||
<div class="oss-tbl-wrap">
|
||||
<table class="oss-tbl oss-tbl--kv">
|
||||
<tbody>
|
||||
@for (item of filingInfo; track item.label) {
|
||||
<tr>
|
||||
<th>{{ item.label }}</th>
|
||||
<td [class.filled]="item.value" [class.audit]="item.label === 'Report status'">{{ item.value }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- PORTFOLIO ANALYSIS -->
|
||||
@if (activeTab === 'portfolio') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">PortfolioAnalysis(loans_advncs) — Portfolio Analysis (By delinquency in Interest Payment)</div>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: portfolioAnalysis }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: portfolioMemo }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- CLASSIFICATION OF RISK ASSETS -->
|
||||
@if (activeTab === 'classification') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Classifcation_Loans_Adv — Classification of Risk Assets</div>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: classificationPartA }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: nettingItems1 }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: nettingItems2 }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: classificationMemo }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- MOVEMENT OF LOANS -->
|
||||
@if (activeTab === 'movement') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Movement_of_loans — Change in Asset Quality Profile and Recoveries of NPAs</div>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: movementPart1 }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: movementPart2 }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: movementPart3 }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- OTHER RISK ASSETS -->
|
||||
@if (activeTab === 'other-risk') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">StatementOtherRiskAssets — Other Risk Assets and Exposures</div>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: otherRiskOnBs }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: otherRiskOffBs }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- TOP IMPAIRED CREDITS -->
|
||||
@if (activeTab === 'impaired') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">TopImpairedCredits — Top Impaired Credits</div>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: capitalMemo }"></ng-container>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">Table 5: Top Fifty Impaired Credit or more than Rs.100 lakhs — Rs in Thousands</div>
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PAN of Borrower</th>
|
||||
<th>Name of Borrower</th>
|
||||
<th class="ta-r">Loans & Advances</th>
|
||||
<th class="ta-r">Funded Exposure</th>
|
||||
<th class="ta-r">Interest in arrear</th>
|
||||
<th class="ta-r">% to Total Advances</th>
|
||||
<th class="ta-r">% to Capital Funds</th>
|
||||
<th>Asset Classification</th>
|
||||
<th class="ta-r">Loss Provision held</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (r of impairedCredits; track $index) {
|
||||
<tr>
|
||||
<td class="mono">{{ r.pan }}</td>
|
||||
<td>{{ r.name }}</td>
|
||||
<td class="ta-r mono">{{ r.loansAdvances | number: '1.2-2' }}</td>
|
||||
<td class="ta-r mono">{{ r.funded | number: '1.2-2' }}</td>
|
||||
<td class="ta-r mono">{{ r.interestInArrear | number: '1.2-2' }}</td>
|
||||
<td class="ta-r mono">{{ r.pctTotalAdvances | number: '1.2-2' }}%</td>
|
||||
<td class="ta-r mono">{{ r.pctCapitalFunds | number: '1.2-2' }}%</td>
|
||||
<td>
|
||||
<span class="report-pill" [class.report-bad]="r.assetClassification !== 'Standard'">
|
||||
{{ r.assetClassification }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="ta-r mono">{{ r.lossProvisionHeld | number: '1.2-2' }}</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="total-row">
|
||||
<td colspan="3">Total ({{ impairedCredits.length }} borrowers)</td>
|
||||
<td class="ta-r mono derived">{{ impairedFundedTotal | number: '1.2-2' }}</td>
|
||||
<td class="ta-r mono derived">{{ impairedInterestTotal | number: '1.2-2' }}</td>
|
||||
<td colspan="3"></td>
|
||||
<td class="ta-r mono derived">{{ impairedProvisionTotal | number: '1.2-2' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@for (t of recoveryTables; track t.title) {
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: t }"></ng-container>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- EXPOSURE LIMITS -->
|
||||
@if (activeTab === 'exposure') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">ExposureLimits — Single and Group Borrower Exposure Limit</div>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: capitalPreviousMarch }"></ng-container>
|
||||
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">
|
||||
Table 2: Statement on Large Exposure — Single Borrower (in excess of 15% of Tier 1 capital as on
|
||||
previous March, or a minimum of 20 large credits)
|
||||
</div>
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PAN of Borrower</th>
|
||||
<th>Name of Borrower</th>
|
||||
<th class="ta-r">Limit sanctioned</th>
|
||||
<th class="ta-r">% to Total Advances</th>
|
||||
<th class="ta-r">Outstanding Balance</th>
|
||||
<th class="ta-r">% to Tier 1 capital</th>
|
||||
<th>Asset Classification</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (r of largeExposures; track $index) {
|
||||
<tr>
|
||||
<td class="mono">{{ r.pan }}</td>
|
||||
<td>{{ r.name }}</td>
|
||||
<td class="ta-r mono">{{ r.limitSanctioned | number: '1.2-2' }}</td>
|
||||
<td class="ta-r mono">{{ r.pctTotalAdvances | number: '1.2-2' }}%</td>
|
||||
<td class="ta-r mono">{{ r.outstandingBalance | number: '1.2-2' }}</td>
|
||||
<td class="ta-r mono">{{ r.pctTier1Capital | number: '1.2-2' }}%</td>
|
||||
<td>
|
||||
<span class="report-pill report-ok">{{ r.assetClassification }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="oss-notes">
|
||||
@for (note of exposureNotes; track note) {
|
||||
<div class="oss-note-line">{{ note }}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- SECURITY PORTFOLIO -->
|
||||
@if (activeTab === 'securities') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">QualitySecurityPortoflio — Quality of Security Portfolio</div>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: investmentQuality }"></ng-container>
|
||||
<div class="oss-sheet-banner">OtherNonSLRSecurity — Other Details of Non-SLR Securities</div>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: nonSlrSecurities }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- SECTORAL CREDIT -->
|
||||
@if (activeTab === 'sectoral') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">SectoralCredit — Sectoral Credit</div>
|
||||
|
||||
<div class="oss-balance" [class.oss-balance--bad]="!sectoralAgreesWithClosing">
|
||||
<fa-icon [icon]="sectoralAgreesWithClosing ? 'check-circle' : 'exclamation-triangle'"></fa-icon>
|
||||
<span>
|
||||
@if (sectoralAgreesWithClosing) {
|
||||
Sectoral totals agree with the Classification statement.
|
||||
} @else {
|
||||
Sectoral gross advances <strong>₹{{ sectoralGross | number: '1.2-2' }}</strong> and NPAs
|
||||
<strong>₹{{ sectoralNpa | number: '1.2-2' }}</strong> do not match the closing position reported by
|
||||
Classification (<strong>₹{{ classificationTotal | number: '1.2-2' }}</strong> /
|
||||
<strong>₹{{ grossNpa | number: '1.2-2' }}</strong>).
|
||||
@if (sectoralMatchesOpening) {
|
||||
They match the <em>opening</em> balances in Movement Parts 1 and 2 instead — this statement appears to
|
||||
have been prepared on the previous quarter's book.
|
||||
}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: sectoralCredit }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: sectoralMemo }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: segmentAnalysis }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- INDUSTRY BREAKUP -->
|
||||
@if (activeTab === 'industry') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">IndustryBreakup — Industry Breakup</div>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: industryBreakup }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- SENSITIVE SECTOR -->
|
||||
@if (activeTab === 'sensitive') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">SensitiveSector — Sensitive Sector</div>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: capitalMarket }"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="matrix" [ngTemplateOutletContext]="{ t: realEstate }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- SIGNATORY -->
|
||||
@if (activeTab === 'signatory') {
|
||||
<div class="oss-section">
|
||||
<div class="oss-sheet-banner">Signatory — Authorised Signatory</div>
|
||||
<div class="oss-sign-grid">
|
||||
@for (p of signatories; track p.heading) {
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">{{ p.heading }}</div>
|
||||
<table class="oss-tbl oss-tbl--kv">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ p.signature }}</td>
|
||||
<th>Signature</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.name }}</td>
|
||||
<th>Name</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.designation }}</td>
|
||||
<th>Designation</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.email }}</td>
|
||||
<th>e-mail ID</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono">{{ p.telephoneOffice }}</td>
|
||||
<th>Tele. No. (O)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono">{{ p.telephoneResidence }}</td>
|
||||
<th>Tele. No. (R)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.place }}</td>
|
||||
<th>Place</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ p.date }}</td>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Shared renderer for every multi-column statement in this return. -->
|
||||
<ng-template #matrix let-t="t">
|
||||
<div class="oss-tbl-wrap">
|
||||
<div class="oss-tbl-h">
|
||||
{{ t.title }}@if (t.unit) {<span class="oss-tbl-unit">{{ t.unit }}</span>}
|
||||
</div>
|
||||
@if (t.subtitle) {
|
||||
<div class="oss-tbl-sub">{{ t.subtitle }}</div>
|
||||
}
|
||||
<table class="oss-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="oss-col-label">Particulars</th>
|
||||
@for (c of t.columns; track $index) {
|
||||
<th class="ta-r">{{ c }}</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (row of t.rows; track $index) {
|
||||
<tr [class.total-row]="row.total" [class.section-row]="row.header">
|
||||
<td [class]="'lvl-' + row.level">{{ row.label }}</td>
|
||||
@if (row.header) {
|
||||
<td [attr.colspan]="t.columns.length"></td>
|
||||
} @else {
|
||||
@for (v of row.values; track $index) {
|
||||
<td class="ta-r mono" [class.derived]="row.total">
|
||||
{{ v === null ? '' : (v | number: '1.2-2') }}{{ v !== null && row.percent ? '%' : '' }}
|
||||
</td>
|
||||
}
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if (t.notes?.length) {
|
||||
<div class="oss-notes">
|
||||
@for (note of t.notes; track note) {
|
||||
<div class="oss-note-line">{{ note }}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<!-- TOAST -->
|
||||
<div class="oss-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|
||||
</div>
|
||||
@@ -0,0 +1,470 @@
|
||||
/**
|
||||
* 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 * as ExcelJS from 'exceljs';
|
||||
import { MatrixTable } from '../oss-return.model';
|
||||
import {
|
||||
addBanner,
|
||||
addMatrixTable,
|
||||
boxAllCells,
|
||||
buildGeneralInfoSheet,
|
||||
buildSignatorySheet,
|
||||
downloadWorkbook,
|
||||
newWorkbook,
|
||||
solidFill,
|
||||
XL
|
||||
} from '../oss-workbook';
|
||||
import {
|
||||
RAQ_CAPITAL_MARKET,
|
||||
RAQ_CAPITAL_MEMO,
|
||||
RAQ_CAPITAL_PREVIOUS_MARCH,
|
||||
RAQ_CLASSIFICATION_MEMO,
|
||||
RAQ_CLASSIFICATION_PART_A,
|
||||
RAQ_EXPOSURE_NOTES,
|
||||
RAQ_FILING_INFO,
|
||||
RAQ_IMPAIRED_CREDITS,
|
||||
RAQ_INDUSTRY_BREAKUP,
|
||||
RAQ_INVESTMENT_QUALITY,
|
||||
RAQ_LARGE_EXPOSURES,
|
||||
RAQ_MOVEMENT_PART_1,
|
||||
RAQ_MOVEMENT_PART_2,
|
||||
RAQ_MOVEMENT_PART_3,
|
||||
RAQ_NETTING_ITEMS_1,
|
||||
RAQ_NETTING_ITEMS_2,
|
||||
RAQ_NON_SLR_SECURITIES,
|
||||
RAQ_OTHER_RISK_OFF_BS,
|
||||
RAQ_OTHER_RISK_ON_BS,
|
||||
RAQ_PORTFOLIO_ANALYSIS,
|
||||
RAQ_PORTFOLIO_MEMO,
|
||||
RAQ_REAL_ESTATE,
|
||||
RAQ_RECOVERY_TABLES,
|
||||
RAQ_SECTORAL_CREDIT,
|
||||
RAQ_SECTORAL_MEMO,
|
||||
RAQ_SEGMENT_ANALYSIS,
|
||||
RAQ_SIGNATORIES
|
||||
} from './oss3-raq-data';
|
||||
|
||||
type Oss3Tab =
|
||||
| 'filing'
|
||||
| 'portfolio'
|
||||
| 'classification'
|
||||
| 'movement'
|
||||
| 'other-risk'
|
||||
| 'impaired'
|
||||
| 'exposure'
|
||||
| 'securities'
|
||||
| 'sectoral'
|
||||
| 'industry'
|
||||
| 'sensitive'
|
||||
| 'signatory';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-oss3-raq',
|
||||
templateUrl: './oss3-raq.component.html',
|
||||
styleUrls: ['../oss-return.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent,
|
||||
FormsModule
|
||||
]
|
||||
})
|
||||
export class Oss3RaqComponent {
|
||||
activeTab: Oss3Tab = 'filing';
|
||||
|
||||
/** Tab strip definition — keeps the template free of a dozen near-identical buttons. */
|
||||
readonly tabs: { id: Oss3Tab; label: string; icon: string }[] = [
|
||||
{ id: 'filing', label: 'Filing Information', icon: 'info-circle' },
|
||||
{ id: 'portfolio', label: 'Portfolio Analysis', icon: 'chart-pie' },
|
||||
{ id: 'classification', label: 'Classification of Risk Assets', icon: 'university' },
|
||||
{ id: 'movement', label: 'Movement of Loans', icon: 'exchange-alt' },
|
||||
{ id: 'other-risk', label: 'Other Risk Assets', icon: 'exclamation-triangle' },
|
||||
{ id: 'impaired', label: 'Top Impaired Credits', icon: 'file-alt' },
|
||||
{ id: 'exposure', label: 'Exposure Limits', icon: 'users' },
|
||||
{ id: 'securities', label: 'Security Portfolio', icon: 'coins' },
|
||||
{ id: 'sectoral', label: 'Sectoral Credit', icon: 'chart-pie' },
|
||||
{ id: 'industry', label: 'Industry Breakup', icon: 'file-alt' },
|
||||
{ id: 'sensitive', label: 'Sensitive Sector', icon: 'shield-alt' },
|
||||
{ id: 'signatory', label: 'Signatory', icon: 'check-circle' }
|
||||
];
|
||||
|
||||
filingInfo = RAQ_FILING_INFO;
|
||||
portfolioAnalysis = RAQ_PORTFOLIO_ANALYSIS;
|
||||
portfolioMemo = RAQ_PORTFOLIO_MEMO;
|
||||
classificationPartA = RAQ_CLASSIFICATION_PART_A;
|
||||
nettingItems1 = RAQ_NETTING_ITEMS_1;
|
||||
nettingItems2 = RAQ_NETTING_ITEMS_2;
|
||||
classificationMemo = RAQ_CLASSIFICATION_MEMO;
|
||||
movementPart1 = RAQ_MOVEMENT_PART_1;
|
||||
movementPart2 = RAQ_MOVEMENT_PART_2;
|
||||
movementPart3 = RAQ_MOVEMENT_PART_3;
|
||||
otherRiskOnBs = RAQ_OTHER_RISK_ON_BS;
|
||||
otherRiskOffBs = RAQ_OTHER_RISK_OFF_BS;
|
||||
capitalMemo = RAQ_CAPITAL_MEMO;
|
||||
recoveryTables = RAQ_RECOVERY_TABLES;
|
||||
impairedCredits = RAQ_IMPAIRED_CREDITS;
|
||||
capitalPreviousMarch = RAQ_CAPITAL_PREVIOUS_MARCH;
|
||||
largeExposures = RAQ_LARGE_EXPOSURES;
|
||||
exposureNotes = RAQ_EXPOSURE_NOTES;
|
||||
investmentQuality = RAQ_INVESTMENT_QUALITY;
|
||||
nonSlrSecurities = RAQ_NON_SLR_SECURITIES;
|
||||
sectoralCredit = RAQ_SECTORAL_CREDIT;
|
||||
sectoralMemo = RAQ_SECTORAL_MEMO;
|
||||
segmentAnalysis = RAQ_SEGMENT_ANALYSIS;
|
||||
industryBreakup = RAQ_INDUSTRY_BREAKUP;
|
||||
capitalMarket = RAQ_CAPITAL_MARKET;
|
||||
realEstate = RAQ_REAL_ESTATE;
|
||||
signatories = RAQ_SIGNATORIES;
|
||||
|
||||
generating = false;
|
||||
toastMsg = '';
|
||||
toastVisible = false;
|
||||
private toastTimer: any;
|
||||
|
||||
showTab(tab: Oss3Tab): void {
|
||||
this.activeTab = tab;
|
||||
}
|
||||
|
||||
private info(label: string): string {
|
||||
return this.filingInfo.find((g) => g.label === label)?.value ?? '';
|
||||
}
|
||||
|
||||
get returnName(): string {
|
||||
return this.info('Return Name');
|
||||
}
|
||||
get returnCode(): string {
|
||||
return this.info('Return Code');
|
||||
}
|
||||
get bankName(): string {
|
||||
return this.info('Name of reporting institution');
|
||||
}
|
||||
get bankCode(): string {
|
||||
return this.info('Bank Code');
|
||||
}
|
||||
get reportingEndDate(): string {
|
||||
return this.info('Reporting end date');
|
||||
}
|
||||
get reportStatus(): string {
|
||||
return this.info('Report status');
|
||||
}
|
||||
|
||||
private matrixValue(table: MatrixTable, prefix: string, col: number): number | null {
|
||||
return table.rows.find((r) => r.label.startsWith(prefix))?.values[col] ?? null;
|
||||
}
|
||||
|
||||
get grossNpa(): number {
|
||||
return this.matrixValue(this.nettingItems2, 'Gross Non Performing Advances', 0) ?? 0;
|
||||
}
|
||||
get grossNpaRatio(): number {
|
||||
return this.matrixValue(this.nettingItems2, 'Gross Non Performing Advances Ratio', 0) ?? 0;
|
||||
}
|
||||
get netNpa(): number {
|
||||
return this.matrixValue(this.nettingItems2, 'Net Non Performing Advances', 0) ?? 0;
|
||||
}
|
||||
get netNpaRatio(): number {
|
||||
return this.matrixValue(this.nettingItems2, 'Net Non Performing Advances Ratio', 0) ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Portfolio Analysis TOTAL must equal Classification TOTAL — the two statements
|
||||
* describe the same book from different angles and RBI validates them against
|
||||
* each other, so a divergence is surfaced rather than left to be spotted.
|
||||
*/
|
||||
get portfolioTotal(): number {
|
||||
return this.matrixValue(this.portfolioAnalysis, 'TOTAL (A + B)', 3) ?? 0;
|
||||
}
|
||||
get classificationTotal(): number {
|
||||
return this.matrixValue(this.classificationPartA, 'TOTAL', 0) ?? 0;
|
||||
}
|
||||
get totalsAgree(): boolean {
|
||||
return this.portfolioTotal === this.classificationTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sectoral Credit reports its own gross advances and NPA figures, which should
|
||||
* describe the same closing book as the Classification statement. In this filing
|
||||
* they instead match the *opening* balances from Movement Part 1/2, so the
|
||||
* divergence is shown rather than buried.
|
||||
*/
|
||||
get sectoralGross(): number {
|
||||
return this.matrixValue(this.sectoralCredit, 'I. Gross Advances', 0) ?? 0;
|
||||
}
|
||||
get sectoralNpa(): number {
|
||||
return this.matrixValue(this.sectoralCredit, 'I. Gross Advances', 1) ?? 0;
|
||||
}
|
||||
get movementOpeningGross(): number {
|
||||
return this.matrixValue(this.movementPart1, 'A. Loans and advances at the beginning', 4) ?? 0;
|
||||
}
|
||||
get movementOpeningNpa(): number {
|
||||
return this.matrixValue(this.movementPart2, 'A. NPAs at the beginning', 3) ?? 0;
|
||||
}
|
||||
get sectoralAgreesWithClosing(): boolean {
|
||||
return this.sectoralGross === this.classificationTotal && this.sectoralNpa === this.grossNpa;
|
||||
}
|
||||
get sectoralMatchesOpening(): boolean {
|
||||
return this.sectoralGross === this.movementOpeningGross && this.sectoralNpa === this.movementOpeningNpa;
|
||||
}
|
||||
|
||||
/** Table 5 footer totals, summed rather than hardcoded so they cannot drift. */
|
||||
get impairedFundedTotal(): number {
|
||||
return this.impairedCredits.reduce((s, r) => s + (r.funded ?? 0), 0);
|
||||
}
|
||||
get impairedInterestTotal(): number {
|
||||
return this.impairedCredits.reduce((s, r) => s + (r.interestInArrear ?? 0), 0);
|
||||
}
|
||||
get impairedProvisionTotal(): number {
|
||||
return this.impairedCredits.reduce((s, r) => s + (r.lossProvisionHeld ?? 0), 0);
|
||||
}
|
||||
|
||||
// ══════════════ Excel generation ══════════════
|
||||
|
||||
/** Rebuilds the RBI iFILE workbook: one sheet per statement, original layout. */
|
||||
async generateExcel(): Promise<void> {
|
||||
this.generating = true;
|
||||
try {
|
||||
const wb = newWorkbook();
|
||||
|
||||
buildGeneralInfoSheet(wb, this.filingInfo);
|
||||
this.sheetOf(wb, 'PortfolioAnalysis', [
|
||||
this.portfolioAnalysis,
|
||||
this.portfolioMemo
|
||||
]);
|
||||
this.sheetOf(wb, 'Classifcation_Loans_Adv', [
|
||||
this.classificationPartA,
|
||||
this.nettingItems1,
|
||||
this.nettingItems2,
|
||||
this.classificationMemo
|
||||
]);
|
||||
this.sheetOf(wb, 'Movement_of_loans', [
|
||||
this.movementPart1,
|
||||
this.movementPart2,
|
||||
this.movementPart3
|
||||
]);
|
||||
this.sheetOf(wb, 'StatementOtherRiskAssets', [
|
||||
this.otherRiskOnBs,
|
||||
this.otherRiskOffBs
|
||||
]);
|
||||
this.buildImpairedCreditsSheet(wb);
|
||||
this.buildExposureLimitsSheet(wb);
|
||||
this.sheetOf(wb, 'QualitySecurityPortoflio', [this.investmentQuality]);
|
||||
this.sheetOf(wb, 'OtherNonSLRSecurity', [this.nonSlrSecurities]);
|
||||
this.sheetOf(wb, 'SectoralCredit', [
|
||||
this.sectoralCredit,
|
||||
this.sectoralMemo,
|
||||
this.segmentAnalysis
|
||||
]);
|
||||
this.sheetOf(wb, 'IndustryBreakup', [this.industryBreakup]);
|
||||
this.sheetOf(wb, 'SensitiveSector', [
|
||||
this.capitalMarket,
|
||||
this.realEstate
|
||||
]);
|
||||
buildSignatorySheet(wb, this.signatories, 'Signatory');
|
||||
|
||||
await downloadWorkbook(wb, `R32312 OSS 3 ${this.returnCode} - ${this.reportingEndDate}.xlsx`);
|
||||
this.toast(`${this.returnCode} workbook generated for quarter ending ${this.reportingEndDate}`);
|
||||
} catch {
|
||||
this.toast('Could not generate the workbook — please retry');
|
||||
} finally {
|
||||
this.generating = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** One worksheet holding a stack of matrix statements. */
|
||||
private sheetOf(wb: ExcelJS.Workbook, name: string, tables: MatrixTable[]): void {
|
||||
const sheet = wb.addWorksheet(name);
|
||||
const widest = Math.max(...tables.map((t) => t.columns.length));
|
||||
sheet.columns = [
|
||||
{ width: 62 },
|
||||
...Array.from({ length: widest }, () => ({ width: 18 }))
|
||||
];
|
||||
addBanner(sheet, name, this.colLetter(widest + 1));
|
||||
for (const table of tables) {
|
||||
addMatrixTable(sheet, table);
|
||||
}
|
||||
}
|
||||
|
||||
private colLetter(index: number): string {
|
||||
let n = index;
|
||||
let s = '';
|
||||
while (n > 0) {
|
||||
const rem = (n - 1) % 26;
|
||||
s = String.fromCharCode(65 + rem) + s;
|
||||
n = Math.floor((n - 1) / 26);
|
||||
}
|
||||
return s || 'A';
|
||||
}
|
||||
|
||||
private buildImpairedCreditsSheet(wb: ExcelJS.Workbook): void {
|
||||
const sheet = wb.addWorksheet('TopImpairedCredits');
|
||||
sheet.columns = [
|
||||
{ width: 16 },
|
||||
{ width: 40 },
|
||||
{ width: 16 },
|
||||
{ width: 16 },
|
||||
{ width: 16 },
|
||||
{ width: 16 },
|
||||
{ width: 18 },
|
||||
{ width: 18 },
|
||||
{ width: 18 }
|
||||
];
|
||||
addBanner(sheet, 'TopImpairedCredits - Top Impaired Credits', 'I');
|
||||
addMatrixTable(sheet, this.capitalMemo);
|
||||
for (const t of this.recoveryTables) {
|
||||
addMatrixTable(sheet, t);
|
||||
}
|
||||
|
||||
const start = sheet.rowCount + 1;
|
||||
const title = sheet.addRow(['Table 5: Top Fifty Impaired Credit or more than Rs.100 lakhs']);
|
||||
sheet.mergeCells(title.number, 1, title.number, 9);
|
||||
title.font = { bold: true };
|
||||
title.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
|
||||
const head = sheet.addRow([
|
||||
'PAN of Borrower',
|
||||
'Name of Borrower',
|
||||
'Loans & Advances',
|
||||
'Funded Exposure',
|
||||
'Interest in arrear',
|
||||
'% to Total Advances',
|
||||
'% to Capital Funds of the Bank',
|
||||
'Asset Classification',
|
||||
'Loss Provision held'
|
||||
]);
|
||||
head.font = { bold: true };
|
||||
head.alignment = { wrapText: true, horizontal: 'center', vertical: 'middle' };
|
||||
head.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
|
||||
for (const r of this.impairedCredits) {
|
||||
const row = sheet.addRow([
|
||||
r.pan,
|
||||
r.name,
|
||||
r.loansAdvances,
|
||||
r.funded,
|
||||
r.interestInArrear,
|
||||
r.pctTotalAdvances,
|
||||
r.pctCapitalFunds,
|
||||
r.assetClassification,
|
||||
r.lossProvisionHeld
|
||||
]);
|
||||
[
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
9
|
||||
].forEach((c) => (row.getCell(c).numFmt = '#,##0.00'));
|
||||
[
|
||||
6,
|
||||
7
|
||||
].forEach((c) => (row.getCell(c).numFmt = '0.00"%"'));
|
||||
}
|
||||
|
||||
const total = sheet.addRow([
|
||||
'Total',
|
||||
'',
|
||||
null,
|
||||
this.impairedFundedTotal,
|
||||
this.impairedInterestTotal,
|
||||
null,
|
||||
null,
|
||||
'',
|
||||
this.impairedProvisionTotal
|
||||
]);
|
||||
total.font = { bold: true };
|
||||
[
|
||||
4,
|
||||
5,
|
||||
9
|
||||
].forEach((c) => {
|
||||
total.getCell(c).numFmt = '#,##0.00';
|
||||
total.getCell(c).fill = solidFill(XL.derivedBlue);
|
||||
});
|
||||
boxAllCells(sheet, start, sheet.rowCount, 9);
|
||||
}
|
||||
|
||||
private buildExposureLimitsSheet(wb: ExcelJS.Workbook): void {
|
||||
const sheet = wb.addWorksheet('ExposureLimits');
|
||||
sheet.columns = [
|
||||
{ width: 16 },
|
||||
{ width: 46 },
|
||||
{ width: 18 },
|
||||
{ width: 18 },
|
||||
{ width: 20 },
|
||||
{ width: 22 },
|
||||
{ width: 20 }
|
||||
];
|
||||
addBanner(sheet, 'ExposureLimits - Single and Group Borrower Exposure Limit', 'G');
|
||||
addMatrixTable(sheet, this.capitalPreviousMarch);
|
||||
|
||||
const start = sheet.rowCount + 1;
|
||||
const title = sheet.addRow(['Table 2: STATEMENT ON LARGE EXPOSURE - SINGLE BORROWER']);
|
||||
sheet.mergeCells(title.number, 1, title.number, 7);
|
||||
title.font = { bold: true };
|
||||
title.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
|
||||
const sub = sheet.addRow([
|
||||
'In excess of 15% of banks Tier 1 capital as on previous March or a minimum of 20 large credits'
|
||||
]);
|
||||
sheet.mergeCells(sub.number, 1, sub.number, 7);
|
||||
sub.font = { bold: true, size: 10 };
|
||||
sub.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
|
||||
const head = sheet.addRow([
|
||||
'PAN of Borrower',
|
||||
'Name of Borrower',
|
||||
'Limit sanctioned',
|
||||
'% to Total Advances',
|
||||
'Outstanding Balance',
|
||||
'% to Tier 1 capital of the Bank',
|
||||
'Asset Classification'
|
||||
]);
|
||||
head.font = { bold: true };
|
||||
head.alignment = { wrapText: true, horizontal: 'center', vertical: 'middle' };
|
||||
head.eachCell((c) => (c.fill = solidFill(XL.headerGrey)));
|
||||
|
||||
for (const r of this.largeExposures) {
|
||||
const row = sheet.addRow([
|
||||
r.pan,
|
||||
r.name,
|
||||
r.limitSanctioned,
|
||||
r.pctTotalAdvances,
|
||||
r.outstandingBalance,
|
||||
r.pctTier1Capital,
|
||||
r.assetClassification
|
||||
]);
|
||||
[
|
||||
3,
|
||||
5
|
||||
].forEach((c) => (row.getCell(c).numFmt = '#,##0.00'));
|
||||
[
|
||||
4,
|
||||
6
|
||||
].forEach((c) => (row.getCell(c).numFmt = '0.00"%"'));
|
||||
}
|
||||
boxAllCells(sheet, start, sheet.rowCount, 7);
|
||||
|
||||
for (const note of this.exposureNotes) {
|
||||
const n = sheet.addRow([note]);
|
||||
sheet.mergeCells(n.number, 1, n.number, 7);
|
||||
n.font = { bold: true, size: 9 };
|
||||
n.alignment = { wrapText: true };
|
||||
n.getCell(1).fill = solidFill(XL.headerGrey);
|
||||
}
|
||||
}
|
||||
|
||||
private toast(msg: string): void {
|
||||
this.toastMsg = msg;
|
||||
this.toastVisible = true;
|
||||
if (this.toastTimer) {
|
||||
clearTimeout(this.toastTimer);
|
||||
}
|
||||
this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,22 @@ const routes: Routes = [
|
||||
path: 'rbi/aml',
|
||||
component: AmlRulesComponent,
|
||||
data: { title: 'AML Rules Master', breadcrumb: 'AML Rules Master' }
|
||||
},
|
||||
{
|
||||
path: 'rbi/oss1',
|
||||
// Lazy — the ExcelJS workbook builder should not weigh down the eager bundle.
|
||||
loadComponent: () => import('./oss1-ral/oss1-ral.component').then((m) => m.Oss1RalComponent),
|
||||
data: { title: 'OSS 1 — Return on Assets and Liabilities', breadcrumb: 'OSS 1 (RAL)' }
|
||||
},
|
||||
{
|
||||
path: 'rbi/oss2',
|
||||
loadComponent: () => import('./oss2-roe/oss2-roe.component').then((m) => m.Oss2RoeComponent),
|
||||
data: { title: 'OSS 2 — Return on Earnings', breadcrumb: 'OSS 2 (RoE)' }
|
||||
},
|
||||
{
|
||||
path: 'rbi/oss3',
|
||||
loadComponent: () => import('./oss3-raq/oss3-raq.component').then((m) => m.Oss3RaqComponent),
|
||||
data: { title: 'OSS 3 — Return on Asset Quality', breadcrumb: 'OSS 3 (RAQ-UCB)' }
|
||||
}
|
||||
])
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user