- Raised by the transaction-monitoring rule engine — review and whitelist or escalate to Principal Officer
+ Mechanical cash transaction threshold breaches — review and whitelist or escalate to Principal Officer
+
+ Pending awaiting Whitelist/Escalate decision ·
+ CTR only whitelisted — cleared, no STR needed ·
+ → STR escalated to Principal Officer as an STR case
+
+ CTR every cash txn ≥ threshold stays in the monthly CTR / TC2
+ file ·
+ + STR also flagged as suspicious and escalated to a separate STR
+ case (see the STR tab)
+
}
@if (activeTab === 'str') {
+
+
+
+
Live AML Alerts (Awaiting Triage)
+
+ Flagged by the monitoring engine for suspicious behavioral patterns — triage and whitelist or escalate
+
+ @if (!allAnswered) {
+
+
+ Answer every verification point to determine the outcome —
+ {{ strChecklist.length - answeredCount }} still pending.
+
+ } @else if (derivedConclusion === 'NOT_SUSPICIOUS') {
+
+
+ Not suspicious. All {{ strChecklist.length }} points found genuine — the case will
+ be closed and nothing filed with FIU-IND.
+
+ } @else {
+
+
+ Suspicious — STR must be filed.
+ {{ failedFindings.length }} of {{ strChecklist.length }} points not satisfied:
+ {{ failedFindings[0].label }}@if (failedFindings.length > 1) {
+ and {{ failedFindings.length - 1 }} more
+ }.
+
+ }
+
6 CSV files in FIU-IND prescribed schema, plus STR XML
+
+ 6 CSV files in FIU-IND prescribed schema, plus STR XML. Counts reflect any filters set on the CTR and
+ STR tabs, so every file in the bundle covers the same transactions.
+
+ Compares the closing position as at {{ range.to }} against the position immediately before
+ this period began. Change the filter above to compare a different span.
+
+ Off-balance-sheet commitments outstanding as at {{ range.to }}. These are memoranda accounts
+ and are deliberately excluded from the Trial Balance and Balance Sheet totals.
+
+ Debit and credit turnover posted between {{ rangeLabel }} for {{ branchLabel }}. Every
+ voucher is double-entry, so the debit and credit totals below must match.
+
+ Note: * Give name if borrower/beneficiary is director/top management/related entity. Other guarantees may be
+ consolidated and reported against others.
+
+
+ }
+
+
+ @if (activeTab === 'annexure2') {
+
+
Annexure 2
+
+ @for (block of sundryDebtors; track block.title) {
+
+
+
+ Operating Profit ₹{{ operatingProfit | number: '1.2-2' }} · Net Profit after tax
+ ₹{{ netProfitAfterTax | number: '1.2-2' }} (Rs in Thousands)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (activeTab === 'general') {
+
+
General Information
+
+
+
+ @for (item of generalInfo; track item.label) {
+
+
{{ item.label }}
+
{{ item.value }}
+
+ }
+
+
+
+
+ }
+
+
+ @if (activeTab === 'earnings') {
+
+
Earnings
+
+
+
+
+
+ {{
+ 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.'
+ }}
+
+
ExposureLimits — Single and Group Borrower Exposure Limit
+
+
+
+
+ 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)
+
QualitySecurityPortoflio — Quality of Security Portfolio
+
+
OtherNonSLRSecurity — Other Details of Non-SLR Securities
+
+
+ }
+
+
+ @if (activeTab === 'sectoral') {
+
+
SectoralCredit — Sectoral Credit
+
+
+
+
+ @if (sectoralAgreesWithClosing) {
+ Sectoral totals agree with the Classification statement.
+ } @else {
+ Sectoral gross advances ₹{{ sectoralGross | number: '1.2-2' }} and NPAs
+ ₹{{ sectoralNpa | number: '1.2-2' }} do not match the closing position reported by
+ Classification (₹{{ classificationTotal | number: '1.2-2' }} /
+ ₹{{ grossNpa | number: '1.2-2' }}).
+ @if (sectoralMatchesOpening) {
+ They match the opening balances in Movement Parts 1 and 2 instead — this statement appears to
+ have been prepared on the previous quarter's book.
+ }
+ }
+
+
+
+
+
+
+
+ }
+
+
+ @if (activeTab === 'industry') {
+
+
IndustryBreakup — Industry Breakup
+
+
+ }
+
+
+ @if (activeTab === 'sensitive') {
+
+
SensitiveSector — Sensitive Sector
+
+
+
+ }
+
+
+ @if (activeTab === 'signatory') {
+
+
Signatory — Authorised Signatory
+
+ @for (p of signatories; track p.heading) {
+
+
{{ p.heading }}
+
+
+
+
{{ p.signature }}
+
Signature
+
+
+
{{ p.name }}
+
Name
+
+
+
{{ p.designation }}
+
Designation
+
+
+
{{ p.email }}
+
e-mail ID
+
+
+
{{ p.telephoneOffice }}
+
Tele. No. (O)
+
+
+
{{ p.telephoneResidence }}
+
Tele. No. (R)
+
+
+
{{ p.place }}
+
Place
+
+
+
{{ p.date }}
+
Date
+
+
+
+
+ }
+
+
+ }
+
+
+
+
+
+ {{ t.title }}@if (t.unit) {{{ t.unit }}}
+
+ @if (t.subtitle) {
+
{{ t.subtitle }}
+ }
+
+
+
+
Particulars
+ @for (c of t.columns; track $index) {
+
{{ c }}
+ }
+
+
+
+ @for (row of t.rows; track $index) {
+
+
{{ row.label }}
+ @if (row.header) {
+
+ } @else {
+ @for (v of row.values; track $index) {
+
+ {{ v === null ? '' : (v | number: '1.2-2') }}{{ v !== null && row.percent ? '%' : '' }}
+
+ }
+ }
+
+ }
+
+
+
+ @if (t.notes?.length) {
+
+ @for (note of t.notes; track note) {
+
{{ note }}
+ }
+
+ }
+
+
+
+
{{ toastMsg }}
+
diff --git a/src/app/rbi-circulars/oss3-raq/oss3-raq.component.ts b/src/app/rbi-circulars/oss3-raq/oss3-raq.component.ts
new file mode 100644
index 000000000..173ae15c1
--- /dev/null
+++ b/src/app/rbi-circulars/oss3-raq/oss3-raq.component.ts
@@ -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 {
+ 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);
+ }
+}
diff --git a/src/app/rbi-circulars/rbi-circulars-routing.module.ts b/src/app/rbi-circulars/rbi-circulars-routing.module.ts
index 3cb6f2329..d0feae670 100644
--- a/src/app/rbi-circulars/rbi-circulars-routing.module.ts
+++ b/src/app/rbi-circulars/rbi-circulars-routing.module.ts
@@ -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)' }
}
])
];
diff --git a/src/environments/.env.ts b/src/environments/.env.ts
index 52cc1da1e..b66d7426a 100644
--- a/src/environments/.env.ts
+++ b/src/environments/.env.ts
@@ -9,8 +9,8 @@
/* tslint:disable */
export default {
'mifos_x': {
- 'version': '260731',
- 'hash': 'fd28788d'
+ 'version': '260801',
+ 'hash': 'b9b37397'
},
'allow_switching_backend_instance': true
};