Compare commits
1
Commits
2a9df828ca
..
uate
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c7d8287f9 |
@@ -64,10 +64,7 @@ import { BankCalendarModule } from './bank-calendar/bank-calendar.module';
|
|||||||
import { DeafModule } from './deaf/deaf.module';
|
import { DeafModule } from './deaf/deaf.module';
|
||||||
import { DicgcModule } from './dicgc/dicgc.module';
|
import { DicgcModule } from './dicgc/dicgc.module';
|
||||||
import { FiuModule } from './fiu/fiu.module';
|
import { FiuModule } from './fiu/fiu.module';
|
||||||
import { GlReportsModule } from './gl-reports/gl-reports.module';
|
|
||||||
import { SftModule } from './sft/sft.module';
|
import { SftModule } from './sft/sft.module';
|
||||||
import { InvestmentModule } from './investment/investment.module';
|
|
||||||
import { NpaModule } from './npa/npa.module';
|
|
||||||
import { DigitalModule } from './digital/digital.module';
|
import { DigitalModule } from './digital/digital.module';
|
||||||
import { LetterMgmtModule } from './letter-mgmt/letter-mgmt.module';
|
import { LetterMgmtModule } from './letter-mgmt/letter-mgmt.module';
|
||||||
import { AccountsModule } from './accounts/accounts.module';
|
import { AccountsModule } from './accounts/accounts.module';
|
||||||
@@ -174,10 +171,7 @@ export function HttpLoaderFactory(http: HttpClient) {
|
|||||||
DeafModule,
|
DeafModule,
|
||||||
DicgcModule,
|
DicgcModule,
|
||||||
FiuModule,
|
FiuModule,
|
||||||
GlReportsModule,
|
|
||||||
SftModule,
|
SftModule,
|
||||||
InvestmentModule,
|
|
||||||
NpaModule,
|
|
||||||
DigitalModule,
|
DigitalModule,
|
||||||
LetterMgmtModule,
|
LetterMgmtModule,
|
||||||
AccountsModule,
|
AccountsModule,
|
||||||
|
|||||||
-266
@@ -1,266 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="pg">
|
|
||||||
<header class="pg-head">
|
|
||||||
<div>
|
|
||||||
<div class="pg-eyebrow">SUB LEDGER · TRANSACTIONS · {{ scopeName | uppercase }}</div>
|
|
||||||
<h1 class="pg-title">Cash / Clearing / Transfer - Summary Report</h1>
|
|
||||||
<p class="pg-sub">
|
|
||||||
Opening, credit, debit and closing position across Cash, Transfer, Clearing and Digital channels as on
|
|
||||||
{{ asOnDate }}.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="pg-toolbar">
|
|
||||||
@if (isHeadOffice) {
|
|
||||||
<label class="pg-field">
|
|
||||||
<span>Branch</span>
|
|
||||||
<select class="pg-select" [ngModel]="selectedBranchCode" (ngModelChange)="setBranch($event)">
|
|
||||||
<option value="ALL">All Branches (Consolidated)</option>
|
|
||||||
@for (b of branches; track b.code) {
|
|
||||||
<option [value]="b.code">{{ b.name }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
}
|
|
||||||
<button type="button" class="pg-btn" [class.pg-btn--active]="showDetails" (click)="toggleDetails()">
|
|
||||||
{{ showDetails ? 'Summary View' : 'Transaction Details' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="cctr-grand-summary">
|
|
||||||
<div class="cctr-grand-summary__item">
|
|
||||||
<span class="cctr-grand-summary__label">Opening</span>
|
|
||||||
<span class="cctr-grand-summary__value">₹{{ grandOpening | number: '1.2-2' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="cctr-grand-summary__item">
|
|
||||||
<span class="cctr-grand-summary__label">Credit</span>
|
|
||||||
<span class="cctr-grand-summary__value col-in">₹{{ grandCredit | number: '1.2-2' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="cctr-grand-summary__item">
|
|
||||||
<span class="cctr-grand-summary__label">Debit</span>
|
|
||||||
<span class="cctr-grand-summary__value col-out">₹{{ grandDebit | number: '1.2-2' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="cctr-grand-summary__item">
|
|
||||||
<span class="cctr-grand-summary__label">Closing</span>
|
|
||||||
<span class="cctr-grand-summary__value">₹{{ grandClosing | number: '1.2-2' }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (isHeadOffice && selectedBranchCode === 'ALL') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Branch-wise Break-up — click a branch to drill in</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th class="num">Opening</th>
|
|
||||||
<th class="num col-in">Credit</th>
|
|
||||||
<th class="num col-out">Debit</th>
|
|
||||||
<th class="num col-net">Closing</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (b of branchBreakup; track b.code) {
|
|
||||||
<tr class="cctr-branch-row" (click)="setBranch(b.code)">
|
|
||||||
<td>{{ b.name }}</td>
|
|
||||||
<td class="num">₹{{ b.opening | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-in">₹{{ b.credit | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-out">₹{{ b.debit | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-net">₹{{ b.closing | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td>All Branches</td>
|
|
||||||
<td class="num">₹{{ grandOpening | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ grandCredit | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ grandDebit | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ grandClosing | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'cash'"
|
|
||||||
(click)="setTab('cash')"
|
|
||||||
>
|
|
||||||
Cash
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'transfer'"
|
|
||||||
(click)="setTab('transfer')"
|
|
||||||
>
|
|
||||||
Transfer
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'clearing'"
|
|
||||||
(click)="setTab('clearing')"
|
|
||||||
>
|
|
||||||
Clearing
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'digital'"
|
|
||||||
(click)="setTab('digital')"
|
|
||||||
>
|
|
||||||
Digital
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Summary (Opening | Credit | Debit | Closing) -->
|
|
||||||
@if (!showDetails) {
|
|
||||||
<div class="pg-card">
|
|
||||||
@if (activeTab === 'digital') {
|
|
||||||
<div class="pos-section-label">RTGS · NEFT · UPI · ATM · NACH — Overall Position</div>
|
|
||||||
}
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
@if (activeTab !== 'digital') {
|
|
||||||
<th>GL Code</th>
|
|
||||||
}
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="num">Opening</th>
|
|
||||||
<th class="num col-in">Credit</th>
|
|
||||||
<th class="num col-out">Debit</th>
|
|
||||||
<th class="num col-net">Closing</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of rowsForActiveTab; track r.code) {
|
|
||||||
<tr>
|
|
||||||
@if (activeTab !== 'digital') {
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ r.code }}</span>
|
|
||||||
</td>
|
|
||||||
}
|
|
||||||
<td>{{ r.name }}</td>
|
|
||||||
<td class="num">₹{{ r.opening | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-in">₹{{ rowCredit(r, txnsForActiveTab) | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-out">₹{{ rowDebit(r, txnsForActiveTab) | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-net">₹{{ closing(r, txnsForActiveTab) | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td [attr.colspan]="activeTab !== 'digital' ? 2 : 1">Total</td>
|
|
||||||
<td class="num">₹{{ total(rowsForActiveTab, txnsForActiveTab, 'opening') | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ total(rowsForActiveTab, txnsForActiveTab, 'credit') | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ total(rowsForActiveTab, txnsForActiveTab, 'debit') | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ totalClosing(rowsForActiveTab, txnsForActiveTab) | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Transaction Details (all txns for the selected category, optionally filtered by GL/channel) -->
|
|
||||||
@if (showDetails) {
|
|
||||||
@if (rowsForActiveTab.length > 1) {
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="glFilter === null"
|
|
||||||
(click)="setGlFilter(null)"
|
|
||||||
>
|
|
||||||
All
|
|
||||||
</button>
|
|
||||||
@for (r of rowsForActiveTab; track r.code) {
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="glFilter === r.code"
|
|
||||||
(click)="setGlFilter(r.code)"
|
|
||||||
>
|
|
||||||
{{ filterLabel(r) }}
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">
|
|
||||||
{{
|
|
||||||
activeTab === 'cash'
|
|
||||||
? 'Cash'
|
|
||||||
: activeTab === 'transfer'
|
|
||||||
? 'Transfer'
|
|
||||||
: activeTab === 'clearing'
|
|
||||||
? 'Clearing'
|
|
||||||
: 'Digital'
|
|
||||||
}}
|
|
||||||
— Transaction Details
|
|
||||||
@if (glFilter !== null) {
|
|
||||||
· {{ glFilter }}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Voucher / Ref No</th>
|
|
||||||
<th>Account No</th>
|
|
||||||
<th>Account Name / Reference</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="num col-out">Debit</th>
|
|
||||||
<th class="num col-in">Credit</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (t of filteredTxns; track t.voucherNo) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ asOnDate }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ display(t.voucherNo) }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ display(t.accountNo) }}</td>
|
|
||||||
<td>{{ t.accountName }}</td>
|
|
||||||
<td>{{ t.particulars }}</td>
|
|
||||||
<td class="num col-out">
|
|
||||||
@if (t.debit > 0) {
|
|
||||||
₹{{ t.debit | number: '1.2-2' }}
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td class="num col-in">
|
|
||||||
@if (t.credit > 0) {
|
|
||||||
₹{{ t.credit | number: '1.2-2' }}
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="7" class="pg-empty">No transactions for this filter.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td colspan="5">Total</td>
|
|
||||||
<td class="num">₹{{ txnTotal(filteredTxns, 'debit') | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ txnTotal(filteredTxns, 'credit') | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
-73
@@ -1,73 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.cctr-tabs {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cctr-total-row {
|
|
||||||
td {
|
|
||||||
background: #f1f5f9;
|
|
||||||
font-weight: 800;
|
|
||||||
border-top: 2px solid #cbd5e1;
|
|
||||||
border-bottom: none;
|
|
||||||
color: #0f172a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cctr-grand-summary {
|
|
||||||
display: flex;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&__item {
|
|
||||||
flex: 1;
|
|
||||||
padding: 14px 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
border-right: 1px solid #e2e8f0;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.07em;
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__value {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 800;
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
color: #0f172a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cctr-subtext {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #94a3b8;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cctr-branch-row {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover td {
|
|
||||||
background: #eff6ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-1569
File diff suppressed because it is too large
Load Diff
@@ -179,17 +179,6 @@ const routes: Routes = [
|
|||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('./my-deposit-requests/my-deposit-requests.component').then((m) => m.MyDepositRequestsComponent),
|
import('./my-deposit-requests/my-deposit-requests.component').then((m) => m.MyDepositRequestsComponent),
|
||||||
data: { title: 'My Deposit Requests', breadcrumb: 'My Deposit Requests' }
|
data: { title: 'My Deposit Requests', breadcrumb: 'My Deposit Requests' }
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'cash-clearing-transfer-summary',
|
|
||||||
loadComponent: () =>
|
|
||||||
import('./cash-clearing-transfer-summary/cash-clearing-transfer-summary.component').then(
|
|
||||||
(m) => m.CashClearingTransferSummaryComponent
|
|
||||||
),
|
|
||||||
data: {
|
|
||||||
title: 'Cash / Clearing / Transfer - Summary Report',
|
|
||||||
breadcrumb: 'Cash / Clearing / Transfer - Summary Report'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,16 +12,16 @@
|
|||||||
* have meaningful variety out of the box.
|
* have meaningful variety out of the box.
|
||||||
*/
|
*/
|
||||||
export const SAMPLE_CKYC_FILE = `10|24401|IN4711|MH|30|31-08-2020|V1.1|||||
|
export const SAMPLE_CKYC_FILE = `10|24401|IN4711|MH|30|31-08-2020|V1.1|||||
|
||||||
20|1|01|01|||||||||||01|||01|10078|MR|RAVI|KUMAR|SHARMA|||||||01|MR|RAJESH|KUMAR|SHARMA||MRS|SUNITA|DEVI|||M|02|IN|B-01|10-08-1985|||||||||01|02|||||02|FLAT 402, SUNRISE TOWERS|MAROL ANDHERI EAST|MUMBAI|MUMBAI|MUMBAI|MH|IN|400069|01||Y|02|FLAT 402, SUNRISE TOWERS|MAROL ANDHERI EAST|MUMBAI|MUMBAI|MUMBAI|MH|IN|400069|01||||||||||||||||9892011001|||||31-08-2020|MUMBAI MAIN|31-08-2020|01|ANJALI MEHTA|ASSISTANT MANAGER|HEAD OFFICE|4|CBSNEXORA BANK|IN4711|1|0||0|4||||||
|
20|1|01|01|||||||||||01|||01|10078|MR|RAVI|KUMAR|SHARMA|||||||01|MR|RAJESH|KUMAR|SHARMA||MRS|SUNITA|DEVI|||M|02|IN|B-01|10-08-1985|||||||||01|02|||||02|FLAT 402, SUNRISE TOWERS|MAROL ANDHERI EAST|MUMBAI|MUMBAI|MUMBAI|MH|IN|400069|01||Y|02|FLAT 402, SUNRISE TOWERS|MAROL ANDHERI EAST|MUMBAI|MUMBAI|MUMBAI|MH|IN|400069|01||||||||||||||||9892011001|||||31-08-2020|MUMBAI MAIN|31-08-2020|01|ANJALI MEHTA|ASSISTANT MANAGER|HEAD OFFICE|4|BHARAT URBAN BANK|IN4711|1|0||0|4||||||
|
||||||
30|1|C|AAQPS8560J||01|02|||||
|
30|1|C|AAQPS8560J||01|02|||||
|
||||||
70|1|10078_Sign.jpeg|09|01|01||||
|
70|1|10078_Sign.jpeg|09|01|01||||
|
||||||
70|1|10078_Photo.jpeg|02|01|01||||
|
70|1|10078_Photo.jpeg|02|01|01||||
|
||||||
70|1|10078_AP.jpg|04|01|01||||
|
70|1|10078_AP.jpg|04|01|01||||
|
||||||
20|2|01|01|||||||||||01|||01|10079|MRS|PRIYA||PATEL|||||||01|MR|HARISH|PATEL|||MRS|MEENA|PATEL|||F|01|IN|O-04|22-03-1990|||||||||01|02|||||02|B-12, SHIVAJI NAGAR|FC ROAD|PUNE|PUNE|PUNE|MH|IN|411005|01||Y|02|B-12, SHIVAJI NAGAR|FC ROAD|PUNE|PUNE|PUNE|MH|IN|411005|01||||||||||||||||9822055002|||||31-08-2020|PUNE FC ROAD|31-08-2020|01|VIKRAM SINGH|BRANCH MANAGER|HEAD OFFICE|4|CBSNEXORA BANK|IN4711|1|0||0|3||||||
|
20|2|01|01|||||||||||01|||01|10079|MRS|PRIYA||PATEL|||||||01|MR|HARISH|PATEL|||MRS|MEENA|PATEL|||F|01|IN|O-04|22-03-1990|||||||||01|02|||||02|B-12, SHIVAJI NAGAR|FC ROAD|PUNE|PUNE|PUNE|MH|IN|411005|01||Y|02|B-12, SHIVAJI NAGAR|FC ROAD|PUNE|PUNE|PUNE|MH|IN|411005|01||||||||||||||||9822055002|||||31-08-2020|PUNE FC ROAD|31-08-2020|01|VIKRAM SINGH|BRANCH MANAGER|HEAD OFFICE|4|BHARAT URBAN BANK|IN4711|1|0||0|3||||||
|
||||||
30|2|E|XXXXXXXX2210||01|02|||||
|
30|2|E|XXXXXXXX2210||01|02|||||
|
||||||
70|2|10079_Sign.jpeg|09|01|01||||
|
70|2|10079_Sign.jpeg|09|01|01||||
|
||||||
70|2|10079_Photo.jpeg|02|01|01||||
|
70|2|10079_Photo.jpeg|02|01|01||||
|
||||||
20|3|01|01|||||||||||01|||01|10080|MR|ARJUN||DESHMUKH|||||||01|MR|VIJAY|DESHMUKH|||MRS|REKHA|DESHMUKH|||M|01|IN|B-01|15-01-1978|||||||||01|02|||||02|HOUSE NO 7, DHARAMPETH|RAMDASPETH|NAGPUR|NAGPUR|NAGPUR|MH|IN|440010|01||Y|02|HOUSE NO 7, DHARAMPETH|RAMDASPETH|NAGPUR|NAGPUR|NAGPUR|MH|IN|440010|01||||||||||||||||9823033003|||||30-08-2020|NAGPUR DHARAMPETH|30-08-2020|01|RAHUL JOSHI|ASSISTANT MANAGER|HEAD OFFICE|4|CBSNEXORA BANK|IN4711|1|0||0|3||||||
|
20|3|01|01|||||||||||01|||01|10080|MR|ARJUN||DESHMUKH|||||||01|MR|VIJAY|DESHMUKH|||MRS|REKHA|DESHMUKH|||M|01|IN|B-01|15-01-1978|||||||||01|02|||||02|HOUSE NO 7, DHARAMPETH|RAMDASPETH|NAGPUR|NAGPUR|NAGPUR|MH|IN|440010|01||Y|02|HOUSE NO 7, DHARAMPETH|RAMDASPETH|NAGPUR|NAGPUR|NAGPUR|MH|IN|440010|01||||||||||||||||9823033003|||||30-08-2020|NAGPUR DHARAMPETH|30-08-2020|01|RAHUL JOSHI|ASSISTANT MANAGER|HEAD OFFICE|4|BHARAT URBAN BANK|IN4711|1|0||0|3||||||
|
||||||
30|3|C|AGTPD1144K||01|02|||||
|
30|3|C|AGTPD1144K||01|02|||||
|
||||||
70|3|10080_Sign.jpeg|09|01|01||||
|
70|3|10080_Sign.jpeg|09|01|01||||
|
||||||
70|3|10080_AP.jpg|04|01|01||||
|
70|3|10080_AP.jpg|04|01|01||||
|
||||||
|
|||||||
+6
-52
@@ -533,8 +533,6 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
screeningLogId: number | null = null;
|
screeningLogId: number | null = null;
|
||||||
/** Snapshot of the name that was last verified — used to invalidate verification when name changes */
|
/** Snapshot of the name that was last verified — used to invalidate verification when name changes */
|
||||||
private verifiedName: string = '';
|
private verifiedName: string = '';
|
||||||
/** True while prefillForEdit() is patching historical monthly/annual values — see linkMonthlyAnnual(). */
|
|
||||||
private suppressMonthlyAnnualLink = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {FormBuilder} formBuilder Form Builder
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
@@ -711,12 +709,6 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
const data = this.clientData;
|
const data = this.clientData;
|
||||||
this.loadExistingSeniorPwdDocuments();
|
this.loadExistingSeniorPwdDocuments();
|
||||||
|
|
||||||
// Loading an existing client's independently-saved monthly/annual values must not run
|
|
||||||
// through the auto-calc in linkMonthlyAnnual() — patching monthlyIncome then annualIncome
|
|
||||||
// (or vice versa) would otherwise have each overwrite the other via its valueChanges,
|
|
||||||
// instead of keeping the exact historical values as saved.
|
|
||||||
this.suppressMonthlyAnnualLink = true;
|
|
||||||
|
|
||||||
// Patch all base controls that exist on the form
|
// Patch all base controls that exist on the form
|
||||||
this.createClientForm.patchValue({
|
this.createClientForm.patchValue({
|
||||||
officeId: data.officeId,
|
officeId: data.officeId,
|
||||||
@@ -751,7 +743,6 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
? new Date(data.timeline.submittedOnDate)
|
? new Date(data.timeline.submittedOnDate)
|
||||||
: this.settingsService.businessDate
|
: this.settingsService.businessDate
|
||||||
});
|
});
|
||||||
this.suppressMonthlyAnnualLink = false;
|
|
||||||
|
|
||||||
// Patch name fields (already created by buildDependencies via legalFormId subscription)
|
// Patch name fields (already created by buildDependencies via legalFormId subscription)
|
||||||
const legalFormId = data.legalForm?.id || LegalFormId.PERSON;
|
const legalFormId = data.legalForm?.id || LegalFormId.PERSON;
|
||||||
@@ -963,43 +954,6 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
Validators.required
|
Validators.required
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
this.linkMonthlyAnnual('monthlyIncome', 'annualIncome');
|
|
||||||
this.linkMonthlyAnnual('monthlyTurnover', 'annualTurnover');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keeps a Monthly/Annual pair of amount fields in sync at a 12x ratio — typing into either
|
|
||||||
* one auto-fills the other. Uses { emitEvent: false } on the write side so the two
|
|
||||||
* subscriptions don't trigger each other back and forth.
|
|
||||||
*/
|
|
||||||
/** Rounds to at most 2 decimals without padding whole numbers with a trailing ".00". */
|
|
||||||
private round2(n: number): string {
|
|
||||||
return (Math.round(n * 100) / 100).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private linkMonthlyAnnual(monthlyControlName: string, annualControlName: string): void {
|
|
||||||
const monthly = this.createClientForm.get(monthlyControlName);
|
|
||||||
const annual = this.createClientForm.get(annualControlName);
|
|
||||||
if (!monthly || !annual) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
monthly.valueChanges.subscribe((value) => {
|
|
||||||
if (this.suppressMonthlyAnnualLink) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const num = parseFloat(value);
|
|
||||||
annual.setValue(!isNaN(num) && num >= 0 ? this.round2(num * 12) : '', { emitEvent: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
annual.valueChanges.subscribe((value) => {
|
|
||||||
if (this.suppressMonthlyAnnualLink) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const num = parseFloat(value);
|
|
||||||
monthly.setValue(!isNaN(num) && num >= 0 ? this.round2(num / 12) : '', { emitEvent: false });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2236,26 +2190,26 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Match monthly income ranges
|
// Match monthly income ranges
|
||||||
if (rule.minMonthlyIncome != null && rule.minMonthlyIncome > 0 && monthlyIncome < rule.minMonthlyIncome) {
|
if (rule.minMonthlyIncome != null && monthlyIncome < rule.minMonthlyIncome) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (rule.maxMonthlyIncome != null && rule.maxMonthlyIncome > 0 && monthlyIncome > rule.maxMonthlyIncome) {
|
if (rule.maxMonthlyIncome != null && monthlyIncome > rule.maxMonthlyIncome) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match annual income ranges (evaluated against totalAnnualIncome)
|
// Match annual income ranges (evaluated against totalAnnualIncome)
|
||||||
if (rule.minAnnualIncome != null && rule.minAnnualIncome > 0 && totalAnnualIncome < rule.minAnnualIncome) {
|
if (rule.minAnnualIncome != null && totalAnnualIncome < rule.minAnnualIncome) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (rule.maxAnnualIncome != null && rule.maxAnnualIncome > 0 && totalAnnualIncome > rule.maxAnnualIncome) {
|
if (rule.maxAnnualIncome != null && totalAnnualIncome > rule.maxAnnualIncome) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match turnover ranges
|
// Match turnover ranges
|
||||||
if (rule.minAnnualTurnover != null && rule.minAnnualTurnover > 0 && annualTurnover < rule.minAnnualTurnover) {
|
if (rule.minAnnualTurnover != null && annualTurnover < rule.minAnnualTurnover) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (rule.maxAnnualTurnover != null && rule.maxAnnualTurnover > 0 && annualTurnover > rule.maxAnnualTurnover) {
|
if (rule.maxAnnualTurnover != null && annualTurnover > rule.maxAnnualTurnover) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -440,18 +440,6 @@ export class AuthenticationService {
|
|||||||
return roleNames.some((n) => n === 'cashier');
|
return roleNames.some((n) => n === 'cashier');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when the logged-in user belongs to the Head Office.
|
|
||||||
* Fineract seeds Head Office as officeId 1 with hierarchy "."; office name is
|
|
||||||
* checked too since some deployments rename/reseed the root office.
|
|
||||||
*/
|
|
||||||
isHeadOfficeUser(): boolean {
|
|
||||||
const creds = this.getCredentials();
|
|
||||||
if (!creds) return false;
|
|
||||||
if (creds.officeId === 1) return true;
|
|
||||||
return (creds.officeName || '').toLowerCase().includes('head office');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the user credentials.
|
* Sets the user credentials.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -362,10 +362,6 @@
|
|||||||
<fa-icon icon="university" class="ni-ico"></fa-icon>RBI
|
<fa-icon icon="university" class="ni-ico"></fa-icon>RBI
|
||||||
<fa-icon [icon]="rbiMenuOpen ? 'caret-up' : 'caret-down'" class="ni-caret"></fa-icon>
|
<fa-icon [icon]="rbiMenuOpen ? 'caret-up' : 'caret-down'" class="ni-caret"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<a class="ni" tabindex="0" (click)="investmentMenuOpen = !investmentMenuOpen; rbiMenuOpen = false; glMenuOpen = false" *mifosxHasPermission="'MENU_RBI'">
|
|
||||||
<fa-icon icon="chart-line" class="ni-ico"></fa-icon>Investment
|
|
||||||
<fa-icon [icon]="investmentMenuOpen ? 'caret-up' : 'caret-down'" class="ni-caret"></fa-icon>
|
|
||||||
</a>
|
|
||||||
<a class="ni" tabindex="0" [matMenuTriggerFor]="digitalMenu" *mifosxHasPermission="'MENU_DIGITAL'">
|
<a class="ni" tabindex="0" [matMenuTriggerFor]="digitalMenu" *mifosxHasPermission="'MENU_DIGITAL'">
|
||||||
<fa-icon icon="mobile-alt" class="ni-ico"></fa-icon>Digital
|
<fa-icon icon="mobile-alt" class="ni-ico"></fa-icon>Digital
|
||||||
<fa-icon icon="caret-down" class="ni-caret"></fa-icon>
|
<fa-icon icon="caret-down" class="ni-caret"></fa-icon>
|
||||||
@@ -496,15 +492,6 @@
|
|||||||
<fa-icon icon="university" class="ni-ico"></fa-icon>RBI
|
<fa-icon icon="university" class="ni-ico"></fa-icon>RBI
|
||||||
<fa-icon [icon]="rbiMenuOpen ? 'caret-up' : 'caret-down'" class="ni-caret"></fa-icon>
|
<fa-icon [icon]="rbiMenuOpen ? 'caret-up' : 'caret-down'" class="ni-caret"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<a
|
|
||||||
class="ni"
|
|
||||||
tabindex="0"
|
|
||||||
(click)="investmentMenuOpen = !investmentMenuOpen; rbiMenuOpen = false; glMenuOpen = false"
|
|
||||||
*mifosxHasPermission="'MENU_RBI'"
|
|
||||||
>
|
|
||||||
<fa-icon icon="chart-line" class="ni-ico"></fa-icon>Investment
|
|
||||||
<fa-icon [icon]="investmentMenuOpen ? 'caret-up' : 'caret-down'" class="ni-caret"></fa-icon>
|
|
||||||
</a>
|
|
||||||
<a class="ni" tabindex="0" [matMenuTriggerFor]="reportsMenu" *mifosxHasPermission="'MENU_REPORTS'">
|
<a class="ni" tabindex="0" [matMenuTriggerFor]="reportsMenu" *mifosxHasPermission="'MENU_REPORTS'">
|
||||||
<fa-icon icon="chart-bar" class="ni-ico"></fa-icon>Reports
|
<fa-icon icon="chart-bar" class="ni-ico"></fa-icon>Reports
|
||||||
<fa-icon icon="caret-down" class="ni-caret"></fa-icon>
|
<fa-icon icon="caret-down" class="ni-caret"></fa-icon>
|
||||||
@@ -666,7 +653,6 @@
|
|||||||
<a
|
<a
|
||||||
class="gl-link"
|
class="gl-link"
|
||||||
[class.hidden]="!matchesSubLedgerSearch('Cash / Clearing / Transfer')"
|
[class.hidden]="!matchesSubLedgerSearch('Cash / Clearing / Transfer')"
|
||||||
routerLink="/cashier/cash-clearing-transfer-summary"
|
|
||||||
(click)="subLedgerOpen = false"
|
(click)="subLedgerOpen = false"
|
||||||
><fa-icon icon="money-bill" class="gl-icon"></fa-icon> Cash / Clearing / Transfer</a
|
><fa-icon icon="money-bill" class="gl-icon"></fa-icon> Cash / Clearing / Transfer</a
|
||||||
>
|
>
|
||||||
@@ -715,11 +701,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="gl-group">
|
<div class="gl-group">
|
||||||
<div class="gl-group-title">Deposits</div>
|
<div class="gl-group-title">Deposits</div>
|
||||||
<a
|
<a class="gl-link" [class.hidden]="!matchesSubLedgerSearch('FD / RD Entry')" (click)="subLedgerOpen = false"
|
||||||
class="gl-link"
|
><fa-icon icon="piggy-bank" class="gl-icon"></fa-icon> FD / RD Entry</a
|
||||||
[class.hidden]="!matchesSubLedgerSearch('FD / RD Entry')"
|
|
||||||
(click)="subLedgerOpen = false; openFdRdEntryDialog()"
|
|
||||||
><fa-icon icon="piggy-bank" class="gl-icon" style="color: #0f766e"></fa-icon> FD / RD Entry</a
|
|
||||||
>
|
>
|
||||||
<a class="gl-link" [class.hidden]="!matchesSubLedgerSearch('DDS Entry')" (click)="subLedgerOpen = false"
|
<a class="gl-link" [class.hidden]="!matchesSubLedgerSearch('DDS Entry')" (click)="subLedgerOpen = false"
|
||||||
><fa-icon icon="coins" class="gl-icon"></fa-icon> DDS Entry</a
|
><fa-icon icon="coins" class="gl-icon"></fa-icon> DDS Entry</a
|
||||||
@@ -825,32 +808,25 @@
|
|||||||
<div class="gl-columns">
|
<div class="gl-columns">
|
||||||
<div class="gl-group">
|
<div class="gl-group">
|
||||||
<div class="gl-group-title">Financial Statements</div>
|
<div class="gl-group-title">Financial Statements</div>
|
||||||
<a
|
<a class="gl-link" [class.hidden]="!matchesGlSearch('Trial Balance')" (click)="glMenuOpen = false"
|
||||||
class="gl-link"
|
><fa-icon icon="balance-scale" class="gl-icon"></fa-icon> Trial Balance</a
|
||||||
[class.hidden]="!matchesGlSearch('Trial Balance')"
|
|
||||||
routerLink="/gl/reports/trial-balance"
|
|
||||||
(click)="glMenuOpen = false"
|
|
||||||
><fa-icon icon="balance-scale" class="gl-icon" style="color: #b45309"></fa-icon> Trial Balance</a
|
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="gl-link"
|
class="gl-link"
|
||||||
[class.hidden]="!matchesGlSearch('Profit and Loss')"
|
[class.hidden]="!matchesGlSearch('Profit and Loss')"
|
||||||
routerLink="/gl/reports/profit-and-loss"
|
(click)="glMenuOpen = false; openAccountingReportsDialog('PNL')"
|
||||||
(click)="glMenuOpen = false"
|
|
||||||
><fa-icon icon="chart-line" class="gl-icon" style="color: #b45309"></fa-icon> Profit and Loss</a
|
><fa-icon icon="chart-line" class="gl-icon" style="color: #b45309"></fa-icon> Profit and Loss</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="gl-link"
|
class="gl-link"
|
||||||
[class.hidden]="!matchesGlSearch('Consolidate Balance Sheet')"
|
[class.hidden]="!matchesGlSearch('Consolidate Balance Sheet')"
|
||||||
routerLink="/gl/reports/balance-sheet"
|
(click)="glMenuOpen = false; openAccountingReportsDialog('BALANCE-SHEET')"
|
||||||
(click)="glMenuOpen = false"
|
><fa-icon icon="file-alt" class="gl-icon" style="color: #b45309"></fa-icon> Consolidate Balance Sheet</a
|
||||||
><fa-icon icon="file-alt" class="gl-icon" style="color: #b45309"></fa-icon> Consolidated Balance Sheet</a
|
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="gl-link"
|
class="gl-link"
|
||||||
[class.hidden]="!matchesGlSearch('Comparative Balance Sheet')"
|
[class.hidden]="!matchesGlSearch('Comparative Balance Sheet')"
|
||||||
routerLink="/gl/reports/comparative-balance-sheet"
|
(click)="glMenuOpen = false; openAccountingReportsDialog('COMPARATIVE-BS')"
|
||||||
(click)="glMenuOpen = false"
|
|
||||||
><fa-icon icon="balance-scale" class="gl-icon" style="color: #b45309"></fa-icon> Comparative Balance
|
><fa-icon icon="balance-scale" class="gl-icon" style="color: #b45309"></fa-icon> Comparative Balance
|
||||||
Sheet</a
|
Sheet</a
|
||||||
>
|
>
|
||||||
@@ -875,8 +851,7 @@
|
|||||||
<a
|
<a
|
||||||
class="gl-link"
|
class="gl-link"
|
||||||
[class.hidden]="!matchesGlSearch('GL Wise DR CR')"
|
[class.hidden]="!matchesGlSearch('GL Wise DR CR')"
|
||||||
routerLink="/gl/reports/gl-wise-dr-cr"
|
(click)="glMenuOpen = false; openAccountingReportsDialog('GL-DR-CR')"
|
||||||
(click)="glMenuOpen = false"
|
|
||||||
><fa-icon icon="project-diagram" class="gl-icon" style="color: #b45309"></fa-icon> GL Wise DR / CR
|
><fa-icon icon="project-diagram" class="gl-icon" style="color: #b45309"></fa-icon> GL Wise DR / CR
|
||||||
Report</a
|
Report</a
|
||||||
>
|
>
|
||||||
@@ -889,13 +864,8 @@
|
|||||||
(click)="glMenuOpen = false"
|
(click)="glMenuOpen = false"
|
||||||
><fa-icon icon="users" class="gl-icon"></fa-icon> Client wise Balance Report</a
|
><fa-icon icon="users" class="gl-icon"></fa-icon> Client wise Balance Report</a
|
||||||
>
|
>
|
||||||
<a
|
<a class="gl-link" [class.hidden]="!matchesGlSearch('Contingent Expense')" (click)="glMenuOpen = false"
|
||||||
class="gl-link"
|
><fa-icon icon="exclamation-circle" class="gl-icon"></fa-icon> Contingent Expense</a
|
||||||
[class.hidden]="!matchesGlSearch('Contingent Liabilities')"
|
|
||||||
routerLink="/gl/reports/contingent-liabilities"
|
|
||||||
(click)="glMenuOpen = false"
|
|
||||||
><fa-icon icon="exclamation-circle" class="gl-icon" style="color: #b45309"></fa-icon> Contingent
|
|
||||||
Liabilities</a
|
|
||||||
>
|
>
|
||||||
<a class="gl-link" [class.hidden]="!matchesGlSearch('Banker Head Report')" (click)="glMenuOpen = false"
|
<a class="gl-link" [class.hidden]="!matchesGlSearch('Banker Head Report')" (click)="glMenuOpen = false"
|
||||||
><fa-icon icon="university" class="gl-icon"></fa-icon> Banker Head Report</a
|
><fa-icon icon="university" class="gl-icon"></fa-icon> Banker Head Report</a
|
||||||
@@ -912,8 +882,7 @@
|
|||||||
<a
|
<a
|
||||||
class="gl-link"
|
class="gl-link"
|
||||||
[class.hidden]="!matchesGlSearch('Misc Expense Ledger')"
|
[class.hidden]="!matchesGlSearch('Misc Expense Ledger')"
|
||||||
routerLink="/gl/reports/misc-expense-ledger"
|
(click)="glMenuOpen = false; openAccountingReportsDialog('MISC-EXP')"
|
||||||
(click)="glMenuOpen = false"
|
|
||||||
><fa-icon icon="receipt" class="gl-icon" style="color: #b45309"></fa-icon> Misc Expense Ledger</a
|
><fa-icon icon="receipt" class="gl-icon" style="color: #b45309"></fa-icon> Misc Expense Ledger</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -1186,7 +1155,7 @@
|
|||||||
<fa-icon icon="user-slash" class="menu-item-icon" style="color: #c2410c"></fa-icon> Non-Customer Entry
|
<fa-icon icon="user-slash" class="menu-item-icon" style="color: #c2410c"></fa-icon> Non-Customer Entry
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0"><fa-icon icon="lock" class="menu-item-icon"></fa-icon> Locker</button>
|
<button mat-menu-item tabindex="0"><fa-icon icon="lock" class="menu-item-icon"></fa-icon> Locker</button>
|
||||||
<button mat-menu-item routerLink="/investment/bonds" tabindex="0">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="chart-line" class="menu-item-icon"></fa-icon> SLR and Non SLR Investment Report
|
<fa-icon icon="chart-line" class="menu-item-icon"></fa-icon> SLR and Non SLR Investment Report
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0" (click)="openCicReportingDialog()">
|
<button mat-menu-item tabindex="0" (click)="openCicReportingDialog()">
|
||||||
@@ -1202,13 +1171,13 @@
|
|||||||
<!-- Investment -->
|
<!-- Investment -->
|
||||||
<div class="deposit-menu-group">
|
<div class="deposit-menu-group">
|
||||||
<div class="deposit-group-title">Investment</div>
|
<div class="deposit-group-title">Investment</div>
|
||||||
<button mat-menu-item routerLink="/investment/dashboard" tabindex="0">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="calendar-alt" class="menu-item-icon"></fa-icon> Due Date Diary for All Investment
|
<fa-icon icon="calendar-alt" class="menu-item-icon"></fa-icon> Due Date Diary for All Investment
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item routerLink="/investment/reports" tabindex="0">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="dollar-sign" class="menu-item-icon"></fa-icon> Total Int Received & Receivable Report
|
<fa-icon icon="dollar-sign" class="menu-item-icon"></fa-icon> Total Int Received & Receivable Report
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item routerLink="/investment/reports" tabindex="0">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="piggy-bank" class="menu-item-icon"></fa-icon> Bucket Wise Investment
|
<fa-icon icon="piggy-bank" class="menu-item-icon"></fa-icon> Bucket Wise Investment
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item routerLink="/deaf" tabindex="0">
|
<button mat-menu-item routerLink="/deaf" tabindex="0">
|
||||||
@@ -1239,41 +1208,39 @@
|
|||||||
<!-- NPA Reports -->
|
<!-- NPA Reports -->
|
||||||
<div class="deposit-menu-group">
|
<div class="deposit-menu-group">
|
||||||
<div class="deposit-group-title">NPA Reports</div>
|
<div class="deposit-group-title">NPA Reports</div>
|
||||||
<button mat-menu-item tabindex="0" routerLink="/npa/reports/interest">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="chart-line" class="menu-item-icon"></fa-icon> NPA Interest
|
<fa-icon icon="chart-line" class="menu-item-icon"></fa-icon> NPA Interest
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0" routerLink="/npa/reports/status">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="file-alt" class="menu-item-icon"></fa-icon> NPA Status Report
|
<fa-icon icon="file-alt" class="menu-item-icon"></fa-icon> NPA Status Report
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0" routerLink="/npa/reports/recovery">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="undo" class="menu-item-icon"></fa-icon> NPA Recovery Report
|
<fa-icon icon="undo" class="menu-item-icon"></fa-icon> NPA Recovery Report
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0" routerLink="/npa/reports/summary">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="clipboard-list" class="menu-item-icon"></fa-icon> NPA Summary Report
|
<fa-icon icon="clipboard-list" class="menu-item-icon"></fa-icon> NPA Summary Report
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- NPA Analysis -->
|
<!-- NPA Analysis -->
|
||||||
<div class="deposit-menu-group">
|
<div class="deposit-menu-group">
|
||||||
<div class="deposit-group-title">NPA Analysis</div>
|
<div class="deposit-group-title">NPA Analysis</div>
|
||||||
<button mat-menu-item tabindex="0" routerLink="/npa/reports/account-detail">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="list-alt" class="menu-item-icon"></fa-icon> NPA Account Detail Report
|
<fa-icon icon="list-alt" class="menu-item-icon"></fa-icon> NPA Account Detail Report
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0" routerLink="/npa/reports/movement">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="exchange" class="menu-item-icon"></fa-icon> NPA Movement Report
|
<fa-icon icon="exchange" class="menu-item-icon"></fa-icon> NPA Movement Report
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0" routerLink="/npa/reports/provision">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="hand-holding-usd" class="menu-item-icon"></fa-icon> NPA with Provision and Required Provision
|
<fa-icon icon="hand-holding-usd" class="menu-item-icon"></fa-icon> NPA with Provision and Required Provision
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- NPA Classification -->
|
<!-- NPA Classification -->
|
||||||
<div class="deposit-menu-group">
|
<div class="deposit-menu-group">
|
||||||
<div class="deposit-group-title">NPA Classification</div>
|
<div class="deposit-group-title">NPA Classification</div>
|
||||||
<button mat-menu-item tabindex="0" routerLink="/npa/reports/probable">
|
<button mat-menu-item tabindex="0">
|
||||||
<fa-icon icon="exclamation-circle" class="menu-item-icon"></fa-icon> Probable NPA
|
<fa-icon icon="exclamation-circle" class="menu-item-icon"></fa-icon> Probable NPA
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0" routerLink="/npa/reports/sma">
|
<button mat-menu-item tabindex="0"><fa-icon icon="flag" class="menu-item-icon"></fa-icon> SMA Report</button>
|
||||||
<fa-icon icon="flag" class="menu-item-icon"></fa-icon> SMA Report
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
@@ -1710,29 +1677,24 @@
|
|||||||
<!-- OSS Reports -->
|
<!-- OSS Reports -->
|
||||||
<div class="gl-group">
|
<div class="gl-group">
|
||||||
<div class="gl-group-title">OSS Reports</div>
|
<div class="gl-group-title">OSS Reports</div>
|
||||||
<a
|
<a class="gl-link" [class.hidden]="!matchesRbiSearch('OSS 1 XBRL/CIMS')" (click)="rbiMenuOpen = false">
|
||||||
class="gl-link"
|
<fa-icon icon="file-alt" class="gl-icon"></fa-icon> OSS 1 XBRL/CIMS
|
||||||
[class.hidden]="!matchesRbiSearch('OSS 1 XBRL/CIMS')"
|
|
||||||
routerLink="/rbi/oss1"
|
|
||||||
(click)="rbiMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="file-alt" class="gl-icon" style="color: #0891b2"></fa-icon> OSS 1 XBRL/CIMS
|
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="gl-link"
|
class="gl-link"
|
||||||
[class.hidden]="!matchesRbiSearch('OSS 2 XBRL')"
|
[class.hidden]="!matchesRbiSearch('OSS 2 XBRL')"
|
||||||
routerLink="/rbi/oss2"
|
(click)="rbiMenuOpen = false; openCimsXbrlDialog('OSS-2')"
|
||||||
(click)="rbiMenuOpen = false"
|
|
||||||
>
|
>
|
||||||
<fa-icon icon="file-alt" class="gl-icon" style="color: #0891b2"></fa-icon> OSS 2 XBRL
|
<fa-icon icon="file-alt" class="gl-icon" style="color: #0891b2"></fa-icon> OSS 2 XBRL
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a class="gl-link" [class.hidden]="!matchesRbiSearch('OSS 3 XBRL/CIMS')" (click)="rbiMenuOpen = false">
|
||||||
class="gl-link"
|
<fa-icon icon="file-alt" class="gl-icon"></fa-icon> OSS 3 XBRL/CIMS
|
||||||
[class.hidden]="!matchesRbiSearch('OSS 3 XBRL/CIMS')"
|
</a>
|
||||||
routerLink="/rbi/oss3"
|
<a class="gl-link" [class.hidden]="!matchesRbiSearch('OSS 4/OSS 5/OSS 6')" (click)="rbiMenuOpen = false">
|
||||||
(click)="rbiMenuOpen = false"
|
<fa-icon icon="file-alt" class="gl-icon"></fa-icon> OSS 4 / OSS 5 / OSS 6
|
||||||
>
|
</a>
|
||||||
<fa-icon icon="file-alt" class="gl-icon" style="color: #0891b2"></fa-icon> OSS 3 XBRL/CIMS
|
<a class="gl-link" [class.hidden]="!matchesRbiSearch('OSS 7')" (click)="rbiMenuOpen = false">
|
||||||
|
<fa-icon icon="file-alt" class="gl-icon"></fa-icon> OSS 7
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- CIMS / XBRL Returns -->
|
<!-- CIMS / XBRL Returns -->
|
||||||
@@ -1863,161 +1825,6 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Investment (Treasury) Mega Menu -->
|
|
||||||
@if (investmentMenuOpen) {
|
|
||||||
<div class="gl-backdrop" (click)="investmentMenuOpen = false"></div>
|
|
||||||
<div class="gl-mega-menu rbi-mega-menu">
|
|
||||||
<div class="gl-mega-menu-header">
|
|
||||||
<fa-icon icon="chart-line" class="gl-mega-menu-header-icon"></fa-icon>
|
|
||||||
<span>Investment</span>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="gl-search"
|
|
||||||
placeholder="Search Investment..."
|
|
||||||
(input)="investmentSearch = $any($event.target).value"
|
|
||||||
/>
|
|
||||||
<div class="rbi-columns">
|
|
||||||
<!-- Dashboard -->
|
|
||||||
<div class="gl-group">
|
|
||||||
<div class="gl-group-title">Dashboard</div>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('Investment Dashboard')"
|
|
||||||
routerLink="/investment/dashboard"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="chart-pie" class="gl-icon"></fa-icon> Portfolio Dashboard
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('Due Date Diary for All Investment')"
|
|
||||||
routerLink="/investment/dashboard"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="calendar-check" class="gl-icon"></fa-icon> Due Date Diary for All Investment
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('Bucket Wise Investment')"
|
|
||||||
routerLink="/investment/reports"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="layer-group" class="gl-icon"></fa-icon> Bucket Wise Investment
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<!-- Entry -->
|
|
||||||
<div class="gl-group">
|
|
||||||
<div class="gl-group-title">Entry</div>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('FD Investment')"
|
|
||||||
routerLink="/investment/fd"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="piggy-bank" class="gl-icon"></fa-icon> FD Investment
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('Govt Security / Non-SLR / Bond / Mutual Fund')"
|
|
||||||
routerLink="/investment/bonds"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="university" class="gl-icon"></fa-icon> Govt Security / Non-SLR / Bond / Mutual Fund
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('SLR and Non SLR Investment Report')"
|
|
||||||
routerLink="/investment/bonds"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="balance-scale" class="gl-icon"></fa-icon> SLR and Non SLR Investment Report
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('Share Entry')"
|
|
||||||
routerLink="/investment/share"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="chart-line" class="gl-icon"></fa-icon> Share Entry
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<!-- Masters -->
|
|
||||||
<div class="gl-group">
|
|
||||||
<div class="gl-group-title">Masters</div>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="
|
|
||||||
!matchesInvestmentSearch(
|
|
||||||
'Bank Party Script Broker SubType Control Parameter Daily Script LIEN Reason Mutual Fund Product SGL Bank Script Market Detail Masters'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
routerLink="/investment/masters"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="cogs" class="gl-icon"></fa-icon> Bank/Party, Script, Broker & Other Masters
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<!-- Interest -->
|
|
||||||
<div class="gl-group">
|
|
||||||
<div class="gl-group-title">Interest</div>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('FD Interest Calculation')"
|
|
||||||
routerLink="/investment/interest"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="calculator" class="gl-icon"></fa-icon> FD Interest Calculation
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('Govt Bond Interest Calculation')"
|
|
||||||
routerLink="/investment/interest"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="calculator" class="gl-icon"></fa-icon> Govt./Bond Int. Calculation
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('Government Bond Int Register FD Interest Register FD Register')"
|
|
||||||
routerLink="/investment/interest"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="list-alt" class="gl-icon"></fa-icon> Bond Int / FD Int / FD Registers
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<!-- Reports -->
|
|
||||||
<div class="gl-group">
|
|
||||||
<div class="gl-group-title">Reports</div>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('Balance Ledger Maturity Investment Reports')"
|
|
||||||
routerLink="/investment/reports"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="file-invoice" class="gl-icon"></fa-icon> Balance / Ledger / Maturity Reports
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('Total Int Received & Receivable Report')"
|
|
||||||
routerLink="/investment/reports"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="hand-holding-usd" class="gl-icon"></fa-icon> Total Int Received & Receivable Report
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="gl-link"
|
|
||||||
[class.hidden]="!matchesInvestmentSearch('Investment Portfolio')"
|
|
||||||
routerLink="/investment/reports"
|
|
||||||
(click)="investmentMenuOpen = false"
|
|
||||||
>
|
|
||||||
<fa-icon icon="briefcase" class="gl-icon"></fa-icon> Investment Portfolio
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Progress Bar -->
|
<!-- Progress Bar -->
|
||||||
@if (progressBarMode !== 'none') {
|
@if (progressBarMode !== 'none') {
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -856,20 +856,6 @@ $gray-700: #334155;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marks a menu link as actually wired to a working screen/dialog (vs a still-unwired
|
|
||||||
// placeholder) — a small green dot at the end of the row, so a reviewer can tell at a glance
|
|
||||||
// which items in a mega-menu are functional without having to click every one of them.
|
|
||||||
.gl-link--live::after {
|
|
||||||
content: '';
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
margin-left: auto;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #16a34a;
|
|
||||||
box-shadow: 0 0 0 2px rgb(22 163 74 / 15%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* RBI Mega Menu */
|
/* RBI Mega Menu */
|
||||||
.rbi-mega-menu {
|
.rbi-mega-menu {
|
||||||
width: 1100px;
|
width: 1100px;
|
||||||
|
|||||||
@@ -98,10 +98,6 @@ export class ShellComponent implements OnInit, OnDestroy {
|
|||||||
rbiMenuOpen = false;
|
rbiMenuOpen = false;
|
||||||
rbiSearch = '';
|
rbiSearch = '';
|
||||||
|
|
||||||
/** Investment (Treasury) mega menu state */
|
|
||||||
investmentMenuOpen = false;
|
|
||||||
investmentSearch = '';
|
|
||||||
|
|
||||||
/* Live KPI ticker — markup is static inside the template; the marquee
|
/* Live KPI ticker — markup is static inside the template; the marquee
|
||||||
animation, LIVE badge, and pause-on-hover handle the live feel. */
|
animation, LIVE badge, and pause-on-hover handle the live feel. */
|
||||||
|
|
||||||
@@ -120,11 +116,6 @@ export class ShellComponent implements OnInit, OnDestroy {
|
|||||||
return label.toLowerCase().includes(this.rbiSearch.toLowerCase());
|
return label.toLowerCase().includes(this.rbiSearch.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
matchesInvestmentSearch(label: string): boolean {
|
|
||||||
if (!this.investmentSearch) return true;
|
|
||||||
return label.toLowerCase().includes(this.investmentSearch.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Route for a facility entry in the Account Facilities hover dropdown — only "Cheque Book" has a
|
/** Route for a facility entry in the Account Facilities hover dropdown — only "Cheque Book" has a
|
||||||
* dedicated action page today; every other facility falls back to the existing overview page. */
|
* dedicated action page today; every other facility falls back to the existing overview page. */
|
||||||
facilityRoute(label: string): string[] {
|
facilityRoute(label: string): string[] {
|
||||||
@@ -251,7 +242,7 @@ export class ShellComponent implements OnInit, OnDestroy {
|
|||||||
* GL Wise DR/CR / Misc Expense Ledger). Initial report selected via menu link.
|
* GL Wise DR/CR / Misc Expense Ledger). Initial report selected via menu link.
|
||||||
*/
|
*/
|
||||||
async openAccountingReportsDialog(
|
async openAccountingReportsDialog(
|
||||||
initialReport: 'PNL' | 'BALANCE-SHEET' | 'COMPARATIVE-BS' | 'CONTINGENT-LIAB' | 'GL-DR-CR' | 'MISC-EXP' = 'PNL'
|
initialReport: 'PNL' | 'BALANCE-SHEET' | 'COMPARATIVE-BS' | 'GL-DR-CR' | 'MISC-EXP' = 'PNL'
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { AccountingReportsDialogComponent } =
|
const { AccountingReportsDialogComponent } =
|
||||||
await import('../../home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component');
|
await import('../../home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component');
|
||||||
@@ -294,9 +285,7 @@ export class ShellComponent implements OnInit, OnDestroy {
|
|||||||
* is selected based on the menu link the user clicked; user can switch between
|
* is selected based on the menu link the user clicked; user can switch between
|
||||||
* forms via the form-pill selector inside the dialog.
|
* forms via the form-pill selector inside the dialog.
|
||||||
*/
|
*/
|
||||||
async openCimsXbrlDialog(
|
async openCimsXbrlDialog(initialForm: 'OSS-2' | 'PSA' | 'RAL' | 'RCP-UCB' = 'OSS-2'): Promise<void> {
|
||||||
initialForm: 'OSS-2' | 'PSA' | 'RAL' | 'RCP-UCB' | 'OSS-4' | 'OSS-5' | 'OSS-6' | 'OSS-7' = 'OSS-2'
|
|
||||||
): Promise<void> {
|
|
||||||
const { CimsXbrlDialogComponent } =
|
const { CimsXbrlDialogComponent } =
|
||||||
await import('../../home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component');
|
await import('../../home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component');
|
||||||
this.dialog.open(CimsXbrlDialogComponent, {
|
this.dialog.open(CimsXbrlDialogComponent, {
|
||||||
@@ -312,26 +301,6 @@ export class ShellComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Opens the FD / RD Entry dialog under Sub Ledger → Deposits. */
|
|
||||||
async openFdRdEntryDialog(): Promise<void> {
|
|
||||||
const { FdRdEntryDialogComponent } =
|
|
||||||
await import('../../home/dashboard/fd-rd-entry-dialog/fd-rd-entry-dialog.component');
|
|
||||||
this.dialog.open(FdRdEntryDialogComponent, {
|
|
||||||
width: '100vw',
|
|
||||||
height: '100vh',
|
|
||||||
maxWidth: '100vw',
|
|
||||||
maxHeight: '100vh',
|
|
||||||
panelClass: [
|
|
||||||
'crr-slr-dialog-panel',
|
|
||||||
'fdrd-fullscreen-panel'
|
|
||||||
],
|
|
||||||
autoFocus: false,
|
|
||||||
disableClose: false,
|
|
||||||
hasBackdrop: true,
|
|
||||||
backdropClass: 'fdrd-dlg-backdrop'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the Asset Liability Management dialog (Form ALM-1).
|
* Opens the Asset Liability Management dialog (Form ALM-1).
|
||||||
* Covers maturity ladder, monthly snapshot, and structural liquidity returns.
|
* Covers maturity ladder, monthly snapshot, and structural liquidity returns.
|
||||||
|
|||||||
@@ -266,7 +266,7 @@
|
|||||||
<a
|
<a
|
||||||
class="subledger-link"
|
class="subledger-link"
|
||||||
[class.hidden]="!matchesSearch('Cash / Clearing / Transfer')"
|
[class.hidden]="!matchesSearch('Cash / Clearing / Transfer')"
|
||||||
(click)="navigateMenu('/cashier/cash-clearing-transfer-summary'); subLedgerOpen = false"
|
(click)="subLedgerOpen = false"
|
||||||
>
|
>
|
||||||
<fa-icon icon="money-bill" class="subledger-icon"></fa-icon> Cash / Clearing / Transfer
|
<fa-icon icon="money-bill" class="subledger-icon"></fa-icon> Cash / Clearing / Transfer
|
||||||
</a>
|
</a>
|
||||||
@@ -403,26 +403,17 @@
|
|||||||
<div class="subledger-columns">
|
<div class="subledger-columns">
|
||||||
<div class="subledger-group">
|
<div class="subledger-group">
|
||||||
<div class="subledger-group-title">Financial Statements</div>
|
<div class="subledger-group-title">Financial Statements</div>
|
||||||
<a
|
<a class="subledger-link" [class.hidden]="!matchesGlSearch('Trial Balance')" (click)="glMenuOpen = false"
|
||||||
class="subledger-link"
|
|
||||||
[class.hidden]="!matchesGlSearch('Trial Balance')"
|
|
||||||
routerLink="/gl/reports/trial-balance"
|
|
||||||
(click)="glMenuOpen = false"
|
|
||||||
><fa-icon icon="balance-scale" class="subledger-icon"></fa-icon> Trial Balance</a
|
><fa-icon icon="balance-scale" class="subledger-icon"></fa-icon> Trial Balance</a
|
||||||
>
|
>
|
||||||
<a
|
<a class="subledger-link" [class.hidden]="!matchesGlSearch('Profit and Loss')" (click)="glMenuOpen = false"
|
||||||
class="subledger-link"
|
|
||||||
[class.hidden]="!matchesGlSearch('Profit and Loss')"
|
|
||||||
routerLink="/gl/reports/profit-and-loss"
|
|
||||||
(click)="glMenuOpen = false"
|
|
||||||
><fa-icon icon="chart-line" class="subledger-icon"></fa-icon> Profit and Loss</a
|
><fa-icon icon="chart-line" class="subledger-icon"></fa-icon> Profit and Loss</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="subledger-link"
|
class="subledger-link"
|
||||||
[class.hidden]="!matchesGlSearch('Consolidated Balance Sheet')"
|
[class.hidden]="!matchesGlSearch('Consolidate Balance Sheet')"
|
||||||
routerLink="/gl/reports/balance-sheet"
|
|
||||||
(click)="glMenuOpen = false"
|
(click)="glMenuOpen = false"
|
||||||
><fa-icon icon="file-alt" class="subledger-icon"></fa-icon> Consolidated Balance Sheet</a
|
><fa-icon icon="file-alt" class="subledger-icon"></fa-icon> Consolidate Balance Sheet</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="subledger-group">
|
<div class="subledger-group">
|
||||||
@@ -457,12 +448,8 @@
|
|||||||
(click)="glMenuOpen = false"
|
(click)="glMenuOpen = false"
|
||||||
><fa-icon icon="users" class="subledger-icon"></fa-icon> Client wise Balance Report</a
|
><fa-icon icon="users" class="subledger-icon"></fa-icon> Client wise Balance Report</a
|
||||||
>
|
>
|
||||||
<a
|
<a class="subledger-link" [class.hidden]="!matchesGlSearch('Contingent Expense')" (click)="glMenuOpen = false"
|
||||||
class="subledger-link"
|
><fa-icon icon="exclamation-circle" class="subledger-icon"></fa-icon> Contingent Expense</a
|
||||||
[class.hidden]="!matchesGlSearch('Contingent Liabilities')"
|
|
||||||
routerLink="/gl/reports/contingent-liabilities"
|
|
||||||
(click)="glMenuOpen = false"
|
|
||||||
><fa-icon icon="exclamation-circle" class="subledger-icon"></fa-icon> Contingent Liabilities</a
|
|
||||||
>
|
>
|
||||||
<a class="subledger-link" [class.hidden]="!matchesGlSearch('Banker Head Report')" (click)="glMenuOpen = false"
|
<a class="subledger-link" [class.hidden]="!matchesGlSearch('Banker Head Report')" (click)="glMenuOpen = false"
|
||||||
><fa-icon icon="university" class="subledger-icon"></fa-icon> Banker Head Report</a
|
><fa-icon icon="university" class="subledger-icon"></fa-icon> Banker Head Report</a
|
||||||
|
|||||||
-15
@@ -365,21 +365,6 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Compliance / AML Action Triage -->
|
|
||||||
<div class="compliance-triage" style="margin-top: 16px; padding: 12px; background: #fff8f8; border: 1px dashed #f5c2c2; border-radius: 8px;">
|
|
||||||
<h4 style="margin: 0 0 8px; font-weight: 600; color: #b91c1c; display: flex; align-items: center; gap: 6px;">
|
|
||||||
Compliance & AML Triage
|
|
||||||
</h4>
|
|
||||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
||||||
<mat-checkbox [(ngModel)]="flagAsSuspicious" [ngModelOptions]="{standalone: true}">
|
|
||||||
Flag as suspicious (escalate to STR cases under RBI compliance)
|
|
||||||
</mat-checkbox>
|
|
||||||
<mat-checkbox [(ngModel)]="markAsWhitelisted" [ngModelOptions]="{standalone: true}">
|
|
||||||
Mark as whitelisted (prevent future AML alerts for this account)
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<div #lightboxRoot style="display: none"></div>
|
<div #lightboxRoot style="display: none"></div>
|
||||||
|
|||||||
-86
@@ -131,90 +131,6 @@ export class ItemDecisionDialogComponent implements OnInit, OnDestroy {
|
|||||||
/** 'any' = mode of operation lets any one signer sign (Either/Anyone or Survivor); 'all' = every mandatory signer must sign. */
|
/** 'any' = mode of operation lets any one signer sign (Either/Anyone or Survivor); 'all' = every mandatory signer must sign. */
|
||||||
signerSelectionMode: 'any' | 'all' = 'any';
|
signerSelectionMode: 'any' | 'all' = 'any';
|
||||||
manualOverrideChecked = false;
|
manualOverrideChecked = false;
|
||||||
flagAsSuspicious = false;
|
|
||||||
markAsWhitelisted = false;
|
|
||||||
|
|
||||||
handleComplianceActions(): void {
|
|
||||||
if (this.flagAsSuspicious) {
|
|
||||||
const existingStrData = localStorage.getItem('fiu_str_cases');
|
|
||||||
let cases: any[] = [];
|
|
||||||
if (existingStrData) {
|
|
||||||
try {
|
|
||||||
cases = JSON.parse(existingStrData);
|
|
||||||
} catch (e) {
|
|
||||||
cases = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newRef = 101 + cases.length;
|
|
||||||
const newCase: any = {
|
|
||||||
reportType: 'STR',
|
|
||||||
reportRefNo: newRef,
|
|
||||||
branchCode: this.item.presentingBranchCode || '1',
|
|
||||||
accountNumber: this.item.accountNo || this.item.accountId || '110001101000838',
|
|
||||||
txnBranchCode: this.item.presentingBranchCode || '1',
|
|
||||||
depositOrWithdrawal: 'Withdrawal',
|
|
||||||
txnId: this.item.id,
|
|
||||||
txnDate: new Date().toLocaleDateString('en-GB').replace(/\//g, '-'),
|
|
||||||
txnTime: new Date().toLocaleTimeString('en-US', { hour12: false }),
|
|
||||||
txnAmount: this.item.amount,
|
|
||||||
instrumentType: 'Cheque',
|
|
||||||
instrumentId: this.item.chequeNumber,
|
|
||||||
nonCustomerRef: '',
|
|
||||||
relationshipFlag: 'Customer',
|
|
||||||
thirdPartyPan: '',
|
|
||||||
declaration: 'NA',
|
|
||||||
customerName: this.item.clientName || 'UNKNOWN CLIENT',
|
|
||||||
detectedOn: new Date().toLocaleDateString('en-GB').replace(/\//g, '-'),
|
|
||||||
flaggedBy: 'CTS Clearing Officer',
|
|
||||||
riskCategory: 'unusual-pattern',
|
|
||||||
narrative: `CTS Cheque #${this.item.chequeNumber} for amount ₹${this.item.amount} flagged as suspicious by Clearing Officer during inward presentment review. Action: ${this.mode}. Reason/Notes: ${this.mode === 'RETURN' ? this.returnForm.value.reason : this.passForm.value.note || 'Manual compliance escalation'}`,
|
|
||||||
status: 'po-review'
|
|
||||||
};
|
|
||||||
|
|
||||||
cases.unshift(newCase);
|
|
||||||
localStorage.setItem('fiu_str_cases', JSON.stringify(cases));
|
|
||||||
|
|
||||||
const storedCtr = localStorage.getItem('fiu_ctr_txns');
|
|
||||||
if (storedCtr) {
|
|
||||||
try {
|
|
||||||
const ctrList = JSON.parse(storedCtr);
|
|
||||||
const matching = ctrList.find((t: any) => t.accountNumber === newCase.accountNumber);
|
|
||||||
if (matching) {
|
|
||||||
matching.complianceStatus = 'SUSPICIOUS';
|
|
||||||
localStorage.setItem('fiu_ctr_txns', JSON.stringify(ctrList));
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
} else if (this.markAsWhitelisted) {
|
|
||||||
const existingWhitelist = localStorage.getItem('compliance_whitelisted_accounts');
|
|
||||||
let whitelisted: string[] = [];
|
|
||||||
if (existingWhitelist) {
|
|
||||||
try {
|
|
||||||
whitelisted = JSON.parse(existingWhitelist);
|
|
||||||
} catch (e) {
|
|
||||||
whitelisted = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const account = this.item.accountNo || this.item.accountId;
|
|
||||||
if (account && !whitelisted.includes(account.toString())) {
|
|
||||||
whitelisted.push(account.toString());
|
|
||||||
localStorage.setItem('compliance_whitelisted_accounts', JSON.stringify(whitelisted));
|
|
||||||
}
|
|
||||||
|
|
||||||
const storedCtr = localStorage.getItem('fiu_ctr_txns');
|
|
||||||
if (storedCtr) {
|
|
||||||
try {
|
|
||||||
const ctrList = JSON.parse(storedCtr);
|
|
||||||
const matching = ctrList.find((t: any) => t.accountNumber === (account ? account.toString() : ''));
|
|
||||||
if (matching) {
|
|
||||||
matching.complianceStatus = 'WHITELISTED';
|
|
||||||
localStorage.setItem('fiu_ctr_txns', JSON.stringify(ctrList));
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Every automatic validation rule InwardClearingValidationService already computes, shown so the clerk sees WHY, not just a decision. */
|
/** Every automatic validation rule InwardClearingValidationService already computes, shown so the clerk sees WHY, not just a decision. */
|
||||||
readonly validationRules: { key: string; label: string; goodWhenTrue: boolean }[] = [
|
readonly validationRules: { key: string; label: string; goodWhenTrue: boolean }[] = [
|
||||||
@@ -736,7 +652,6 @@ export class ItemDecisionDialogComponent implements OnInit, OnDestroy {
|
|||||||
this.ctsService.passInwardItem(this.item.id, payload).subscribe({
|
this.ctsService.passInwardItem(this.item.id, payload).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.busy = false;
|
this.busy = false;
|
||||||
this.handleComplianceActions();
|
|
||||||
this.notify('Cheque passed');
|
this.notify('Cheque passed');
|
||||||
this.dialogRef.close({ actioned: true });
|
this.dialogRef.close({ actioned: true });
|
||||||
},
|
},
|
||||||
@@ -761,7 +676,6 @@ export class ItemDecisionDialogComponent implements OnInit, OnDestroy {
|
|||||||
this.ctsService.returnInwardItem(this.item.id, payload).subscribe({
|
this.ctsService.returnInwardItem(this.item.id, payload).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.busy = false;
|
this.busy = false;
|
||||||
this.handleComplianceActions();
|
|
||||||
this.notify('Cheque returned');
|
this.notify('Cheque returned');
|
||||||
this.dialogRef.close({ actioned: true });
|
this.dialogRef.close({ actioned: true });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export interface DeafEligibleAccount {
|
|||||||
|
|
||||||
/** Form I — Dec 2025 cycle */
|
/** Form I — Dec 2025 cycle */
|
||||||
export const FORM_I: DeafForm = {
|
export const FORM_I: DeafForm = {
|
||||||
bankName: 'CBSNexora Bank Ltd, Mumbai Main Branch',
|
bankName: 'Bharat Urban Bank Ltd, Mumbai Main Branch',
|
||||||
bankDeafCode: '1100',
|
bankDeafCode: '1100',
|
||||||
remittedThroughSponsor: 'Y',
|
remittedThroughSponsor: 'Y',
|
||||||
sponsorBankName: 'State Bank of India',
|
sponsorBankName: 'State Bank of India',
|
||||||
@@ -170,7 +170,7 @@ export const FORM_I: DeafForm = {
|
|||||||
|
|
||||||
/** Form II — Jan 2026 (head office consolidated) */
|
/** Form II — Jan 2026 (head office consolidated) */
|
||||||
export const FORM_II: DeafForm = {
|
export const FORM_II: DeafForm = {
|
||||||
bankName: 'CBSNexora Bank Ltd, Head Office',
|
bankName: 'Bharat Urban Bank Ltd, Head Office',
|
||||||
bankDeafCode: '1100',
|
bankDeafCode: '1100',
|
||||||
remittedThroughSponsor: 'Y',
|
remittedThroughSponsor: 'Y',
|
||||||
sponsorBankName: 'State Bank of India',
|
sponsorBankName: 'State Bank of India',
|
||||||
@@ -271,7 +271,7 @@ export const REFUND_CLAIMS: {
|
|||||||
auditor: string;
|
auditor: string;
|
||||||
rows: DeafClaim[];
|
rows: DeafClaim[];
|
||||||
} = {
|
} = {
|
||||||
bankName: 'CBSNexora Bank Ltd',
|
bankName: 'Bharat Urban Bank Ltd',
|
||||||
bankDeafCode: '1100',
|
bankDeafCode: '1100',
|
||||||
currentAcWithRbi: '03010200099001',
|
currentAcWithRbi: '03010200099001',
|
||||||
sponsorBank: 'State Bank of India',
|
sponsorBank: 'State Bank of India',
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export interface SahayDepositor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DICGC_BANK = {
|
export const DICGC_BANK = {
|
||||||
name: 'CBSNexora Bank Ltd',
|
name: 'Bharat Urban Bank Ltd',
|
||||||
branch: 'Head Office',
|
branch: 'Head Office',
|
||||||
reportAsOn: '29-01-2026',
|
reportAsOn: '29-01-2026',
|
||||||
printDate: '29-01-2026 04:19 PM',
|
printDate: '29-01-2026 04:19 PM',
|
||||||
|
|||||||
+267
-344
@@ -42,12 +42,8 @@ export interface CtrTransaction {
|
|||||||
declaration: string;
|
declaration: string;
|
||||||
/** Customer name resolved from KC1 (UI convenience, not in actual TC2 schema) */
|
/** Customer name resolved from KC1 (UI convenience, not in actual TC2 schema) */
|
||||||
customerName?: string;
|
customerName?: string;
|
||||||
complianceStatus?: 'PENDING' | 'WHITELISTED' | 'SUSPICIOUS';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Outcome of the Principal Officer's investigation into an STR case */
|
|
||||||
export type PoFinding = 'CLEAN' | 'SUSPICIOUS';
|
|
||||||
|
|
||||||
/** STR record — workflow extension on top of TC2 */
|
/** STR record — workflow extension on top of TC2 */
|
||||||
export interface StrTransaction extends CtrTransaction {
|
export interface StrTransaction extends CtrTransaction {
|
||||||
detectedOn: string;
|
detectedOn: string;
|
||||||
@@ -62,8 +58,6 @@ export interface StrTransaction extends CtrTransaction {
|
|||||||
narrative: string;
|
narrative: string;
|
||||||
status: StrStatus;
|
status: StrStatus;
|
||||||
poRemarks?: string;
|
poRemarks?: string;
|
||||||
/** Set once the Principal Officer concludes the investigation */
|
|
||||||
poFinding?: PoFinding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** KC1 — Individual customer card (subset of the 70+ fields, only what UI shows) */
|
/** KC1 — Individual customer card (subset of the 70+ fields, only what UI shows) */
|
||||||
@@ -120,25 +114,20 @@ export interface KycEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const FIU_BANK = {
|
export const FIU_BANK = {
|
||||||
name: 'CBSNexora Bank Ltd',
|
name: 'Bharat Urban Bank Ltd',
|
||||||
branch: 'Head Office',
|
branch: 'Head Office',
|
||||||
reBankingFlag: 'SCB',
|
reBankingFlag: 'SCB',
|
||||||
reportingMonth: 'January 2026',
|
reportingMonth: 'January 2026',
|
||||||
reBuid: 'REBANK-2031',
|
reBuid: 'REBANK-2031',
|
||||||
principalOfficer: 'Karthik Kamath',
|
principalOfficer: 'Karthik Kamath',
|
||||||
principalOfficerEmail: 'po.fiu@cbsnexorabank.example',
|
principalOfficerEmail: 'po.fiu@bharaturbanbank.example',
|
||||||
filerUserId: 'PO-001',
|
filerUserId: 'PO-001',
|
||||||
cashThreshold: 1000000, // ₹10 L per single cash txn
|
cashThreshold: 1000000, // ₹10 L per single cash txn
|
||||||
monthlyAggThreshold: 1000000, // aggregate ≥ ₹10 L in a month
|
monthlyAggThreshold: 1000000, // aggregate ≥ ₹10 L in a month
|
||||||
cbwtrThreshold: 500000 // CBWTR threshold ₹5 L
|
cbwtrThreshold: 500000 // CBWTR threshold ₹5 L
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/** ── Sample CTR transactions (FINnet TC2-style) ── */
|
||||||
* ── CTR transactions (FINnet TC2-style) ──
|
|
||||||
* Only CASH transactions that breach the RBI/PMLA reporting limit of ₹10,00,000
|
|
||||||
* (single txn, or connected transactions aggregating to ₹10 L in a calendar month)
|
|
||||||
* belong here. Nothing below the threshold and no non-cash instruments.
|
|
||||||
*/
|
|
||||||
export const CTR_TXNS: CtrTransaction[] = [
|
export const CTR_TXNS: CtrTransaction[] = [
|
||||||
{
|
{
|
||||||
reportType: 'CTR',
|
reportType: 'CTR',
|
||||||
@@ -146,489 +135,423 @@ export const CTR_TXNS: CtrTransaction[] = [
|
|||||||
branchCode: '1',
|
branchCode: '1',
|
||||||
accountNumber: '110001101000838',
|
accountNumber: '110001101000838',
|
||||||
txnBranchCode: '1',
|
txnBranchCode: '1',
|
||||||
depositOrWithdrawal: 'Deposit',
|
depositOrWithdrawal: 'Withdrawal',
|
||||||
txnId: 1,
|
txnId: 1,
|
||||||
txnDate: '05-01-2026',
|
txnDate: '20-01-2026',
|
||||||
txnTime: '10:22:41',
|
txnTime: '16:51:28',
|
||||||
txnAmount: 1450000,
|
txnAmount: 1500000,
|
||||||
instrumentType: 'Cash',
|
instrumentType: 'Cheque',
|
||||||
instrumentId: 'CASH',
|
instrumentId: '24304',
|
||||||
nonCustomerRef: '',
|
nonCustomerRef: '',
|
||||||
relationshipFlag: 'Customer',
|
relationshipFlag: 'Customer',
|
||||||
thirdPartyPan: '',
|
thirdPartyPan: '',
|
||||||
declaration: 'NA',
|
declaration: 'NA',
|
||||||
customerName: 'ANAND KRISHNAN'
|
customerName: 'RAVI KUMAR SHARMA'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reportType: 'CTR',
|
||||||
|
reportRefNo: 1,
|
||||||
|
branchCode: '1',
|
||||||
|
accountNumber: '110001101000838',
|
||||||
|
txnBranchCode: '1',
|
||||||
|
depositOrWithdrawal: 'Withdrawal',
|
||||||
|
txnId: 2,
|
||||||
|
txnDate: '27-01-2026',
|
||||||
|
txnTime: '16:14:22',
|
||||||
|
txnAmount: 932000,
|
||||||
|
instrumentType: 'Cheque',
|
||||||
|
instrumentId: '24305',
|
||||||
|
nonCustomerRef: '',
|
||||||
|
relationshipFlag: 'Customer',
|
||||||
|
thirdPartyPan: '',
|
||||||
|
declaration: 'NA',
|
||||||
|
customerName: 'RAVI KUMAR SHARMA'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportType: 'CTR',
|
reportType: 'CTR',
|
||||||
reportRefNo: 2,
|
reportRefNo: 2,
|
||||||
branchCode: '3',
|
branchCode: '1',
|
||||||
accountNumber: '110001204000712',
|
accountNumber: '110001352000357',
|
||||||
txnBranchCode: '3',
|
txnBranchCode: '1',
|
||||||
depositOrWithdrawal: 'Deposit',
|
depositOrWithdrawal: 'Withdrawal',
|
||||||
txnId: 2,
|
txnId: 3,
|
||||||
txnDate: '09-01-2026',
|
txnDate: '02-01-2026',
|
||||||
txnTime: '12:47:09',
|
txnTime: '15:05:36',
|
||||||
txnAmount: 3200000,
|
txnAmount: 600000,
|
||||||
instrumentType: 'Cash',
|
instrumentType: 'Cheque',
|
||||||
instrumentId: 'CASH',
|
instrumentId: '100039',
|
||||||
nonCustomerRef: '',
|
nonCustomerRef: '',
|
||||||
relationshipFlag: 'Customer',
|
relationshipFlag: 'Customer',
|
||||||
thirdPartyPan: '',
|
thirdPartyPan: '',
|
||||||
declaration: 'NA',
|
declaration: 'NA',
|
||||||
customerName: 'MEERA SUNDARAM'
|
customerName: 'PRIYA PATEL'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reportType: 'CTR',
|
||||||
|
reportRefNo: 2,
|
||||||
|
branchCode: '1',
|
||||||
|
accountNumber: '110001352000357',
|
||||||
|
txnBranchCode: '1',
|
||||||
|
depositOrWithdrawal: 'Withdrawal',
|
||||||
|
txnId: 4,
|
||||||
|
txnDate: '07-01-2026',
|
||||||
|
txnTime: '14:14:17',
|
||||||
|
txnAmount: 700000,
|
||||||
|
instrumentType: 'Cheque',
|
||||||
|
instrumentId: '100040',
|
||||||
|
nonCustomerRef: '',
|
||||||
|
relationshipFlag: 'Customer',
|
||||||
|
thirdPartyPan: '',
|
||||||
|
declaration: 'NA',
|
||||||
|
customerName: 'PRIYA PATEL'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportType: 'CTR',
|
reportType: 'CTR',
|
||||||
reportRefNo: 3,
|
reportRefNo: 3,
|
||||||
branchCode: '6',
|
branchCode: '7',
|
||||||
accountNumber: '110001307000455',
|
accountNumber: '110001352000420',
|
||||||
txnBranchCode: '6',
|
txnBranchCode: '7',
|
||||||
depositOrWithdrawal: 'Withdrawal',
|
depositOrWithdrawal: 'Deposit',
|
||||||
txnId: 3,
|
txnId: 5,
|
||||||
txnDate: '13-01-2026',
|
txnDate: '12-01-2026',
|
||||||
txnTime: '15:03:55',
|
txnTime: '11:28:50',
|
||||||
txnAmount: 1075000,
|
txnAmount: 1200000,
|
||||||
instrumentType: 'Cash',
|
instrumentType: 'Cash',
|
||||||
instrumentId: 'CASH',
|
instrumentId: 'CASH',
|
||||||
nonCustomerRef: '',
|
nonCustomerRef: '',
|
||||||
relationshipFlag: 'Customer',
|
relationshipFlag: 'Customer',
|
||||||
thirdPartyPan: '',
|
thirdPartyPan: '',
|
||||||
declaration: 'NA',
|
declaration: 'NA',
|
||||||
customerName: 'HARPREET SINGH BEDI'
|
customerName: 'VIKRAM REDDY'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportType: 'CTR',
|
reportType: 'CTR',
|
||||||
reportRefNo: 4,
|
reportRefNo: 4,
|
||||||
branchCode: '2',
|
branchCode: '4',
|
||||||
accountNumber: '110001408000933',
|
accountNumber: '110001352000511',
|
||||||
txnBranchCode: '2',
|
txnBranchCode: '4',
|
||||||
depositOrWithdrawal: 'Deposit',
|
depositOrWithdrawal: 'Deposit',
|
||||||
txnId: 4,
|
txnId: 6,
|
||||||
txnDate: '16-01-2026',
|
txnDate: '18-01-2026',
|
||||||
txnTime: '11:19:27',
|
txnTime: '10:14:05',
|
||||||
txnAmount: 5600000,
|
txnAmount: 2500000,
|
||||||
instrumentType: 'Cash',
|
instrumentType: 'Cash',
|
||||||
instrumentId: 'CASH',
|
instrumentId: 'CASH',
|
||||||
nonCustomerRef: '',
|
nonCustomerRef: '',
|
||||||
relationshipFlag: 'Customer',
|
relationshipFlag: 'Customer',
|
||||||
thirdPartyPan: '',
|
thirdPartyPan: '',
|
||||||
declaration: 'NA',
|
declaration: 'NA',
|
||||||
customerName: 'FAROOQ ABDULLAH KHAN'
|
customerName: 'NIRAV SHAH'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportType: 'CTR',
|
reportType: 'CTR',
|
||||||
reportRefNo: 5,
|
reportRefNo: 5,
|
||||||
branchCode: '9',
|
branchCode: '11',
|
||||||
accountNumber: '110001509000268',
|
accountNumber: '110001352000604',
|
||||||
txnBranchCode: '9',
|
txnBranchCode: '11',
|
||||||
depositOrWithdrawal: 'Withdrawal',
|
depositOrWithdrawal: 'Withdrawal',
|
||||||
txnId: 5,
|
|
||||||
txnDate: '20-01-2026',
|
|
||||||
txnTime: '14:35:12',
|
|
||||||
txnAmount: 2850000,
|
|
||||||
instrumentType: 'Cash',
|
|
||||||
instrumentId: 'CASH',
|
|
||||||
nonCustomerRef: '',
|
|
||||||
relationshipFlag: 'Customer',
|
|
||||||
thirdPartyPan: '',
|
|
||||||
declaration: 'NA',
|
|
||||||
customerName: 'DEEPIKA RANGANATHAN'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reportType: 'CTR',
|
|
||||||
reportRefNo: 6,
|
|
||||||
branchCode: '5',
|
|
||||||
accountNumber: '110001610000841',
|
|
||||||
txnBranchCode: '5',
|
|
||||||
depositOrWithdrawal: 'Deposit',
|
|
||||||
txnId: 6,
|
|
||||||
txnDate: '23-01-2026',
|
|
||||||
txnTime: '09:58:33',
|
|
||||||
txnAmount: 1000000,
|
|
||||||
instrumentType: 'Cash',
|
|
||||||
instrumentId: 'CASH',
|
|
||||||
nonCustomerRef: '',
|
|
||||||
relationshipFlag: 'Customer',
|
|
||||||
thirdPartyPan: '',
|
|
||||||
declaration: 'NA',
|
|
||||||
customerName: 'RAJENDRA PRASAD YADAV'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reportType: 'CTR',
|
|
||||||
reportRefNo: 7,
|
|
||||||
branchCode: '12',
|
|
||||||
accountNumber: '110001711000527',
|
|
||||||
txnBranchCode: '12',
|
|
||||||
depositOrWithdrawal: 'Deposit',
|
|
||||||
txnId: 7,
|
txnId: 7,
|
||||||
txnDate: '27-01-2026',
|
txnDate: '24-01-2026',
|
||||||
txnTime: '16:41:08',
|
txnTime: '13:42:11',
|
||||||
txnAmount: 4300000,
|
txnAmount: 1100000,
|
||||||
instrumentType: 'Cash',
|
instrumentType: 'Cheque',
|
||||||
instrumentId: 'CASH',
|
instrumentId: '88123',
|
||||||
nonCustomerRef: 'NCR-014',
|
nonCustomerRef: 'NCR-001',
|
||||||
relationshipFlag: 'Non-Customer',
|
relationshipFlag: 'Non-Customer',
|
||||||
thirdPartyPan: 'BXKPT7788M',
|
thirdPartyPan: 'AKHPV2244R',
|
||||||
declaration: 'NA',
|
declaration: 'NA',
|
||||||
customerName: 'SHABANA TARIQ MIRZA'
|
customerName: 'KAVITA VERMA'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/** ── Sample STR transactions ── */
|
||||||
* ── STR cases ──
|
|
||||||
* Raised either by the AML rules engine or by an officer flagging a CTR row as
|
|
||||||
* suspicious. Each case is investigated by the Principal Officer, who concludes
|
|
||||||
* whether the funds are legitimate (CLEAN) or linked to terror financing.
|
|
||||||
*/
|
|
||||||
export const STR_TXNS: StrTransaction[] = [
|
export const STR_TXNS: StrTransaction[] = [
|
||||||
{
|
{
|
||||||
reportType: 'STR',
|
reportType: 'STR',
|
||||||
reportRefNo: 101,
|
reportRefNo: 101,
|
||||||
branchCode: '8',
|
branchCode: '1',
|
||||||
accountNumber: '110001812000390',
|
accountNumber: '110001101000841',
|
||||||
txnBranchCode: '8',
|
txnBranchCode: '1',
|
||||||
depositOrWithdrawal: 'Deposit',
|
depositOrWithdrawal: 'Deposit',
|
||||||
txnId: 101,
|
txnId: 8,
|
||||||
txnDate: '11-01-2026',
|
txnDate: '15-01-2026',
|
||||||
txnTime: '13:08:21',
|
txnTime: '11:32:00',
|
||||||
txnAmount: 2750000,
|
txnAmount: 950000,
|
||||||
instrumentType: 'Cash',
|
instrumentType: 'Cash',
|
||||||
instrumentId: 'CASH',
|
instrumentId: 'CASH',
|
||||||
nonCustomerRef: '',
|
nonCustomerRef: '',
|
||||||
relationshipFlag: 'Customer',
|
relationshipFlag: 'Customer',
|
||||||
thirdPartyPan: '',
|
thirdPartyPan: '',
|
||||||
declaration: 'NA',
|
declaration: 'NA',
|
||||||
customerName: 'IMRAN SHEIKH QURESHI',
|
customerName: 'ARJUN DESHMUKH',
|
||||||
detectedOn: '12-01-2026',
|
detectedOn: '16-01-2026',
|
||||||
flaggedBy: 'AML Engine — Rule R-031',
|
flaggedBy: 'AML Engine — Rule R-007',
|
||||||
riskCategory: 'sanctions-hit',
|
riskCategory: 'structuring',
|
||||||
narrative:
|
narrative:
|
||||||
'Cash deposit of ₹27.5L followed within 48 hours by outward remittances to three beneficiaries whose names partially match the UAPA Schedule IV designated-persons list. Awaiting Principal Officer investigation.',
|
'Three sub-₹10L cash deposits within 5 days totalling ₹28.4L; pattern indicative of structuring to avoid CTR threshold.',
|
||||||
status: 'po-review'
|
status: 'po-review'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportType: 'STR',
|
reportType: 'STR',
|
||||||
reportRefNo: 102,
|
reportRefNo: 102,
|
||||||
branchCode: '4',
|
branchCode: '7',
|
||||||
accountNumber: '110001915000174',
|
accountNumber: '110001101000922',
|
||||||
txnBranchCode: '4',
|
txnBranchCode: '7',
|
||||||
depositOrWithdrawal: 'Deposit',
|
depositOrWithdrawal: 'Deposit',
|
||||||
txnId: 102,
|
txnId: 9,
|
||||||
txnDate: '07-01-2026',
|
txnDate: '21-01-2026',
|
||||||
txnTime: '10:44:52',
|
txnTime: '14:18:30',
|
||||||
txnAmount: 1980000,
|
txnAmount: 4800000,
|
||||||
instrumentType: 'Cash',
|
instrumentType: 'NEFT',
|
||||||
instrumentId: 'CASH',
|
instrumentId: 'N1264821',
|
||||||
nonCustomerRef: '',
|
nonCustomerRef: '',
|
||||||
relationshipFlag: 'Customer',
|
relationshipFlag: 'Customer',
|
||||||
thirdPartyPan: '',
|
thirdPartyPan: '',
|
||||||
declaration: 'NA',
|
declaration: 'NA',
|
||||||
customerName: 'GOPAL VENKATARAMAN',
|
customerName: 'LAKSHMI IYER',
|
||||||
detectedOn: '08-01-2026',
|
detectedOn: '22-01-2026',
|
||||||
flaggedBy: 'Branch Compliance Officer',
|
flaggedBy: 'Branch Compliance Officer',
|
||||||
riskCategory: 'unusual-pattern',
|
riskCategory: 'dormant-revival',
|
||||||
narrative:
|
narrative:
|
||||||
'Declared annual income ₹6L but deposited ₹19.8L cash in a single visit. Customer produced sale deed for ancestral agricultural land.',
|
'Account dormant for 18 months suddenly received ₹48L NEFT followed by 4 immediate cash withdrawals of ₹2L each.',
|
||||||
status: 'draft',
|
status: 'filed',
|
||||||
poFinding: 'CLEAN',
|
poRemarks: 'STR filed with FIU-IND. Acknowledgement no: STR-2026-9821.'
|
||||||
poRemarks:
|
|
||||||
'Source of funds verified against registered sale deed dated 02-01-2026 and buyer bank statement. Legitimate agricultural land sale — no adverse findings. Case closed, no STR filed with FIU-IND.'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportType: 'STR',
|
reportType: 'STR',
|
||||||
reportRefNo: 103,
|
reportRefNo: 103,
|
||||||
branchCode: '10',
|
branchCode: '2',
|
||||||
accountNumber: '110002016000628',
|
accountNumber: '110001302001012',
|
||||||
txnBranchCode: '10',
|
txnBranchCode: '2',
|
||||||
depositOrWithdrawal: 'Withdrawal',
|
depositOrWithdrawal: 'Withdrawal',
|
||||||
txnId: 103,
|
txnId: 10,
|
||||||
txnDate: '19-01-2026',
|
txnDate: '23-01-2026',
|
||||||
txnTime: '16:27:40',
|
txnTime: '15:42:18',
|
||||||
txnAmount: 3400000,
|
txnAmount: 1800000,
|
||||||
instrumentType: 'Cash',
|
instrumentType: 'RTGS',
|
||||||
instrumentId: 'CASH',
|
instrumentId: 'R889013',
|
||||||
nonCustomerRef: '',
|
nonCustomerRef: '',
|
||||||
relationshipFlag: 'Customer',
|
relationshipFlag: 'Customer',
|
||||||
thirdPartyPan: '',
|
thirdPartyPan: '',
|
||||||
declaration: 'NA',
|
declaration: 'NA',
|
||||||
customerName: 'ABDUL RAHMAN BAIG',
|
customerName: 'SAMEER KAPOOR',
|
||||||
detectedOn: '20-01-2026',
|
detectedOn: '24-01-2026',
|
||||||
flaggedBy: 'AML Engine — Rule R-045',
|
flaggedBy: 'AML Engine — Rule R-014',
|
||||||
riskCategory: 'high-risk-country',
|
riskCategory: 'high-risk-country',
|
||||||
narrative:
|
narrative:
|
||||||
'Account credited by 14 small inward remittances from a FATF high-risk jurisdiction over 9 days, then emptied via ₹34L cash withdrawal at a border-district branch. Classic terror-funding placement-and-dispersal pattern.',
|
'RTGS transfer to beneficiary in jurisdiction listed under FATF grey list (Cayman Islands intermediary). KYC documentation incomplete.',
|
||||||
status: 'filed',
|
status: 'draft'
|
||||||
poFinding: 'SUSPICIOUS',
|
|
||||||
poRemarks:
|
|
||||||
'Investigation confirmed layered inward remittances from a designated high-risk corridor with no commercial rationale. Beneficial owner could not evidence source. STR filed with FIU-IND under PMLA s.12; acknowledgement STR-2026-10457. Account frozen and reported to the Nodal Officer.'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportType: 'STR',
|
reportType: 'STR',
|
||||||
reportRefNo: 104,
|
reportRefNo: 104,
|
||||||
branchCode: '1',
|
branchCode: '13',
|
||||||
accountNumber: '110002117000315',
|
accountNumber: '110001352001145',
|
||||||
txnBranchCode: '1',
|
txnBranchCode: '13',
|
||||||
depositOrWithdrawal: 'Deposit',
|
depositOrWithdrawal: 'Deposit',
|
||||||
txnId: 104,
|
txnId: 11,
|
||||||
txnDate: '24-01-2026',
|
txnDate: '08-01-2026',
|
||||||
txnTime: '11:52:14',
|
txnTime: '12:55:42',
|
||||||
txnAmount: 2300000,
|
txnAmount: 6500000,
|
||||||
instrumentType: 'Cash',
|
instrumentType: 'Cash',
|
||||||
instrumentId: 'CASH',
|
instrumentId: 'CASH',
|
||||||
nonCustomerRef: '',
|
nonCustomerRef: '',
|
||||||
relationshipFlag: 'Customer',
|
relationshipFlag: 'Customer',
|
||||||
thirdPartyPan: '',
|
thirdPartyPan: '',
|
||||||
declaration: 'NA',
|
declaration: 'NA',
|
||||||
customerName: 'SUNITA CHAUDHARY',
|
customerName: 'MANMOHAN SINGH GILL',
|
||||||
detectedOn: '25-01-2026',
|
detectedOn: '09-01-2026',
|
||||||
flaggedBy: 'AML Engine — Rule R-018',
|
flaggedBy: 'AML Engine — Rule R-022',
|
||||||
riskCategory: 'structuring',
|
riskCategory: 'unusual-pattern',
|
||||||
narrative:
|
narrative:
|
||||||
'Six cash deposits of ₹3.8L each across four branches on the same day, aggregating ₹23L — deliberate splitting to stay under the ₹10L single-transaction CTR limit.',
|
'Account avg monthly turnover ₹1.5L; received ₹65L cash in single deposit, source-of-funds documentation pending.',
|
||||||
status: 'po-review'
|
status: 'po-review'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
/** ── KC1 individual customer cards (one per CTR report reference) ── */
|
/** ── KC1 sample individual customers ── */
|
||||||
export const KC1_INDIVIDUALS: KycIndividual[] = [
|
export const KC1_INDIVIDUALS: KycIndividual[] = [
|
||||||
{
|
{
|
||||||
reportRefNo: 1,
|
reportRefNo: 1,
|
||||||
ucic: '30115',
|
ucic: '10078',
|
||||||
pan: 'AFTPK4471B',
|
pan: 'AAQPS8560J',
|
||||||
ckycNumber: '60031115447722',
|
ckycNumber: '60011225033445',
|
||||||
firstName: 'ANAND',
|
firstName: 'RAVI',
|
||||||
middleName: '',
|
middleName: 'KUMAR',
|
||||||
lastName: 'KRISHNAN',
|
lastName: 'SHARMA',
|
||||||
fatherName: 'SUBRAMANIAN KRISHNAN',
|
fatherName: 'RAJESH KUMAR SHARMA',
|
||||||
motherName: 'VASANTHA KRISHNAN',
|
motherName: 'SUNITA DEVI',
|
||||||
gender: 'Male',
|
gender: 'Male',
|
||||||
dob: '14-06-1979',
|
dob: '10-08-1985',
|
||||||
mobileNumber: '91-9840133015',
|
mobileNumber: '91-9892011001',
|
||||||
email: 'anand.krishnan@example.com',
|
email: 'ravi.sharma@example.com',
|
||||||
primaryAddress: 'NO 18 SECOND STREET ADYAR',
|
primaryAddress: 'FLAT 402 SUNRISE TOWERS MAROL ANDHERI EAST',
|
||||||
state: 'TN',
|
state: 'MH',
|
||||||
city: 'CHENNAI',
|
city: 'MUMBAI',
|
||||||
pincode: '600020',
|
pincode: '400069',
|
||||||
customerType: 'Individual',
|
customerType: 'Individual',
|
||||||
annualIncome: 2200000,
|
annualIncome: 750000,
|
||||||
occupation: 'Business',
|
occupation: 'Service',
|
||||||
onboardingDate: '22-07-2016',
|
onboardingDate: '15-03-2020',
|
||||||
lastKycDate: '18-06-2025',
|
lastKycDate: '12-04-2025',
|
||||||
riskLevel: 'Medium',
|
riskLevel: 'Medium',
|
||||||
reportType: 'CTR',
|
reportType: 'CTR',
|
||||||
otherIdentifierName: 'AADHAAR',
|
otherIdentifierName: 'AADHAAR',
|
||||||
otherIdentifierNumber: 'XXXXXXXX4471'
|
otherIdentifierNumber: 'XXXXXXXX1234'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportRefNo: 2,
|
reportRefNo: 2,
|
||||||
ucic: '30228',
|
ucic: '10079',
|
||||||
pan: 'AJSPS6612D',
|
pan: 'AKLPS9933P',
|
||||||
ckycNumber: '60032228661133',
|
ckycNumber: '60022144558899',
|
||||||
firstName: 'MEERA',
|
firstName: 'PRIYA',
|
||||||
middleName: '',
|
middleName: '',
|
||||||
lastName: 'SUNDARAM',
|
lastName: 'PATEL',
|
||||||
fatherName: 'RAGHAVAN SUNDARAM',
|
fatherName: 'HARISH PATEL',
|
||||||
motherName: 'KALYANI SUNDARAM',
|
motherName: 'MEENA PATEL',
|
||||||
gender: 'Female',
|
gender: 'Female',
|
||||||
dob: '29-11-1986',
|
dob: '22-03-1990',
|
||||||
mobileNumber: '91-9886044028',
|
mobileNumber: '91-9822055002',
|
||||||
email: 'meera.sundaram@example.com',
|
email: 'priya.patel@example.com',
|
||||||
primaryAddress: 'FLAT 9C BRIGADE HERITAGE WHITEFIELD',
|
primaryAddress: 'B-12 SHIVAJI NAGAR FC ROAD',
|
||||||
state: 'KA',
|
state: 'MH',
|
||||||
city: 'BENGALURU',
|
city: 'PUNE',
|
||||||
pincode: '560066',
|
pincode: '411005',
|
||||||
customerType: 'Individual',
|
customerType: 'Individual',
|
||||||
annualIncome: 3600000,
|
annualIncome: 480000,
|
||||||
occupation: 'Business',
|
occupation: 'Self-Employed',
|
||||||
onboardingDate: '11-01-2019',
|
onboardingDate: '10-09-2023',
|
||||||
lastKycDate: '05-05-2025',
|
lastKycDate: '15-03-2025',
|
||||||
riskLevel: 'High',
|
riskLevel: 'Low',
|
||||||
reportType: 'CTR',
|
reportType: 'CTR',
|
||||||
otherIdentifierName: 'AADHAAR',
|
otherIdentifierName: 'AADHAAR',
|
||||||
otherIdentifierNumber: 'XXXXXXXX6612'
|
otherIdentifierNumber: 'XXXXXXXX2210'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportRefNo: 3,
|
reportRefNo: 3,
|
||||||
ucic: '30334',
|
ucic: '20101',
|
||||||
pan: 'AMBPB9027F',
|
pan: 'AAEPR9352L',
|
||||||
ckycNumber: '',
|
ckycNumber: '',
|
||||||
firstName: 'HARPREET',
|
firstName: 'VIKRAM',
|
||||||
middleName: 'SINGH',
|
middleName: '',
|
||||||
lastName: 'BEDI',
|
lastName: 'REDDY',
|
||||||
fatherName: 'GURMEET SINGH BEDI',
|
fatherName: 'SURESH REDDY',
|
||||||
motherName: 'JASWINDER KAUR',
|
motherName: 'LAKSHMI REDDY',
|
||||||
gender: 'Male',
|
gender: 'Male',
|
||||||
dob: '03-02-1974',
|
dob: '07-07-1982',
|
||||||
mobileNumber: '91-9815066034',
|
mobileNumber: '91-9845022004',
|
||||||
email: '',
|
email: '',
|
||||||
primaryAddress: 'HOUSE 221 SECTOR 34A CHANDIGARH ROAD',
|
primaryAddress: 'NO 23 4TH CROSS JAYANAGAR 5TH BLOCK',
|
||||||
state: 'PB',
|
state: 'KA',
|
||||||
city: 'LUDHIANA',
|
city: 'BENGALURU',
|
||||||
pincode: '141001',
|
pincode: '560041',
|
||||||
customerType: 'Individual',
|
customerType: 'Individual',
|
||||||
annualIncome: 1450000,
|
annualIncome: 950000,
|
||||||
occupation: 'Business',
|
occupation: 'Profession',
|
||||||
onboardingDate: '30-08-2012',
|
onboardingDate: '12-11-2021',
|
||||||
lastKycDate: '14-03-2025',
|
lastKycDate: '20-02-2025',
|
||||||
riskLevel: 'Medium',
|
riskLevel: 'Medium',
|
||||||
reportType: 'CTR',
|
reportType: 'CTR',
|
||||||
otherIdentifierName: 'AADHAAR',
|
otherIdentifierName: 'AADHAAR',
|
||||||
otherIdentifierNumber: 'XXXXXXXX9027'
|
otherIdentifierNumber: 'XXXXXXXX6622'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportRefNo: 4,
|
reportRefNo: 4,
|
||||||
ucic: '30447',
|
ucic: '40301',
|
||||||
pan: 'ANQPK3358H',
|
pan: 'AHKPS5544Z',
|
||||||
ckycNumber: '60034447335844',
|
ckycNumber: '60040301229988',
|
||||||
firstName: 'FAROOQ',
|
firstName: 'NIRAV',
|
||||||
middleName: 'ABDULLAH',
|
middleName: '',
|
||||||
lastName: 'KHAN',
|
lastName: 'SHAH',
|
||||||
fatherName: 'ISHTIAQ AHMED KHAN',
|
fatherName: 'JAYESH SHAH',
|
||||||
motherName: 'NASREEN BEGUM',
|
motherName: 'RITA SHAH',
|
||||||
gender: 'Male',
|
gender: 'Male',
|
||||||
dob: '17-09-1981',
|
dob: '18-05-1992',
|
||||||
mobileNumber: '91-9701077047',
|
mobileNumber: '91-9824012010',
|
||||||
email: 'farooq.khan@example.com',
|
email: 'nirav.shah@example.com',
|
||||||
primaryAddress: '12-2-417 MEHDIPATNAM MAIN ROAD',
|
primaryAddress: 'A-401 ATLANTIS APARTMENTS SATELLITE ROAD',
|
||||||
state: 'TG',
|
state: 'GJ',
|
||||||
city: 'HYDERABAD',
|
city: 'AHMEDABAD',
|
||||||
pincode: '500028',
|
pincode: '380015',
|
||||||
customerType: 'Individual',
|
customerType: 'Individual',
|
||||||
annualIncome: 890000,
|
annualIncome: 1800000,
|
||||||
occupation: 'Business',
|
occupation: 'Service',
|
||||||
onboardingDate: '05-04-2020',
|
onboardingDate: '14-02-2018',
|
||||||
lastKycDate: '09-01-2025',
|
lastKycDate: '20-12-2024',
|
||||||
riskLevel: 'High',
|
riskLevel: 'High',
|
||||||
reportType: 'CTR',
|
reportType: 'CTR',
|
||||||
otherIdentifierName: 'PASSPORT',
|
otherIdentifierName: 'PASSPORT',
|
||||||
otherIdentifierNumber: 'P3358214'
|
otherIdentifierNumber: 'M5544321'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportRefNo: 5,
|
reportRefNo: 5,
|
||||||
ucic: '30552',
|
ucic: '50402',
|
||||||
pan: 'APLPR7783C',
|
pan: 'AKHPV2244R',
|
||||||
ckycNumber: '60035552778355',
|
ckycNumber: '',
|
||||||
firstName: 'DEEPIKA',
|
firstName: 'KAVITA',
|
||||||
middleName: '',
|
middleName: '',
|
||||||
lastName: 'RANGANATHAN',
|
lastName: 'VERMA',
|
||||||
fatherName: 'MOHAN RANGANATHAN',
|
fatherName: 'DEEPAK VERMA',
|
||||||
motherName: 'SHOBHA RANGANATHAN',
|
motherName: 'USHA VERMA',
|
||||||
gender: 'Female',
|
gender: 'Female',
|
||||||
dob: '08-12-1991',
|
dob: '01-04-1988',
|
||||||
mobileNumber: '91-9739088052',
|
mobileNumber: '91-9415055014',
|
||||||
email: 'deepika.r@example.com',
|
|
||||||
primaryAddress: 'VILLA 7 PALM MEADOWS SARJAPUR ROAD',
|
|
||||||
state: 'KA',
|
|
||||||
city: 'BENGALURU',
|
|
||||||
pincode: '560035',
|
|
||||||
customerType: 'Individual',
|
|
||||||
annualIncome: 2750000,
|
|
||||||
occupation: 'Profession',
|
|
||||||
onboardingDate: '19-06-2021',
|
|
||||||
lastKycDate: '27-07-2025',
|
|
||||||
riskLevel: 'Low',
|
|
||||||
reportType: 'CTR',
|
|
||||||
otherIdentifierName: 'AADHAAR',
|
|
||||||
otherIdentifierNumber: 'XXXXXXXX7783'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reportRefNo: 6,
|
|
||||||
ucic: '30669',
|
|
||||||
pan: 'AQWPY2194K',
|
|
||||||
ckycNumber: '',
|
|
||||||
firstName: 'RAJENDRA',
|
|
||||||
middleName: 'PRASAD',
|
|
||||||
lastName: 'YADAV',
|
|
||||||
fatherName: 'BHOLA NATH YADAV',
|
|
||||||
motherName: 'KAMLA DEVI',
|
|
||||||
gender: 'Male',
|
|
||||||
dob: '25-05-1968',
|
|
||||||
mobileNumber: '91-9451099069',
|
|
||||||
email: '',
|
email: '',
|
||||||
primaryAddress: '78 GOMTI NAGAR VIBHUTI KHAND',
|
primaryAddress: '45 CIVIL LINES MALL ROAD',
|
||||||
state: 'UP',
|
state: 'UP',
|
||||||
city: 'LUCKNOW',
|
city: 'KANPUR',
|
||||||
pincode: '226010',
|
pincode: '208001',
|
||||||
customerType: 'Individual',
|
customerType: 'Individual',
|
||||||
annualIncome: 640000,
|
annualIncome: 540000,
|
||||||
occupation: 'Agriculture',
|
occupation: 'Service',
|
||||||
onboardingDate: '17-10-2014',
|
onboardingDate: '08-09-2022',
|
||||||
lastKycDate: '22-11-2024',
|
lastKycDate: '15-09-2024',
|
||||||
riskLevel: 'Low',
|
riskLevel: 'Low',
|
||||||
reportType: 'CTR',
|
reportType: 'CTR',
|
||||||
otherIdentifierName: 'AADHAAR',
|
otherIdentifierName: 'AADHAAR',
|
||||||
otherIdentifierNumber: 'XXXXXXXX2194'
|
otherIdentifierNumber: 'XXXXXXXX2244'
|
||||||
},
|
|
||||||
{
|
|
||||||
reportRefNo: 7,
|
|
||||||
ucic: '30774',
|
|
||||||
pan: 'BXKPT7788M',
|
|
||||||
ckycNumber: '',
|
|
||||||
firstName: 'SHABANA',
|
|
||||||
middleName: 'TARIQ',
|
|
||||||
lastName: 'MIRZA',
|
|
||||||
fatherName: 'TARIQ HUSSAIN MIRZA',
|
|
||||||
motherName: 'RUKHSANA MIRZA',
|
|
||||||
gender: 'Female',
|
|
||||||
dob: '11-03-1989',
|
|
||||||
mobileNumber: '91-9958100074',
|
|
||||||
email: '',
|
|
||||||
primaryAddress: '54 ZAKIR NAGAR OKHLA',
|
|
||||||
state: 'DL',
|
|
||||||
city: 'NEW DELHI',
|
|
||||||
pincode: '110025',
|
|
||||||
customerType: 'Individual',
|
|
||||||
annualIncome: 720000,
|
|
||||||
occupation: 'Self-Employed',
|
|
||||||
onboardingDate: '02-02-2023',
|
|
||||||
lastKycDate: '16-08-2025',
|
|
||||||
riskLevel: 'High',
|
|
||||||
reportType: 'CTR',
|
|
||||||
otherIdentifierName: 'AADHAAR',
|
|
||||||
otherIdentifierNumber: 'XXXXXXXX7788'
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
/** ── KC2 entity customer cards ── */
|
/** ── KC2 sample entity customers ── */
|
||||||
export const KC2_ENTITIES: KycEntity[] = [
|
export const KC2_ENTITIES: KycEntity[] = [
|
||||||
{
|
{
|
||||||
reportRefNo: 2,
|
reportRefNo: 11,
|
||||||
ucic: 'E30228',
|
ucic: 'E10101',
|
||||||
entityName: 'SUNDARAM BULLION TRADERS',
|
entityName: 'BHARAT TRADING COMPANY',
|
||||||
panNumber: 'AAGFS3391Q',
|
panNumber: 'ABCFB1234N',
|
||||||
gstin: '29AAGFS3391Q1ZP',
|
gstin: '27ABCFB1234N1ZT',
|
||||||
mobileNumber: '91-9886044028',
|
mobileNumber: '91-9892011010',
|
||||||
address: 'SHOP 14 COMMERCIAL STREET SHIVAJI NAGAR',
|
address: 'GALA 22 LOWER PAREL',
|
||||||
state: 'KA',
|
state: 'MH',
|
||||||
city: 'BENGALURU',
|
city: 'MUMBAI',
|
||||||
pincode: '560001',
|
pincode: '400013',
|
||||||
dateOfIncorporation: '11-01-2019',
|
dateOfIncorporation: '24-03-2008',
|
||||||
customerType: 'Sole Proprietor',
|
customerType: 'Sole Proprietor',
|
||||||
riskLevel: 'High',
|
riskLevel: 'High',
|
||||||
onboardingDate: '11-01-2019',
|
onboardingDate: '24-03-2008',
|
||||||
lastKycDate: '05-05-2025',
|
lastKycDate: '03-09-2025',
|
||||||
isSoleProprietor: true,
|
isSoleProprietor: true,
|
||||||
ownerUcic: '30228',
|
ownerUcic: '10078',
|
||||||
ownerName: 'MEERA SUNDARAM',
|
ownerName: 'RAVI KUMAR SHARMA',
|
||||||
reportType: 'CTR'
|
reportType: 'CTR'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reportRefNo: 4,
|
reportRefNo: 12,
|
||||||
ucic: 'E30447',
|
ucic: 'E10102',
|
||||||
entityName: 'DECCAN SCRAP & METALS PVT LTD',
|
entityName: 'NEXTGEN TECH PVT LTD',
|
||||||
panNumber: 'AABCD8842R',
|
panNumber: 'AABCN5566K',
|
||||||
gstin: '36AABCD8842R1ZN',
|
gstin: '27AABCN5566K1Z2',
|
||||||
mobileNumber: '91-9701077047',
|
mobileNumber: '91-9824012011',
|
||||||
address: 'PLOT 61 IDA JEEDIMETLA PHASE II',
|
address: 'TOWER B SUNRISE BUSINESS PARK ANDHERI EAST',
|
||||||
state: 'TG',
|
state: 'MH',
|
||||||
city: 'HYDERABAD',
|
city: 'MUMBAI',
|
||||||
pincode: '500055',
|
pincode: '400093',
|
||||||
dateOfIncorporation: '05-04-2020',
|
dateOfIncorporation: '18-09-2015',
|
||||||
customerType: 'Private Limited',
|
customerType: 'Private Limited',
|
||||||
riskLevel: 'High',
|
riskLevel: 'Medium',
|
||||||
onboardingDate: '05-04-2020',
|
onboardingDate: '18-09-2015',
|
||||||
lastKycDate: '09-01-2025',
|
lastKycDate: '12-08-2025',
|
||||||
isSoleProprietor: false,
|
isSoleProprietor: false,
|
||||||
ownerUcic: '',
|
ownerUcic: '',
|
||||||
ownerName: '',
|
ownerName: '',
|
||||||
|
|||||||
+26
-273
@@ -71,19 +71,17 @@
|
|||||||
<div class="fiu-kpi fiu-kpi--amber">
|
<div class="fiu-kpi fiu-kpi--amber">
|
||||||
<div class="fiu-kpi-ic"><fa-icon icon="exclamation-triangle"></fa-icon></div>
|
<div class="fiu-kpi-ic"><fa-icon icon="exclamation-triangle"></fa-icon></div>
|
||||||
<div>
|
<div>
|
||||||
<div class="fiu-kpi-n">{{ liveStrPending | number }}</div>
|
<div class="fiu-kpi-n">{{ pendingStr | number }}</div>
|
||||||
<div class="fiu-kpi-l">STR Pending</div>
|
<div class="fiu-kpi-l">STR Pending</div>
|
||||||
<div class="fiu-kpi-sub">
|
<div class="fiu-kpi-sub">draft / PO review</div>
|
||||||
{{ liveStrOpen }} to investigate · {{ liveStrAwaitingFiling }} awaiting filing
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="fiu-kpi fiu-kpi--green">
|
<div class="fiu-kpi fiu-kpi--green">
|
||||||
<div class="fiu-kpi-ic"><fa-icon icon="check-circle"></fa-icon></div>
|
<div class="fiu-kpi-ic"><fa-icon icon="check-circle"></fa-icon></div>
|
||||||
<div>
|
<div>
|
||||||
<div class="fiu-kpi-n">{{ liveStrFiled | number }}</div>
|
<div class="fiu-kpi-n">{{ filedStr | number }}</div>
|
||||||
<div class="fiu-kpi-l">STR Filed</div>
|
<div class="fiu-kpi-l">STR Filed</div>
|
||||||
<div class="fiu-kpi-sub">{{ liveStrCleared }} concluded clear</div>
|
<div class="fiu-kpi-sub">acknowledged by FIU</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="fiu-kpi fiu-kpi--purple">
|
<div class="fiu-kpi fiu-kpi--purple">
|
||||||
@@ -114,12 +112,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="dc-meta">Filing window: <strong>within 7 days of detection</strong></div>
|
<div class="dc-meta">Filing window: <strong>within 7 days of detection</strong></div>
|
||||||
<div class="dc-meta">
|
<div class="dc-meta">
|
||||||
To investigate: <strong>{{ liveStrOpen }}</strong> · Awaiting filing:
|
Pending: <strong>{{ pendingStr }}</strong> · Filed: <strong>{{ filedStr }}</strong>
|
||||||
<strong>{{ liveStrAwaitingFiling }}</strong>
|
|
||||||
</div>
|
|
||||||
<div class="dc-meta">
|
|
||||||
Filed: <strong>{{ liveStrFiled }}</strong> · Concluded clear:
|
|
||||||
<strong>{{ liveStrCleared }}</strong>
|
|
||||||
</div>
|
</div>
|
||||||
<button class="dc-btn" (click)="showTab('str')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
|
<button class="dc-btn" (click)="showTab('str')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,12 +129,12 @@
|
|||||||
<!-- CTR -->
|
<!-- CTR -->
|
||||||
@if (activeTab === 'ctr') {
|
@if (activeTab === 'ctr') {
|
||||||
<div class="fiu-section">
|
<div class="fiu-section">
|
||||||
<!-- LIVE: cash-related CTR threshold alerts -->
|
<!-- LIVE: cash-related AML alerts awaiting AML Officer review -->
|
||||||
<div class="fiu-form-meta">
|
<div class="fiu-form-meta">
|
||||||
<div class="fiu-form-title">
|
<div class="fiu-form-title">
|
||||||
<h3>Live CTR Threshold Alerts</h3>
|
<h3>Live Cash Alerts</h3>
|
||||||
<p>
|
<p>
|
||||||
Mechanical cash transaction threshold breaches — review and whitelist or escalate to Principal Officer
|
Raised by the transaction-monitoring rule engine — review and whitelist or escalate to Principal Officer
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<select class="fiu-inp" [(ngModel)]="amlAlertStatusFilter" (ngModelChange)="loadAmlAlerts()">
|
<select class="fiu-inp" [(ngModel)]="amlAlertStatusFilter" (ngModelChange)="loadAmlAlerts()">
|
||||||
@@ -163,13 +156,12 @@
|
|||||||
<th class="ta-r">Amount</th>
|
<th class="ta-r">Amount</th>
|
||||||
<th>Raised On</th>
|
<th>Raised On</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th class="ta-c">Routes To</th>
|
|
||||||
<th>Notes</th>
|
<th>Notes</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (alert of ctrAlerts; track alert.id) {
|
@for (alert of amlAlerts; track alert.id) {
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ alert.id }}</td>
|
<td>{{ alert.id }}</td>
|
||||||
<td>{{ alert.clientDisplayName }}</td>
|
<td>{{ alert.clientDisplayName }}</td>
|
||||||
@@ -180,9 +172,6 @@
|
|||||||
<td>
|
<td>
|
||||||
<span [class]="amlStatusClass(alert.status)">{{ alert.status }}</span>
|
<span [class]="amlStatusClass(alert.status)">{{ alert.status }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="ta-c">
|
|
||||||
<span [class]="alertRouteClass(alert.status)">{{ alertRouteLabel(alert.status) }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
@if (alert.status === 'RAISED' || alert.status === 'UNDER_REVIEW') {
|
@if (alert.status === 'RAISED' || alert.status === 'UNDER_REVIEW') {
|
||||||
<input
|
<input
|
||||||
@@ -206,17 +195,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
} @empty {
|
} @empty {
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="10" class="fiu-empty">No live alerts.</td>
|
<td colspan="9" class="fiu-empty">No live alerts.</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<p class="fiu-tbl-legend">
|
|
||||||
<span class="report-pill report-pending">Pending</span> awaiting Whitelist/Escalate decision ·
|
|
||||||
<span class="report-pill report-ctr">CTR only</span> whitelisted — cleared, no STR needed ·
|
|
||||||
<span class="report-pill report-str">→ STR</span> escalated to Principal Officer as an STR case
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- LIVE: monthly CTR reports (mechanical, threshold-based) -->
|
<!-- LIVE: monthly CTR reports (mechanical, threshold-based) -->
|
||||||
<div class="fiu-form-meta">
|
<div class="fiu-form-meta">
|
||||||
@@ -311,8 +295,6 @@
|
|||||||
<th>Instrument</th>
|
<th>Instrument</th>
|
||||||
<th>Branch</th>
|
<th>Branch</th>
|
||||||
<th class="ta-c">Relationship</th>
|
<th class="ta-c">Relationship</th>
|
||||||
<th class="ta-c">Filed Under</th>
|
|
||||||
<th>Compliance Action</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -345,131 +327,21 @@
|
|||||||
t.relationshipFlag
|
t.relationshipFlag
|
||||||
}}</span>
|
}}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="ta-c">
|
|
||||||
<span class="report-pill report-ctr" title="Always included in the monthly CTR / TC2 file">CTR</span>
|
|
||||||
@if (t.complianceStatus === 'SUSPICIOUS') {
|
|
||||||
<span class="report-pill report-str" title="Also escalated to an STR case for Principal Officer review">+ STR</span>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@if (!t.complianceStatus || t.complianceStatus === 'PENDING') {
|
|
||||||
<div class="fiu-action-group" style="gap: 4px;">
|
|
||||||
<button class="fiu-btn" style="padding: 4px 8px; font-size: 11px; color: var(--f-green); border-color: var(--f-green); background: var(--f-green-l);" (click)="whitelistCtrTxn(t)">
|
|
||||||
Whitelist
|
|
||||||
</button>
|
|
||||||
<button class="fiu-btn" style="padding: 4px 8px; font-size: 11px; color: var(--f-red); border-color: var(--f-red); background: var(--f-red-l);" (click)="flagCtrTxnSuspicious(t)">
|
|
||||||
Flag STR
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
} @else {
|
|
||||||
<span [class]="t.complianceStatus === 'WHITELISTED' ? 'status-pill status-whitelisted' : 'status-pill status-escalated'">
|
|
||||||
{{ t.complianceStatus }}
|
|
||||||
</span>
|
|
||||||
@if (t.complianceStatus === 'WHITELISTED') {
|
|
||||||
<div class="muted" style="margin-top: 3px;">Stays in CTR only</div>
|
|
||||||
} @else {
|
|
||||||
<button class="fiu-link-btn" (click)="openStrCase(t)">View in STR tab →</button>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
} @empty {
|
} @empty {
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="11" class="fiu-empty">No transactions match the filters.</td>
|
<td colspan="9" class="fiu-empty">No transactions match the filters.</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<p class="fiu-tbl-legend">
|
|
||||||
<span class="report-pill report-ctr">CTR</span> every cash txn ≥ threshold stays in the monthly CTR / TC2
|
|
||||||
file ·
|
|
||||||
<span class="report-pill report-str">+ STR</span> also flagged as suspicious and escalated to a separate STR
|
|
||||||
case (see the STR tab)
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- STR -->
|
<!-- STR -->
|
||||||
@if (activeTab === 'str') {
|
@if (activeTab === 'str') {
|
||||||
<div class="fiu-section">
|
<div class="fiu-section">
|
||||||
<!-- LIVE: suspicious AML alerts awaiting AML Officer review (Structuring, PEP, etc.) -->
|
|
||||||
<div class="fiu-form-meta">
|
|
||||||
<div class="fiu-form-title">
|
|
||||||
<h3>Live AML Alerts (Awaiting Triage)</h3>
|
|
||||||
<p>
|
|
||||||
Flagged by the monitoring engine for suspicious behavioral patterns — triage and whitelist or escalate
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="fiu-tbl-wrap">
|
|
||||||
<table class="fiu-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>Client</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Rule</th>
|
|
||||||
<th class="ta-r">Amount</th>
|
|
||||||
<th>Raised On</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th class="ta-c">Routes To</th>
|
|
||||||
<th>Notes</th>
|
|
||||||
<th>Action</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (alert of amlSuspiciousAlerts; track alert.id) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ alert.id }}</td>
|
|
||||||
<td>{{ alert.clientDisplayName }}</td>
|
|
||||||
<td>{{ alert.transactionType }}</td>
|
|
||||||
<td>{{ alert.ruleCode }} — {{ alert.ruleDescription }}</td>
|
|
||||||
<td class="ta-r mono">₹{{ alert.amount | number: '1.0-0' }}</td>
|
|
||||||
<td>{{ alert.raisedOn | date: 'medium' }}</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="amlStatusClass(alert.status)">{{ alert.status }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="ta-c">
|
|
||||||
<span [class]="alertRouteClass(alert.status, false)">{{ alertRouteLabel(alert.status, false) }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@if (alert.status === 'RAISED' || alert.status === 'UNDER_REVIEW') {
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="fiu-inp"
|
|
||||||
placeholder="Review notes"
|
|
||||||
[(ngModel)]="amlReviewNotes[alert.id]"
|
|
||||||
/>
|
|
||||||
} @else {
|
|
||||||
<span class="muted">{{ alert.reviewNotes }}</span>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@if (alert.status === 'RAISED' || alert.status === 'UNDER_REVIEW') {
|
|
||||||
<div class="fiu-action-group" *mifosxHasPermission="'REVIEW_AML_ALERT'">
|
|
||||||
<button class="fiu-btn fiu-btn-g" (click)="whitelistAlert(alert)">Whitelist</button>
|
|
||||||
<button class="fiu-btn fiu-btn-xml" (click)="escalateAlert(alert)">Escalate</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="10" class="fiu-empty">No suspicious behavioral alerts.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<p class="fiu-tbl-legend">
|
|
||||||
<span class="report-pill report-pending">Pending</span> awaiting Whitelist/Escalate decision ·
|
|
||||||
<span class="report-pill report-cleared">Cleared</span> whitelisted — no report filed ·
|
|
||||||
<span class="report-pill report-str">→ STR</span> escalated to Principal Officer as an STR case
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<hr style="border: 0; border-top: 1px solid var(--f-g200); margin: 8px 0;" />
|
|
||||||
|
|
||||||
<!-- LIVE: STR cases with full Principal Officer actions -->
|
<!-- LIVE: STR cases with full Principal Officer actions -->
|
||||||
<div class="fiu-form-meta">
|
<div class="fiu-form-meta">
|
||||||
<div class="fiu-form-title">
|
<div class="fiu-form-title">
|
||||||
@@ -535,17 +407,6 @@
|
|||||||
<button class="fiu-btn fiu-btn-g" (click)="closeStrCaseDetail()">Close</button>
|
<button class="fiu-btn fiu-btn-g" (click)="closeStrCaseDetail()">Close</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
class="case-outcome"
|
|
||||||
[class.case-outcome--clear]="selectedStrCase.status === 'CONCLUDED_CLEAR'"
|
|
||||||
[class.case-outcome--suspicious]="
|
|
||||||
selectedStrCase.status === 'CONCLUDED_SUSPICIOUS' || selectedStrCase.status === 'FILED'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<span [class]="amlStatusClass(selectedStrCase.status)">{{ selectedStrCase.status }}</span>
|
|
||||||
<span>{{ strCaseOutcome(selectedStrCase) }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="case-form" *mifosxHasPermission="'CONCLUDE_AML_STR'">
|
<div class="case-form" *mifosxHasPermission="'CONCLUDE_AML_STR'">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="fiuInvestigationNotes">Investigation notes (source-of-transaction history reviewed)</label>
|
<label for="fiuInvestigationNotes">Investigation notes (source-of-transaction history reviewed)</label>
|
||||||
@@ -555,96 +416,17 @@
|
|||||||
<label for="fiuSourceOfFundsNotes">Source-of-funds notes</label>
|
<label for="fiuSourceOfFundsNotes">Source-of-funds notes</label>
|
||||||
<textarea id="fiuSourceOfFundsNotes" rows="2" [(ngModel)]="concludeSourceOfFundsNotes"></textarea>
|
<textarea id="fiuSourceOfFundsNotes" rows="2" [(ngModel)]="concludeSourceOfFundsNotes"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<!-- Verification checklist — the verdict is derived, not chosen. -->
|
<div class="form-row conclusion-row">
|
||||||
<div class="checklist">
|
<label>
|
||||||
<div class="checklist-head">
|
<input type="radio" name="fiuConclusion" value="NOT_SUSPICIOUS" [(ngModel)]="concludeConclusion" />
|
||||||
<span>Principal Officer verification — mark each point as found</span>
|
Not suspicious (whitelist)
|
||||||
<span class="checklist-progress" [class.done]="allAnswered">
|
</label>
|
||||||
{{ answeredCount }} / {{ strChecklist.length }} answered
|
<label>
|
||||||
</span>
|
<input type="radio" name="fiuConclusion" value="SUSPICIOUS" [(ngModel)]="concludeConclusion" />
|
||||||
</div>
|
Suspicious (file STR)
|
||||||
<table class="checklist-tbl">
|
</label>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Verification point</th>
|
|
||||||
<th class="ta-c col-yes">Genuine</th>
|
|
||||||
<th class="ta-c col-no">Not satisfied</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (item of strChecklist; track item.key) {
|
|
||||||
<tr
|
|
||||||
[class.row-yes]="strFindings[item.key] === true"
|
|
||||||
[class.row-no]="strFindings[item.key] === false"
|
|
||||||
[class.row-pending]="strFindings[item.key] === null"
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
<div class="chk-label">{{ item.label }}</div>
|
|
||||||
<div class="chk-hint">{{ item.hint }}</div>
|
|
||||||
</td>
|
|
||||||
<td class="ta-c">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="chk-box chk-box--yes"
|
|
||||||
[class.on]="strFindings[item.key] === true"
|
|
||||||
[attr.aria-pressed]="strFindings[item.key] === true"
|
|
||||||
[attr.aria-label]="'Mark ' + item.label + ' as genuine'"
|
|
||||||
(click)="setFinding(item.key, true)"
|
|
||||||
>
|
|
||||||
<fa-icon icon="check-circle"></fa-icon>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
<td class="ta-c">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="chk-box chk-box--no"
|
|
||||||
[class.on]="strFindings[item.key] === false"
|
|
||||||
[attr.aria-pressed]="strFindings[item.key] === false"
|
|
||||||
[attr.aria-label]="'Mark ' + item.label + ' as not satisfied'"
|
|
||||||
(click)="setFinding(item.key, false)"
|
|
||||||
>
|
|
||||||
<fa-icon icon="exclamation-triangle"></fa-icon>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
<button class="fiu-btn fiu-btn-xml" (click)="concludeStrCase()">Conclude</button>
|
||||||
<!-- Derived verdict -->
|
|
||||||
<div
|
|
||||||
class="verdict"
|
|
||||||
[class.verdict--clear]="allAnswered && derivedConclusion === 'NOT_SUSPICIOUS'"
|
|
||||||
[class.verdict--suspicious]="allAnswered && derivedConclusion === 'SUSPICIOUS'"
|
|
||||||
>
|
|
||||||
@if (!allAnswered) {
|
|
||||||
<fa-icon icon="info-circle"></fa-icon>
|
|
||||||
<span>
|
|
||||||
Answer every verification point to determine the outcome —
|
|
||||||
{{ strChecklist.length - answeredCount }} still pending.
|
|
||||||
</span>
|
|
||||||
} @else if (derivedConclusion === 'NOT_SUSPICIOUS') {
|
|
||||||
<fa-icon icon="check-circle"></fa-icon>
|
|
||||||
<span>
|
|
||||||
<strong>Not suspicious.</strong> All {{ strChecklist.length }} points found genuine — the case will
|
|
||||||
be closed and nothing filed with FIU-IND.
|
|
||||||
</span>
|
|
||||||
} @else {
|
|
||||||
<fa-icon icon="exclamation-triangle"></fa-icon>
|
|
||||||
<span>
|
|
||||||
<strong>Suspicious — STR must be filed.</strong>
|
|
||||||
{{ failedFindings.length }} of {{ strChecklist.length }} points not satisfied:
|
|
||||||
{{ failedFindings[0].label }}@if (failedFindings.length > 1) {
|
|
||||||
<span> and {{ failedFindings.length - 1 }} more</span>
|
|
||||||
}.
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="fiu-btn fiu-btn-xml" [disabled]="!allAnswered" (click)="concludeStrCase()">
|
|
||||||
Conclude as {{ allAnswered ? (derivedConclusion === 'SUSPICIOUS' ? 'Suspicious' : 'Not Suspicious') : '…' }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
@if (selectedStrCase.status === 'CONCLUDED_SUSPICIOUS') {
|
@if (selectedStrCase.status === 'CONCLUDED_SUSPICIOUS') {
|
||||||
<div class="file-str-row">
|
<div class="file-str-row">
|
||||||
@@ -696,12 +478,7 @@
|
|||||||
|
|
||||||
<div class="fiu-str-list">
|
<div class="fiu-str-list">
|
||||||
@for (s of filteredStr; track s.reportRefNo) {
|
@for (s of filteredStr; track s.reportRefNo) {
|
||||||
<div
|
<div class="str-card" [class.str-card--filed]="s.status === 'filed'">
|
||||||
class="str-card"
|
|
||||||
[id]="'str-card-' + s.reportRefNo"
|
|
||||||
[class.str-card--filed]="s.status === 'filed'"
|
|
||||||
[class.str-card--highlight]="highlightedStrRef === s.reportRefNo"
|
|
||||||
>
|
|
||||||
<div class="str-card-h">
|
<div class="str-card-h">
|
||||||
<div class="str-card-id">
|
<div class="str-card-id">
|
||||||
<span class="str-ref">STR-{{ s.reportRefNo }}</span>
|
<span class="str-ref">STR-{{ s.reportRefNo }}</span>
|
||||||
@@ -744,27 +521,6 @@
|
|||||||
<p>{{ s.poRemarks }}</p>
|
<p>{{ s.poRemarks }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (s.poFinding) {
|
|
||||||
<div class="str-finding" [class.str-finding--suspicious]="s.poFinding === 'SUSPICIOUS'">
|
|
||||||
<label>
|
|
||||||
<fa-icon [icon]="s.poFinding === 'CLEAN' ? 'check-circle' : 'exclamation-triangle'"></fa-icon>
|
|
||||||
Principal Officer Finding
|
|
||||||
</label>
|
|
||||||
<p>{{ poFindingLabel(s.poFinding) }}</p>
|
|
||||||
</div>
|
|
||||||
} @else {
|
|
||||||
<div class="str-po-action" *mifosxHasPermission="'CONCLUDE_AML_STR'">
|
|
||||||
<label>Principal Officer investigation — is the source of funds legitimate?</label>
|
|
||||||
<div class="fiu-action-group">
|
|
||||||
<button class="fiu-btn fiu-btn-clean" (click)="concludeLocalStr(s, 'CLEAN')">
|
|
||||||
Clean — close case
|
|
||||||
</button>
|
|
||||||
<button class="fiu-btn fiu-btn-xml" (click)="concludeLocalStr(s, 'SUSPICIOUS')">
|
|
||||||
Suspicious — file STR with FIU-IND
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
} @empty {
|
} @empty {
|
||||||
@@ -870,13 +626,10 @@
|
|||||||
<div class="fiu-form-meta">
|
<div class="fiu-form-meta">
|
||||||
<div class="fiu-form-title">
|
<div class="fiu-form-title">
|
||||||
<h3>FINnet 2.0 Submission Files</h3>
|
<h3>FINnet 2.0 Submission Files</h3>
|
||||||
<p>
|
<p>6 CSV files in FIU-IND prescribed schema, plus STR XML</p>
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<button class="fiu-btn fiu-btn-xls" (click)="exportAll()">
|
<button class="fiu-btn fiu-btn-xls" (click)="exportAll()">
|
||||||
<fa-icon icon="file-csv"></fa-icon> Download All 7
|
<fa-icon icon="file-csv"></fa-icon> Download All
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -888,7 +641,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="file-card">
|
<div class="file-card">
|
||||||
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>TC2.csv</div>
|
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>TC2.csv</div>
|
||||||
<p>Branch / cash transactions ({{ filteredCtr.length }} rows)</p>
|
<p>Branch / cash transactions ({{ ctrTxns.length }} rows)</p>
|
||||||
<button class="fiu-btn fiu-btn-csv" (click)="exportTc2()">
|
<button class="fiu-btn fiu-btn-csv" (click)="exportTc2()">
|
||||||
<fa-icon icon="file-csv"></fa-icon> Download
|
<fa-icon icon="file-csv"></fa-icon> Download
|
||||||
</button>
|
</button>
|
||||||
@@ -923,7 +676,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="file-card file-card--xml">
|
<div class="file-card file-card--xml">
|
||||||
<div class="file-card-h"><fa-icon icon="file-alt"></fa-icon>STR.xml</div>
|
<div class="file-card-h"><fa-icon icon="file-alt"></fa-icon>STR.xml</div>
|
||||||
<p>Suspicious Transaction Report ({{ strSuspects.length }} suspects — cases concluded clean are excluded)</p>
|
<p>Suspicious Transaction Report ({{ filedStr + pendingStr }} suspects)</p>
|
||||||
<button class="fiu-btn fiu-btn-xml" (click)="exportStrXml()">
|
<button class="fiu-btn fiu-btn-xml" (click)="exportStrXml()">
|
||||||
<fa-icon icon="file-alt"></fa-icon> Download XML
|
<fa-icon icon="file-alt"></fa-icon> Download XML
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -386,100 +386,6 @@
|
|||||||
align-self: end;
|
align-self: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fiu-link-btn {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 3px;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--f-red);
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #9a2f22;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--muted {
|
|
||||||
color: var(--f-g500);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--f-red);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* PRINCIPAL OFFICER INVESTIGATION */
|
|
||||||
.str-po-action {
|
|
||||||
background: var(--f-amber-l);
|
|
||||||
border: 1px dashed #fcd34d;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 10px 14px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
> label {
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #92400e;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fiu-btn-clean {
|
|
||||||
background: var(--f-green);
|
|
||||||
border-color: var(--f-green);
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #166534;
|
|
||||||
border-color: #166534;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.str-finding {
|
|
||||||
background: var(--f-green-l);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 10px 14px;
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--f-green);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #14532d;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--suspicious {
|
|
||||||
background: var(--f-red-l);
|
|
||||||
|
|
||||||
label {
|
|
||||||
color: var(--f-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: #7f1d1d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* INPUTS */
|
/* INPUTS */
|
||||||
.fiu-inp {
|
.fiu-inp {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -632,50 +538,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.report-pill {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 2px 9px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&.report-ctr {
|
|
||||||
background: var(--f-blue-l);
|
|
||||||
color: var(--f-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.report-str {
|
|
||||||
background: var(--f-red-l);
|
|
||||||
color: var(--f-red);
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.report-pending {
|
|
||||||
background: var(--f-amber-l);
|
|
||||||
color: var(--f-amber);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.report-cleared {
|
|
||||||
background: var(--f-green-l);
|
|
||||||
color: var(--f-green);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fiu-tbl-legend {
|
|
||||||
margin: -6px 2px 0;
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--f-g500);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
.report-pill {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rel-pill {
|
.rel-pill {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px 9px;
|
padding: 2px 9px;
|
||||||
@@ -835,11 +697,6 @@
|
|||||||
&--filed {
|
&--filed {
|
||||||
border-color: #86efac;
|
border-color: #86efac;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--highlight {
|
|
||||||
border-color: var(--f-red);
|
|
||||||
box-shadow: 0 0 0 3px rgb(192 57 43 / 20%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.str-card-h {
|
.str-card-h {
|
||||||
@@ -1099,29 +956,6 @@
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.case-outcome {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin: 12px 0;
|
|
||||||
padding: 10px 14px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--f-g100);
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--f-g700);
|
|
||||||
|
|
||||||
&--clear {
|
|
||||||
background: var(--f-green-l);
|
|
||||||
color: #14532d;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--suspicious {
|
|
||||||
background: var(--f-red-l);
|
|
||||||
color: #7f1d1d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-form {
|
.case-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1161,181 +995,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Principal Officer verification checklist ───────────────────────────── */
|
|
||||||
.checklist {
|
|
||||||
border: 1px solid var(--f-g200);
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checklist-head {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 10px 14px;
|
|
||||||
background: var(--f-g50);
|
|
||||||
border-bottom: 1px solid var(--f-g200);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--f-g700);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checklist-progress {
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 2px 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: var(--f-amber-l);
|
|
||||||
color: var(--f-amber);
|
|
||||||
text-transform: none;
|
|
||||||
letter-spacing: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&.done {
|
|
||||||
background: var(--f-green-l);
|
|
||||||
color: var(--f-green);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.checklist-tbl {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
|
|
||||||
th {
|
|
||||||
padding: 8px 12px;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--f-g500);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
background: var(--f-g50);
|
|
||||||
border-bottom: 1px solid var(--f-g200);
|
|
||||||
text-align: left;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
th.col-yes {
|
|
||||||
color: var(--f-green);
|
|
||||||
width: 96px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th.col-no {
|
|
||||||
color: var(--f-red);
|
|
||||||
width: 112px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 9px 12px;
|
|
||||||
border-bottom: 1px solid var(--f-g100);
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:last-child td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Answered rows tint so an unresolved finding is obvious at a glance. */
|
|
||||||
.row-yes td {
|
|
||||||
background: rgb(220 252 231 / 45%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-no td {
|
|
||||||
background: rgb(253 236 234 / 60%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-pending td {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chk-label {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--f-g900);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chk-hint {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--f-g500);
|
|
||||||
margin-top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Two-sided toggle: one box on the genuine side, one on the not-satisfied side. */
|
|
||||||
.chk-box {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 7px;
|
|
||||||
border: 2px solid var(--f-g300);
|
|
||||||
background: #fff;
|
|
||||||
color: var(--f-g300);
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 14px;
|
|
||||||
transition: all 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: scale(1.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--yes:hover {
|
|
||||||
border-color: var(--f-green);
|
|
||||||
color: var(--f-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--no:hover {
|
|
||||||
border-color: var(--f-red);
|
|
||||||
color: var(--f-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--yes.on {
|
|
||||||
border-color: var(--f-green);
|
|
||||||
background: var(--f-green);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--no.on {
|
|
||||||
border-color: var(--f-red);
|
|
||||||
background: var(--f-red);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Derived verdict banner */
|
|
||||||
.verdict {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 11px 15px;
|
|
||||||
border-radius: 9px;
|
|
||||||
font-size: 13px;
|
|
||||||
background: var(--f-g100);
|
|
||||||
border: 1px solid var(--f-g200);
|
|
||||||
color: var(--f-g700);
|
|
||||||
|
|
||||||
&--clear {
|
|
||||||
background: var(--f-green-l);
|
|
||||||
border-color: #86efac;
|
|
||||||
color: #14532d;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--suspicious {
|
|
||||||
background: var(--f-red-l);
|
|
||||||
border-color: #fca5a5;
|
|
||||||
color: #7f1d1d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-form .fiu-btn-xml:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-str-row {
|
.file-str-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
+30
-344
@@ -20,7 +20,6 @@ import {
|
|||||||
KC2_ENTITIES,
|
KC2_ENTITIES,
|
||||||
KycEntity,
|
KycEntity,
|
||||||
KycIndividual,
|
KycIndividual,
|
||||||
PoFinding,
|
|
||||||
StrStatus,
|
StrStatus,
|
||||||
StrTransaction,
|
StrTransaction,
|
||||||
STR_TXNS
|
STR_TXNS
|
||||||
@@ -37,9 +36,6 @@ import {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class FiuComponent implements OnInit {
|
export class FiuComponent implements OnInit {
|
||||||
/** Bump when CTR_TXNS / STR_TXNS change so cached rows are re-seeded. */
|
|
||||||
private static readonly SEED_VERSION = '2026-01-cash-threshold';
|
|
||||||
|
|
||||||
private amlService = inject(AmlService);
|
private amlService = inject(AmlService);
|
||||||
private authenticationService = inject(AuthenticationService);
|
private authenticationService = inject(AuthenticationService);
|
||||||
/** Active tab */
|
/** Active tab */
|
||||||
@@ -60,9 +56,6 @@ export class FiuComponent implements OnInit {
|
|||||||
strFilterStatus: '' | StrStatus = '';
|
strFilterStatus: '' | StrStatus = '';
|
||||||
strFilterRisk: string = '';
|
strFilterRisk: string = '';
|
||||||
|
|
||||||
/** Ref of the STR card to highlight after jumping across from the CTR tab */
|
|
||||||
highlightedStrRef: number | null = null;
|
|
||||||
|
|
||||||
/** Toast */
|
/** Toast */
|
||||||
toastMsg = '';
|
toastMsg = '';
|
||||||
toastVisible = false;
|
toastVisible = false;
|
||||||
@@ -83,89 +76,6 @@ export class FiuComponent implements OnInit {
|
|||||||
concludeSourceOfFundsNotes = '';
|
concludeSourceOfFundsNotes = '';
|
||||||
fileReferenceNo = '';
|
fileReferenceNo = '';
|
||||||
|
|
||||||
/**
|
|
||||||
* The Principal Officer's verification checklist. Each point is answered
|
|
||||||
* "Genuine" or "Not satisfied"; the case conclusion is derived from the answers
|
|
||||||
* rather than picked by hand, so a case with an unresolved finding cannot be
|
|
||||||
* whitelisted by accident.
|
|
||||||
*/
|
|
||||||
readonly strChecklist: { key: string; label: string; hint: string }[] = [
|
|
||||||
{
|
|
||||||
key: 'sourceOfFunds',
|
|
||||||
label: 'Source of funds established and documented',
|
|
||||||
hint: 'Salary slips, sale deed, business receipts or other credible evidence on file'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'profileConsistent',
|
|
||||||
label: 'Transaction consistent with the customer’s declared profile',
|
|
||||||
hint: 'Value and frequency in line with declared income, occupation and turnover'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'kycCurrent',
|
|
||||||
label: 'KYC records complete and within re-KYC validity',
|
|
||||||
hint: 'Identity, address and beneficial-ownership records verified and current'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'screeningClear',
|
|
||||||
label: 'Sanctions, PEP and adverse-media screening clear',
|
|
||||||
hint: 'No UNSCC / MHA listing, no PEP linkage, no negative news on the customer'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'patternConsistent',
|
|
||||||
label: 'Pattern consistent with the account’s own history',
|
|
||||||
hint: 'No structuring, sudden dormancy revival or rapid pass-through observed'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'counterpartyKnown',
|
|
||||||
label: 'Counterparty identifiable and legitimate',
|
|
||||||
hint: 'Remitter / beneficiary traceable and holding a plausible commercial relationship'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'documentsGenuine',
|
|
||||||
label: 'Supporting documents verified as genuine',
|
|
||||||
hint: 'Invoices, contracts and bank statements independently corroborated'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'noLawEnforcement',
|
|
||||||
label: 'No law-enforcement or regulatory reference pending',
|
|
||||||
hint: 'No summons, freeze order or LEA request outstanding against the account'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/** null = not yet answered, true = genuine, false = not satisfied. */
|
|
||||||
strFindings: Record<string, boolean | null> = {};
|
|
||||||
|
|
||||||
private resetChecklist(): void {
|
|
||||||
this.strFindings = {};
|
|
||||||
for (const item of this.strChecklist) {
|
|
||||||
this.strFindings[item.key] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setFinding(key: string, genuine: boolean): void {
|
|
||||||
this.strFindings[key] = genuine;
|
|
||||||
this.concludeConclusion = this.derivedConclusion;
|
|
||||||
}
|
|
||||||
|
|
||||||
get answeredCount(): number {
|
|
||||||
return this.strChecklist.filter((i) => this.strFindings[i.key] !== null).length;
|
|
||||||
}
|
|
||||||
get allAnswered(): boolean {
|
|
||||||
return this.answeredCount === this.strChecklist.length;
|
|
||||||
}
|
|
||||||
/** Points the officer marked as not satisfied — these are what force an STR. */
|
|
||||||
get failedFindings(): { key: string; label: string; hint: string }[] {
|
|
||||||
return this.strChecklist.filter((i) => this.strFindings[i.key] === false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Everything genuine means the case is cleared; a single unsatisfied point
|
|
||||||
* means it must be filed as suspicious.
|
|
||||||
*/
|
|
||||||
get derivedConclusion(): 'NOT_SUSPICIOUS' | 'SUSPICIOUS' {
|
|
||||||
return this.failedFindings.length > 0 ? 'SUSPICIOUS' : 'NOT_SUSPICIOUS';
|
|
||||||
}
|
|
||||||
|
|
||||||
get isPrincipalOfficer(): boolean {
|
get isPrincipalOfficer(): boolean {
|
||||||
return this.authenticationService.isPrincipalOfficerUser();
|
return this.authenticationService.isPrincipalOfficerUser();
|
||||||
}
|
}
|
||||||
@@ -178,84 +88,6 @@ export class FiuComponent implements OnInit {
|
|||||||
this.loadAmlAlerts();
|
this.loadAmlAlerts();
|
||||||
this.loadAmlCtrReports();
|
this.loadAmlCtrReports();
|
||||||
this.loadAmlStrCases();
|
this.loadAmlStrCases();
|
||||||
|
|
||||||
// Bumping SEED_VERSION discards previously cached rows so a new data set takes effect.
|
|
||||||
if (localStorage.getItem('fiu_seed_version') !== FiuComponent.SEED_VERSION) {
|
|
||||||
localStorage.setItem('fiu_seed_version', FiuComponent.SEED_VERSION);
|
|
||||||
localStorage.setItem('fiu_ctr_txns', JSON.stringify(this.ctrTxns));
|
|
||||||
localStorage.setItem('fiu_str_cases', JSON.stringify(this.strTxns));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const storedCtr = localStorage.getItem('fiu_ctr_txns');
|
|
||||||
if (storedCtr) {
|
|
||||||
this.ctrTxns = JSON.parse(storedCtr);
|
|
||||||
} else {
|
|
||||||
localStorage.setItem('fiu_ctr_txns', JSON.stringify(this.ctrTxns));
|
|
||||||
}
|
|
||||||
|
|
||||||
const storedStr = localStorage.getItem('fiu_str_cases');
|
|
||||||
if (storedStr) {
|
|
||||||
this.strTxns = JSON.parse(storedStr);
|
|
||||||
} else {
|
|
||||||
localStorage.setItem('fiu_str_cases', JSON.stringify(this.strTxns));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
whitelistCtrTxn(txn: CtrTransaction): void {
|
|
||||||
txn.complianceStatus = 'WHITELISTED';
|
|
||||||
localStorage.setItem('fiu_ctr_txns', JSON.stringify(this.ctrTxns));
|
|
||||||
this.toast(`Transaction for ${txn.customerName} whitelisted — stays in the CTR report only`);
|
|
||||||
}
|
|
||||||
|
|
||||||
flagCtrTxnSuspicious(txn: CtrTransaction): void {
|
|
||||||
txn.complianceStatus = 'SUSPICIOUS';
|
|
||||||
localStorage.setItem('fiu_ctr_txns', JSON.stringify(this.ctrTxns));
|
|
||||||
|
|
||||||
const newRef = Math.max(100, ...this.strTxns.map((s) => s.reportRefNo)) + 1;
|
|
||||||
const newStrCase: StrTransaction = {
|
|
||||||
...txn,
|
|
||||||
reportType: 'STR',
|
|
||||||
reportRefNo: newRef,
|
|
||||||
detectedOn: new Date().toLocaleDateString('en-GB').replace(/\//g, '-'),
|
|
||||||
flaggedBy: 'AML Officer',
|
|
||||||
riskCategory: 'unusual-pattern',
|
|
||||||
narrative:
|
|
||||||
`Cash ${txn.depositOrWithdrawal.toLowerCase()} of ₹${txn.txnAmount.toLocaleString('en-IN')} breached the ` +
|
|
||||||
`₹${this.bank.cashThreshold.toLocaleString('en-IN')} CTR limit and was flagged as suspicious by the AML Officer. ` +
|
|
||||||
`Referred to the Principal Officer to establish whether the source of funds is legitimate or linked to terror financing.`,
|
|
||||||
status: 'po-review',
|
|
||||||
poFinding: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
this.strTxns.unshift(newStrCase);
|
|
||||||
localStorage.setItem('fiu_str_cases', JSON.stringify(this.strTxns));
|
|
||||||
this.toast(`STR-${newRef} opened for ${txn.customerName} — awaiting Principal Officer investigation`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Principal Officer closes an STR case as legitimate, or confirms it and files an STR. */
|
|
||||||
concludeLocalStr(rec: StrTransaction, finding: PoFinding): void {
|
|
||||||
rec.poFinding = finding;
|
|
||||||
if (finding === 'CLEAN') {
|
|
||||||
rec.status = 'rejected';
|
|
||||||
rec.poRemarks =
|
|
||||||
'Source-of-funds documentation reviewed and verified by the Principal Officer. No link to terror financing or ' +
|
|
||||||
'any other predicate offence. Case closed — no STR filed with FIU-IND.';
|
|
||||||
this.toast(`STR-${rec.reportRefNo} closed as clean — no STR filed`);
|
|
||||||
} else {
|
|
||||||
rec.status = 'filed';
|
|
||||||
rec.poRemarks =
|
|
||||||
'Principal Officer investigation could not establish a legitimate source of funds. STR filed with FIU-IND under ' +
|
|
||||||
'PMLA s.12 within the 7-day window; account placed under enhanced monitoring and reported to the Nodal Officer.';
|
|
||||||
this.toast(`STR-${rec.reportRefNo} confirmed suspicious — filed with FIU-IND`);
|
|
||||||
}
|
|
||||||
localStorage.setItem('fiu_str_cases', JSON.stringify(this.strTxns));
|
|
||||||
}
|
|
||||||
|
|
||||||
poFindingLabel(finding: PoFinding): string {
|
|
||||||
return finding === 'CLEAN'
|
|
||||||
? 'Clean — source of funds verified as legitimate. No STR filed.'
|
|
||||||
: 'Suspicious — STR filed with FIU-IND and account escalated.';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAmlAlerts(): void {
|
loadAmlAlerts(): void {
|
||||||
@@ -272,35 +104,23 @@ export class FiuComponent implements OnInit {
|
|||||||
this.amlService.getStrCases().subscribe((data) => (this.amlStrCases = data || []));
|
this.amlService.getStrCases().subscribe((data) => (this.amlStrCases = data || []));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Surfaces a failed backend call — without this a rejected request looks like a dead button. */
|
|
||||||
private apiError(action: string) {
|
|
||||||
return (err: any) => this.toast(err?.error?.defaultUserMessage || `${action} failed — please retry`);
|
|
||||||
}
|
|
||||||
|
|
||||||
whitelistAlert(alert: any): void {
|
whitelistAlert(alert: any): void {
|
||||||
this.amlService.reviewAlert(alert.id, 'WHITELIST', this.amlReviewNotes[alert.id] || '').subscribe({
|
this.amlService.reviewAlert(alert.id, 'WHITELIST', this.amlReviewNotes[alert.id] || '').subscribe(() => {
|
||||||
next: () => {
|
this.toast(`Alert #${alert.id} whitelisted`);
|
||||||
this.toast(`Alert #${alert.id} whitelisted — no STR raised`);
|
this.loadAmlAlerts();
|
||||||
this.loadAmlAlerts();
|
|
||||||
},
|
|
||||||
error: this.apiError(`Whitelisting alert #${alert.id}`)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
escalateAlert(alert: any): void {
|
escalateAlert(alert: any): void {
|
||||||
this.amlService.reviewAlert(alert.id, 'ESCALATE', this.amlReviewNotes[alert.id] || '').subscribe({
|
this.amlService.reviewAlert(alert.id, 'ESCALATE', this.amlReviewNotes[alert.id] || '').subscribe(() => {
|
||||||
next: () => {
|
this.toast(`Alert #${alert.id} escalated to Principal Officer`);
|
||||||
this.toast(`Alert #${alert.id} escalated — now an STR case for the Principal Officer`);
|
this.loadAmlAlerts();
|
||||||
this.loadAmlAlerts();
|
this.loadAmlStrCases();
|
||||||
this.loadAmlStrCases();
|
|
||||||
},
|
|
||||||
error: this.apiError(`Escalating alert #${alert.id}`)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
generateCtrReport(): void {
|
generateCtrReport(): void {
|
||||||
if (!this.ctrGenerateMonth) {
|
if (!this.ctrGenerateMonth) {
|
||||||
this.toast('Pick a reporting month first');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.amlService.generateCtrReport(this.ctrGenerateMonth).subscribe({
|
this.amlService.generateCtrReport(this.ctrGenerateMonth).subscribe({
|
||||||
@@ -308,32 +128,16 @@ export class FiuComponent implements OnInit {
|
|||||||
this.toast(`CTR report generated for ${this.ctrGenerateMonth}`);
|
this.toast(`CTR report generated for ${this.ctrGenerateMonth}`);
|
||||||
this.loadAmlCtrReports();
|
this.loadAmlCtrReports();
|
||||||
},
|
},
|
||||||
error: this.apiError('CTR report generation')
|
error: (err) => this.toast(err?.error?.defaultUserMessage || 'CTR report generation failed')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectStrCase(strCase: any): void {
|
selectStrCase(strCase: any): void {
|
||||||
this.selectedStrCase = strCase;
|
this.selectedStrCase = strCase;
|
||||||
// Preserve an existing verdict instead of silently resetting it to NOT_SUSPICIOUS.
|
this.concludeConclusion = 'NOT_SUSPICIOUS';
|
||||||
this.concludeConclusion = strCase.conclusion === 'SUSPICIOUS' ? 'SUSPICIOUS' : 'NOT_SUSPICIOUS';
|
|
||||||
this.concludeInvestigationNotes = strCase.investigationNotes || '';
|
this.concludeInvestigationNotes = strCase.investigationNotes || '';
|
||||||
this.concludeSourceOfFundsNotes = strCase.sourceOfFundsNotes || '';
|
this.concludeSourceOfFundsNotes = strCase.sourceOfFundsNotes || '';
|
||||||
this.fileReferenceNo = strCase.strReferenceNo || '';
|
this.fileReferenceNo = '';
|
||||||
this.resetChecklist();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Plain-language outcome for a live STR case, shown in the investigation panel. */
|
|
||||||
strCaseOutcome(strCase: any): string {
|
|
||||||
switch (strCase?.status) {
|
|
||||||
case 'CONCLUDED_CLEAR':
|
|
||||||
return 'Concluded not suspicious — case closed, nothing filed with FIU-IND.';
|
|
||||||
case 'CONCLUDED_SUSPICIOUS':
|
|
||||||
return `Concluded suspicious — STR must be filed with FIU-IND by ${strCase.fiuDueDate || 'the due date'}.`;
|
|
||||||
case 'FILED':
|
|
||||||
return `STR filed with FIU-IND${strCase.strReferenceNo ? ` under reference ${strCase.strReferenceNo}` : ''}.`;
|
|
||||||
default:
|
|
||||||
return 'Open — awaiting the Principal Officer’s conclusion.';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closeStrCaseDetail(): void {
|
closeStrCaseDetail(): void {
|
||||||
@@ -344,60 +148,32 @@ export class FiuComponent implements OnInit {
|
|||||||
if (!this.selectedStrCase) {
|
if (!this.selectedStrCase) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.allAnswered) {
|
|
||||||
const pending = this.strChecklist.length - this.answeredCount;
|
|
||||||
this.toast(`Answer all verification points first — ${pending} still pending`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.concludeInvestigationNotes.trim()) {
|
|
||||||
this.toast('Record your investigation notes before concluding');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// The verdict always follows the checklist, never a stale radio selection.
|
|
||||||
this.concludeConclusion = this.derivedConclusion;
|
|
||||||
// Fold the unsatisfied points into the notes so the STR carries its own reasons.
|
|
||||||
const failed = this.failedFindings;
|
|
||||||
const notes = failed.length
|
|
||||||
? `${this.concludeInvestigationNotes.trim()}\n\nUnsatisfied verification points:\n` +
|
|
||||||
failed.map((f) => `• ${f.label}`).join('\n')
|
|
||||||
: `${this.concludeInvestigationNotes.trim()}\n\nAll ${this.strChecklist.length} verification points found genuine.`;
|
|
||||||
|
|
||||||
this.amlService
|
this.amlService
|
||||||
.concludeStrCase(
|
.concludeStrCase(
|
||||||
this.selectedStrCase.id,
|
this.selectedStrCase.id,
|
||||||
this.concludeConclusion,
|
this.concludeConclusion,
|
||||||
notes,
|
this.concludeInvestigationNotes,
|
||||||
this.concludeSourceOfFundsNotes
|
this.concludeSourceOfFundsNotes
|
||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe((updated) => {
|
||||||
next: (updated) => {
|
this.toast(
|
||||||
this.toast(
|
this.concludeConclusion === 'SUSPICIOUS'
|
||||||
this.concludeConclusion === 'SUSPICIOUS'
|
? `STR case #${updated.id} concluded suspicious — FIU due date ${updated.fiuDueDate}`
|
||||||
? `STR case #${updated.id} concluded suspicious — file with FIU-IND by ${updated.fiuDueDate}`
|
: `STR case #${updated.id} concluded not suspicious`
|
||||||
: `STR case #${updated.id} concluded not suspicious — no filing required`
|
);
|
||||||
);
|
this.selectedStrCase = updated;
|
||||||
this.selectedStrCase = updated;
|
this.loadAmlStrCases();
|
||||||
this.loadAmlStrCases();
|
|
||||||
},
|
|
||||||
error: this.apiError(`Concluding STR case #${this.selectedStrCase.id}`)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fileStrCase(): void {
|
fileStrCase(): void {
|
||||||
if (!this.selectedStrCase) {
|
if (!this.selectedStrCase || !this.fileReferenceNo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.fileReferenceNo.trim()) {
|
this.amlService.fileStr(this.selectedStrCase.id, this.fileReferenceNo).subscribe((updated) => {
|
||||||
this.toast('Enter the FIU-IND acknowledgement number first');
|
this.toast(`STR case #${updated.id} recorded as filed (ref ${updated.strReferenceNo})`);
|
||||||
return;
|
this.selectedStrCase = updated;
|
||||||
}
|
this.loadAmlStrCases();
|
||||||
this.amlService.fileStr(this.selectedStrCase.id, this.fileReferenceNo).subscribe({
|
|
||||||
next: (updated) => {
|
|
||||||
this.toast(`STR case #${updated.id} recorded as filed (ref ${updated.strReferenceNo})`);
|
|
||||||
this.selectedStrCase = updated;
|
|
||||||
this.loadAmlStrCases();
|
|
||||||
},
|
|
||||||
error: this.apiError(`Filing STR case #${this.selectedStrCase.id}`)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,57 +181,10 @@ export class FiuComponent implements OnInit {
|
|||||||
return 'status-pill status-' + (status || '').toLowerCase().replace(/_/g, '-');
|
return 'status-pill status-' + (status || '').toLowerCase().replace(/_/g, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Where a live AML alert will end up once triaged — drives the "Routes To" column.
|
|
||||||
* `isCtrRule` distinguishes mechanical CTR-threshold alerts (whitelisting just keeps them
|
|
||||||
* in the CTR file) from behavioral alerts (whitelisting clears them with no report at all).
|
|
||||||
*/
|
|
||||||
alertRouteLabel(status: string, isCtrRule: boolean = true): string {
|
|
||||||
switch (status) {
|
|
||||||
case 'WHITELISTED':
|
|
||||||
return isCtrRule ? 'CTR only' : 'Cleared';
|
|
||||||
case 'ESCALATED':
|
|
||||||
return '→ STR';
|
|
||||||
default:
|
|
||||||
return 'Pending';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alertRouteClass(status: string, isCtrRule: boolean = true): string {
|
|
||||||
switch (status) {
|
|
||||||
case 'WHITELISTED':
|
|
||||||
return isCtrRule ? 'report-pill report-ctr' : 'report-pill report-cleared';
|
|
||||||
case 'ESCALATED':
|
|
||||||
return 'report-pill report-str';
|
|
||||||
default:
|
|
||||||
return 'report-pill report-pending';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showTab(tab: 'dashboard' | 'ctr' | 'str' | 'customers' | 'files'): void {
|
showTab(tab: 'dashboard' | 'ctr' | 'str' | 'customers' | 'files'): void {
|
||||||
this.activeTab = tab;
|
this.activeTab = tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Jump from a flagged CTR row to its STR card. Clears any status/risk filter that
|
|
||||||
* would hide the case, then scrolls it into view and highlights it.
|
|
||||||
*/
|
|
||||||
openStrCase(txn: CtrTransaction): void {
|
|
||||||
const match = this.strTxns.find((s) => s.txnId === txn.txnId);
|
|
||||||
if (!match) {
|
|
||||||
this.toast('No STR case found for this transaction');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.activeTab = 'str';
|
|
||||||
this.resetStrFilters();
|
|
||||||
this.highlightedStrRef = match.reportRefNo;
|
|
||||||
setTimeout(() => {
|
|
||||||
document
|
|
||||||
.getElementById(`str-card-${match.reportRefNo}`)
|
|
||||||
?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- KPIs --------------------
|
// -------------------- KPIs --------------------
|
||||||
get totalReports(): number {
|
get totalReports(): number {
|
||||||
return new Set(this.ctrTxns.map((t) => t.reportRefNo)).size;
|
return new Set(this.ctrTxns.map((t) => t.reportRefNo)).size;
|
||||||
@@ -472,39 +201,10 @@ export class FiuComponent implements OnInit {
|
|||||||
get filedStr(): number {
|
get filedStr(): number {
|
||||||
return this.strTxns.filter((s) => s.status === 'filed').length;
|
return this.strTxns.filter((s) => s.status === 'filed').length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Live STR case counts (backend `amlStrCases`) — these are the cases the
|
|
||||||
// Principal Officer actually works through the Investigate → Conclude → File flow.
|
|
||||||
get liveStrOpen(): number {
|
|
||||||
return this.amlStrCases.filter((c) => c.status === 'OPEN').length;
|
|
||||||
}
|
|
||||||
get liveStrAwaitingFiling(): number {
|
|
||||||
return this.amlStrCases.filter((c) => c.status === 'CONCLUDED_SUSPICIOUS').length;
|
|
||||||
}
|
|
||||||
get liveStrCleared(): number {
|
|
||||||
return this.amlStrCases.filter((c) => c.status === 'CONCLUDED_CLEAR').length;
|
|
||||||
}
|
|
||||||
get liveStrFiled(): number {
|
|
||||||
return this.amlStrCases.filter((c) => c.status === 'FILED').length;
|
|
||||||
}
|
|
||||||
/** Still needs PO action: untouched, or concluded suspicious but not yet filed with FIU-IND. */
|
|
||||||
get liveStrPending(): number {
|
|
||||||
return this.liveStrOpen + this.liveStrAwaitingFiling;
|
|
||||||
}
|
|
||||||
get totalCustomers(): number {
|
get totalCustomers(): number {
|
||||||
return this.kc1.length + this.kc2.length;
|
return this.kc1.length + this.kc2.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get ctrAlerts(): any[] {
|
|
||||||
const ctrRuleCodes = ['CTR_THRESHOLD_10L', 'STRUCTURED_CTR_10L_MONTHLY', 'LARGE_CASH_TXN_25L'];
|
|
||||||
return this.amlAlerts.filter((a) => ctrRuleCodes.includes(a.ruleCode));
|
|
||||||
}
|
|
||||||
|
|
||||||
get amlSuspiciousAlerts(): any[] {
|
|
||||||
const ctrRuleCodes = ['CTR_THRESHOLD_10L', 'STRUCTURED_CTR_10L_MONTHLY', 'LARGE_CASH_TXN_25L'];
|
|
||||||
return this.amlAlerts.filter((a) => !ctrRuleCodes.includes(a.ruleCode));
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Filtered lists --------------------
|
// -------------------- Filtered lists --------------------
|
||||||
get filteredCtr(): CtrTransaction[] {
|
get filteredCtr(): CtrTransaction[] {
|
||||||
const acc = this.ctrFilterAccount.trim();
|
const acc = this.ctrFilterAccount.trim();
|
||||||
@@ -524,14 +224,6 @@ export class FiuComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Cases that actually belong in an STR filing. A case the Principal Officer
|
|
||||||
* concluded clean is not a suspect and must never reach FIU-IND.
|
|
||||||
*/
|
|
||||||
get strSuspects(): StrTransaction[] {
|
|
||||||
return this.filteredStr.filter((s) => s.poFinding !== 'CLEAN' && s.status !== 'rejected');
|
|
||||||
}
|
|
||||||
|
|
||||||
resetCtrFilters(): void {
|
resetCtrFilters(): void {
|
||||||
this.ctrFilterMode = '';
|
this.ctrFilterMode = '';
|
||||||
this.ctrFilterAccount = '';
|
this.ctrFilterAccount = '';
|
||||||
@@ -831,11 +523,11 @@ export class FiuComponent implements OnInit {
|
|||||||
const rows = [headers];
|
const rows = [headers];
|
||||||
// Build one aggregate row per (reportRefNo, accountNumber) pair
|
// Build one aggregate row per (reportRefNo, accountNumber) pair
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
for (const t of this.filteredCtr) {
|
for (const t of this.ctrTxns) {
|
||||||
const key = `${t.reportRefNo}|${t.accountNumber}`;
|
const key = `${t.reportRefNo}|${t.accountNumber}`;
|
||||||
if (seen.has(key)) continue;
|
if (seen.has(key)) continue;
|
||||||
seen.add(key);
|
seen.add(key);
|
||||||
const sameKey = this.filteredCtr.filter(
|
const sameKey = this.ctrTxns.filter(
|
||||||
(x) => x.reportRefNo === t.reportRefNo && x.accountNumber === t.accountNumber
|
(x) => x.reportRefNo === t.reportRefNo && x.accountNumber === t.accountNumber
|
||||||
);
|
);
|
||||||
const debits = sameKey.filter((x) => x.depositOrWithdrawal === 'Withdrawal');
|
const debits = sameKey.filter((x) => x.depositOrWithdrawal === 'Withdrawal');
|
||||||
@@ -884,7 +576,7 @@ export class FiuComponent implements OnInit {
|
|||||||
];
|
];
|
||||||
const rows = [headers];
|
const rows = [headers];
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
for (const t of this.filteredCtr) {
|
for (const t of this.ctrTxns) {
|
||||||
const key = `${t.reportRefNo}|${t.accountNumber}`;
|
const key = `${t.reportRefNo}|${t.accountNumber}`;
|
||||||
if (seen.has(key)) continue;
|
if (seen.has(key)) continue;
|
||||||
seen.add(key);
|
seen.add(key);
|
||||||
@@ -907,17 +599,12 @@ export class FiuComponent implements OnInit {
|
|||||||
|
|
||||||
/** STR XML stub (FINnet 2.0 STR XML) */
|
/** STR XML stub (FINnet 2.0 STR XML) */
|
||||||
exportStrXml(): void {
|
exportStrXml(): void {
|
||||||
const suspects = this.strSuspects;
|
|
||||||
if (!suspects.length) {
|
|
||||||
this.toast('No suspect cases to export — nothing matches the current filters');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const xml: string[] = [];
|
const xml: string[] = [];
|
||||||
xml.push('<?xml version="1.0" encoding="UTF-8"?>');
|
xml.push('<?xml version="1.0" encoding="UTF-8"?>');
|
||||||
xml.push(`<STR xmlns="http://fiuind.gov.in/finnet2/str" reportingMonth="${this.bank.reportingMonth}">`);
|
xml.push(`<STR xmlns="http://fiuind.gov.in/finnet2/str" reportingMonth="${this.bank.reportingMonth}">`);
|
||||||
xml.push(` <REBank buid="${this.bank.reBuid}" name="${this.bank.name}" />`);
|
xml.push(` <REBank buid="${this.bank.reBuid}" name="${this.bank.name}" />`);
|
||||||
xml.push(` <PrincipalOfficer name="${this.bank.principalOfficer}" email="${this.bank.principalOfficerEmail}" />`);
|
xml.push(` <PrincipalOfficer name="${this.bank.principalOfficer}" email="${this.bank.principalOfficerEmail}" />`);
|
||||||
for (const s of suspects) {
|
for (const s of this.filteredStr) {
|
||||||
xml.push(` <Suspect ref="${s.reportRefNo}" status="${s.status}">`);
|
xml.push(` <Suspect ref="${s.reportRefNo}" status="${s.status}">`);
|
||||||
xml.push(` <Account>${s.accountNumber}</Account>`);
|
xml.push(` <Account>${s.accountNumber}</Account>`);
|
||||||
xml.push(
|
xml.push(
|
||||||
@@ -937,10 +624,10 @@ export class FiuComponent implements OnInit {
|
|||||||
a.download = `STR-${this.bank.reportingMonth.replace(/\s+/g, '-')}.xml`;
|
a.download = `STR-${this.bank.reportingMonth.replace(/\s+/g, '-')}.xml`;
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
this.toast(`STR XML exported (${suspects.length} suspects)`);
|
this.toast(`STR XML exported (${this.filteredStr.length} suspects)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Bundle: the 6 CSVs plus the STR XML — emitted as separate downloads sequentially */
|
/** Bundle: all 6 CSVs zipped — emitted as separate downloads sequentially */
|
||||||
exportAll(): void {
|
exportAll(): void {
|
||||||
setTimeout(() => this.exportTc1(), 0);
|
setTimeout(() => this.exportTc1(), 0);
|
||||||
setTimeout(() => this.exportTc2(), 200);
|
setTimeout(() => this.exportTc2(), 200);
|
||||||
@@ -948,7 +635,6 @@ export class FiuComponent implements OnInit {
|
|||||||
setTimeout(() => this.exportKc2(), 600);
|
setTimeout(() => this.exportKc2(), 600);
|
||||||
setTimeout(() => this.exportAccountDetail(), 800);
|
setTimeout(() => this.exportAccountDetail(), 800);
|
||||||
setTimeout(() => this.exportAccountPersonRelation(), 1000);
|
setTimeout(() => this.exportAccountPersonRelation(), 1000);
|
||||||
setTimeout(() => this.exportStrXml(), 1200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private escapeXml(s: string): string {
|
private escapeXml(s: string): string {
|
||||||
|
|||||||
@@ -1,496 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data layer for the General Ledger report suite.
|
|
||||||
*
|
|
||||||
* Rather than hard-coding seven finished reports, this file holds a chart of
|
|
||||||
* accounts plus a generated ledger of double-entry postings. Every report is
|
|
||||||
* then computed from those postings, which is what makes the day / month /
|
|
||||||
* branch filters genuinely recompute instead of just re-labelling a static table.
|
|
||||||
*
|
|
||||||
* The generator is deterministic — a fixed-seed LCG, never Math.random — so the
|
|
||||||
* figures are identical on every reload and across users. Amounts are in rupees.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type GlAccountType = 'ASSET' | 'LIABILITY' | 'INCOME' | 'EXPENSE' | 'CONTINGENT';
|
|
||||||
|
|
||||||
export interface GlAccount {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
type: GlAccountType;
|
|
||||||
/** Balance-sheet / P&L grouping used as a sub-heading in the reports. */
|
|
||||||
group: string;
|
|
||||||
/** Included in the Misc Expense Ledger report. */
|
|
||||||
miscExpense?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GlBranch {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** One side of a double-entry voucher. */
|
|
||||||
export interface GlPosting {
|
|
||||||
/** ISO yyyy-MM-dd — the posting (value) date. */
|
|
||||||
date: string;
|
|
||||||
branchCode: string;
|
|
||||||
accountCode: string;
|
|
||||||
debit: number;
|
|
||||||
credit: number;
|
|
||||||
voucherNo: string;
|
|
||||||
narration: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GL_BRANCHES: GlBranch[] = [
|
|
||||||
{ code: 'HO', name: 'Head Office' },
|
|
||||||
{ code: '0002', name: 'Andheri (E)' },
|
|
||||||
{ code: '0003', name: 'Bandra (W)' },
|
|
||||||
{ code: '0004', name: 'Borivali' },
|
|
||||||
{ code: '0005', name: 'Dadar' },
|
|
||||||
{ code: '0006', name: 'Thane' }
|
|
||||||
];
|
|
||||||
|
|
||||||
export const GL_ACCOUNTS: GlAccount[] = [
|
|
||||||
// ── Assets ────────────────────────────────────────────────────────────────
|
|
||||||
{ code: '110101', name: 'Cash in Hand', type: 'ASSET', group: 'Cash & Bank Balances' },
|
|
||||||
{ code: '110102', name: 'Balance with RBI', type: 'ASSET', group: 'Cash & Bank Balances' },
|
|
||||||
{ code: '110103', name: 'Balance with Other Banks — Current', type: 'ASSET', group: 'Cash & Bank Balances' },
|
|
||||||
{ code: '110104', name: 'Balance with Other Banks — Fixed', type: 'ASSET', group: 'Cash & Bank Balances' },
|
|
||||||
{ code: '120101', name: 'SLR Investments — Govt Securities', type: 'ASSET', group: 'Investments' },
|
|
||||||
{ code: '120102', name: 'SLR Investments — State Govt Securities', type: 'ASSET', group: 'Investments' },
|
|
||||||
{ code: '120201', name: 'Non-SLR Investments — Bonds', type: 'ASSET', group: 'Investments' },
|
|
||||||
{ code: '120202', name: 'Non-SLR Investments — Mutual Funds', type: 'ASSET', group: 'Investments' },
|
|
||||||
{ code: '130101', name: 'Cash Credit & Overdrafts', type: 'ASSET', group: 'Loans & Advances' },
|
|
||||||
{ code: '130102', name: 'Term Loans — Short Term', type: 'ASSET', group: 'Loans & Advances' },
|
|
||||||
{ code: '130103', name: 'Term Loans — Medium Term', type: 'ASSET', group: 'Loans & Advances' },
|
|
||||||
{ code: '130104', name: 'Term Loans — Long Term', type: 'ASSET', group: 'Loans & Advances' },
|
|
||||||
{ code: '130105', name: 'Housing Loans', type: 'ASSET', group: 'Loans & Advances' },
|
|
||||||
{ code: '130106', name: 'Gold Loans', type: 'ASSET', group: 'Loans & Advances' },
|
|
||||||
{ code: '140101', name: 'Interest Receivable', type: 'ASSET', group: 'Other Assets' },
|
|
||||||
{ code: '140102', name: 'Premises (net of depreciation)', type: 'ASSET', group: 'Fixed Assets' },
|
|
||||||
{ code: '140103', name: 'Furniture & Fixtures (net)', type: 'ASSET', group: 'Fixed Assets' },
|
|
||||||
{ code: '140104', name: 'Sundry Debtors', type: 'ASSET', group: 'Other Assets' },
|
|
||||||
{ code: '140105', name: 'Advance Tax & TDS', type: 'ASSET', group: 'Other Assets' },
|
|
||||||
{ code: '140199', name: 'Opening Balance Suspense (unreconciled)', type: 'ASSET', group: 'Other Assets' },
|
|
||||||
|
|
||||||
// ── Liabilities & Capital ─────────────────────────────────────────────────
|
|
||||||
{ code: '210101', name: 'Share Capital — Individuals', type: 'LIABILITY', group: 'Capital & Reserves' },
|
|
||||||
{ code: '210201', name: 'Statutory Reserve Fund', type: 'LIABILITY', group: 'Capital & Reserves' },
|
|
||||||
{ code: '210202', name: 'Building Fund', type: 'LIABILITY', group: 'Capital & Reserves' },
|
|
||||||
{ code: '210203', name: 'Investment Depreciation Reserve', type: 'LIABILITY', group: 'Capital & Reserves' },
|
|
||||||
{ code: '210204', name: 'BDDR / Special BDDR', type: 'LIABILITY', group: 'Capital & Reserves' },
|
|
||||||
{ code: '220101', name: 'Savings Bank Deposits', type: 'LIABILITY', group: 'Deposits' },
|
|
||||||
{ code: '220102', name: 'Current Deposits', type: 'LIABILITY', group: 'Deposits' },
|
|
||||||
{ code: '220103', name: 'Term Deposits', type: 'LIABILITY', group: 'Deposits' },
|
|
||||||
{ code: '220104', name: 'Recurring Deposits', type: 'LIABILITY', group: 'Deposits' },
|
|
||||||
{ code: '230101', name: 'Interest Payable on Deposits', type: 'LIABILITY', group: 'Other Liabilities' },
|
|
||||||
{ code: '230102', name: 'Bills Payable', type: 'LIABILITY', group: 'Other Liabilities' },
|
|
||||||
{ code: '230103', name: 'Sundry Creditors', type: 'LIABILITY', group: 'Other Liabilities' },
|
|
||||||
{ code: '230104', name: 'TDS / GST Payable', type: 'LIABILITY', group: 'Other Liabilities' },
|
|
||||||
{ code: '230105', name: 'Overdue Interest Reserve', type: 'LIABILITY', group: 'Other Liabilities' },
|
|
||||||
|
|
||||||
// ── Income ────────────────────────────────────────────────────────────────
|
|
||||||
{ code: '310101', name: 'Interest on Loans & Advances', type: 'INCOME', group: 'Interest Income' },
|
|
||||||
{ code: '310102', name: 'Interest on Investments', type: 'INCOME', group: 'Interest Income' },
|
|
||||||
{ code: '310103', name: 'Interest on Inter-bank Deposits', type: 'INCOME', group: 'Interest Income' },
|
|
||||||
{ code: '320101', name: 'Commission & Exchange', type: 'INCOME', group: 'Other Income' },
|
|
||||||
{ code: '320102', name: 'Locker Rent', type: 'INCOME', group: 'Other Income' },
|
|
||||||
{ code: '320103', name: 'Processing Fees', type: 'INCOME', group: 'Other Income' },
|
|
||||||
{ code: '320104', name: 'Profit on Sale of Securities', type: 'INCOME', group: 'Other Income' },
|
|
||||||
{ code: '320105', name: 'Miscellaneous Income', type: 'INCOME', group: 'Other Income' },
|
|
||||||
|
|
||||||
// ── Expenditure ───────────────────────────────────────────────────────────
|
|
||||||
{ code: '410101', name: 'Interest on Savings Deposits', type: 'EXPENSE', group: 'Interest Expended' },
|
|
||||||
{ code: '410102', name: 'Interest on Term Deposits', type: 'EXPENSE', group: 'Interest Expended' },
|
|
||||||
{ code: '410103', name: 'Interest on Borrowings', type: 'EXPENSE', group: 'Interest Expended' },
|
|
||||||
{ code: '420101', name: 'Salaries & Allowances', type: 'EXPENSE', group: 'Operating Expenses' },
|
|
||||||
{ code: '420102', name: "Directors' Fees", type: 'EXPENSE', group: 'Operating Expenses' },
|
|
||||||
{ code: '420103', name: "Auditors' Fees", type: 'EXPENSE', group: 'Operating Expenses' },
|
|
||||||
{ code: '420104', name: 'Rent, Taxes & Lighting', type: 'EXPENSE', group: 'Operating Expenses' },
|
|
||||||
{ code: '420105', name: 'Depreciation on Bank Property', type: 'EXPENSE', group: 'Operating Expenses' },
|
|
||||||
{ code: '430101', name: 'Printing & Stationery', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
{ code: '430102', name: 'Postage, Telegram & Telephone', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
{ code: '430103', name: 'Advertisement & Publicity', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
{ code: '430104', name: 'Repairs & Maintenance', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
{ code: '430105', name: 'Travelling & Conveyance', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
{ code: '430106', name: 'Computer & Software Expenses', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
{ code: '430107', name: 'Legal & Professional Charges', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
{ code: '430108', name: 'Insurance (DICGC & Other)', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
{ code: '430109', name: 'Bank Charges & Commission Paid', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
{ code: '430110', name: 'AGM & Meeting Expenses', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
{ code: '430111', name: 'Electricity Charges', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
{ code: '430112', name: 'Other Miscellaneous Expenses', type: 'EXPENSE', group: 'Miscellaneous Expenses', miscExpense: true },
|
|
||||||
|
|
||||||
// ── Contingent (memoranda, not part of the balance sheet totals) ──────────
|
|
||||||
{ code: '910101', name: 'Guarantees Issued — Financial', type: 'CONTINGENT', group: 'Guarantees' },
|
|
||||||
{ code: '910102', name: 'Guarantees Issued — Performance', type: 'CONTINGENT', group: 'Guarantees' },
|
|
||||||
{ code: '910103', name: 'Letters of Credit Outstanding', type: 'CONTINGENT', group: 'Letters of Credit' },
|
|
||||||
{ code: '910104', name: 'Bills for Collection', type: 'CONTINGENT', group: 'Bills & Claims' },
|
|
||||||
{ code: '910105', name: 'Claims Against Bank Not Acknowledged as Debt', type: 'CONTINGENT', group: 'Bills & Claims' },
|
|
||||||
{ code: '910106', name: 'Forward Exchange Contracts', type: 'CONTINGENT', group: 'Other Commitments' },
|
|
||||||
{ code: '910107', name: 'Capital Commitments Not Provided For', type: 'CONTINGENT', group: 'Other Commitments' }
|
|
||||||
];
|
|
||||||
|
|
||||||
/** Financial year covered by the generated ledger. */
|
|
||||||
export const GL_PERIOD = {
|
|
||||||
/** FY 2026-27 opens here. */
|
|
||||||
fyStart: '2026-04-01',
|
|
||||||
/** Postings run to the day before the app's "today" (01 Aug 2026). */
|
|
||||||
fyEnd: '2026-07-31',
|
|
||||||
label: 'FY 2026-27'
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opening balances as at 01-Apr-2026, keyed by account code. Positive means the
|
|
||||||
* account opens on its natural side (debit for assets/expenses, credit for the
|
|
||||||
* rest). Head Office carries the investments and reserves; branches carry the
|
|
||||||
* customer-facing book.
|
|
||||||
*/
|
|
||||||
const OPENING_BALANCES: Record<string, number> = {
|
|
||||||
'110101': 9_996_000,
|
|
||||||
'110102': 12_400_000,
|
|
||||||
'110103': 13_667_000,
|
|
||||||
// Surplus deposits parked with correspondent banks. Sized so the opening
|
|
||||||
// balance sheet tallies exactly — see the assertion in glPostings().
|
|
||||||
'110104': 54_206_000,
|
|
||||||
'120101': 63_839_000,
|
|
||||||
'120102': 147_895_000,
|
|
||||||
'120201': 22_500_000,
|
|
||||||
'120202': 24_234_000,
|
|
||||||
'130101': 166_614_000,
|
|
||||||
'130102': 125_082_000,
|
|
||||||
'130103': 70_236_000,
|
|
||||||
'130104': 29_356_000,
|
|
||||||
'130105': 10_241_000,
|
|
||||||
'130106': 8_450_000,
|
|
||||||
'140101': 33_541_000,
|
|
||||||
'140102': 3_185_000,
|
|
||||||
'140103': 1_763_000,
|
|
||||||
'140104': 1_240_000,
|
|
||||||
'140105': 657_000,
|
|
||||||
'210101': 27_278_000,
|
|
||||||
'210201': 31_149_000,
|
|
||||||
'210202': 13_642_000,
|
|
||||||
'210203': 3_750_000,
|
|
||||||
'210204': 24_341_000,
|
|
||||||
'220101': 208_116_000,
|
|
||||||
'220102': 19_592_000,
|
|
||||||
'220103': 406_601_000,
|
|
||||||
'220104': 24_880_000,
|
|
||||||
'230101': 731_000,
|
|
||||||
'230102': 291_000,
|
|
||||||
'230103': 5_515_000,
|
|
||||||
'230104': 1_120_000,
|
|
||||||
'230105': 32_096_000,
|
|
||||||
'910101': 1_450_000,
|
|
||||||
'910102': 980_000,
|
|
||||||
'910103': 620_000,
|
|
||||||
'910104': 1_180_000,
|
|
||||||
'910105': 2_638_000,
|
|
||||||
'910106': 0,
|
|
||||||
'910107': 340_000
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Deterministic linear congruential generator — same figures on every load. */
|
|
||||||
function lcg(seed: number): () => number {
|
|
||||||
let s = seed >>> 0;
|
|
||||||
return () => {
|
|
||||||
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
||||||
return s / 4294967296;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function isoDate(d: Date): string {
|
|
||||||
const y = d.getUTCFullYear();
|
|
||||||
const m = `${d.getUTCMonth() + 1}`.padStart(2, '0');
|
|
||||||
const day = `${d.getUTCDate()}`.padStart(2, '0');
|
|
||||||
return `${y}-${m}-${day}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Every posting date in the ledger period, Sundays excluded (bank holiday). */
|
|
||||||
export function ledgerDates(): string[] {
|
|
||||||
const out: string[] = [];
|
|
||||||
const cur = new Date(`${GL_PERIOD.fyStart}T00:00:00Z`);
|
|
||||||
const end = new Date(`${GL_PERIOD.fyEnd}T00:00:00Z`);
|
|
||||||
while (cur <= end) {
|
|
||||||
if (cur.getUTCDay() !== 0) {
|
|
||||||
out.push(isoDate(cur));
|
|
||||||
}
|
|
||||||
cur.setUTCDate(cur.getUTCDate() + 1);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Templates for the vouchers the generator posts each day. */
|
|
||||||
const VOUCHER_TEMPLATES: {
|
|
||||||
debit: string;
|
|
||||||
credit: string;
|
|
||||||
narration: string;
|
|
||||||
/** Rough rupee scale of the voucher. */
|
|
||||||
scale: number;
|
|
||||||
/** Chance the voucher is raised on any given branch-day. */
|
|
||||||
frequency: number;
|
|
||||||
}[] = [
|
|
||||||
{ debit: '110101', credit: '220101', narration: 'Cash deposit — savings accounts', scale: 900_000, frequency: 1 },
|
|
||||||
{ debit: '220101', credit: '110101', narration: 'Cash withdrawal — savings accounts', scale: 780_000, frequency: 1 },
|
|
||||||
{ debit: '110101', credit: '220103', narration: 'Term deposit received', scale: 1_400_000, frequency: 0.75 },
|
|
||||||
{ debit: '220103', credit: '110101', narration: 'Term deposit matured and paid', scale: 1_150_000, frequency: 0.6 },
|
|
||||||
{ debit: '110101', credit: '220102', narration: 'Current account deposit', scale: 620_000, frequency: 0.8 },
|
|
||||||
{ debit: '220102', credit: '110101', narration: 'Current account withdrawal', scale: 560_000, frequency: 0.8 },
|
|
||||||
{ debit: '110101', credit: '220104', narration: 'Recurring deposit instalment', scale: 180_000, frequency: 0.7 },
|
|
||||||
{ debit: '130101', credit: '110101', narration: 'Cash credit disbursed', scale: 1_250_000, frequency: 0.55 },
|
|
||||||
{ debit: '110101', credit: '130101', narration: 'Cash credit repayment', scale: 1_100_000, frequency: 0.6 },
|
|
||||||
{ debit: '130102', credit: '110101', narration: 'Short term loan disbursed', scale: 850_000, frequency: 0.4 },
|
|
||||||
{ debit: '110101', credit: '130102', narration: 'Short term loan repayment', scale: 760_000, frequency: 0.5 },
|
|
||||||
{ debit: '130103', credit: '110101', narration: 'Medium term loan disbursed', scale: 640_000, frequency: 0.3 },
|
|
||||||
{ debit: '110101', credit: '130104', narration: 'Long term loan repayment', scale: 410_000, frequency: 0.35 },
|
|
||||||
{ debit: '130105', credit: '110101', narration: 'Housing loan disbursed', scale: 520_000, frequency: 0.2 },
|
|
||||||
{ debit: '130106', credit: '110101', narration: 'Gold loan disbursed', scale: 260_000, frequency: 0.4 },
|
|
||||||
// Interest on advances is the dominant earning line — sized so the bank runs a
|
|
||||||
// ~13% net margin rather than the loss the first-cut figures produced.
|
|
||||||
{ debit: '110101', credit: '310101', narration: 'Interest recovered on advances', scale: 430_000, frequency: 1 },
|
|
||||||
{ debit: '140101', credit: '310102', narration: 'Interest accrued on investments', scale: 148_000, frequency: 0.5 },
|
|
||||||
{ debit: '110103', credit: '310103', narration: 'Interest on inter-bank deposits', scale: 42_000, frequency: 0.25 },
|
|
||||||
{ debit: '110101', credit: '320101', narration: 'Commission and exchange collected', scale: 26_000, frequency: 0.9 },
|
|
||||||
{ debit: '110101', credit: '320102', narration: 'Locker rent collected', scale: 9_000, frequency: 0.3 },
|
|
||||||
{ debit: '110101', credit: '320103', narration: 'Loan processing fees', scale: 18_000, frequency: 0.5 },
|
|
||||||
{ debit: '110101', credit: '320105', narration: 'Miscellaneous income', scale: 7_500, frequency: 0.4 },
|
|
||||||
{ debit: '410101', credit: '230101', narration: 'Interest provided on savings deposits', scale: 128_000, frequency: 0.8 },
|
|
||||||
{ debit: '410102', credit: '230101', narration: 'Interest provided on term deposits', scale: 265_000, frequency: 0.9 },
|
|
||||||
{ debit: '420101', credit: '110101', narration: 'Salaries and allowances paid', scale: 210_000, frequency: 0.35 },
|
|
||||||
{ debit: '420104', credit: '110101', narration: 'Rent, taxes and lighting', scale: 46_000, frequency: 0.3 },
|
|
||||||
{ debit: '430101', credit: '110101', narration: 'Printing and stationery purchased', scale: 9_400, frequency: 0.3 },
|
|
||||||
{ debit: '430102', credit: '110101', narration: 'Postage and telephone charges', scale: 6_200, frequency: 0.35 },
|
|
||||||
{ debit: '430103', credit: '110101', narration: 'Advertisement and publicity', scale: 12_500, frequency: 0.15 },
|
|
||||||
{ debit: '430104', credit: '110101', narration: 'Repairs and maintenance', scale: 15_800, frequency: 0.25 },
|
|
||||||
{ debit: '430105', credit: '110101', narration: 'Travelling and conveyance', scale: 7_100, frequency: 0.3 },
|
|
||||||
{ debit: '430106', credit: '110101', narration: 'Computer and software expenses', scale: 22_400, frequency: 0.2 },
|
|
||||||
{ debit: '430107', credit: '110101', narration: 'Legal and professional charges', scale: 11_300, frequency: 0.15 },
|
|
||||||
{ debit: '430109', credit: '110101', narration: 'Bank charges and commission paid', scale: 4_800, frequency: 0.3 },
|
|
||||||
{ debit: '430111', credit: '110101', narration: 'Electricity charges', scale: 13_600, frequency: 0.25 },
|
|
||||||
{ debit: '430112', credit: '110101', narration: 'Other miscellaneous expenses', scale: 8_900, frequency: 0.3 },
|
|
||||||
{ debit: '110102', credit: '110101', narration: 'Cash remitted to RBI currency chest', scale: 1_800_000, frequency: 0.2 },
|
|
||||||
{ debit: '110101', credit: '110103', narration: 'Funds drawn from correspondent bank', scale: 1_400_000, frequency: 0.2 }
|
|
||||||
];
|
|
||||||
|
|
||||||
/** Head-Office-only vouchers — investments, reserves and centralised charges. */
|
|
||||||
const HO_TEMPLATES: typeof VOUCHER_TEMPLATES = [
|
|
||||||
{ debit: '120101', credit: '110102', narration: 'Purchase of Govt securities', scale: 2_400_000, frequency: 0.12 },
|
|
||||||
{ debit: '120102', credit: '110102', narration: 'Purchase of State Govt securities', scale: 1_900_000, frequency: 0.12 },
|
|
||||||
{ debit: '110102', credit: '320104', narration: 'Profit on sale of securities', scale: 78_000, frequency: 0.1 },
|
|
||||||
{ debit: '420102', credit: '110101', narration: "Directors' sitting fees", scale: 32_000, frequency: 0.08 },
|
|
||||||
{ debit: '420103', credit: '110101', narration: "Statutory auditors' fees", scale: 45_000, frequency: 0.06 },
|
|
||||||
{ debit: '420105', credit: '140103', narration: 'Depreciation charged on assets', scale: 64_000, frequency: 0.12 },
|
|
||||||
{ debit: '430108', credit: '110101', narration: 'DICGC premium and other insurance', scale: 38_000, frequency: 0.1 },
|
|
||||||
{ debit: '430110', credit: '110101', narration: 'AGM and meeting expenses', scale: 26_000, frequency: 0.06 },
|
|
||||||
{ debit: '410103', credit: '230101', narration: 'Interest on borrowings', scale: 54_000, frequency: 0.1 },
|
|
||||||
{ debit: '230104', credit: '110102', narration: 'TDS and GST remitted', scale: 145_000, frequency: 0.15 }
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contingent-account movements are memoranda, each posted single-sided.
|
|
||||||
* Guarantees, LCs and collection bills are raised at the counter, so these run
|
|
||||||
* at every branch — not just Head Office.
|
|
||||||
*/
|
|
||||||
const CONTINGENT_TEMPLATES: { account: string; narration: string; scale: number; frequency: number }[] = [
|
|
||||||
{ account: '910101', narration: 'Financial guarantee issued', scale: 320_000, frequency: 0.1 },
|
|
||||||
{ account: '910102', narration: 'Performance guarantee issued', scale: 240_000, frequency: 0.08 },
|
|
||||||
{ account: '910103', narration: 'Letter of credit opened', scale: 180_000, frequency: 0.07 },
|
|
||||||
{ account: '910104', narration: 'Bills lodged for collection', scale: 95_000, frequency: 0.12 },
|
|
||||||
{ account: '910105', narration: 'Claim lodged against the bank', scale: 140_000, frequency: 0.03 },
|
|
||||||
{ account: '910107', narration: 'Capital commitment contracted', scale: 85_000, frequency: 0.04 }
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How the opening contingent book is spread across the network. Head Office
|
|
||||||
* carries the capital commitments and large claims; the branches carry the
|
|
||||||
* counter-issued guarantees. Weights are applied per account and the rounding
|
|
||||||
* remainder is absorbed by Head Office so each account's total stays exact.
|
|
||||||
*/
|
|
||||||
const CONTINGENT_BRANCH_WEIGHTS: Record<string, number> = {
|
|
||||||
HO: 0.2,
|
|
||||||
'0002': 0.18,
|
|
||||||
'0003': 0.16,
|
|
||||||
'0004': 0.16,
|
|
||||||
'0005': 0.15,
|
|
||||||
'0006': 0.15
|
|
||||||
};
|
|
||||||
|
|
||||||
let cachedPostings: GlPosting[] | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The full generated ledger. Built once and memoised — every report reads from
|
|
||||||
* this array, so the reports can never disagree with one another.
|
|
||||||
*/
|
|
||||||
export function glPostings(): GlPosting[] {
|
|
||||||
if (cachedPostings) {
|
|
||||||
return cachedPostings;
|
|
||||||
}
|
|
||||||
const rand = lcg(20260401);
|
|
||||||
const out: GlPosting[] = [];
|
|
||||||
const dates = ledgerDates();
|
|
||||||
let voucherSeq = 1;
|
|
||||||
|
|
||||||
// Opening entries, posted at Head Office on day one so the books start in balance.
|
|
||||||
const openingVoucher = `OB/${GL_PERIOD.label.replace(/\s/g, '')}`;
|
|
||||||
let openingDebit = 0;
|
|
||||||
let openingCredit = 0;
|
|
||||||
|
|
||||||
for (const [code, amount] of Object.entries(OPENING_BALANCES)) {
|
|
||||||
if (!amount) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const account = GL_ACCOUNTS.find((a) => a.code === code);
|
|
||||||
if (!account) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const debitSide = account.type === 'ASSET' || account.type === 'EXPENSE' || account.type === 'CONTINGENT';
|
|
||||||
|
|
||||||
// Contingent accounts are single-sided memoranda held at the issuing branch,
|
|
||||||
// so their opening book is spread across the network rather than sat at HO.
|
|
||||||
if (account.type === 'CONTINGENT') {
|
|
||||||
let allocated = 0;
|
|
||||||
for (const branch of GL_BRANCHES) {
|
|
||||||
if (branch.code === 'HO') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const share = Math.round((amount * (CONTINGENT_BRANCH_WEIGHTS[branch.code] ?? 0)) / 100) * 100;
|
|
||||||
if (!share) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
allocated += share;
|
|
||||||
out.push({
|
|
||||||
date: GL_PERIOD.fyStart,
|
|
||||||
branchCode: branch.code,
|
|
||||||
accountCode: code,
|
|
||||||
debit: share,
|
|
||||||
credit: 0,
|
|
||||||
voucherNo: openingVoucher,
|
|
||||||
narration: 'Opening balance brought forward'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Head Office takes its own share plus whatever rounding left behind.
|
|
||||||
out.push({
|
|
||||||
date: GL_PERIOD.fyStart,
|
|
||||||
branchCode: 'HO',
|
|
||||||
accountCode: code,
|
|
||||||
debit: amount - allocated,
|
|
||||||
credit: 0,
|
|
||||||
voucherNo: openingVoucher,
|
|
||||||
narration: 'Opening balance brought forward'
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.push({
|
|
||||||
date: GL_PERIOD.fyStart,
|
|
||||||
branchCode: 'HO',
|
|
||||||
accountCode: code,
|
|
||||||
debit: debitSide ? amount : 0,
|
|
||||||
credit: debitSide ? 0 : amount,
|
|
||||||
voucherNo: openingVoucher,
|
|
||||||
narration: 'Opening balance brought forward'
|
|
||||||
});
|
|
||||||
if (debitSide) {
|
|
||||||
openingDebit += amount;
|
|
||||||
} else {
|
|
||||||
openingCredit += amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If OPENING_BALANCES is ever edited so the two sides diverge, park the residual
|
|
||||||
// in a visibly-named suspense account rather than letting the trial balance drift.
|
|
||||||
const openingResidual = openingCredit - openingDebit;
|
|
||||||
if (openingResidual !== 0) {
|
|
||||||
out.push({
|
|
||||||
date: GL_PERIOD.fyStart,
|
|
||||||
branchCode: 'HO',
|
|
||||||
accountCode: '140199',
|
|
||||||
debit: openingResidual > 0 ? openingResidual : 0,
|
|
||||||
credit: openingResidual < 0 ? -openingResidual : 0,
|
|
||||||
voucherNo: openingVoucher,
|
|
||||||
narration: 'Unreconciled opening balance difference'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const round = (n: number): number => Math.round(n / 100) * 100;
|
|
||||||
|
|
||||||
for (const date of dates) {
|
|
||||||
for (const branch of GL_BRANCHES) {
|
|
||||||
// Head Office posts fewer counter transactions than the branches.
|
|
||||||
const weight = branch.code === 'HO' ? 0.45 : 1;
|
|
||||||
const templates = branch.code === 'HO' ? [...VOUCHER_TEMPLATES, ...HO_TEMPLATES] : VOUCHER_TEMPLATES;
|
|
||||||
|
|
||||||
for (const tpl of templates) {
|
|
||||||
if (rand() > tpl.frequency * weight) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 0.55x .. 1.45x of the template scale keeps daily figures varied but plausible.
|
|
||||||
const amount = round(tpl.scale * (0.55 + rand() * 0.9));
|
|
||||||
if (amount <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const voucherNo = `${branch.code}/${date.slice(5, 7)}/${`${voucherSeq++}`.padStart(5, '0')}`;
|
|
||||||
out.push({
|
|
||||||
date,
|
|
||||||
branchCode: branch.code,
|
|
||||||
accountCode: tpl.debit,
|
|
||||||
debit: amount,
|
|
||||||
credit: 0,
|
|
||||||
voucherNo,
|
|
||||||
narration: tpl.narration
|
|
||||||
});
|
|
||||||
out.push({
|
|
||||||
date,
|
|
||||||
branchCode: branch.code,
|
|
||||||
accountCode: tpl.credit,
|
|
||||||
debit: 0,
|
|
||||||
credit: amount,
|
|
||||||
voucherNo,
|
|
||||||
narration: tpl.narration
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contingent memoranda are raised at every branch, weighted by its size.
|
|
||||||
const contingentWeight = CONTINGENT_BRANCH_WEIGHTS[branch.code] ?? 0.15;
|
|
||||||
for (const tpl of CONTINGENT_TEMPLATES) {
|
|
||||||
// Claims against the bank and capital commitments are Head Office matters.
|
|
||||||
const headOfficeOnly = tpl.account === '910105' || tpl.account === '910107';
|
|
||||||
if (headOfficeOnly && branch.code !== 'HO') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const frequency = headOfficeOnly ? tpl.frequency : tpl.frequency * (contingentWeight / 0.2);
|
|
||||||
if (rand() > frequency) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const amount = round(tpl.scale * (0.55 + rand() * 0.9));
|
|
||||||
out.push({
|
|
||||||
date,
|
|
||||||
branchCode: branch.code,
|
|
||||||
accountCode: tpl.account,
|
|
||||||
debit: amount,
|
|
||||||
credit: 0,
|
|
||||||
voucherNo: `CNT/${branch.code}/${date.slice(5, 7)}/${`${voucherSeq++}`.padStart(5, '0')}`,
|
|
||||||
narration: tpl.narration
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedPostings = out;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function glAccount(code: string): GlAccount | undefined {
|
|
||||||
return GL_ACCOUNTS.find((a) => a.code === code);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function glBranchName(code: string): string {
|
|
||||||
return GL_BRANCHES.find((b) => b.code === code)?.name ?? code;
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright since 2025 Mifos Initiative
|
|
||||||
*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
|
||||||
|
|
||||||
import { Route } from '../core/route/route.service';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
Route.withShell([
|
|
||||||
{
|
|
||||||
path: 'gl/reports',
|
|
||||||
// Lazy — the generated ledger and ExcelJS should not weigh down the eager bundle.
|
|
||||||
loadComponent: () => import('./gl-reports.component').then((m) => m.GlReportsComponent),
|
|
||||||
data: { title: 'General Ledger Reports', breadcrumb: 'GL Reports' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'gl/reports/:report',
|
|
||||||
loadComponent: () => import('./gl-reports.component').then((m) => m.GlReportsComponent),
|
|
||||||
data: { title: 'General Ledger Reports', breadcrumb: 'GL Reports' }
|
|
||||||
}
|
|
||||||
])
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [RouterModule.forChild(routes)],
|
|
||||||
exports: [RouterModule]
|
|
||||||
})
|
|
||||||
export class GlReportsRoutingModule {}
|
|
||||||
@@ -1,523 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="glr-root">
|
|
||||||
<!-- HEADER -->
|
|
||||||
<div class="glr-head">
|
|
||||||
<div class="glr-head-left">
|
|
||||||
<fa-icon icon="book-open" class="glr-head-ic"></fa-icon>
|
|
||||||
<div>
|
|
||||||
<h2 class="glr-head-t">General Ledger — {{ reportLabel }}</h2>
|
|
||||||
<div class="glr-head-s">
|
|
||||||
CBSNexora Bank Ltd · {{ branchLabel }} · Period <strong>{{ rangeLabel }}</strong> ·
|
|
||||||
{{ ledgerPeriod.label }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="glr-btn glr-btn-xls" [disabled]="generating" (click)="exportExcel()">
|
|
||||||
<fa-icon icon="file-csv"></fa-icon>
|
|
||||||
{{ generating ? 'Exporting…' : 'Export to Excel' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- FILTER BAR -->
|
|
||||||
<div class="glr-filters">
|
|
||||||
<div class="glr-filter">
|
|
||||||
<label>Period</label>
|
|
||||||
<div class="glr-seg">
|
|
||||||
<button [class.active]="periodMode === 'day'" (click)="periodMode = 'day'">Day-wise</button>
|
|
||||||
<button [class.active]="periodMode === 'month'" (click)="periodMode = 'month'">Month-wise</button>
|
|
||||||
<button [class.active]="periodMode === 'range'" (click)="periodMode = 'range'">Date Range</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (periodMode === 'day') {
|
|
||||||
<div class="glr-filter">
|
|
||||||
<label>Date</label>
|
|
||||||
<input
|
|
||||||
class="glr-inp"
|
|
||||||
type="date"
|
|
||||||
[(ngModel)]="filterDay"
|
|
||||||
[min]="ledgerPeriod.fyStart"
|
|
||||||
[max]="ledgerPeriod.fyEnd"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (periodMode === 'month') {
|
|
||||||
<div class="glr-filter">
|
|
||||||
<label>Month</label>
|
|
||||||
<input class="glr-inp" type="month" [(ngModel)]="filterMonth" min="2026-04" max="2026-07" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (periodMode === 'range') {
|
|
||||||
<div class="glr-filter">
|
|
||||||
<label>From</label>
|
|
||||||
<input
|
|
||||||
class="glr-inp"
|
|
||||||
type="date"
|
|
||||||
[(ngModel)]="filterFrom"
|
|
||||||
[min]="ledgerPeriod.fyStart"
|
|
||||||
[max]="ledgerPeriod.fyEnd"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="glr-filter">
|
|
||||||
<label>To</label>
|
|
||||||
<input
|
|
||||||
class="glr-inp"
|
|
||||||
type="date"
|
|
||||||
[(ngModel)]="filterTo"
|
|
||||||
[min]="ledgerPeriod.fyStart"
|
|
||||||
[max]="ledgerPeriod.fyEnd"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="glr-filter">
|
|
||||||
<label>Branch</label>
|
|
||||||
<select class="glr-inp" [(ngModel)]="branchCode">
|
|
||||||
<option value="">All Branches (Consolidated)</option>
|
|
||||||
@for (b of branches; track b.code) {
|
|
||||||
<option [value]="b.code">{{ b.code }} — {{ b.name }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="glr-btn" (click)="resetFilters()">Reset</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- REPORT SELECTOR -->
|
|
||||||
<div class="glr-tabs">
|
|
||||||
@for (r of reports; track r.code) {
|
|
||||||
<button class="glr-tab" [class.active]="activeReport === r.code" (click)="showReport(r.code)">
|
|
||||||
<fa-icon [icon]="r.icon"></fa-icon> {{ r.label }}
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ══════════════ TRIAL BALANCE ══════════════ -->
|
|
||||||
@if (activeReport === 'trial-balance') {
|
|
||||||
<div class="glr-section">
|
|
||||||
<div class="glr-balance" [class.glr-balance--bad]="!tbBalanced">
|
|
||||||
<fa-icon [icon]="tbBalanced ? 'check-circle' : 'exclamation-triangle'"></fa-icon>
|
|
||||||
<span>
|
|
||||||
Closing Dr <strong>₹{{ tbClosingDebit | number: '1.2-2' }}</strong> · Closing Cr
|
|
||||||
<strong>₹{{ tbClosingCredit | number: '1.2-2' }}</strong> —
|
|
||||||
{{ tbBalanced ? 'trial balance tallies' : 'OUT OF BALANCE — investigate before closing the period' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="glr-tbl-wrap">
|
|
||||||
<table class="glr-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>GL Code</th>
|
|
||||||
<th>Account</th>
|
|
||||||
<th>Group</th>
|
|
||||||
<th class="ta-r">Opening Dr</th>
|
|
||||||
<th class="ta-r">Opening Cr</th>
|
|
||||||
<th class="ta-r">Period Dr</th>
|
|
||||||
<th class="ta-r">Period Cr</th>
|
|
||||||
<th class="ta-r">Closing Dr</th>
|
|
||||||
<th class="ta-r">Closing Cr</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of data.trialBalance; track r.code) {
|
|
||||||
<tr>
|
|
||||||
<td class="mono">{{ r.code }}</td>
|
|
||||||
<td>{{ r.name }}</td>
|
|
||||||
<td class="muted">{{ r.group }}</td>
|
|
||||||
<td class="ta-r mono">{{ r.openingDebit ? (r.openingDebit | number: '1.2-2') : '' }}</td>
|
|
||||||
<td class="ta-r mono">{{ r.openingCredit ? (r.openingCredit | number: '1.2-2') : '' }}</td>
|
|
||||||
<td class="ta-r mono">{{ r.periodDebit ? (r.periodDebit | number: '1.2-2') : '' }}</td>
|
|
||||||
<td class="ta-r mono">{{ r.periodCredit ? (r.periodCredit | number: '1.2-2') : '' }}</td>
|
|
||||||
<td class="ta-r mono">{{ r.closingDebit ? (r.closingDebit | number: '1.2-2') : '' }}</td>
|
|
||||||
<td class="ta-r mono">{{ r.closingCredit ? (r.closingCredit | number: '1.2-2') : '' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="9" class="glr-empty">No ledger activity for this period and branch.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
<tr class="total-row">
|
|
||||||
<td colspan="3">TOTAL ({{ data.trialBalance.length }} accounts)</td>
|
|
||||||
<td class="ta-r mono">{{ tbOpeningDebit | number: '1.2-2' }}</td>
|
|
||||||
<td class="ta-r mono">{{ tbOpeningCredit | number: '1.2-2' }}</td>
|
|
||||||
<td class="ta-r mono">{{ tbPeriodDebit | number: '1.2-2' }}</td>
|
|
||||||
<td class="ta-r mono">{{ tbPeriodCredit | number: '1.2-2' }}</td>
|
|
||||||
<td class="ta-r mono">{{ tbClosingDebit | number: '1.2-2' }}</td>
|
|
||||||
<td class="ta-r mono">{{ tbClosingCredit | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ══════════════ PROFIT AND LOSS ══════════════ -->
|
|
||||||
@if (activeReport === 'profit-and-loss') {
|
|
||||||
<div class="glr-section">
|
|
||||||
<div class="glr-kpis">
|
|
||||||
<div class="glr-kpi glr-kpi--teal">
|
|
||||||
<div class="glr-kpi-n">₹{{ totalIncome | number: '1.2-2' }}</div>
|
|
||||||
<div class="glr-kpi-l">Total Income</div>
|
|
||||||
</div>
|
|
||||||
<div class="glr-kpi glr-kpi--amber">
|
|
||||||
<div class="glr-kpi-n">₹{{ totalExpense | number: '1.2-2' }}</div>
|
|
||||||
<div class="glr-kpi-l">Total Expenditure</div>
|
|
||||||
</div>
|
|
||||||
<div class="glr-kpi" [class.glr-kpi--green]="netProfit >= 0" [class.glr-kpi--red]="netProfit < 0">
|
|
||||||
<div class="glr-kpi-n">₹{{ netProfit | number: '1.2-2' }}</div>
|
|
||||||
<div class="glr-kpi-l">{{ netProfit >= 0 ? 'Net Profit' : 'Net Loss' }}</div>
|
|
||||||
<div class="glr-kpi-sub">{{ netProfitPct | number: '1.2-2' }}% of income</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="glr-two-col">
|
|
||||||
<div class="glr-tbl-wrap">
|
|
||||||
<div class="glr-tbl-h">Expenditure</div>
|
|
||||||
<table class="glr-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>GL Code</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="ta-r">Amount (₹)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of pnlExpense; track r.code) {
|
|
||||||
<tr>
|
|
||||||
<td class="mono">{{ r.code }}</td>
|
|
||||||
<td>{{ r.name }}<span class="muted"> · {{ r.group }}</span></td>
|
|
||||||
<td class="ta-r mono">{{ r.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="3" class="glr-empty">No expenditure posted in this period.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
<tr class="total-row">
|
|
||||||
<td colspan="2">Total Expenditure</td>
|
|
||||||
<td class="ta-r mono">{{ totalExpense | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
@if (netProfit >= 0) {
|
|
||||||
<tr class="total-row glr-profit-row">
|
|
||||||
<td colspan="2">Net Profit carried to Balance Sheet</td>
|
|
||||||
<td class="ta-r mono">{{ netProfit | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="glr-tbl-wrap">
|
|
||||||
<div class="glr-tbl-h">Income</div>
|
|
||||||
<table class="glr-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>GL Code</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="ta-r">Amount (₹)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of pnlIncome; track r.code) {
|
|
||||||
<tr>
|
|
||||||
<td class="mono">{{ r.code }}</td>
|
|
||||||
<td>{{ r.name }}<span class="muted"> · {{ r.group }}</span></td>
|
|
||||||
<td class="ta-r mono">{{ r.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="3" class="glr-empty">No income posted in this period.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
<tr class="total-row">
|
|
||||||
<td colspan="2">Total Income</td>
|
|
||||||
<td class="ta-r mono">{{ totalIncome | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
@if (netProfit < 0) {
|
|
||||||
<tr class="total-row glr-loss-row">
|
|
||||||
<td colspan="2">Net Loss carried to Balance Sheet</td>
|
|
||||||
<td class="ta-r mono">{{ -netProfit | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ══════════════ CONSOLIDATED BALANCE SHEET ══════════════ -->
|
|
||||||
@if (activeReport === 'balance-sheet') {
|
|
||||||
<div class="glr-section">
|
|
||||||
<div class="glr-balance" [class.glr-balance--bad]="!bsBalanced">
|
|
||||||
<fa-icon [icon]="bsBalanced ? 'check-circle' : 'exclamation-triangle'"></fa-icon>
|
|
||||||
<span>
|
|
||||||
Total Assets <strong>₹{{ bsTotalAssets | number: '1.2-2' }}</strong> · Total Liabilities
|
|
||||||
<strong>₹{{ bsTotalLiabilities | number: '1.2-2' }}</strong> —
|
|
||||||
{{ bsBalanced ? 'balance sheet tallies' : 'OUT OF BALANCE' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="glr-two-col">
|
|
||||||
<div class="glr-tbl-wrap">
|
|
||||||
<div class="glr-tbl-h">Capital & Liabilities — as at {{ range.to }}</div>
|
|
||||||
<table class="glr-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>GL Code</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="ta-r">Amount (₹)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of bsLiabilities; track r.code) {
|
|
||||||
<tr>
|
|
||||||
<td class="mono">{{ r.code }}</td>
|
|
||||||
<td>{{ r.name }}<span class="muted"> · {{ r.group }}</span></td>
|
|
||||||
<td class="ta-r mono">{{ r.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
<tr>
|
|
||||||
<td class="mono">—</td>
|
|
||||||
<td>Surplus for the period<span class="muted"> · Profit & Loss</span></td>
|
|
||||||
<td class="ta-r mono">{{ bsSurplus | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="total-row">
|
|
||||||
<td colspan="2">Total Capital & Liabilities</td>
|
|
||||||
<td class="ta-r mono">{{ bsTotalLiabilities | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="glr-tbl-wrap">
|
|
||||||
<div class="glr-tbl-h">Assets — as at {{ range.to }}</div>
|
|
||||||
<table class="glr-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>GL Code</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="ta-r">Amount (₹)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of bsAssets; track r.code) {
|
|
||||||
<tr>
|
|
||||||
<td class="mono">{{ r.code }}</td>
|
|
||||||
<td>{{ r.name }}<span class="muted"> · {{ r.group }}</span></td>
|
|
||||||
<td class="ta-r mono">{{ r.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
<tr class="total-row">
|
|
||||||
<td colspan="2">Total Assets</td>
|
|
||||||
<td class="ta-r mono">{{ bsTotalAssets | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ══════════════ COMPARATIVE BALANCE SHEET ══════════════ -->
|
|
||||||
@if (activeReport === 'comparative-balance-sheet') {
|
|
||||||
<div class="glr-section">
|
|
||||||
<p class="glr-note">
|
|
||||||
Compares the closing position as at <strong>{{ range.to }}</strong> against the position immediately before
|
|
||||||
this period began. Change the filter above to compare a different span.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
@for (block of [{ title: 'Capital & Liabilities', rows: compLiabilities }, { title: 'Assets', rows: compAssets }]; track block.title) {
|
|
||||||
<div class="glr-tbl-wrap">
|
|
||||||
<div class="glr-tbl-h">{{ block.title }}</div>
|
|
||||||
<table class="glr-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>GL Code</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="ta-r">Current (₹)</th>
|
|
||||||
<th class="ta-r">Previous (₹)</th>
|
|
||||||
<th class="ta-r">Variance (₹)</th>
|
|
||||||
<th class="ta-r">Variance %</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of block.rows; track r.code) {
|
|
||||||
<tr>
|
|
||||||
<td class="mono">{{ r.code }}</td>
|
|
||||||
<td>{{ r.name }}<span class="muted"> · {{ r.group }}</span></td>
|
|
||||||
<td class="ta-r mono">{{ r.current | number: '1.2-2' }}</td>
|
|
||||||
<td class="ta-r mono">{{ r.previous | number: '1.2-2' }}</td>
|
|
||||||
<td class="ta-r mono" [class.var-up]="r.variance > 0" [class.var-down]="r.variance < 0">
|
|
||||||
{{ r.variance | number: '1.2-2' }}
|
|
||||||
</td>
|
|
||||||
<td class="ta-r mono" [class.var-up]="r.variance > 0" [class.var-down]="r.variance < 0">
|
|
||||||
{{ r.variancePct === null ? '—' : (r.variancePct | number: '1.2-2') + '%' }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="glr-empty">Nothing to compare for this period and branch.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ══════════════ CONTINGENT LIABILITIES ══════════════ -->
|
|
||||||
@if (activeReport === 'contingent-liabilities') {
|
|
||||||
<div class="glr-section">
|
|
||||||
<p class="glr-note">
|
|
||||||
Off-balance-sheet commitments outstanding as at <strong>{{ range.to }}</strong>. These are memoranda accounts
|
|
||||||
and are deliberately excluded from the Trial Balance and Balance Sheet totals.
|
|
||||||
</p>
|
|
||||||
<div class="glr-tbl-wrap">
|
|
||||||
<table class="glr-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>GL Code</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th>Group</th>
|
|
||||||
<th class="ta-r">Amount (₹)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of data.contingent; track r.code) {
|
|
||||||
<tr>
|
|
||||||
<td class="mono">{{ r.code }}</td>
|
|
||||||
<td>{{ r.name }}</td>
|
|
||||||
<td class="muted">{{ r.group }}</td>
|
|
||||||
<td class="ta-r mono">{{ r.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" class="glr-empty">No contingent exposures outstanding for this selection.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
<tr class="total-row">
|
|
||||||
<td colspan="3">Total Contingent Liabilities</td>
|
|
||||||
<td class="ta-r mono">{{ contingentTotal | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ══════════════ GL WISE DR / CR ══════════════ -->
|
|
||||||
@if (activeReport === 'gl-wise-dr-cr') {
|
|
||||||
<div class="glr-section">
|
|
||||||
<p class="glr-note">
|
|
||||||
Debit and credit turnover posted between <strong>{{ rangeLabel }}</strong> for {{ branchLabel }}. Every
|
|
||||||
voucher is double-entry, so the debit and credit totals below must match.
|
|
||||||
</p>
|
|
||||||
<div class="glr-tbl-wrap">
|
|
||||||
<table class="glr-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>GL Code</th>
|
|
||||||
<th>Account</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Group</th>
|
|
||||||
<th class="ta-r">Debit (₹)</th>
|
|
||||||
<th class="ta-r">Credit (₹)</th>
|
|
||||||
<th class="ta-r">Net (Dr − Cr)</th>
|
|
||||||
<th class="ta-c">Vouchers</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of data.glWise; track r.code) {
|
|
||||||
<tr>
|
|
||||||
<td class="mono">{{ r.code }}</td>
|
|
||||||
<td>{{ r.name }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="type-pill" [class]="'type-' + r.type.toLowerCase()">{{ r.type }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="muted">{{ r.group }}</td>
|
|
||||||
<td class="ta-r mono">{{ r.debit ? (r.debit | number: '1.2-2') : '' }}</td>
|
|
||||||
<td class="ta-r mono">{{ r.credit ? (r.credit | number: '1.2-2') : '' }}</td>
|
|
||||||
<td class="ta-r mono" [class.var-up]="r.net > 0" [class.var-down]="r.net < 0">
|
|
||||||
{{ r.net | number: '1.2-2' }}
|
|
||||||
</td>
|
|
||||||
<td class="ta-c mono">{{ r.vouchers }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="8" class="glr-empty">No postings for this period and branch.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
<tr class="total-row">
|
|
||||||
<td colspan="4">TOTAL ({{ data.glWise.length }} accounts)</td>
|
|
||||||
<td class="ta-r mono">{{ glWiseDebit | number: '1.2-2' }}</td>
|
|
||||||
<td class="ta-r mono">{{ glWiseCredit | number: '1.2-2' }}</td>
|
|
||||||
<td class="ta-r mono">{{ glWiseDebit - glWiseCredit | number: '1.2-2' }}</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ══════════════ MISC EXPENSE LEDGER ══════════════ -->
|
|
||||||
@if (activeReport === 'misc-expense-ledger') {
|
|
||||||
<div class="glr-section">
|
|
||||||
<p class="glr-note">
|
|
||||||
Every miscellaneous-expense voucher posted between <strong>{{ rangeLabel }}</strong> for {{ branchLabel }}
|
|
||||||
— {{ miscRows.length | number }} entries.
|
|
||||||
</p>
|
|
||||||
<div class="glr-tbl-wrap glr-tbl-wrap--tall">
|
|
||||||
<table class="glr-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Voucher No</th>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th>GL Code</th>
|
|
||||||
<th>Account</th>
|
|
||||||
<th>Narration</th>
|
|
||||||
<th class="ta-r">Amount (₹)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of miscRows; track r.voucherNo + r.code) {
|
|
||||||
<tr>
|
|
||||||
<td class="mono">{{ r.date }}</td>
|
|
||||||
<td class="mono">{{ r.voucherNo }}</td>
|
|
||||||
<td>{{ branchName(r.branchCode) }}</td>
|
|
||||||
<td class="mono">{{ r.code }}</td>
|
|
||||||
<td>{{ r.name }}</td>
|
|
||||||
<td class="muted">{{ r.narration }}</td>
|
|
||||||
<td class="ta-r mono">{{ r.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="7" class="glr-empty">No miscellaneous expenses posted for this selection.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="total-row">
|
|
||||||
<td colspan="6">Total Miscellaneous Expenses</td>
|
|
||||||
<td class="ta-r mono">{{ miscTotal | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- TOAST -->
|
|
||||||
<div class="glr-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,506 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 {
|
|
||||||
--g-navy: #1a237e;
|
|
||||||
--g-blue: #1d4ed8;
|
|
||||||
--g-blue-l: #eff6ff;
|
|
||||||
--g-teal: #0e7e6b;
|
|
||||||
--g-teal-l: #e0f4f0;
|
|
||||||
--g-green: #15803d;
|
|
||||||
--g-green-l: #dcfce7;
|
|
||||||
--g-amber: #b45309;
|
|
||||||
--g-amber-l: #fef3c7;
|
|
||||||
--g-red: #c0392b;
|
|
||||||
--g-red-l: #fdecea;
|
|
||||||
--g-g50: #f8fafc;
|
|
||||||
--g-g100: #f1f5f9;
|
|
||||||
--g-g200: #e2e8f0;
|
|
||||||
--g-g300: #cbd5e1;
|
|
||||||
--g-g500: #64748b;
|
|
||||||
--g-g700: #334155;
|
|
||||||
--g-g900: #0f172a;
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
font-family:
|
|
||||||
'DM Sans',
|
|
||||||
system-ui,
|
|
||||||
-apple-system,
|
|
||||||
sans-serif;
|
|
||||||
background: var(--g-g50);
|
|
||||||
color: var(--g-g900);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-root {
|
|
||||||
padding: 18px 22px;
|
|
||||||
max-width: 1700px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* HEADER */
|
|
||||||
.glr-head {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-head-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-head-ic {
|
|
||||||
font-size: 28px;
|
|
||||||
color: var(--g-navy);
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-head-t {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-head-s {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--g-g500);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BUTTONS */
|
|
||||||
.glr-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 9px 16px;
|
|
||||||
border: 1px solid var(--g-g300);
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--g-g700);
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: inherit;
|
|
||||||
transition: all 0.15s;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: var(--g-g50);
|
|
||||||
border-color: var(--g-g500);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-btn-xls {
|
|
||||||
background: #16a34a;
|
|
||||||
border-color: #16a34a;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: #15803d;
|
|
||||||
border-color: #15803d;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FILTER BAR */
|
|
||||||
.glr-filters {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid var(--g-g200);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 14px;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: 14px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-filter {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 5px;
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--g-g500);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-inp {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid var(--g-g200);
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
background: #fff;
|
|
||||||
font-family: inherit;
|
|
||||||
min-width: 190px;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--g-navy);
|
|
||||||
box-shadow: 0 0 0 3px rgb(26 35 126 / 12%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Day / Month / Range segmented control */
|
|
||||||
.glr-seg {
|
|
||||||
display: inline-flex;
|
|
||||||
border: 1px solid var(--g-g200);
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 8px 14px;
|
|
||||||
border: none;
|
|
||||||
background: #fff;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--g-g500);
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: inherit;
|
|
||||||
transition: all 0.15s;
|
|
||||||
|
|
||||||
+ button {
|
|
||||||
border-left: 1px solid var(--g-g200);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--g-g50);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: var(--g-navy);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* REPORT SELECTOR */
|
|
||||||
.glr-tabs {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid var(--g-g200);
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 4px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-tab {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 9px 14px;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--g-g500);
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 7px;
|
|
||||||
white-space: nowrap;
|
|
||||||
transition: all 0.15s;
|
|
||||||
font-family: inherit;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--g-g900);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: var(--g-navy);
|
|
||||||
color: #fff;
|
|
||||||
box-shadow: 0 2px 6px rgb(26 35 126 / 25%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BALANCE BANNER */
|
|
||||||
.glr-balance {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px 16px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: var(--g-green-l);
|
|
||||||
border: 1px solid #86efac;
|
|
||||||
color: #14532d;
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
&--bad {
|
|
||||||
background: var(--g-red-l);
|
|
||||||
border-color: #fca5a5;
|
|
||||||
color: #7f1d1d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-note {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--g-g500);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* KPIs */
|
|
||||||
.glr-kpis {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-kpi {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid var(--g-g200);
|
|
||||||
border-left: 4px solid var(--g-g300);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 14px 18px;
|
|
||||||
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
|
|
||||||
|
|
||||||
&--teal {
|
|
||||||
border-left-color: var(--g-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--amber {
|
|
||||||
border-left-color: var(--g-amber);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--green {
|
|
||||||
border-left-color: var(--g-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--red {
|
|
||||||
border-left-color: var(--g-red);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-kpi-n {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 700;
|
|
||||||
font-family: 'DM Mono', monospace;
|
|
||||||
color: var(--g-g900);
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-kpi-l {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--g-g700);
|
|
||||||
font-weight: 600;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-kpi-sub {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--g-g500);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TABLES */
|
|
||||||
.glr-two-col {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 16px;
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-tbl-wrap {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid var(--g-g200);
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: auto;
|
|
||||||
box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
|
|
||||||
|
|
||||||
&--tall {
|
|
||||||
max-height: 70vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-tbl-h {
|
|
||||||
background: var(--g-blue-l);
|
|
||||||
color: var(--g-navy);
|
|
||||||
padding: 10px 16px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
border-bottom: 1px solid var(--g-g200);
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-tbl {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: left;
|
|
||||||
padding: 9px 12px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--g-g500);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
background: var(--g-g50);
|
|
||||||
border-bottom: 1px solid var(--g-g200);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-bottom: 1px solid var(--g-g100);
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--g-g700);
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr:hover td {
|
|
||||||
background: var(--g-g50);
|
|
||||||
}
|
|
||||||
|
|
||||||
tfoot td {
|
|
||||||
position: sticky;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-row td {
|
|
||||||
background: var(--g-g100) !important;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--g-g900);
|
|
||||||
border-top: 2px solid var(--g-g300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-profit-row td {
|
|
||||||
background: var(--g-green-l) !important;
|
|
||||||
color: #14532d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-loss-row td {
|
|
||||||
background: var(--g-red-l) !important;
|
|
||||||
color: #7f1d1d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ta-c {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ta-r {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mono {
|
|
||||||
font-family: 'DM Mono', monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.muted {
|
|
||||||
color: var(--g-g500);
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.var-up {
|
|
||||||
color: var(--g-green);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.var-down {
|
|
||||||
color: var(--g-red);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-empty {
|
|
||||||
text-align: center;
|
|
||||||
padding: 34px;
|
|
||||||
color: var(--g-g500);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Account-type pills on the GL Wise report */
|
|
||||||
.type-pill {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 2px 9px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0.03em;
|
|
||||||
background: var(--g-g100);
|
|
||||||
color: var(--g-g700);
|
|
||||||
|
|
||||||
&.type-asset {
|
|
||||||
background: var(--g-blue-l);
|
|
||||||
color: var(--g-blue);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type-liability {
|
|
||||||
background: var(--g-teal-l);
|
|
||||||
color: var(--g-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type-income {
|
|
||||||
background: var(--g-green-l);
|
|
||||||
color: var(--g-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type-expense {
|
|
||||||
background: var(--g-amber-l);
|
|
||||||
color: var(--g-amber);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type-contingent {
|
|
||||||
background: var(--g-red-l);
|
|
||||||
color: var(--g-red);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TOAST */
|
|
||||||
.glr-toast {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 32px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%) translateY(20px);
|
|
||||||
background: var(--g-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 <= 1200px) {
|
|
||||||
.glr-two-col {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glr-kpis {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,470 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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, OnInit, inject } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
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 { GL_BRANCHES, GL_PERIOD, glBranchName } from './gl-reports-data';
|
|
||||||
import {
|
|
||||||
BalanceSheetRow,
|
|
||||||
ComparativeRow,
|
|
||||||
GlDateRange,
|
|
||||||
GlReportSet,
|
|
||||||
MiscExpenseRow,
|
|
||||||
PnlRow,
|
|
||||||
buildReports,
|
|
||||||
clampToLedger,
|
|
||||||
sum
|
|
||||||
} from './gl-reports.compute';
|
|
||||||
|
|
||||||
export type GlReportCode =
|
|
||||||
| 'trial-balance'
|
|
||||||
| 'profit-and-loss'
|
|
||||||
| 'balance-sheet'
|
|
||||||
| 'comparative-balance-sheet'
|
|
||||||
| 'contingent-liabilities'
|
|
||||||
| 'gl-wise-dr-cr'
|
|
||||||
| 'misc-expense-ledger';
|
|
||||||
|
|
||||||
type PeriodMode = 'day' | 'month' | 'range';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-gl-reports',
|
|
||||||
templateUrl: './gl-reports.component.html',
|
|
||||||
styleUrls: ['./gl-reports.component.scss'],
|
|
||||||
imports: [
|
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
|
||||||
FaIconComponent,
|
|
||||||
FormsModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class GlReportsComponent implements OnInit {
|
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private router = inject(Router);
|
|
||||||
|
|
||||||
readonly reports: { code: GlReportCode; label: string; icon: string }[] = [
|
|
||||||
{ code: 'trial-balance', label: 'Trial Balance', icon: 'balance-scale' },
|
|
||||||
{ code: 'profit-and-loss', label: 'Profit and Loss', icon: 'chart-line' },
|
|
||||||
{ code: 'balance-sheet', label: 'Consolidated Balance Sheet', icon: 'file-alt' },
|
|
||||||
{ code: 'comparative-balance-sheet', label: 'Comparative Balance Sheet', icon: 'balance-scale' },
|
|
||||||
{ code: 'contingent-liabilities', label: 'Contingent Liabilities', icon: 'exclamation-circle' },
|
|
||||||
{ code: 'gl-wise-dr-cr', label: 'GL Wise DR / CR', icon: 'project-diagram' },
|
|
||||||
{ code: 'misc-expense-ledger', label: 'Misc Expense Ledger', icon: 'receipt' }
|
|
||||||
];
|
|
||||||
|
|
||||||
activeReport: GlReportCode = 'trial-balance';
|
|
||||||
|
|
||||||
branches = GL_BRANCHES;
|
|
||||||
ledgerPeriod = GL_PERIOD;
|
|
||||||
|
|
||||||
// ── Filter state ───────────────────────────────────────────────────────────
|
|
||||||
periodMode: PeriodMode = 'month';
|
|
||||||
/** Last full month of the generated ledger. */
|
|
||||||
filterDay = '2026-07-31';
|
|
||||||
filterMonth = '2026-07';
|
|
||||||
filterFrom = GL_PERIOD.fyStart;
|
|
||||||
filterTo = GL_PERIOD.fyEnd;
|
|
||||||
branchCode = '';
|
|
||||||
|
|
||||||
generating = false;
|
|
||||||
toastMsg = '';
|
|
||||||
toastVisible = false;
|
|
||||||
private toastTimer: any;
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
const param = this.route.snapshot.paramMap.get('report') as GlReportCode | null;
|
|
||||||
if (param && this.reports.some((r) => r.code === param)) {
|
|
||||||
this.activeReport = param;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showReport(code: GlReportCode): void {
|
|
||||||
this.activeReport = code;
|
|
||||||
// Keep the URL deep-linkable so the GL menu items land on the right report.
|
|
||||||
this.router.navigate(['/gl/reports', code], { replaceUrl: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
get reportLabel(): string {
|
|
||||||
return this.reports.find((r) => r.code === this.activeReport)?.label ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Filter resolution ──────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/** Turns the filter controls into the concrete date range the reports use. */
|
|
||||||
get range(): GlDateRange {
|
|
||||||
if (this.periodMode === 'day') {
|
|
||||||
return clampToLedger({ from: this.filterDay, to: this.filterDay });
|
|
||||||
}
|
|
||||||
if (this.periodMode === 'month') {
|
|
||||||
const [
|
|
||||||
y,
|
|
||||||
m
|
|
||||||
] = this.filterMonth.split('-').map(Number);
|
|
||||||
const last = new Date(Date.UTC(y, m, 0)).getUTCDate();
|
|
||||||
return clampToLedger({ from: `${this.filterMonth}-01`, to: `${this.filterMonth}-${`${last}`.padStart(2, '0')}` });
|
|
||||||
}
|
|
||||||
return clampToLedger({ from: this.filterFrom, to: this.filterTo });
|
|
||||||
}
|
|
||||||
|
|
||||||
get rangeLabel(): string {
|
|
||||||
const r = this.range;
|
|
||||||
return r.from === r.to ? r.from : `${r.from} to ${r.to}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get branchLabel(): string {
|
|
||||||
return this.branchCode ? glBranchName(this.branchCode) : 'All Branches (Consolidated)';
|
|
||||||
}
|
|
||||||
|
|
||||||
resetFilters(): void {
|
|
||||||
this.periodMode = 'month';
|
|
||||||
this.filterMonth = '2026-07';
|
|
||||||
this.filterDay = '2026-07-31';
|
|
||||||
this.filterFrom = GL_PERIOD.fyStart;
|
|
||||||
this.filterTo = GL_PERIOD.fyEnd;
|
|
||||||
this.branchCode = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** All seven reports for the current filter — memoised inside buildReports. */
|
|
||||||
get data(): GlReportSet {
|
|
||||||
return buildReports(this.range, this.branchCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Trial Balance ──────────────────────────────────────────────────────────
|
|
||||||
get tbOpeningDebit(): number {
|
|
||||||
return sum(this.data.trialBalance, (r) => r.openingDebit);
|
|
||||||
}
|
|
||||||
get tbOpeningCredit(): number {
|
|
||||||
return sum(this.data.trialBalance, (r) => r.openingCredit);
|
|
||||||
}
|
|
||||||
get tbPeriodDebit(): number {
|
|
||||||
return sum(this.data.trialBalance, (r) => r.periodDebit);
|
|
||||||
}
|
|
||||||
get tbPeriodCredit(): number {
|
|
||||||
return sum(this.data.trialBalance, (r) => r.periodCredit);
|
|
||||||
}
|
|
||||||
get tbClosingDebit(): number {
|
|
||||||
return sum(this.data.trialBalance, (r) => r.closingDebit);
|
|
||||||
}
|
|
||||||
get tbClosingCredit(): number {
|
|
||||||
return sum(this.data.trialBalance, (r) => r.closingCredit);
|
|
||||||
}
|
|
||||||
/** A trial balance that does not tally is the single most important thing to surface. */
|
|
||||||
get tbBalanced(): boolean {
|
|
||||||
return Math.round(this.tbClosingDebit) === Math.round(this.tbClosingCredit);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Profit and Loss ────────────────────────────────────────────────────────
|
|
||||||
get pnlIncome(): PnlRow[] {
|
|
||||||
return this.data.pnl.income;
|
|
||||||
}
|
|
||||||
get pnlExpense(): PnlRow[] {
|
|
||||||
return this.data.pnl.expense;
|
|
||||||
}
|
|
||||||
get totalIncome(): number {
|
|
||||||
return sum(this.pnlIncome, (r) => r.amount);
|
|
||||||
}
|
|
||||||
get totalExpense(): number {
|
|
||||||
return sum(this.pnlExpense, (r) => r.amount);
|
|
||||||
}
|
|
||||||
get netProfit(): number {
|
|
||||||
return this.totalIncome - this.totalExpense;
|
|
||||||
}
|
|
||||||
get netProfitPct(): number {
|
|
||||||
return this.totalIncome ? (this.netProfit / this.totalIncome) * 100 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Balance Sheet ──────────────────────────────────────────────────────────
|
|
||||||
get bsAssets(): BalanceSheetRow[] {
|
|
||||||
return this.data.balanceSheet.assets;
|
|
||||||
}
|
|
||||||
get bsLiabilities(): BalanceSheetRow[] {
|
|
||||||
return this.data.balanceSheet.liabilities;
|
|
||||||
}
|
|
||||||
get bsSurplus(): number {
|
|
||||||
return this.data.balanceSheet.surplus;
|
|
||||||
}
|
|
||||||
get bsTotalAssets(): number {
|
|
||||||
return sum(this.bsAssets, (r) => r.amount);
|
|
||||||
}
|
|
||||||
get bsTotalLiabilities(): number {
|
|
||||||
return sum(this.bsLiabilities, (r) => r.amount) + this.bsSurplus;
|
|
||||||
}
|
|
||||||
get bsBalanced(): boolean {
|
|
||||||
return Math.round(this.bsTotalAssets) === Math.round(this.bsTotalLiabilities);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Comparative Balance Sheet ──────────────────────────────────────────────
|
|
||||||
get compAssets(): ComparativeRow[] {
|
|
||||||
return this.data.comparative.filter((r) => r.side === 'ASSET');
|
|
||||||
}
|
|
||||||
get compLiabilities(): ComparativeRow[] {
|
|
||||||
return this.data.comparative.filter((r) => r.side === 'LIABILITY');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Contingent Liabilities ─────────────────────────────────────────────────
|
|
||||||
get contingentTotal(): number {
|
|
||||||
return sum(this.data.contingent, (r) => r.amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── GL Wise DR/CR ──────────────────────────────────────────────────────────
|
|
||||||
get glWiseDebit(): number {
|
|
||||||
return sum(this.data.glWise, (r) => r.debit);
|
|
||||||
}
|
|
||||||
get glWiseCredit(): number {
|
|
||||||
return sum(this.data.glWise, (r) => r.credit);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Misc Expense Ledger ────────────────────────────────────────────────────
|
|
||||||
get miscRows(): MiscExpenseRow[] {
|
|
||||||
return this.data.miscExpense;
|
|
||||||
}
|
|
||||||
get miscTotal(): number {
|
|
||||||
return sum(this.miscRows, (r) => r.amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
branchName(code: string): string {
|
|
||||||
return glBranchName(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Excel export ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/** Exports whichever report is on screen, carrying the active filter into the header. */
|
|
||||||
async exportExcel(): Promise<void> {
|
|
||||||
this.generating = true;
|
|
||||||
try {
|
|
||||||
const wb = new ExcelJS.Workbook();
|
|
||||||
wb.creator = 'CBSNexora';
|
|
||||||
wb.created = new Date();
|
|
||||||
const sheet = wb.addWorksheet(this.reportLabel.slice(0, 31));
|
|
||||||
|
|
||||||
const { headers, rows, widths } = this.exportShape();
|
|
||||||
sheet.columns = widths.map((w) => ({ width: w }));
|
|
||||||
|
|
||||||
const title = sheet.addRow([this.reportLabel]);
|
|
||||||
sheet.mergeCells(title.number, 1, title.number, headers.length);
|
|
||||||
title.font = { bold: true, size: 14, color: { argb: 'FFFFFFFF' } };
|
|
||||||
title.alignment = { horizontal: 'center', vertical: 'middle' };
|
|
||||||
title.height = 24;
|
|
||||||
title.getCell(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF1A237E' } };
|
|
||||||
|
|
||||||
const meta = sheet.addRow([`Period: ${this.rangeLabel} · Branch: ${this.branchLabel}`]);
|
|
||||||
sheet.mergeCells(meta.number, 1, meta.number, headers.length);
|
|
||||||
meta.font = { italic: true, size: 10 };
|
|
||||||
sheet.addRow([]);
|
|
||||||
|
|
||||||
const head = sheet.addRow(headers);
|
|
||||||
head.font = { bold: true };
|
|
||||||
head.alignment = { wrapText: true, horizontal: 'center' };
|
|
||||||
head.eachCell((c) => {
|
|
||||||
c.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE2E8F0' } };
|
|
||||||
c.border = { bottom: { style: 'thin' } };
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const r of rows) {
|
|
||||||
const row = sheet.addRow(r);
|
|
||||||
for (let c = 1; c <= headers.length; c++) {
|
|
||||||
if (typeof r[c - 1] === 'number') {
|
|
||||||
row.getCell(c).numFmt = '#,##0.00';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sheet.views = [{ state: 'frozen', ySplit: 4 }];
|
|
||||||
|
|
||||||
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 = `${this.reportLabel.replace(/[^\w]+/g, '-')}-${this.range.from}-to-${this.range.to}.xlsx`;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
this.toast(`${this.reportLabel} exported for ${this.rangeLabel}`);
|
|
||||||
} catch {
|
|
||||||
this.toast('Could not generate the workbook — please retry');
|
|
||||||
} finally {
|
|
||||||
this.generating = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Header row, data rows and column widths for the active report. */
|
|
||||||
private exportShape(): { headers: string[]; rows: (string | number | null)[][]; widths: number[] } {
|
|
||||||
switch (this.activeReport) {
|
|
||||||
case 'trial-balance':
|
|
||||||
return {
|
|
||||||
headers: [
|
|
||||||
'GL Code',
|
|
||||||
'Account',
|
|
||||||
'Group',
|
|
||||||
'Opening Dr',
|
|
||||||
'Opening Cr',
|
|
||||||
'Period Dr',
|
|
||||||
'Period Cr',
|
|
||||||
'Closing Dr',
|
|
||||||
'Closing Cr'
|
|
||||||
],
|
|
||||||
widths: [12, 42, 26, 16, 16, 16, 16, 16, 16],
|
|
||||||
rows: [
|
|
||||||
...this.data.trialBalance.map((r) => [
|
|
||||||
r.code,
|
|
||||||
r.name,
|
|
||||||
r.group,
|
|
||||||
r.openingDebit,
|
|
||||||
r.openingCredit,
|
|
||||||
r.periodDebit,
|
|
||||||
r.periodCredit,
|
|
||||||
r.closingDebit,
|
|
||||||
r.closingCredit
|
|
||||||
]),
|
|
||||||
[
|
|
||||||
'TOTAL',
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
this.tbOpeningDebit,
|
|
||||||
this.tbOpeningCredit,
|
|
||||||
this.tbPeriodDebit,
|
|
||||||
this.tbPeriodCredit,
|
|
||||||
this.tbClosingDebit,
|
|
||||||
this.tbClosingCredit
|
|
||||||
]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
case 'profit-and-loss':
|
|
||||||
return {
|
|
||||||
headers: [
|
|
||||||
'Side',
|
|
||||||
'GL Code',
|
|
||||||
'Account',
|
|
||||||
'Group',
|
|
||||||
'Amount'
|
|
||||||
],
|
|
||||||
widths: [14, 12, 42, 26, 18],
|
|
||||||
rows: [
|
|
||||||
...this.pnlExpense.map((r) => ['Expenditure', r.code, r.name, r.group, r.amount]),
|
|
||||||
['Total Expenditure', '', '', '', this.totalExpense],
|
|
||||||
...this.pnlIncome.map((r) => ['Income', r.code, r.name, r.group, r.amount]),
|
|
||||||
['Total Income', '', '', '', this.totalIncome],
|
|
||||||
['Net Profit', '', '', '', this.netProfit]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
case 'balance-sheet':
|
|
||||||
return {
|
|
||||||
headers: [
|
|
||||||
'Side',
|
|
||||||
'GL Code',
|
|
||||||
'Account',
|
|
||||||
'Group',
|
|
||||||
'Amount'
|
|
||||||
],
|
|
||||||
widths: [14, 12, 42, 26, 18],
|
|
||||||
rows: [
|
|
||||||
...this.bsLiabilities.map((r) => ['Liability', r.code, r.name, r.group, r.amount]),
|
|
||||||
['Liability', '', 'Surplus for the period', 'Profit & Loss', this.bsSurplus],
|
|
||||||
['Total Liabilities', '', '', '', this.bsTotalLiabilities],
|
|
||||||
...this.bsAssets.map((r) => ['Asset', r.code, r.name, r.group, r.amount]),
|
|
||||||
['Total Assets', '', '', '', this.bsTotalAssets]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
case 'comparative-balance-sheet':
|
|
||||||
return {
|
|
||||||
headers: [
|
|
||||||
'Side',
|
|
||||||
'GL Code',
|
|
||||||
'Account',
|
|
||||||
'Current',
|
|
||||||
'Previous',
|
|
||||||
'Variance',
|
|
||||||
'Variance %'
|
|
||||||
],
|
|
||||||
widths: [12, 12, 42, 18, 18, 18, 14],
|
|
||||||
rows: this.data.comparative.map((r) => [
|
|
||||||
r.side,
|
|
||||||
r.code,
|
|
||||||
r.name,
|
|
||||||
r.current,
|
|
||||||
r.previous,
|
|
||||||
r.variance,
|
|
||||||
r.variancePct === null ? '—' : Number(r.variancePct.toFixed(2))
|
|
||||||
])
|
|
||||||
};
|
|
||||||
case 'contingent-liabilities':
|
|
||||||
return {
|
|
||||||
headers: [
|
|
||||||
'GL Code',
|
|
||||||
'Particulars',
|
|
||||||
'Group',
|
|
||||||
'Amount'
|
|
||||||
],
|
|
||||||
widths: [12, 48, 26, 18],
|
|
||||||
rows: [
|
|
||||||
...this.data.contingent.map((r) => [r.code, r.name, r.group, r.amount]),
|
|
||||||
['TOTAL', '', '', this.contingentTotal]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
case 'gl-wise-dr-cr':
|
|
||||||
return {
|
|
||||||
headers: [
|
|
||||||
'GL Code',
|
|
||||||
'Account',
|
|
||||||
'Type',
|
|
||||||
'Group',
|
|
||||||
'Debit',
|
|
||||||
'Credit',
|
|
||||||
'Net (Dr-Cr)',
|
|
||||||
'Vouchers'
|
|
||||||
],
|
|
||||||
widths: [12, 42, 14, 26, 18, 18, 18, 12],
|
|
||||||
rows: [
|
|
||||||
...this.data.glWise.map((r) => [r.code, r.name, r.type, r.group, r.debit, r.credit, r.net, r.vouchers]),
|
|
||||||
['TOTAL', '', '', '', this.glWiseDebit, this.glWiseCredit, this.glWiseDebit - this.glWiseCredit, null]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
case 'misc-expense-ledger':
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
headers: [
|
|
||||||
'Date',
|
|
||||||
'Voucher No',
|
|
||||||
'Branch',
|
|
||||||
'GL Code',
|
|
||||||
'Account',
|
|
||||||
'Narration',
|
|
||||||
'Amount'
|
|
||||||
],
|
|
||||||
widths: [14, 20, 18, 12, 36, 46, 18],
|
|
||||||
rows: [
|
|
||||||
...this.miscRows.map((r) => [
|
|
||||||
r.date,
|
|
||||||
r.voucherNo,
|
|
||||||
this.branchName(r.branchCode),
|
|
||||||
r.code,
|
|
||||||
r.name,
|
|
||||||
r.narration,
|
|
||||||
r.amount
|
|
||||||
]),
|
|
||||||
['TOTAL', '', '', '', '', '', this.miscTotal]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private toast(msg: string): void {
|
|
||||||
this.toastMsg = msg;
|
|
||||||
this.toastVisible = true;
|
|
||||||
if (this.toastTimer) {
|
|
||||||
clearTimeout(this.toastTimer);
|
|
||||||
}
|
|
||||||
this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,379 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives all seven General Ledger reports from the posting ledger.
|
|
||||||
*
|
|
||||||
* Each report is a pure function of (postings, date range, branch), so changing
|
|
||||||
* the day / month / branch filter genuinely recomputes the figures. Results are
|
|
||||||
* memoised per filter key because the ledger runs to roughly twenty thousand
|
|
||||||
* postings and Angular re-evaluates template getters on every change detection.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
GL_ACCOUNTS,
|
|
||||||
GL_PERIOD,
|
|
||||||
GlAccount,
|
|
||||||
GlPosting,
|
|
||||||
glAccount,
|
|
||||||
glPostings
|
|
||||||
} from './gl-reports-data';
|
|
||||||
|
|
||||||
export interface GlDateRange {
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TrialBalanceRow {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
group: string;
|
|
||||||
openingDebit: number;
|
|
||||||
openingCredit: number;
|
|
||||||
periodDebit: number;
|
|
||||||
periodCredit: number;
|
|
||||||
closingDebit: number;
|
|
||||||
closingCredit: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PnlRow {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
group: string;
|
|
||||||
amount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BalanceSheetRow {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
group: string;
|
|
||||||
amount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ComparativeRow {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
group: string;
|
|
||||||
side: 'ASSET' | 'LIABILITY';
|
|
||||||
current: number;
|
|
||||||
previous: number;
|
|
||||||
variance: number;
|
|
||||||
variancePct: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GlWiseRow {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
debit: number;
|
|
||||||
credit: number;
|
|
||||||
net: number;
|
|
||||||
vouchers: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MiscExpenseRow {
|
|
||||||
date: string;
|
|
||||||
voucherNo: string;
|
|
||||||
branchCode: string;
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
narration: string;
|
|
||||||
amount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Natural side of an account — assets and expenses sit on the debit side. */
|
|
||||||
function isDebitSide(account: GlAccount): boolean {
|
|
||||||
return account.type === 'ASSET' || account.type === 'EXPENSE' || account.type === 'CONTINGENT';
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesBranch(p: GlPosting, branchCode: string): boolean {
|
|
||||||
return !branchCode || p.branchCode === branchCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Signed balance on the account's natural side, for postings up to and including `to`. */
|
|
||||||
function cumulativeBalances(to: string, branchCode: string): Map<string, number> {
|
|
||||||
const out = new Map<string, number>();
|
|
||||||
for (const p of glPostings()) {
|
|
||||||
if (p.date > to || !matchesBranch(p, branchCode)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const account = glAccount(p.accountCode);
|
|
||||||
if (!account) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const delta = isDebitSide(account) ? p.debit - p.credit : p.credit - p.debit;
|
|
||||||
out.set(p.accountCode, (out.get(p.accountCode) ?? 0) + delta);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Debit and credit turnover strictly inside the range. */
|
|
||||||
function periodTurnover(range: GlDateRange, branchCode: string): Map<string, { debit: number; credit: number; vouchers: Set<string> }> {
|
|
||||||
const out = new Map<string, { debit: number; credit: number; vouchers: Set<string> }>();
|
|
||||||
for (const p of glPostings()) {
|
|
||||||
if (p.date < range.from || p.date > range.to || !matchesBranch(p, branchCode)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let entry = out.get(p.accountCode);
|
|
||||||
if (!entry) {
|
|
||||||
entry = { debit: 0, credit: 0, vouchers: new Set<string>() };
|
|
||||||
out.set(p.accountCode, entry);
|
|
||||||
}
|
|
||||||
entry.debit += p.debit;
|
|
||||||
entry.credit += p.credit;
|
|
||||||
entry.vouchers.add(p.voucherNo);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The day before `date`, ISO formatted — used for opening balances. */
|
|
||||||
function previousDay(date: string): string {
|
|
||||||
const d = new Date(`${date}T00:00:00Z`);
|
|
||||||
d.setUTCDate(d.getUTCDate() - 1);
|
|
||||||
return d.toISOString().slice(0, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Reports ──────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function trialBalance(range: GlDateRange, branchCode: string): TrialBalanceRow[] {
|
|
||||||
const opening = cumulativeBalances(previousDay(range.from), branchCode);
|
|
||||||
const turnover = periodTurnover(range, branchCode);
|
|
||||||
const rows: TrialBalanceRow[] = [];
|
|
||||||
|
|
||||||
for (const account of GL_ACCOUNTS) {
|
|
||||||
// Contingent accounts are memoranda and never appear in the trial balance.
|
|
||||||
if (account.type === 'CONTINGENT') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const open = opening.get(account.code) ?? 0;
|
|
||||||
const move = turnover.get(account.code);
|
|
||||||
const periodDebit = move?.debit ?? 0;
|
|
||||||
const periodCredit = move?.credit ?? 0;
|
|
||||||
if (!open && !periodDebit && !periodCredit) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const debitSide = isDebitSide(account);
|
|
||||||
const closingNatural = open + (debitSide ? periodDebit - periodCredit : periodCredit - periodDebit);
|
|
||||||
const closingSigned = debitSide ? closingNatural : -closingNatural;
|
|
||||||
|
|
||||||
rows.push({
|
|
||||||
code: account.code,
|
|
||||||
name: account.name,
|
|
||||||
group: account.group,
|
|
||||||
openingDebit: debitSide && open > 0 ? open : !debitSide && open < 0 ? -open : 0,
|
|
||||||
openingCredit: !debitSide && open > 0 ? open : debitSide && open < 0 ? -open : 0,
|
|
||||||
periodDebit,
|
|
||||||
periodCredit,
|
|
||||||
closingDebit: closingSigned > 0 ? closingSigned : 0,
|
|
||||||
closingCredit: closingSigned < 0 ? -closingSigned : 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function profitAndLoss(range: GlDateRange, branchCode: string): { income: PnlRow[]; expense: PnlRow[] } {
|
|
||||||
const turnover = periodTurnover(range, branchCode);
|
|
||||||
const income: PnlRow[] = [];
|
|
||||||
const expense: PnlRow[] = [];
|
|
||||||
|
|
||||||
for (const account of GL_ACCOUNTS) {
|
|
||||||
if (account.type !== 'INCOME' && account.type !== 'EXPENSE') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const move = turnover.get(account.code);
|
|
||||||
if (!move) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const amount =
|
|
||||||
account.type === 'INCOME' ? move.credit - move.debit : move.debit - move.credit;
|
|
||||||
if (!amount) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const row: PnlRow = { code: account.code, name: account.name, group: account.group, amount };
|
|
||||||
(account.type === 'INCOME' ? income : expense).push(row);
|
|
||||||
}
|
|
||||||
return { income, expense };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Balance sheet as at the period end. The current-period surplus is folded in as
|
|
||||||
* a liability line so the two sides tally the way the printed statement does.
|
|
||||||
*/
|
|
||||||
export function balanceSheet(range: GlDateRange, branchCode: string): {
|
|
||||||
assets: BalanceSheetRow[];
|
|
||||||
liabilities: BalanceSheetRow[];
|
|
||||||
surplus: number;
|
|
||||||
} {
|
|
||||||
const closing = cumulativeBalances(range.to, branchCode);
|
|
||||||
const assets: BalanceSheetRow[] = [];
|
|
||||||
const liabilities: BalanceSheetRow[] = [];
|
|
||||||
let income = 0;
|
|
||||||
let expense = 0;
|
|
||||||
|
|
||||||
for (const account of GL_ACCOUNTS) {
|
|
||||||
const bal = closing.get(account.code) ?? 0;
|
|
||||||
if (account.type === 'INCOME') {
|
|
||||||
income += bal;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (account.type === 'EXPENSE') {
|
|
||||||
expense += bal;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (account.type === 'CONTINGENT' || !bal) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const row: BalanceSheetRow = { code: account.code, name: account.name, group: account.group, amount: bal };
|
|
||||||
(account.type === 'ASSET' ? assets : liabilities).push(row);
|
|
||||||
}
|
|
||||||
return { assets, liabilities, surplus: income - expense };
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Current period against the equivalent span immediately before it. */
|
|
||||||
export function comparativeBalanceSheet(range: GlDateRange, branchCode: string): ComparativeRow[] {
|
|
||||||
const priorTo = previousDay(range.from);
|
|
||||||
const current = cumulativeBalances(range.to, branchCode);
|
|
||||||
const previous = cumulativeBalances(priorTo, branchCode);
|
|
||||||
const rows: ComparativeRow[] = [];
|
|
||||||
|
|
||||||
for (const account of GL_ACCOUNTS) {
|
|
||||||
if (account.type !== 'ASSET' && account.type !== 'LIABILITY') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const cur = current.get(account.code) ?? 0;
|
|
||||||
const prev = previous.get(account.code) ?? 0;
|
|
||||||
if (!cur && !prev) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const variance = cur - prev;
|
|
||||||
rows.push({
|
|
||||||
code: account.code,
|
|
||||||
name: account.name,
|
|
||||||
group: account.group,
|
|
||||||
side: account.type,
|
|
||||||
current: cur,
|
|
||||||
previous: prev,
|
|
||||||
variance,
|
|
||||||
variancePct: prev ? (variance / Math.abs(prev)) * 100 : null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function contingentLiabilities(range: GlDateRange, branchCode: string): BalanceSheetRow[] {
|
|
||||||
const closing = cumulativeBalances(range.to, branchCode);
|
|
||||||
return GL_ACCOUNTS.filter((a) => a.type === 'CONTINGENT')
|
|
||||||
.map((a) => ({ code: a.code, name: a.name, group: a.group, amount: closing.get(a.code) ?? 0 }))
|
|
||||||
.filter((r) => r.amount !== 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function glWiseDrCr(range: GlDateRange, branchCode: string): GlWiseRow[] {
|
|
||||||
const turnover = periodTurnover(range, branchCode);
|
|
||||||
const rows: GlWiseRow[] = [];
|
|
||||||
for (const account of GL_ACCOUNTS) {
|
|
||||||
const move = turnover.get(account.code);
|
|
||||||
if (!move || (!move.debit && !move.credit)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
rows.push({
|
|
||||||
code: account.code,
|
|
||||||
name: account.name,
|
|
||||||
group: account.group,
|
|
||||||
type: account.type,
|
|
||||||
debit: move.debit,
|
|
||||||
credit: move.credit,
|
|
||||||
net: move.debit - move.credit,
|
|
||||||
vouchers: move.vouchers.size
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function miscExpenseLedger(range: GlDateRange, branchCode: string): MiscExpenseRow[] {
|
|
||||||
const miscCodes = new Set(GL_ACCOUNTS.filter((a) => a.miscExpense).map((a) => a.code));
|
|
||||||
const rows: MiscExpenseRow[] = [];
|
|
||||||
for (const p of glPostings()) {
|
|
||||||
if (p.date < range.from || p.date > range.to || !matchesBranch(p, branchCode)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!miscCodes.has(p.accountCode) || !p.debit) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
rows.push({
|
|
||||||
date: p.date,
|
|
||||||
voucherNo: p.voucherNo,
|
|
||||||
branchCode: p.branchCode,
|
|
||||||
code: p.accountCode,
|
|
||||||
name: glAccount(p.accountCode)?.name ?? p.accountCode,
|
|
||||||
narration: p.narration,
|
|
||||||
amount: p.debit
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return rows.sort((a, b) => (a.date === b.date ? a.voucherNo.localeCompare(b.voucherNo) : a.date.localeCompare(b.date)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Memoisation ──────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export interface GlReportSet {
|
|
||||||
trialBalance: TrialBalanceRow[];
|
|
||||||
pnl: { income: PnlRow[]; expense: PnlRow[] };
|
|
||||||
balanceSheet: { assets: BalanceSheetRow[]; liabilities: BalanceSheetRow[]; surplus: number };
|
|
||||||
comparative: ComparativeRow[];
|
|
||||||
contingent: BalanceSheetRow[];
|
|
||||||
glWise: GlWiseRow[];
|
|
||||||
miscExpense: MiscExpenseRow[];
|
|
||||||
}
|
|
||||||
|
|
||||||
let cacheKey = '';
|
|
||||||
let cached: GlReportSet | null = null;
|
|
||||||
|
|
||||||
/** All seven reports for one filter selection, computed once per selection. */
|
|
||||||
export function buildReports(range: GlDateRange, branchCode: string): GlReportSet {
|
|
||||||
const key = `${range.from}|${range.to}|${branchCode}`;
|
|
||||||
if (cached && cacheKey === key) {
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
cached = {
|
|
||||||
trialBalance: trialBalance(range, branchCode),
|
|
||||||
pnl: profitAndLoss(range, branchCode),
|
|
||||||
balanceSheet: balanceSheet(range, branchCode),
|
|
||||||
comparative: comparativeBalanceSheet(range, branchCode),
|
|
||||||
contingent: contingentLiabilities(range, branchCode),
|
|
||||||
glWise: glWiseDrCr(range, branchCode),
|
|
||||||
miscExpense: miscExpenseLedger(range, branchCode)
|
|
||||||
};
|
|
||||||
cacheKey = key;
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Clamp a range to the generated ledger so filters cannot query empty space. */
|
|
||||||
export function clampToLedger(range: GlDateRange): GlDateRange {
|
|
||||||
return {
|
|
||||||
from: range.from < GL_PERIOD.fyStart ? GL_PERIOD.fyStart : range.from,
|
|
||||||
to: range.to > GL_PERIOD.fyEnd ? GL_PERIOD.fyEnd : range.to
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sum<T>(rows: T[], pick: (row: T) => number): number {
|
|
||||||
return rows.reduce((acc, r) => acc + pick(r), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Groups rows by their `group` field, preserving chart-of-accounts order. */
|
|
||||||
export function groupBy<T extends { group: string }>(rows: T[]): { group: string; rows: T[]; total: number }[] {
|
|
||||||
const order: string[] = [];
|
|
||||||
const map = new Map<string, T[]>();
|
|
||||||
for (const r of rows) {
|
|
||||||
if (!map.has(r.group)) {
|
|
||||||
map.set(r.group, []);
|
|
||||||
order.push(r.group);
|
|
||||||
}
|
|
||||||
map.get(r.group)!.push(r);
|
|
||||||
}
|
|
||||||
return order.map((g) => ({ group: g, rows: map.get(g)!, total: 0 }));
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright since 2025 Mifos Initiative
|
|
||||||
*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
|
|
||||||
import { SharedModule } from '../shared/shared.module';
|
|
||||||
import { GlReportsRoutingModule } from './gl-reports-routing.module';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
SharedModule,
|
|
||||||
GlReportsRoutingModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class GlReportsModule {}
|
|
||||||
-58
@@ -397,64 +397,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- ════════════ CONTINGENT LIABILITIES ════════════ -->
|
|
||||||
<ng-container *ngIf="activeReport === 'CONTINGENT-LIAB'">
|
|
||||||
<div class="acr-stats">
|
|
||||||
<div class="acr-stat-card alert">
|
|
||||||
<div class="acr-stat-l">Total Contingent Liabilities — {{ bank.fiscalYear }}</div>
|
|
||||||
<div class="acr-stat-v">₹ {{ contingentLiab.current | number: '1.2-2' }} Lakh</div>
|
|
||||||
<div class="acr-stat-meta">vs {{ contingentLiab.previous | number: '1.2-2' }} Lakh (PY)</div>
|
|
||||||
</div>
|
|
||||||
<div class="acr-stat-card" [ngClass]="clVariation <= 0 ? 'good' : 'alert'">
|
|
||||||
<div class="acr-stat-l">Variation over Previous Year</div>
|
|
||||||
<div class="acr-stat-v">{{ clVariation >= 0 ? '+' : '' }}{{ clVariation | number: '1.2-2' }} Lakh</div>
|
|
||||||
<div class="acr-stat-meta">{{ clVariationPct >= 0 ? '+' : '' }}{{ clVariationPct.toFixed(2) }}%</div>
|
|
||||||
</div>
|
|
||||||
<div class="acr-stat-card">
|
|
||||||
<div class="acr-stat-l">As On Date</div>
|
|
||||||
<div class="acr-stat-v" style="font-size: 16px">{{ bank.asOnDate }}</div>
|
|
||||||
<div class="acr-stat-meta">Schedule 12</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="acr-section">
|
|
||||||
<div class="acr-sec-h">
|
|
||||||
<h3 class="acr-sec-t">Contingent Liabilities — As on {{ bank.asOnDate }} (₹ in Lakhs)</h3>
|
|
||||||
<button mat-stroked-button class="acr-btn-xls" (click)="exportContingentLiabCsv()">
|
|
||||||
<fa-icon icon="file-csv"></fa-icon> Export CSV
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="acr-tbl-wrap">
|
|
||||||
<table class="acr-tbl">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" class="acr-sub-h">Items Covered Under This Schedule</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let item of contingentLiabItems">
|
|
||||||
<td class="mono">{{ item.code }}</td>
|
|
||||||
<td>{{ item.label }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="row-total expense">
|
|
||||||
<td colspan="2">
|
|
||||||
<strong>Total Contingent Liabilities: ₹ {{ contingentLiab.current | number: '1.2-2' }} Lakh</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<p class="acr-note">
|
|
||||||
<fa-icon icon="info-circle"></fa-icon> Individual item-wise figures for this schedule are not broken
|
|
||||||
out here — only the audited total is shown, to avoid presenting a sub-item split that could not be
|
|
||||||
independently verified against the source statement.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- ════════════ GL DR/CR ════════════ -->
|
<!-- ════════════ GL DR/CR ════════════ -->
|
||||||
<ng-container *ngIf="activeReport === 'GL-DR-CR'">
|
<ng-container *ngIf="activeReport === 'GL-DR-CR'">
|
||||||
<div class="acr-stats">
|
<div class="acr-stats">
|
||||||
|
|||||||
-10
@@ -318,16 +318,6 @@
|
|||||||
border-bottom: 1px dashed var(--acr-g200);
|
border-bottom: 1px dashed var(--acr-g200);
|
||||||
}
|
}
|
||||||
|
|
||||||
.acr-note {
|
|
||||||
margin: 12px 0 0;
|
|
||||||
font-size: 12px;
|
|
||||||
font-style: italic;
|
|
||||||
color: var(--acr-g500);
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acr-btn-xls {
|
.acr-btn-xls {
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
height: 34px !important;
|
height: 34px !important;
|
||||||
|
|||||||
+217
-156
@@ -14,7 +14,7 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
|
||||||
export type AcctReportCode = 'PNL' | 'BALANCE-SHEET' | 'COMPARATIVE-BS' | 'CONTINGENT-LIAB' | 'GL-DR-CR' | 'MISC-EXP';
|
export type AcctReportCode = 'PNL' | 'BALANCE-SHEET' | 'COMPARATIVE-BS' | 'GL-DR-CR' | 'MISC-EXP';
|
||||||
|
|
||||||
export interface AcctReportsDialogData {
|
export interface AcctReportsDialogData {
|
||||||
initialReport?: AcctReportCode;
|
initialReport?: AcctReportCode;
|
||||||
@@ -47,11 +47,6 @@ interface GlLine {
|
|||||||
closing: number;
|
closing: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContingentLiabItem {
|
|
||||||
code: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MiscExpRow {
|
interface MiscExpRow {
|
||||||
txnDate: string;
|
txnDate: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
@@ -86,7 +81,7 @@ export class AccountingReportsDialogComponent {
|
|||||||
activeReport: AcctReportCode;
|
activeReport: AcctReportCode;
|
||||||
|
|
||||||
bank = {
|
bank = {
|
||||||
name: 'CBSNexora Bank Ltd',
|
name: 'Bharat Urban Bank Ltd',
|
||||||
branch: 'Head Office',
|
branch: 'Head Office',
|
||||||
asOnDate: '31-03-2026',
|
asOnDate: '31-03-2026',
|
||||||
period: '01-04-2025 to 31-03-2026',
|
period: '01-04-2025 to 31-03-2026',
|
||||||
@@ -99,7 +94,7 @@ export class AccountingReportsDialogComponent {
|
|||||||
poDesignation: 'Chief Finance Officer',
|
poDesignation: 'Chief Finance Officer',
|
||||||
auditor: 'M/s Sharma & Associates, Chartered Accountants',
|
auditor: 'M/s Sharma & Associates, Chartered Accountants',
|
||||||
reportRefNo: '157/2',
|
reportRefNo: '157/2',
|
||||||
userCode: 'CBSN01',
|
userCode: 'BUBL01',
|
||||||
branchCount: 15
|
branchCount: 15
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,13 +120,6 @@ export class AccountingReportsDialogComponent {
|
|||||||
description: 'Side-by-side comparison of current vs previous fiscal year.',
|
description: 'Side-by-side comparison of current vs previous fiscal year.',
|
||||||
group: 'Financial Statement'
|
group: 'Financial Statement'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
code: 'CONTINGENT-LIAB' as AcctReportCode,
|
|
||||||
name: 'Contingent Liabilities',
|
|
||||||
icon: 'exclamation-triangle',
|
|
||||||
description: 'Off-balance-sheet claims, guarantees & contingent exposures (Schedule 12).',
|
|
||||||
group: 'Financial Statement'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
code: 'GL-DR-CR' as AcctReportCode,
|
code: 'GL-DR-CR' as AcctReportCode,
|
||||||
name: 'GL Wise DR/CR',
|
name: 'GL Wise DR/CR',
|
||||||
@@ -148,192 +136,309 @@ export class AccountingReportsDialogComponent {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// -------------------- P&L Data (₹ in Lakhs) — FY 2025-26 audited figures --------------------
|
// -------------------- P&L Data (₹ in Lakhs) --------------------
|
||||||
pnl: PnlLine[] = [
|
pnl: PnlLine[] = [
|
||||||
{ code: 'I-01', group: 'INCOME', subgroup: 'Interest Earned', label: 'Interest on Advances & Bills', amount: 102641.73 },
|
{ code: 'I-01', group: 'INCOME', subgroup: 'Interest Earned', label: 'Interest on Advances', amount: 38420 },
|
||||||
{ code: 'I-02', group: 'INCOME', subgroup: 'Interest Earned', label: 'Income on Investments', amount: 41936.75 },
|
{ code: 'I-02', group: 'INCOME', subgroup: 'Interest Earned', label: 'Interest on Investments', amount: 14860 },
|
||||||
{
|
{
|
||||||
code: 'I-03',
|
code: 'I-03',
|
||||||
group: 'INCOME',
|
group: 'INCOME',
|
||||||
subgroup: 'Other Income',
|
subgroup: 'Interest Earned',
|
||||||
label: 'Commission, Exchange & Brokerage',
|
label: 'Interest on Balances with RBI / Other Banks',
|
||||||
amount: 144.2
|
amount: 1240
|
||||||
},
|
},
|
||||||
{ code: 'I-04', group: 'INCOME', subgroup: 'Other Income', label: 'Dividend Income', amount: 4.11 },
|
|
||||||
{
|
{
|
||||||
code: 'I-05',
|
code: 'I-04',
|
||||||
group: 'INCOME',
|
group: 'INCOME',
|
||||||
subgroup: 'Other Income',
|
subgroup: 'Other Income',
|
||||||
label: 'Net Income on Sale of Fixed Assets',
|
label: 'Commission, Exchange & Brokerage',
|
||||||
amount: 0.3
|
amount: 1820
|
||||||
},
|
|
||||||
{ code: 'I-06', group: 'INCOME', subgroup: 'Other Income', label: 'Other Income (ATM/POS/UPI/Rental/Misc)', amount: 280 },
|
|
||||||
{
|
|
||||||
code: 'E-01',
|
|
||||||
group: 'EXPENDITURE',
|
|
||||||
subgroup: 'Interest Expended',
|
|
||||||
label: 'Interest Paid on Deposits & Other Borrowings',
|
|
||||||
amount: 62861.8
|
|
||||||
},
|
},
|
||||||
|
{ code: 'I-05', group: 'INCOME', subgroup: 'Other Income', label: 'Profit on Sale of Investments', amount: 480 },
|
||||||
|
{ code: 'I-06', group: 'INCOME', subgroup: 'Other Income', label: 'Dividend Income', amount: 142 },
|
||||||
|
{ code: 'I-07', group: 'INCOME', subgroup: 'Other Income', label: 'Miscellaneous Income', amount: 318 },
|
||||||
|
{ code: 'E-01', group: 'EXPENDITURE', subgroup: 'Interest Expended', label: 'Interest on Deposits', amount: 28640 },
|
||||||
{
|
{
|
||||||
code: 'E-02',
|
code: 'E-02',
|
||||||
group: 'EXPENDITURE',
|
group: 'EXPENDITURE',
|
||||||
subgroup: 'Interest Expended',
|
subgroup: 'Interest Expended',
|
||||||
label: 'Interest Paid on NABARD Borrowings',
|
label: 'Interest on RBI / Inter-Bank Borrowings',
|
||||||
amount: 47083.71
|
amount: 980
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'E-03',
|
code: 'E-03',
|
||||||
group: 'EXPENDITURE',
|
group: 'EXPENDITURE',
|
||||||
subgroup: 'Operating Expenses',
|
subgroup: 'Operating Expenses',
|
||||||
label: 'Operating Expenses (Staff, Admin, Establishment)',
|
label: 'Payments to & Provisions for Employees',
|
||||||
amount: 14503.79
|
amount: 8420
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'E-04',
|
code: 'E-04',
|
||||||
group: 'EXPENDITURE',
|
group: 'EXPENDITURE',
|
||||||
subgroup: 'Provisions',
|
subgroup: 'Operating Expenses',
|
||||||
label: 'Provision for Standard Assets',
|
label: 'Rent, Taxes & Lighting',
|
||||||
amount: 730
|
amount: 1240
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'E-05',
|
|
||||||
group: 'EXPENDITURE',
|
|
||||||
subgroup: 'Provisions',
|
|
||||||
label: 'Provision for Doubtful & Bad Debts (Loans & Advances)',
|
|
||||||
amount: 2068.2
|
|
||||||
},
|
},
|
||||||
|
{ code: 'E-05', group: 'EXPENDITURE', subgroup: 'Operating Expenses', label: 'Printing & Stationery', amount: 184 },
|
||||||
{
|
{
|
||||||
code: 'E-06',
|
code: 'E-06',
|
||||||
group: 'EXPENDITURE',
|
group: 'EXPENDITURE',
|
||||||
subgroup: 'Provisions',
|
subgroup: 'Operating Expenses',
|
||||||
label: 'Bad Debts Written Off',
|
label: 'Depreciation on Bank Property',
|
||||||
amount: 906.44
|
amount: 642
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'E-07',
|
code: 'E-07',
|
||||||
group: 'EXPENDITURE',
|
group: 'EXPENDITURE',
|
||||||
subgroup: 'Provisions',
|
subgroup: 'Operating Expenses',
|
||||||
label: 'Provision for Staff Training Reserve',
|
label: 'Auditors Fees & Expenses',
|
||||||
amount: 245.06
|
amount: 124
|
||||||
},
|
},
|
||||||
{ code: 'E-08', group: 'EXPENDITURE', subgroup: 'Provisions', label: 'Provision for Income Tax', amount: 4600 }
|
{
|
||||||
|
code: 'E-08',
|
||||||
|
group: 'EXPENDITURE',
|
||||||
|
subgroup: 'Operating Expenses',
|
||||||
|
label: 'Repairs, Maintenance & Insurance',
|
||||||
|
amount: 386
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'E-09',
|
||||||
|
group: 'EXPENDITURE',
|
||||||
|
subgroup: 'Operating Expenses',
|
||||||
|
label: 'Other Operating Expenses',
|
||||||
|
amount: 824
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'E-10',
|
||||||
|
group: 'EXPENDITURE',
|
||||||
|
subgroup: 'Provisions',
|
||||||
|
label: 'Provisions for NPA / Standard Assets',
|
||||||
|
amount: 4280
|
||||||
|
},
|
||||||
|
{ code: 'E-11', group: 'EXPENDITURE', subgroup: 'Provisions', label: 'Provisions for Tax', amount: 2120 }
|
||||||
];
|
];
|
||||||
|
|
||||||
// -------------------- Balance Sheet (₹ in Lakhs) — as on 31-03-2026, audited --------------------
|
// -------------------- Balance Sheet (₹ in Lakhs) --------------------
|
||||||
bs: BsLine[] = [
|
bs: BsLine[] = [
|
||||||
{
|
{
|
||||||
code: 'L-01',
|
code: 'L-01',
|
||||||
side: 'CAPITAL-LIAB',
|
side: 'CAPITAL-LIAB',
|
||||||
group: 'Capital & Reserves',
|
group: 'Capital & Reserves',
|
||||||
label: 'Capital (Paid-up Share Capital)',
|
label: 'Capital (Paid-up Share Capital)',
|
||||||
amount: 92837.18,
|
amount: 22000,
|
||||||
prevAmount: 88842.87
|
prevAmount: 20800
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'L-02',
|
code: 'L-02',
|
||||||
side: 'CAPITAL-LIAB',
|
side: 'CAPITAL-LIAB',
|
||||||
group: 'Capital & Reserves',
|
group: 'Capital & Reserves',
|
||||||
label: 'Reserves & Surplus',
|
label: 'Reserves & Surplus',
|
||||||
amount: 155847.97,
|
amount: 32400,
|
||||||
prevAmount: 145524.37
|
prevAmount: 28640
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'L-03',
|
code: 'L-03',
|
||||||
side: 'CAPITAL-LIAB',
|
side: 'CAPITAL-LIAB',
|
||||||
group: 'Deposits',
|
group: 'Deposits',
|
||||||
label: 'Deposits',
|
label: 'Demand Deposits — Current Accounts',
|
||||||
amount: 1154054.96,
|
amount: 84200,
|
||||||
prevAmount: 997239.51
|
prevAmount: 78420
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'L-04',
|
code: 'L-04',
|
||||||
side: 'CAPITAL-LIAB',
|
side: 'CAPITAL-LIAB',
|
||||||
group: 'Borrowings',
|
group: 'Deposits',
|
||||||
label: 'Borrowings (NABARD / Other Banks)',
|
label: 'Savings Bank Deposits',
|
||||||
amount: 1113954.48,
|
amount: 218600,
|
||||||
prevAmount: 993301.8
|
prevAmount: 198400
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'L-05',
|
code: 'L-05',
|
||||||
side: 'CAPITAL-LIAB',
|
side: 'CAPITAL-LIAB',
|
||||||
|
group: 'Deposits',
|
||||||
|
label: 'Term Deposits',
|
||||||
|
amount: 462800,
|
||||||
|
prevAmount: 412400
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'L-06',
|
||||||
|
side: 'CAPITAL-LIAB',
|
||||||
|
group: 'Borrowings',
|
||||||
|
label: 'Borrowings from RBI / NABARD',
|
||||||
|
amount: 14180,
|
||||||
|
prevAmount: 16240
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'L-07',
|
||||||
|
side: 'CAPITAL-LIAB',
|
||||||
|
group: 'Borrowings',
|
||||||
|
label: 'Other Borrowings',
|
||||||
|
amount: 10000,
|
||||||
|
prevAmount: 9420
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'L-08',
|
||||||
|
side: 'CAPITAL-LIAB',
|
||||||
|
group: 'Other Liabilities',
|
||||||
|
label: 'Bills Payable',
|
||||||
|
amount: 4420,
|
||||||
|
prevAmount: 3980
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'L-09',
|
||||||
|
side: 'CAPITAL-LIAB',
|
||||||
|
group: 'Other Liabilities',
|
||||||
|
label: 'Inter-Office Adjustments (Net)',
|
||||||
|
amount: 1820,
|
||||||
|
prevAmount: 1640
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'L-10',
|
||||||
|
side: 'CAPITAL-LIAB',
|
||||||
|
group: 'Other Liabilities',
|
||||||
|
label: 'Interest Accrued',
|
||||||
|
amount: 6240,
|
||||||
|
prevAmount: 5840
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'L-11',
|
||||||
|
side: 'CAPITAL-LIAB',
|
||||||
group: 'Other Liabilities',
|
group: 'Other Liabilities',
|
||||||
label: 'Other Liabilities & Provisions',
|
label: 'Other Liabilities & Provisions',
|
||||||
amount: 121037.61,
|
amount: 5940,
|
||||||
prevAmount: 124706.3
|
prevAmount: 5420
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'A-01',
|
code: 'A-01',
|
||||||
side: 'ASSETS',
|
side: 'ASSETS',
|
||||||
group: 'Cash & Bank Balances',
|
group: 'Cash & Bank Balances',
|
||||||
label: 'Cash & Balances with RBI',
|
label: 'Cash on Hand',
|
||||||
amount: 32627.16,
|
amount: 8420,
|
||||||
prevAmount: 33361.93
|
prevAmount: 7820
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'A-02',
|
code: 'A-02',
|
||||||
side: 'ASSETS',
|
side: 'ASSETS',
|
||||||
group: 'Money at Call',
|
group: 'Cash & Bank Balances',
|
||||||
label: 'Balances with Other Banks & Money at Call/Short Notice',
|
label: 'Balances with RBI',
|
||||||
amount: 604619.45,
|
amount: 38400,
|
||||||
prevAmount: 549599.47
|
prevAmount: 34200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'A-03',
|
code: 'A-03',
|
||||||
side: 'ASSETS',
|
side: 'ASSETS',
|
||||||
group: 'Investments',
|
group: 'Cash & Bank Balances',
|
||||||
label: 'Investments',
|
label: 'Balances with Other Banks — Current',
|
||||||
amount: 316953.61,
|
amount: 14800,
|
||||||
prevAmount: 248274.61
|
prevAmount: 12400
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'A-04',
|
code: 'A-04',
|
||||||
side: 'ASSETS',
|
side: 'ASSETS',
|
||||||
group: 'Advances',
|
group: 'Cash & Bank Balances',
|
||||||
label: 'Loans & Advances',
|
label: 'Balances with Other Banks — Term Deposits',
|
||||||
amount: 1627711.4,
|
amount: 10000,
|
||||||
prevAmount: 1466401.79
|
prevAmount: 9200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'A-05',
|
code: 'A-05',
|
||||||
side: 'ASSETS',
|
side: 'ASSETS',
|
||||||
group: 'Fixed Assets',
|
group: 'Money at Call',
|
||||||
label: 'Fixed Assets (Property, Plant & Equipment)',
|
label: 'Money at Call & Short Notice',
|
||||||
amount: 33791.75,
|
amount: 12400,
|
||||||
prevAmount: 33727.66
|
prevAmount: 10800
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'A-06',
|
code: 'A-06',
|
||||||
side: 'ASSETS',
|
side: 'ASSETS',
|
||||||
|
group: 'Investments',
|
||||||
|
label: 'Government Securities (SLR)',
|
||||||
|
amount: 142400,
|
||||||
|
prevAmount: 128600
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'A-07',
|
||||||
|
side: 'ASSETS',
|
||||||
|
group: 'Investments',
|
||||||
|
label: 'Other Approved Securities',
|
||||||
|
amount: 28200,
|
||||||
|
prevAmount: 24800
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'A-08',
|
||||||
|
side: 'ASSETS',
|
||||||
|
group: 'Investments',
|
||||||
|
label: 'Shares / Debentures / Bonds',
|
||||||
|
amount: 14000,
|
||||||
|
prevAmount: 12200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'A-09',
|
||||||
|
side: 'ASSETS',
|
||||||
|
group: 'Advances',
|
||||||
|
label: 'Cash Credit, Overdraft & Demand Loans',
|
||||||
|
amount: 168400,
|
||||||
|
prevAmount: 152800
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'A-10',
|
||||||
|
side: 'ASSETS',
|
||||||
|
group: 'Advances',
|
||||||
|
label: 'Term Loans (Secured)',
|
||||||
|
amount: 217865,
|
||||||
|
prevAmount: 198400
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'A-11',
|
||||||
|
side: 'ASSETS',
|
||||||
|
group: 'Advances',
|
||||||
|
label: 'Term Loans (Unsecured)',
|
||||||
|
amount: 30000,
|
||||||
|
prevAmount: 27200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'A-12',
|
||||||
|
side: 'ASSETS',
|
||||||
|
group: 'Fixed Assets',
|
||||||
|
label: 'Premises (Net of Depreciation)',
|
||||||
|
amount: 9420,
|
||||||
|
prevAmount: 8820
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'A-13',
|
||||||
|
side: 'ASSETS',
|
||||||
|
group: 'Fixed Assets',
|
||||||
|
label: 'Other Fixed Assets',
|
||||||
|
amount: 4780,
|
||||||
|
prevAmount: 4620
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'A-14',
|
||||||
|
side: 'ASSETS',
|
||||||
group: 'Other Assets',
|
group: 'Other Assets',
|
||||||
label: 'Other Assets',
|
label: 'Inter-Office Adjustments (Net)',
|
||||||
amount: 22028.83,
|
amount: 0,
|
||||||
prevAmount: 18249.4
|
prevAmount: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'A-15',
|
||||||
|
side: 'ASSETS',
|
||||||
|
group: 'Other Assets',
|
||||||
|
label: 'Tax Paid in Advance & TDS',
|
||||||
|
amount: 5420,
|
||||||
|
prevAmount: 4820
|
||||||
|
},
|
||||||
|
{ code: 'A-16', side: 'ASSETS', group: 'Other Assets', label: 'Stationery & Stamps', amount: 184, prevAmount: 142 },
|
||||||
|
{
|
||||||
|
code: 'A-17',
|
||||||
|
side: 'ASSETS',
|
||||||
|
group: 'Other Assets',
|
||||||
|
label: 'Sundry Debtors & Other Assets',
|
||||||
|
amount: 2876,
|
||||||
|
prevAmount: 2540
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// -------------------- Contingent Liabilities (Schedule 12, ₹ in Lakhs) --------------------
|
|
||||||
// Only the audited TOTAL is reported here — the schedule's individual line items (claims not
|
|
||||||
// acknowledged as debts, letter of guarantees, DEAF transfers, gratuity/RBI-penalty/GST disputes)
|
|
||||||
// could not be re-verified against the source figures with confidence, so they are omitted
|
|
||||||
// rather than risk showing an unverified breakdown.
|
|
||||||
contingentLiabItems: ContingentLiabItem[] = [
|
|
||||||
{ code: 'I', label: 'Claims against the bank not acknowledged as debts' },
|
|
||||||
{ code: 'II', label: 'Letter of Guarantees' },
|
|
||||||
{ code: 'III', label: 'Amounts transferred to Depositor Education and Awareness Fund (DEAF)' },
|
|
||||||
{ code: 'IV', label: 'Other items for which the bank is contingently liable (Gratuity / RBI Penalty / GST Demand)' }
|
|
||||||
];
|
|
||||||
contingentLiab = {
|
|
||||||
current: 1633.3,
|
|
||||||
previous: 1522.13
|
|
||||||
};
|
|
||||||
get clVariation(): number {
|
|
||||||
return this.variation(this.contingentLiab.current, this.contingentLiab.previous);
|
|
||||||
}
|
|
||||||
get clVariationPct(): number {
|
|
||||||
return this.variationPct(this.contingentLiab.current, this.contingentLiab.previous);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- GL Wise DR/CR Data --------------------
|
// -------------------- GL Wise DR/CR Data --------------------
|
||||||
glReport: GlLine[] = [
|
glReport: GlLine[] = [
|
||||||
{
|
{
|
||||||
@@ -903,7 +1008,6 @@ export class AccountingReportsDialogComponent {
|
|||||||
if (this.activeReport === 'PNL') return this.exportPnlCsv();
|
if (this.activeReport === 'PNL') return this.exportPnlCsv();
|
||||||
if (this.activeReport === 'BALANCE-SHEET') return this.exportBsCsv(false);
|
if (this.activeReport === 'BALANCE-SHEET') return this.exportBsCsv(false);
|
||||||
if (this.activeReport === 'COMPARATIVE-BS') return this.exportBsCsv(true);
|
if (this.activeReport === 'COMPARATIVE-BS') return this.exportBsCsv(true);
|
||||||
if (this.activeReport === 'CONTINGENT-LIAB') return this.exportContingentLiabCsv();
|
|
||||||
if (this.activeReport === 'GL-DR-CR') return this.exportGlCsv();
|
if (this.activeReport === 'GL-DR-CR') return this.exportGlCsv();
|
||||||
if (this.activeReport === 'MISC-EXP') return this.exportMiscCsv();
|
if (this.activeReport === 'MISC-EXP') return this.exportMiscCsv();
|
||||||
}
|
}
|
||||||
@@ -1051,49 +1155,6 @@ export class AccountingReportsDialogComponent {
|
|||||||
this.saveCsv(rows, `${comparative ? 'Comparative-' : ''}Balance-Sheet-${this.dateTag()}.csv`);
|
this.saveCsv(rows, `${comparative ? 'Comparative-' : ''}Balance-Sheet-${this.dateTag()}.csv`);
|
||||||
}
|
}
|
||||||
|
|
||||||
exportContingentLiabCsv(): void {
|
|
||||||
const rows: string[][] = [];
|
|
||||||
rows.push([
|
|
||||||
this.bank.name,
|
|
||||||
this.bank.branch
|
|
||||||
]);
|
|
||||||
rows.push([
|
|
||||||
'CONTINGENT LIABILITIES (SCHEDULE 12)',
|
|
||||||
`As on ${this.bank.asOnDate}`
|
|
||||||
]);
|
|
||||||
rows.push([]);
|
|
||||||
rows.push(['Items covered:']);
|
|
||||||
for (const item of this.contingentLiabItems) {
|
|
||||||
rows.push([
|
|
||||||
item.code,
|
|
||||||
item.label
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
rows.push([]);
|
|
||||||
rows.push([
|
|
||||||
'',
|
|
||||||
'TOTAL CONTINGENT LIABILITIES',
|
|
||||||
`FY ${this.bank.fiscalYear} (₹ Lakh)`,
|
|
||||||
`FY ${this.bank.prevFiscalYear} (₹ Lakh)`,
|
|
||||||
'Variation',
|
|
||||||
'Variation %'
|
|
||||||
]);
|
|
||||||
rows.push([
|
|
||||||
'',
|
|
||||||
'',
|
|
||||||
this.contingentLiab.current.toFixed(2),
|
|
||||||
this.contingentLiab.previous.toFixed(2),
|
|
||||||
this.clVariation.toFixed(2),
|
|
||||||
this.clVariationPct.toFixed(2) + '%'
|
|
||||||
]);
|
|
||||||
rows.push([]);
|
|
||||||
rows.push([
|
|
||||||
'Certified by',
|
|
||||||
this.bank.principalOfficer + ' — ' + this.bank.poDesignation
|
|
||||||
]);
|
|
||||||
this.saveCsv(rows, `Contingent-Liabilities-${this.dateTag()}.csv`);
|
|
||||||
}
|
|
||||||
|
|
||||||
exportGlCsv(): void {
|
exportGlCsv(): void {
|
||||||
const rows: string[][] = [];
|
const rows: string[][] = [];
|
||||||
rows.push([
|
rows.push([
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export class AlmReportDialogComponent {
|
|||||||
activeTab: 'dashboard' | 'ladder' | 'monthly' | 'returns' = 'dashboard';
|
activeTab: 'dashboard' | 'ladder' | 'monthly' | 'returns' = 'dashboard';
|
||||||
|
|
||||||
bank = {
|
bank = {
|
||||||
name: 'CBSNexora Bank Ltd',
|
name: 'Bharat Urban Bank Ltd',
|
||||||
branch: 'Head Office',
|
branch: 'Head Office',
|
||||||
asOnDate: '31-03-2026',
|
asOnDate: '31-03-2026',
|
||||||
quarter: 'Q4 FY 2025-26',
|
quarter: 'Q4 FY 2025-26',
|
||||||
@@ -63,7 +63,7 @@ export class AlmReportDialogComponent {
|
|||||||
principalOfficer: 'Karthik Kamath',
|
principalOfficer: 'Karthik Kamath',
|
||||||
formCode: 'Form ALM-1',
|
formCode: 'Form ALM-1',
|
||||||
reportRefNo: '1036',
|
reportRefNo: '1036',
|
||||||
userCode: 'CBSN01',
|
userCode: 'BUBL01',
|
||||||
/** Net Demand & Time Liabilities — denominator for ratios */
|
/** Net Demand & Time Liabilities — denominator for ratios */
|
||||||
ndtl: 740000, // ₹7,400 Cr in Lakhs
|
ndtl: 740000, // ₹7,400 Cr in Lakhs
|
||||||
/** RBI tolerance: cumulative negative gap up to 14 days ≤ 5%, 28 days ≤ 10% */
|
/** RBI tolerance: cumulative negative gap up to 14 days ≤ 5%, 28 days ≤ 10% */
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
|
||||||
export type CimsFormCode = 'OSS-2' | 'PSA' | 'RAL' | 'RCP-UCB' | 'OSS-4' | 'OSS-5' | 'OSS-6' | 'OSS-7';
|
export type CimsFormCode = 'OSS-2' | 'PSA' | 'RAL' | 'RCP-UCB';
|
||||||
|
|
||||||
export interface CimsXbrlDialogData {
|
export interface CimsXbrlDialogData {
|
||||||
initialForm?: CimsFormCode;
|
initialForm?: CimsFormCode;
|
||||||
@@ -64,20 +64,20 @@ export class CimsXbrlDialogComponent {
|
|||||||
activeTab: 'filing' | 'data' | 'preview' = 'filing';
|
activeTab: 'filing' | 'data' | 'preview' = 'filing';
|
||||||
|
|
||||||
bank = {
|
bank = {
|
||||||
name: 'CBSNexora Bank Ltd',
|
name: 'Bharat Urban Bank Ltd',
|
||||||
branch: 'Head Office',
|
branch: 'Head Office',
|
||||||
bsrCode: 'CBSN0000123',
|
bsrCode: 'BUBL0000123',
|
||||||
ifsc: 'CBSN0000123',
|
ifsc: 'BUBL0000123',
|
||||||
legalEntityId: 'CBSN-LEI-2025',
|
legalEntityId: 'BUBL-LEI-2025',
|
||||||
pan: 'AAACB1234L',
|
pan: 'AAACB1234L',
|
||||||
asOnDate: '31-03-2026',
|
asOnDate: '31-03-2026',
|
||||||
quarter: 'Q4 FY 2025-26',
|
quarter: 'Q4 FY 2025-26',
|
||||||
fiscalYear: '2025-26',
|
fiscalYear: '2025-26',
|
||||||
principalOfficer: 'Karthik Kamath',
|
principalOfficer: 'Karthik Kamath',
|
||||||
poDesignation: 'Chief Compliance Officer',
|
poDesignation: 'Chief Compliance Officer',
|
||||||
poEmail: 'compliance@cbsnexorabank.in',
|
poEmail: 'compliance@bharaturbanbank.in',
|
||||||
poMobile: '+91-9988002211',
|
poMobile: '+91-9988002211',
|
||||||
cimsUserId: 'CBSN_CIMS_001',
|
cimsUserId: 'BUBL_CIMS_001',
|
||||||
cimsPortal: 'https://cims.rbi.org.in/'
|
cimsPortal: 'https://cims.rbi.org.in/'
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:CapAdq.CRAR',
|
ref: 'oss2:CapAdq.CRAR',
|
||||||
section: 'Capital Adequacy',
|
section: 'Capital Adequacy',
|
||||||
label: 'CRAR (Total)',
|
label: 'CRAR (Total)',
|
||||||
value: 21.82,
|
value: 14.82,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -105,7 +105,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:CapAdq.Tier1',
|
ref: 'oss2:CapAdq.Tier1',
|
||||||
section: 'Capital Adequacy',
|
section: 'Capital Adequacy',
|
||||||
label: 'Tier-I CRAR',
|
label: 'Tier-I CRAR',
|
||||||
value: 20.51,
|
value: 12.18,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -113,7 +113,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:CapAdq.Tier2',
|
ref: 'oss2:CapAdq.Tier2',
|
||||||
section: 'Capital Adequacy',
|
section: 'Capital Adequacy',
|
||||||
label: 'Tier-II CRAR',
|
label: 'Tier-II CRAR',
|
||||||
value: 1.31,
|
value: 2.64,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -121,7 +121,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Asset.Total',
|
ref: 'oss2:Asset.Total',
|
||||||
section: 'Asset Classification',
|
section: 'Asset Classification',
|
||||||
label: 'Total Advances (₹ Lakh)',
|
label: 'Total Advances (₹ Lakh)',
|
||||||
value: 4112.04,
|
value: 416265,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -129,7 +129,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Asset.Standard',
|
ref: 'oss2:Asset.Standard',
|
||||||
section: 'Asset Classification',
|
section: 'Asset Classification',
|
||||||
label: 'Standard Assets (₹ Lakh)',
|
label: 'Standard Assets (₹ Lakh)',
|
||||||
value: 3845.88,
|
value: 401820,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -137,7 +137,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Asset.SubStd',
|
ref: 'oss2:Asset.SubStd',
|
||||||
section: 'Asset Classification',
|
section: 'Asset Classification',
|
||||||
label: 'Sub-Standard (₹ Lakh)',
|
label: 'Sub-Standard (₹ Lakh)',
|
||||||
value: 102.41,
|
value: 6220,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -145,7 +145,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Asset.Doubtful',
|
ref: 'oss2:Asset.Doubtful',
|
||||||
section: 'Asset Classification',
|
section: 'Asset Classification',
|
||||||
label: 'Doubtful (₹ Lakh)',
|
label: 'Doubtful (₹ Lakh)',
|
||||||
value: 163.75,
|
value: 5840,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -153,7 +153,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Asset.Loss',
|
ref: 'oss2:Asset.Loss',
|
||||||
section: 'Asset Classification',
|
section: 'Asset Classification',
|
||||||
label: 'Loss (₹ Lakh)',
|
label: 'Loss (₹ Lakh)',
|
||||||
value: 0,
|
value: 2385,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -161,7 +161,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Asset.GNPA',
|
ref: 'oss2:Asset.GNPA',
|
||||||
section: 'NPA Position',
|
section: 'NPA Position',
|
||||||
label: 'Gross NPA (₹ Lakh)',
|
label: 'Gross NPA (₹ Lakh)',
|
||||||
value: 266.16,
|
value: 14445,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -169,7 +169,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Asset.NNPA',
|
ref: 'oss2:Asset.NNPA',
|
||||||
section: 'NPA Position',
|
section: 'NPA Position',
|
||||||
label: 'Net NPA (₹ Lakh)',
|
label: 'Net NPA (₹ Lakh)',
|
||||||
value: -96.33,
|
value: 5212,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -177,7 +177,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Asset.GNPAPct',
|
ref: 'oss2:Asset.GNPAPct',
|
||||||
section: 'NPA Position',
|
section: 'NPA Position',
|
||||||
label: 'GNPA %',
|
label: 'GNPA %',
|
||||||
value: 6.47,
|
value: 3.47,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -185,7 +185,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Asset.NNPAPct',
|
ref: 'oss2:Asset.NNPAPct',
|
||||||
section: 'NPA Position',
|
section: 'NPA Position',
|
||||||
label: 'NNPA %',
|
label: 'NNPA %',
|
||||||
value: -2.6,
|
value: 1.27,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -193,7 +193,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Asset.PCR',
|
ref: 'oss2:Asset.PCR',
|
||||||
section: 'NPA Position',
|
section: 'NPA Position',
|
||||||
label: 'Provision Coverage Ratio',
|
label: 'Provision Coverage Ratio',
|
||||||
value: 136.24,
|
value: 64.13,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -201,7 +201,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Profit.NIM',
|
ref: 'oss2:Profit.NIM',
|
||||||
section: 'Profitability',
|
section: 'Profitability',
|
||||||
label: 'Net Interest Margin',
|
label: 'Net Interest Margin',
|
||||||
value: 0.51,
|
value: 3.21,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -209,7 +209,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Profit.ROA',
|
ref: 'oss2:Profit.ROA',
|
||||||
section: 'Profitability',
|
section: 'Profitability',
|
||||||
label: 'Return on Assets',
|
label: 'Return on Assets',
|
||||||
value: 0.67,
|
value: 0.92,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -217,7 +217,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'oss2:Profit.ROE',
|
ref: 'oss2:Profit.ROE',
|
||||||
section: 'Profitability',
|
section: 'Profitability',
|
||||||
label: 'Return on Equity',
|
label: 'Return on Equity',
|
||||||
value: 5.38,
|
value: 11.48,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -254,7 +254,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:Reference.ANBC',
|
ref: 'psa:Reference.ANBC',
|
||||||
section: 'Reference',
|
section: 'Reference',
|
||||||
label: 'Adjusted Net Bank Credit (₹ Lakh)',
|
label: 'Adjusted Net Bank Credit (₹ Lakh)',
|
||||||
value: 3274.83,
|
value: 600000,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -262,7 +262,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:Reference.OBE',
|
ref: 'psa:Reference.OBE',
|
||||||
section: 'Reference',
|
section: 'Reference',
|
||||||
label: 'Credit Equivalent of OBE (₹ Lakh)',
|
label: 'Credit Equivalent of OBE (₹ Lakh)',
|
||||||
value: 0,
|
value: 25000,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -270,7 +270,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.Total',
|
ref: 'psa:PSL.Total',
|
||||||
section: 'PSL Achievement',
|
section: 'PSL Achievement',
|
||||||
label: 'Total PSL Outstanding (₹ Lakh)',
|
label: 'Total PSL Outstanding (₹ Lakh)',
|
||||||
value: 2485.67,
|
value: 241700,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -278,7 +278,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.TotalPct',
|
ref: 'psa:PSL.TotalPct',
|
||||||
section: 'PSL Achievement',
|
section: 'PSL Achievement',
|
||||||
label: 'Total PSL % of ANBC',
|
label: 'Total PSL % of ANBC',
|
||||||
value: 75.9,
|
value: 40.28,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -286,7 +286,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.Agri',
|
ref: 'psa:PSL.Agri',
|
||||||
section: 'Sub-targets',
|
section: 'Sub-targets',
|
||||||
label: 'Agriculture (₹ Lakh)',
|
label: 'Agriculture (₹ Lakh)',
|
||||||
value: 39.0,
|
value: 124000,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -294,7 +294,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.AgriPct',
|
ref: 'psa:PSL.AgriPct',
|
||||||
section: 'Sub-targets',
|
section: 'Sub-targets',
|
||||||
label: 'Agriculture % of ANBC',
|
label: 'Agriculture % of ANBC',
|
||||||
value: 1.19,
|
value: 20.67,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -302,7 +302,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.SMF',
|
ref: 'psa:PSL.SMF',
|
||||||
section: 'Sub-targets',
|
section: 'Sub-targets',
|
||||||
label: 'Small & Marginal Farmers (₹ Lakh)',
|
label: 'Small & Marginal Farmers (₹ Lakh)',
|
||||||
value: 0,
|
value: 72000,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -310,23 +310,23 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.SMFPct',
|
ref: 'psa:PSL.SMFPct',
|
||||||
section: 'Sub-targets',
|
section: 'Sub-targets',
|
||||||
label: 'SMF % of ANBC',
|
label: 'SMF % of ANBC',
|
||||||
value: 0,
|
value: 12.0,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ref: 'psa:PSL.MicroEnt',
|
ref: 'psa:PSL.MicroEnt',
|
||||||
section: 'Sub-targets',
|
section: 'Sub-targets',
|
||||||
label: 'MSME Enterprises (₹ Lakh)',
|
label: 'Micro Enterprises (₹ Lakh)',
|
||||||
value: 2313.83,
|
value: 51000,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ref: 'psa:PSL.MicroPct',
|
ref: 'psa:PSL.MicroPct',
|
||||||
section: 'Sub-targets',
|
section: 'Sub-targets',
|
||||||
label: 'MSME Enterprises % of ANBC',
|
label: 'Micro Enterprises % of ANBC',
|
||||||
value: 70.65,
|
value: 8.5,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -334,7 +334,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.Weaker',
|
ref: 'psa:PSL.Weaker',
|
||||||
section: 'Sub-targets',
|
section: 'Sub-targets',
|
||||||
label: 'Weaker Sections (₹ Lakh)',
|
label: 'Weaker Sections (₹ Lakh)',
|
||||||
value: 990.28,
|
value: 85500,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -342,7 +342,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.WeakerPct',
|
ref: 'psa:PSL.WeakerPct',
|
||||||
section: 'Sub-targets',
|
section: 'Sub-targets',
|
||||||
label: 'Weaker Sections % of ANBC',
|
label: 'Weaker Sections % of ANBC',
|
||||||
value: 30.22,
|
value: 14.25,
|
||||||
unit: 'Percent',
|
unit: 'Percent',
|
||||||
type: 'percent'
|
type: 'percent'
|
||||||
},
|
},
|
||||||
@@ -350,7 +350,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.Housing',
|
ref: 'psa:PSL.Housing',
|
||||||
section: 'Other Categories',
|
section: 'Other Categories',
|
||||||
label: 'Housing (₹ Lakh)',
|
label: 'Housing (₹ Lakh)',
|
||||||
value: 115.12,
|
value: 9200,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -358,7 +358,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.Education',
|
ref: 'psa:PSL.Education',
|
||||||
section: 'Other Categories',
|
section: 'Other Categories',
|
||||||
label: 'Education (₹ Lakh)',
|
label: 'Education (₹ Lakh)',
|
||||||
value: 17.32,
|
value: 4200,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -366,7 +366,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.Export',
|
ref: 'psa:PSL.Export',
|
||||||
section: 'Other Categories',
|
section: 'Other Categories',
|
||||||
label: 'Export Credit (₹ Lakh)',
|
label: 'Export Credit (₹ Lakh)',
|
||||||
value: 0,
|
value: 3800,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -374,7 +374,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'psa:PSL.RenEnergy',
|
ref: 'psa:PSL.RenEnergy',
|
||||||
section: 'Other Categories',
|
section: 'Other Categories',
|
||||||
label: 'Renewable Energy (₹ Lakh)',
|
label: 'Renewable Energy (₹ Lakh)',
|
||||||
value: 0,
|
value: 2400,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
}
|
}
|
||||||
@@ -395,15 +395,15 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Liab.Capital',
|
ref: 'ral:Liab.Capital',
|
||||||
section: 'Liabilities',
|
section: 'Liabilities',
|
||||||
label: 'Capital (₹ Lakh)',
|
label: 'Capital (₹ Lakh)',
|
||||||
value: 272.78,
|
value: 22000,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ref: 'ral:Liab.Reserves',
|
ref: 'ral:Liab.Reserves',
|
||||||
section: 'Liabilities',
|
section: 'Liabilities',
|
||||||
label: 'Reserves, Surplus & Profit (₹ Lakh)',
|
label: 'Reserves & Surplus (₹ Lakh)',
|
||||||
value: 712.6,
|
value: 32400,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -411,7 +411,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Liab.DepDemand',
|
ref: 'ral:Liab.DepDemand',
|
||||||
section: 'Liabilities',
|
section: 'Liabilities',
|
||||||
label: 'Demand Deposits (₹ Lakh)',
|
label: 'Demand Deposits (₹ Lakh)',
|
||||||
value: 195.92,
|
value: 84200,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -419,7 +419,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Liab.DepSavings',
|
ref: 'ral:Liab.DepSavings',
|
||||||
section: 'Liabilities',
|
section: 'Liabilities',
|
||||||
label: 'Savings Deposits (₹ Lakh)',
|
label: 'Savings Deposits (₹ Lakh)',
|
||||||
value: 2081.16,
|
value: 218600,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -427,7 +427,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Liab.DepTerm',
|
ref: 'ral:Liab.DepTerm',
|
||||||
section: 'Liabilities',
|
section: 'Liabilities',
|
||||||
label: 'Term Deposits (₹ Lakh)',
|
label: 'Term Deposits (₹ Lakh)',
|
||||||
value: 4066.01,
|
value: 462800,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -435,7 +435,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Liab.Borrow',
|
ref: 'ral:Liab.Borrow',
|
||||||
section: 'Liabilities',
|
section: 'Liabilities',
|
||||||
label: 'Borrowings (₹ Lakh)',
|
label: 'Borrowings (₹ Lakh)',
|
||||||
value: 0,
|
value: 24180,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -443,7 +443,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Liab.Other',
|
ref: 'ral:Liab.Other',
|
||||||
section: 'Liabilities',
|
section: 'Liabilities',
|
||||||
label: 'Other Liabilities & Provisions (₹ Lakh)',
|
label: 'Other Liabilities & Provisions (₹ Lakh)',
|
||||||
value: 780.73,
|
value: 18420,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -451,7 +451,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Asset.Cash',
|
ref: 'ral:Asset.Cash',
|
||||||
section: 'Assets',
|
section: 'Assets',
|
||||||
label: 'Cash on Hand (₹ Lakh)',
|
label: 'Cash on Hand (₹ Lakh)',
|
||||||
value: 99.96,
|
value: 8420,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -459,15 +459,15 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Asset.RBI',
|
ref: 'ral:Asset.RBI',
|
||||||
section: 'Assets',
|
section: 'Assets',
|
||||||
label: 'Balances with RBI (₹ Lakh)',
|
label: 'Balances with RBI (₹ Lakh)',
|
||||||
value: 0,
|
value: 38400,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ref: 'ral:Asset.Banks',
|
ref: 'ral:Asset.Banks',
|
||||||
section: 'Assets',
|
section: 'Assets',
|
||||||
label: 'Balances with Banks & Other Institutions (₹ Lakh)',
|
label: 'Balances with Banks (₹ Lakh)',
|
||||||
value: 1349.53,
|
value: 24800,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -475,7 +475,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Asset.Money',
|
ref: 'ral:Asset.Money',
|
||||||
section: 'Assets',
|
section: 'Assets',
|
||||||
label: 'Money at Call & Short Notice (₹ Lakh)',
|
label: 'Money at Call & Short Notice (₹ Lakh)',
|
||||||
value: 0,
|
value: 12400,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -483,7 +483,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Asset.Invest',
|
ref: 'ral:Asset.Invest',
|
||||||
section: 'Assets',
|
section: 'Assets',
|
||||||
label: 'Investments (₹ Lakh)',
|
label: 'Investments (₹ Lakh)',
|
||||||
value: 2342.97,
|
value: 184600,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -491,7 +491,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Asset.Adv',
|
ref: 'ral:Asset.Adv',
|
||||||
section: 'Assets',
|
section: 'Assets',
|
||||||
label: 'Advances (₹ Lakh)',
|
label: 'Advances (₹ Lakh)',
|
||||||
value: 3912.88,
|
value: 416265,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -499,23 +499,23 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:Asset.Fixed',
|
ref: 'ral:Asset.Fixed',
|
||||||
section: 'Assets',
|
section: 'Assets',
|
||||||
label: 'Fixed Assets (₹ Lakh)',
|
label: 'Fixed Assets (₹ Lakh)',
|
||||||
value: 49.48,
|
value: 14200,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ref: 'ral:Asset.Other',
|
ref: 'ral:Asset.Other',
|
||||||
section: 'Assets',
|
section: 'Assets',
|
||||||
label: 'Other Assets & Interest Receivable (₹ Lakh)',
|
label: 'Other Assets (₹ Lakh)',
|
||||||
value: 354.38,
|
value: 12480,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ref: 'ral:OBE.Guar',
|
ref: 'ral:OBE.Guar',
|
||||||
section: 'Off-Balance Sheet',
|
section: 'Off-Balance Sheet',
|
||||||
label: 'Contingent Liabilities (₹ Lakh)',
|
label: 'Bank Guarantees (₹ Lakh)',
|
||||||
value: 26.38,
|
value: 18420,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
},
|
},
|
||||||
@@ -523,7 +523,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
ref: 'ral:OBE.LC',
|
ref: 'ral:OBE.LC',
|
||||||
section: 'Off-Balance Sheet',
|
section: 'Off-Balance Sheet',
|
||||||
label: 'Letters of Credit (₹ Lakh)',
|
label: 'Letters of Credit (₹ Lakh)',
|
||||||
value: 0,
|
value: 6580,
|
||||||
unit: 'INR-Lakh',
|
unit: 'INR-Lakh',
|
||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
}
|
}
|
||||||
@@ -653,293 +653,6 @@ export class CimsXbrlDialogComponent {
|
|||||||
type: 'monetary'
|
type: 'monetary'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
'OSS-4': {
|
|
||||||
code: 'OSS-4',
|
|
||||||
name: 'OSS-4',
|
|
||||||
fullName: 'Connected Lending & Large Exposure Statement',
|
|
||||||
description:
|
|
||||||
'Quarterly statement of loans to directors/relatives, senior officers, and exposures exceeding prudential thresholds. Illustrative figures — no source filing was provided for this return; values are modelled on this bank\'s own capital/advances scale for consistency.',
|
|
||||||
cimsCode: 'CIMS_OSS_004',
|
|
||||||
taxonomy: 'rbi-cims-oss4-2024-04-01',
|
|
||||||
periodicity: 'Quarterly',
|
|
||||||
lastFiled: '15-01-2026',
|
|
||||||
nextDue: '15-04-2026',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
ref: 'oss4:Connected.Directors',
|
|
||||||
section: 'Connected Lending',
|
|
||||||
label: 'Loans to Directors & Relatives (₹ Lakh)',
|
|
||||||
value: 42.5,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss4:Connected.SrOfficers',
|
|
||||||
section: 'Connected Lending',
|
|
||||||
label: 'Loans to Senior Officers (₹ Lakh)',
|
|
||||||
value: 18.75,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss4:Connected.Accounts',
|
|
||||||
section: 'Connected Lending',
|
|
||||||
label: 'Number of Connected Lending Accounts',
|
|
||||||
value: 12,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss4:LargeExp.Amount',
|
|
||||||
section: 'Large Exposures',
|
|
||||||
label: 'Exposures > 10% of Capital Funds (₹ Lakh)',
|
|
||||||
value: 285.0,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss4:LargeExp.Pct',
|
|
||||||
section: 'Large Exposures',
|
|
||||||
label: 'Large Exposures as % of Capital Funds',
|
|
||||||
value: 30.58,
|
|
||||||
unit: 'Percent',
|
|
||||||
type: 'percent'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss4:LargeExp.SingleBreach',
|
|
||||||
section: 'Large Exposures',
|
|
||||||
label: 'Single Borrower Limit Breaches',
|
|
||||||
value: 0,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss4:LargeExp.GroupBreach',
|
|
||||||
section: 'Large Exposures',
|
|
||||||
label: 'Group Borrower Limit Breaches',
|
|
||||||
value: 0,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'OSS-5': {
|
|
||||||
code: 'OSS-5',
|
|
||||||
name: 'OSS-5',
|
|
||||||
fullName: 'Institutional & Membership Profile',
|
|
||||||
description:
|
|
||||||
'Annual statement of branch network, staff strength, membership and depositor/borrower base. Illustrative figures — no source filing was provided for this return; membership share capital is aligned to this bank\'s real paid-up capital figure.',
|
|
||||||
cimsCode: 'CIMS_OSS_005',
|
|
||||||
taxonomy: 'rbi-cims-oss5-2024-04-01',
|
|
||||||
periodicity: 'Annual',
|
|
||||||
lastFiled: '30-04-2025',
|
|
||||||
nextDue: '30-04-2026',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
ref: 'oss5:Profile.Branches',
|
|
||||||
section: 'Network',
|
|
||||||
label: 'Number of Branches',
|
|
||||||
value: 8,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss5:Profile.Employees',
|
|
||||||
section: 'Network',
|
|
||||||
label: 'Number of Employees',
|
|
||||||
value: 64,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss5:Profile.AreaOfOperation',
|
|
||||||
section: 'Network',
|
|
||||||
label: 'Area of Operation',
|
|
||||||
value: 'Single District',
|
|
||||||
type: 'string'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss5:Profile.Members',
|
|
||||||
section: 'Membership',
|
|
||||||
label: 'Number of Regular Members',
|
|
||||||
value: 18450,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss5:Profile.ShareCapital',
|
|
||||||
section: 'Membership',
|
|
||||||
label: 'Membership Share Capital (₹ Lakh)',
|
|
||||||
value: 272.78,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss5:Profile.Depositors',
|
|
||||||
section: 'Customer Base',
|
|
||||||
label: 'Number of Depositors',
|
|
||||||
value: 42180,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss5:Profile.Borrowers',
|
|
||||||
section: 'Customer Base',
|
|
||||||
label: 'Number of Borrowers',
|
|
||||||
value: 3860,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'OSS-6': {
|
|
||||||
code: 'OSS-6',
|
|
||||||
name: 'OSS-6',
|
|
||||||
fullName: 'Structural Liquidity & Interest Rate Sensitivity Statement',
|
|
||||||
description:
|
|
||||||
'Fortnightly maturity-bucket gap analysis of rate-sensitive assets and liabilities. Illustrative figures — no source filing was provided for this return.',
|
|
||||||
cimsCode: 'CIMS_OSS_006',
|
|
||||||
taxonomy: 'rbi-cims-oss6-2024-04-01',
|
|
||||||
periodicity: 'Monthly',
|
|
||||||
lastFiled: '14-04-2026',
|
|
||||||
nextDue: '28-04-2026',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
ref: 'oss6:Gap.1to14Days',
|
|
||||||
section: 'Maturity Gap (₹ Lakh)',
|
|
||||||
label: '1-14 Days',
|
|
||||||
value: -45.2,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss6:Gap.15to28Days',
|
|
||||||
section: 'Maturity Gap (₹ Lakh)',
|
|
||||||
label: '15-28 Days',
|
|
||||||
value: -12.8,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss6:Gap.29DaysTo3M',
|
|
||||||
section: 'Maturity Gap (₹ Lakh)',
|
|
||||||
label: '29 Days to 3 Months',
|
|
||||||
value: 88.4,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss6:Gap.Over3To6M',
|
|
||||||
section: 'Maturity Gap (₹ Lakh)',
|
|
||||||
label: 'Over 3 Months to 6 Months',
|
|
||||||
value: 62.15,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss6:Gap.Over6MTo1Y',
|
|
||||||
section: 'Maturity Gap (₹ Lakh)',
|
|
||||||
label: 'Over 6 Months to 1 Year',
|
|
||||||
value: -28.9,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss6:Gap.Over1To3Y',
|
|
||||||
section: 'Maturity Gap (₹ Lakh)',
|
|
||||||
label: 'Over 1 Year to 3 Years',
|
|
||||||
value: 210.6,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss6:Gap.Over3To5Y',
|
|
||||||
section: 'Maturity Gap (₹ Lakh)',
|
|
||||||
label: 'Over 3 Years to 5 Years',
|
|
||||||
value: 95.3,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss6:Gap.Over5Y',
|
|
||||||
section: 'Maturity Gap (₹ Lakh)',
|
|
||||||
label: 'Over 5 Years',
|
|
||||||
value: -18.75,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss6:Liq.CumNegGapPct',
|
|
||||||
section: 'Liquidity Ratios',
|
|
||||||
label: 'Cumulative Negative Gap (14-Day) as % of Cumulative Outflows',
|
|
||||||
value: 4.85,
|
|
||||||
unit: 'Percent',
|
|
||||||
type: 'percent'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'OSS-7': {
|
|
||||||
code: 'OSS-7',
|
|
||||||
name: 'OSS-7',
|
|
||||||
fullName: 'Statement of Frauds & Customer Complaints',
|
|
||||||
description:
|
|
||||||
'Quarterly statement of frauds reported and customer-complaint disposal. Illustrative figures — no source filing was provided for this return.',
|
|
||||||
cimsCode: 'CIMS_OSS_007',
|
|
||||||
taxonomy: 'rbi-cims-oss7-2024-04-01',
|
|
||||||
periodicity: 'Quarterly',
|
|
||||||
lastFiled: '15-01-2026',
|
|
||||||
nextDue: '15-04-2026',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
ref: 'oss7:Fraud.Count',
|
|
||||||
section: 'Frauds',
|
|
||||||
label: 'Number of Frauds Reported',
|
|
||||||
value: 1,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss7:Fraud.Amount',
|
|
||||||
section: 'Frauds',
|
|
||||||
label: 'Amount Involved in Frauds (₹ Lakh)',
|
|
||||||
value: 2.4,
|
|
||||||
unit: 'INR-Lakh',
|
|
||||||
type: 'monetary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss7:Complaints.Received',
|
|
||||||
section: 'Customer Complaints',
|
|
||||||
label: 'Complaints Received (Quarter)',
|
|
||||||
value: 14,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss7:Complaints.Resolved',
|
|
||||||
section: 'Customer Complaints',
|
|
||||||
label: 'Complaints Resolved',
|
|
||||||
value: 12,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss7:Complaints.Pending',
|
|
||||||
section: 'Customer Complaints',
|
|
||||||
label: 'Complaints Pending',
|
|
||||||
value: 2,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ref: 'oss7:Complaints.PendingOver30',
|
|
||||||
section: 'Customer Complaints',
|
|
||||||
label: 'Complaints Pending > 30 Days',
|
|
||||||
value: 0,
|
|
||||||
unit: 'Number',
|
|
||||||
type: 'numeric'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -947,11 +660,7 @@ export class CimsXbrlDialogComponent {
|
|||||||
'OSS-2',
|
'OSS-2',
|
||||||
'PSA',
|
'PSA',
|
||||||
'RAL',
|
'RAL',
|
||||||
'RCP-UCB',
|
'RCP-UCB'
|
||||||
'OSS-4',
|
|
||||||
'OSS-5',
|
|
||||||
'OSS-6',
|
|
||||||
'OSS-7'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
data = inject<CimsXbrlDialogData | null>(MAT_DIALOG_DATA, { optional: true });
|
data = inject<CimsXbrlDialogData | null>(MAT_DIALOG_DATA, { optional: true });
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export class CkycReportsDialogComponent {
|
|||||||
activeReport: CkycReportCode;
|
activeReport: CkycReportCode;
|
||||||
|
|
||||||
bank = {
|
bank = {
|
||||||
name: 'CBSNexora Bank Ltd',
|
name: 'Bharat Urban Bank Ltd',
|
||||||
branch: 'Head Office',
|
branch: 'Head Office',
|
||||||
asOnDate: '29-01-2026',
|
asOnDate: '29-01-2026',
|
||||||
fiscalYear: '2025-26',
|
fiscalYear: '2025-26',
|
||||||
@@ -84,7 +84,7 @@ export class CkycReportsDialogComponent {
|
|||||||
principalOfficer: 'Karthik Kamath',
|
principalOfficer: 'Karthik Kamath',
|
||||||
poDesignation: 'Principal Compliance Officer',
|
poDesignation: 'Principal Compliance Officer',
|
||||||
reportRefNo: '2075/1',
|
reportRefNo: '2075/1',
|
||||||
userCode: 'CBSN01',
|
userCode: 'BUBL01',
|
||||||
fyStart: '01-04-2025',
|
fyStart: '01-04-2025',
|
||||||
fyEnd: '31-03-2026'
|
fyEnd: '31-03-2026'
|
||||||
};
|
};
|
||||||
@@ -121,7 +121,7 @@ export class CkycReportsDialogComponent {
|
|||||||
|
|
||||||
rekyc: RekycRow[] = [
|
rekyc: RekycRow[] = [
|
||||||
{
|
{
|
||||||
ucode: 'CBSN000201',
|
ucode: 'BUBL000201',
|
||||||
name: 'Ravi Kumar Sharma',
|
name: 'Ravi Kumar Sharma',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'LOW',
|
risk: 'LOW',
|
||||||
@@ -132,7 +132,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'BELOW-25-LACS'
|
segment: 'BELOW-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN000418',
|
ucode: 'BUBL000418',
|
||||||
name: 'Priya Patel',
|
name: 'Priya Patel',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'MEDIUM',
|
risk: 'MEDIUM',
|
||||||
@@ -143,7 +143,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'ABOVE-25-LACS'
|
segment: 'ABOVE-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN000522',
|
ucode: 'BUBL000522',
|
||||||
name: 'Arjun Deshmukh',
|
name: 'Arjun Deshmukh',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'LOW',
|
risk: 'LOW',
|
||||||
@@ -154,7 +154,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'BELOW-25-LACS'
|
segment: 'BELOW-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN000711',
|
ucode: 'BUBL000711',
|
||||||
name: 'Vikram Reddy',
|
name: 'Vikram Reddy',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'HIGH',
|
risk: 'HIGH',
|
||||||
@@ -165,7 +165,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'ABOVE-25-LACS'
|
segment: 'ABOVE-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN000814',
|
ucode: 'BUBL000814',
|
||||||
name: 'Lakshmi Iyer',
|
name: 'Lakshmi Iyer',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'LOW',
|
risk: 'LOW',
|
||||||
@@ -176,7 +176,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'BELOW-25-LACS'
|
segment: 'BELOW-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN001012',
|
ucode: 'BUBL001012',
|
||||||
name: 'Nirav Shah',
|
name: 'Nirav Shah',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'MEDIUM',
|
risk: 'MEDIUM',
|
||||||
@@ -187,7 +187,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'BELOW-25-LACS'
|
segment: 'BELOW-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN001145',
|
ucode: 'BUBL001145',
|
||||||
name: 'Kavita Verma',
|
name: 'Kavita Verma',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'HIGH',
|
risk: 'HIGH',
|
||||||
@@ -198,7 +198,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'ABOVE-25-LACS'
|
segment: 'ABOVE-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN001458',
|
ucode: 'BUBL001458',
|
||||||
name: 'Sameer Kapoor',
|
name: 'Sameer Kapoor',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'MEDIUM',
|
risk: 'MEDIUM',
|
||||||
@@ -209,7 +209,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'ABOVE-25-LACS'
|
segment: 'ABOVE-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN001721',
|
ucode: 'BUBL001721',
|
||||||
name: 'Karthik Subramanian',
|
name: 'Karthik Subramanian',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'LOW',
|
risk: 'LOW',
|
||||||
@@ -220,7 +220,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'BELOW-25-LACS'
|
segment: 'BELOW-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN002012',
|
ucode: 'BUBL002012',
|
||||||
name: 'Abhishek Banerjee',
|
name: 'Abhishek Banerjee',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'MEDIUM',
|
risk: 'MEDIUM',
|
||||||
@@ -231,7 +231,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'ABOVE-25-LACS'
|
segment: 'ABOVE-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN002314',
|
ucode: 'BUBL002314',
|
||||||
name: 'Gaurav Thakur',
|
name: 'Gaurav Thakur',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'LOW',
|
risk: 'LOW',
|
||||||
@@ -242,7 +242,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'BELOW-25-LACS'
|
segment: 'BELOW-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN002615',
|
ucode: 'BUBL002615',
|
||||||
name: 'Manmohan Singh Gill',
|
name: 'Manmohan Singh Gill',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'HIGH',
|
risk: 'HIGH',
|
||||||
@@ -253,7 +253,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'BELOW-25-LACS'
|
segment: 'BELOW-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN002918',
|
ucode: 'BUBL002918',
|
||||||
name: 'Sunita Devi',
|
name: 'Sunita Devi',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'LOW',
|
risk: 'LOW',
|
||||||
@@ -264,7 +264,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'BELOW-25-LACS'
|
segment: 'BELOW-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN003221',
|
ucode: 'BUBL003221',
|
||||||
name: 'Deepak Tiwari',
|
name: 'Deepak Tiwari',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'MEDIUM',
|
risk: 'MEDIUM',
|
||||||
@@ -275,7 +275,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'ABOVE-25-LACS'
|
segment: 'ABOVE-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN003524',
|
ucode: 'BUBL003524',
|
||||||
name: 'Harpreet Kaur',
|
name: 'Harpreet Kaur',
|
||||||
customerType: 'INDIVIDUAL',
|
customerType: 'INDIVIDUAL',
|
||||||
risk: 'MEDIUM',
|
risk: 'MEDIUM',
|
||||||
@@ -286,7 +286,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'ABOVE-25-LACS'
|
segment: 'ABOVE-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN003812',
|
ucode: 'BUBL003812',
|
||||||
name: 'Saraswati Mahila Co-op Society',
|
name: 'Saraswati Mahila Co-op Society',
|
||||||
customerType: 'SOCIETY',
|
customerType: 'SOCIETY',
|
||||||
risk: 'MEDIUM',
|
risk: 'MEDIUM',
|
||||||
@@ -297,7 +297,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'ABOVE-25-LACS'
|
segment: 'ABOVE-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN004115',
|
ucode: 'BUBL004115',
|
||||||
name: 'Aditya Enterprises',
|
name: 'Aditya Enterprises',
|
||||||
customerType: 'FIRM',
|
customerType: 'FIRM',
|
||||||
risk: 'HIGH',
|
risk: 'HIGH',
|
||||||
@@ -308,7 +308,7 @@ export class CkycReportsDialogComponent {
|
|||||||
segment: 'ABOVE-25-LACS'
|
segment: 'ABOVE-25-LACS'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ucode: 'CBSN004318',
|
ucode: 'BUBL004318',
|
||||||
name: 'Vasudha Trading HUF',
|
name: 'Vasudha Trading HUF',
|
||||||
customerType: 'HUF',
|
customerType: 'HUF',
|
||||||
risk: 'MEDIUM',
|
risk: 'MEDIUM',
|
||||||
@@ -332,7 +332,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '01',
|
branchCode: '01',
|
||||||
branch: 'Mumbai Main',
|
branch: 'Mumbai Main',
|
||||||
acType: 'SB',
|
acType: 'SB',
|
||||||
clientId: 'CBSN000201',
|
clientId: 'BUBL000201',
|
||||||
customerName: 'Ravi Kumar Sharma',
|
customerName: 'Ravi Kumar Sharma',
|
||||||
oldRisk: 'LOW',
|
oldRisk: 'LOW',
|
||||||
newRisk: 'LOW',
|
newRisk: 'LOW',
|
||||||
@@ -345,7 +345,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '02',
|
branchCode: '02',
|
||||||
branch: 'Pune FC Road',
|
branch: 'Pune FC Road',
|
||||||
acType: 'CA',
|
acType: 'CA',
|
||||||
clientId: 'CBSN000418',
|
clientId: 'BUBL000418',
|
||||||
customerName: 'Priya Patel',
|
customerName: 'Priya Patel',
|
||||||
oldRisk: 'LOW',
|
oldRisk: 'LOW',
|
||||||
newRisk: 'MEDIUM',
|
newRisk: 'MEDIUM',
|
||||||
@@ -358,7 +358,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '03',
|
branchCode: '03',
|
||||||
branch: 'Nagpur Dharampeth',
|
branch: 'Nagpur Dharampeth',
|
||||||
acType: 'TL',
|
acType: 'TL',
|
||||||
clientId: 'CBSN000522',
|
clientId: 'BUBL000522',
|
||||||
customerName: 'Arjun Deshmukh',
|
customerName: 'Arjun Deshmukh',
|
||||||
oldRisk: 'MEDIUM',
|
oldRisk: 'MEDIUM',
|
||||||
newRisk: 'LOW',
|
newRisk: 'LOW',
|
||||||
@@ -371,7 +371,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '04',
|
branchCode: '04',
|
||||||
branch: 'Bengaluru Jayanagar',
|
branch: 'Bengaluru Jayanagar',
|
||||||
acType: 'OD',
|
acType: 'OD',
|
||||||
clientId: 'CBSN000711',
|
clientId: 'BUBL000711',
|
||||||
customerName: 'Vikram Reddy',
|
customerName: 'Vikram Reddy',
|
||||||
oldRisk: 'MEDIUM',
|
oldRisk: 'MEDIUM',
|
||||||
newRisk: 'HIGH',
|
newRisk: 'HIGH',
|
||||||
@@ -384,7 +384,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '05',
|
branchCode: '05',
|
||||||
branch: 'Bengaluru Indiranagar',
|
branch: 'Bengaluru Indiranagar',
|
||||||
acType: 'SB',
|
acType: 'SB',
|
||||||
clientId: 'CBSN000814',
|
clientId: 'BUBL000814',
|
||||||
customerName: 'Lakshmi Iyer',
|
customerName: 'Lakshmi Iyer',
|
||||||
oldRisk: 'LOW',
|
oldRisk: 'LOW',
|
||||||
newRisk: 'LOW',
|
newRisk: 'LOW',
|
||||||
@@ -397,7 +397,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '06',
|
branchCode: '06',
|
||||||
branch: 'Ahmedabad Satellite',
|
branch: 'Ahmedabad Satellite',
|
||||||
acType: 'CC',
|
acType: 'CC',
|
||||||
clientId: 'CBSN001012',
|
clientId: 'BUBL001012',
|
||||||
customerName: 'Nirav Shah',
|
customerName: 'Nirav Shah',
|
||||||
oldRisk: 'LOW',
|
oldRisk: 'LOW',
|
||||||
newRisk: 'MEDIUM',
|
newRisk: 'MEDIUM',
|
||||||
@@ -410,7 +410,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '07',
|
branchCode: '07',
|
||||||
branch: 'Lucknow Gomti Nagar',
|
branch: 'Lucknow Gomti Nagar',
|
||||||
acType: 'CA',
|
acType: 'CA',
|
||||||
clientId: 'CBSN001145',
|
clientId: 'BUBL001145',
|
||||||
customerName: 'Kavita Verma',
|
customerName: 'Kavita Verma',
|
||||||
oldRisk: 'MEDIUM',
|
oldRisk: 'MEDIUM',
|
||||||
newRisk: 'HIGH',
|
newRisk: 'HIGH',
|
||||||
@@ -423,7 +423,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '08',
|
branchCode: '08',
|
||||||
branch: 'Delhi Greater Kailash',
|
branch: 'Delhi Greater Kailash',
|
||||||
acType: 'OD',
|
acType: 'OD',
|
||||||
clientId: 'CBSN001458',
|
clientId: 'BUBL001458',
|
||||||
customerName: 'Sameer Kapoor',
|
customerName: 'Sameer Kapoor',
|
||||||
oldRisk: 'MEDIUM',
|
oldRisk: 'MEDIUM',
|
||||||
newRisk: 'MEDIUM',
|
newRisk: 'MEDIUM',
|
||||||
@@ -436,7 +436,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '09',
|
branchCode: '09',
|
||||||
branch: 'Chennai Valasaravakkam',
|
branch: 'Chennai Valasaravakkam',
|
||||||
acType: 'SB',
|
acType: 'SB',
|
||||||
clientId: 'CBSN001721',
|
clientId: 'BUBL001721',
|
||||||
customerName: 'Karthik Subramanian',
|
customerName: 'Karthik Subramanian',
|
||||||
oldRisk: 'LOW',
|
oldRisk: 'LOW',
|
||||||
newRisk: 'LOW',
|
newRisk: 'LOW',
|
||||||
@@ -449,7 +449,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '10',
|
branchCode: '10',
|
||||||
branch: 'Kolkata Salt Lake',
|
branch: 'Kolkata Salt Lake',
|
||||||
acType: 'TL',
|
acType: 'TL',
|
||||||
clientId: 'CBSN002012',
|
clientId: 'BUBL002012',
|
||||||
customerName: 'Abhishek Banerjee',
|
customerName: 'Abhishek Banerjee',
|
||||||
oldRisk: 'LOW',
|
oldRisk: 'LOW',
|
||||||
newRisk: 'MEDIUM',
|
newRisk: 'MEDIUM',
|
||||||
@@ -462,7 +462,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '11',
|
branchCode: '11',
|
||||||
branch: 'Shimla Sector 4',
|
branch: 'Shimla Sector 4',
|
||||||
acType: 'SB',
|
acType: 'SB',
|
||||||
clientId: 'CBSN002314',
|
clientId: 'BUBL002314',
|
||||||
customerName: 'Gaurav Thakur',
|
customerName: 'Gaurav Thakur',
|
||||||
oldRisk: 'LOW',
|
oldRisk: 'LOW',
|
||||||
newRisk: 'LOW',
|
newRisk: 'LOW',
|
||||||
@@ -475,7 +475,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '12',
|
branchCode: '12',
|
||||||
branch: 'Amritsar Model Town',
|
branch: 'Amritsar Model Town',
|
||||||
acType: 'CC',
|
acType: 'CC',
|
||||||
clientId: 'CBSN002615',
|
clientId: 'BUBL002615',
|
||||||
customerName: 'Manmohan Singh Gill',
|
customerName: 'Manmohan Singh Gill',
|
||||||
oldRisk: 'HIGH',
|
oldRisk: 'HIGH',
|
||||||
newRisk: 'HIGH',
|
newRisk: 'HIGH',
|
||||||
@@ -488,7 +488,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '13',
|
branchCode: '13',
|
||||||
branch: 'Bhopal Arera Colony',
|
branch: 'Bhopal Arera Colony',
|
||||||
acType: 'SB',
|
acType: 'SB',
|
||||||
clientId: 'CBSN002918',
|
clientId: 'BUBL002918',
|
||||||
customerName: 'Sunita Devi',
|
customerName: 'Sunita Devi',
|
||||||
oldRisk: 'LOW',
|
oldRisk: 'LOW',
|
||||||
newRisk: 'LOW',
|
newRisk: 'LOW',
|
||||||
@@ -501,7 +501,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '14',
|
branchCode: '14',
|
||||||
branch: 'Indore Vijay Nagar',
|
branch: 'Indore Vijay Nagar',
|
||||||
acType: 'OD',
|
acType: 'OD',
|
||||||
clientId: 'CBSN003221',
|
clientId: 'BUBL003221',
|
||||||
customerName: 'Deepak Tiwari',
|
customerName: 'Deepak Tiwari',
|
||||||
oldRisk: 'MEDIUM',
|
oldRisk: 'MEDIUM',
|
||||||
newRisk: 'HIGH',
|
newRisk: 'HIGH',
|
||||||
@@ -514,7 +514,7 @@ export class CkycReportsDialogComponent {
|
|||||||
branchCode: '15',
|
branchCode: '15',
|
||||||
branch: 'Ludhiana Sarabha Nagar',
|
branch: 'Ludhiana Sarabha Nagar',
|
||||||
acType: 'TL',
|
acType: 'TL',
|
||||||
clientId: 'CBSN003524',
|
clientId: 'BUBL003524',
|
||||||
customerName: 'Harpreet Kaur',
|
customerName: 'Harpreet Kaur',
|
||||||
oldRisk: 'MEDIUM',
|
oldRisk: 'MEDIUM',
|
||||||
newRisk: 'MEDIUM',
|
newRisk: 'MEDIUM',
|
||||||
|
|||||||
@@ -165,12 +165,12 @@
|
|||||||
<div class="da-compare">
|
<div class="da-compare">
|
||||||
<div class="da-item">
|
<div class="da-item">
|
||||||
<div class="da-label">Total Deposits</div>
|
<div class="da-label">Total Deposits</div>
|
||||||
<div class="da-value deposit">₹ 11,540.55 Cr</div>
|
<div class="da-value deposit">₹ 3,264.42 Cr</div>
|
||||||
<div class="da-bar"><div class="da-fill deposit-fill" style="width: 71%"></div></div>
|
<div class="da-bar"><div class="da-fill deposit-fill" style="width: 78%"></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="da-item">
|
<div class="da-item">
|
||||||
<div class="da-label">Total Advances</div>
|
<div class="da-label">Total Advances</div>
|
||||||
<div class="da-value advance">₹ 16,277.11 Cr</div>
|
<div class="da-value advance">₹ 4,162.65 Cr</div>
|
||||||
<div class="da-bar"><div class="da-fill advance-fill" style="width: 100%"></div></div>
|
<div class="da-bar"><div class="da-fill advance-fill" style="width: 100%"></div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="fdrd-dlg">
|
|
||||||
<!-- HEADER -->
|
|
||||||
<div class="fdrd-head">
|
|
||||||
<div class="fdrd-head-left">
|
|
||||||
<div class="fdrd-head-ic"><fa-icon icon="piggy-bank"></fa-icon></div>
|
|
||||||
<div>
|
|
||||||
<h2 class="fdrd-title">FD / RD Entry</h2>
|
|
||||||
<p class="fdrd-sub">Open a new Fixed Deposit or Recurring Deposit account.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="fdrd-head-actions">
|
|
||||||
<span class="fdrd-meta"> <fa-icon icon="university"></fa-icon> {{ bank.name }} </span>
|
|
||||||
<span class="fdrd-meta"> <fa-icon icon="map-marker-alt"></fa-icon> {{ bank.branch }} </span>
|
|
||||||
<button class="fdrd-close" (click)="close()" matTooltip="Close" aria-label="Close">✕</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="fdrd-body">
|
|
||||||
<!-- Success banner -->
|
|
||||||
<div class="fdrd-success" *ngIf="saved">
|
|
||||||
<fa-icon icon="check-circle"></fa-icon>
|
|
||||||
<div>
|
|
||||||
<strong>{{ lastAccountNo }}</strong> created for <strong>{{ lastClientName }}</strong>
|
|
||||||
<div class="fdrd-success-sub">You can enter another deposit below, or close this dialog.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- FORM -->
|
|
||||||
<div class="fdrd-section">
|
|
||||||
<!-- Row 1: Deposit Info | Client & Amount -->
|
|
||||||
<div class="ws-group-row">
|
|
||||||
<div class="ws-group">
|
|
||||||
<span class="ws-caption">Deposit Info</span>
|
|
||||||
<div class="ws-group__fields">
|
|
||||||
<div class="ws-f" style="width: 200px">
|
|
||||||
<label class="ws-f__lbl">Deposit Type</label>
|
|
||||||
<select class="ws-f__ctl" [(ngModel)]="depositType" (ngModelChange)="onDepositTypeChange()">
|
|
||||||
<option *ngFor="let dt of depositTypes" [value]="dt.code">{{ dt.label }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="ws-f" style="width: 150px">
|
|
||||||
<label class="ws-f__lbl">Pay Mode</label>
|
|
||||||
<select class="ws-f__ctl" [(ngModel)]="payMode">
|
|
||||||
<option *ngFor="let m of payModes" [value]="m">{{ m }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ws-vdivider"></div>
|
|
||||||
|
|
||||||
<div class="ws-group ws-group--flex">
|
|
||||||
<span class="ws-caption">Client & Amount</span>
|
|
||||||
<div class="ws-group__fields">
|
|
||||||
<div class="ws-f" style="flex: 1.5; min-width: 180px">
|
|
||||||
<label class="ws-f__lbl">Client</label>
|
|
||||||
<select class="ws-f__ctl" [(ngModel)]="clientId">
|
|
||||||
<option value="" disabled>-- Select Client --</option>
|
|
||||||
<option *ngFor="let c of clients" [value]="c.id">{{ c.name }} ({{ c.id }})</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="ws-f" style="flex: 1; min-width: 120px">
|
|
||||||
<label class="ws-f__lbl">{{ isRecurring ? 'Monthly Instalment (₹)' : 'Deposit Amount (₹)' }}</label>
|
|
||||||
<input
|
|
||||||
class="ws-f__ctl ws-f__ctl--mono"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
[(ngModel)]="principal"
|
|
||||||
placeholder="e.g. 100000"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Row 2: Term & Rate | Instructions -->
|
|
||||||
<div class="ws-group-row ws-group-row--mt">
|
|
||||||
<div class="ws-group">
|
|
||||||
<span class="ws-caption">Interest & Tenure</span>
|
|
||||||
<div class="ws-group__fields">
|
|
||||||
<div class="ws-f" style="width: 110px">
|
|
||||||
<label class="ws-f__lbl">Int. Rate (% p.a.)</label>
|
|
||||||
<input class="ws-f__ctl" type="number" min="0" step="0.05" [(ngModel)]="interestRate" />
|
|
||||||
</div>
|
|
||||||
<div class="ws-f" style="width: 240px">
|
|
||||||
<label class="ws-f__lbl">Tenure</label>
|
|
||||||
<div class="fdrd-tenure-row">
|
|
||||||
<input class="ws-f__ctl" type="number" min="0" [(ngModel)]="tenureYears" />
|
|
||||||
<span>Yrs</span>
|
|
||||||
<input class="ws-f__ctl" type="number" min="0" max="11" [(ngModel)]="tenureMonths" />
|
|
||||||
<span>Mo</span>
|
|
||||||
<input class="ws-f__ctl" type="number" min="0" max="30" [(ngModel)]="tenureDays" />
|
|
||||||
<span>Days</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ws-vdivider"></div>
|
|
||||||
|
|
||||||
<div class="ws-group ws-group--flex">
|
|
||||||
<span class="ws-caption">Nominee & Maturity Details</span>
|
|
||||||
<div class="ws-group__fields">
|
|
||||||
<div class="ws-f" style="flex: 1; min-width: 120px">
|
|
||||||
<label class="ws-f__lbl">Interest Payout</label>
|
|
||||||
<select class="ws-f__ctl" [(ngModel)]="interestPayout">
|
|
||||||
<option *ngFor="let o of interestPayoutOptions" [value]="o">{{ o }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="ws-f" style="flex: 1; min-width: 130px">
|
|
||||||
<label class="ws-f__lbl">Nominee Name (optional)</label>
|
|
||||||
<input class="ws-f__ctl" type="text" [(ngModel)]="nomineeName" placeholder="Spouse / Child name" />
|
|
||||||
</div>
|
|
||||||
<div class="ws-f" style="flex: 1.2; min-width: 160px">
|
|
||||||
<label class="ws-f__lbl">Maturity Instruction</label>
|
|
||||||
<select class="ws-f__ctl" [(ngModel)]="maturityInstruction">
|
|
||||||
<option *ngFor="let m of maturityInstructions" [value]="m">{{ m }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- SUMMARY -->
|
|
||||||
<div class="fdrd-summary">
|
|
||||||
<div class="fdrd-stat">
|
|
||||||
<div class="fdrd-stat-l">Principal</div>
|
|
||||||
<div class="fdrd-stat-v">₹ {{ principal || 0 | number: '1.0-2' }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="fdrd-stat">
|
|
||||||
<div class="fdrd-stat-l">Tenure</div>
|
|
||||||
<div class="fdrd-stat-v">{{ tenureYears }}y {{ tenureMonths }}m {{ tenureDays }}d</div>
|
|
||||||
</div>
|
|
||||||
<div class="fdrd-stat">
|
|
||||||
<div class="fdrd-stat-l">Maturity Date</div>
|
|
||||||
<div class="fdrd-stat-v">{{ maturityDate | date: 'dd-MM-yyyy' }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="fdrd-stat fdrd-stat-good">
|
|
||||||
<div class="fdrd-stat-l">Maturity Amount</div>
|
|
||||||
<div class="fdrd-stat-v">₹ {{ maturityAmount | number: '1.2-2' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- FOOTER -->
|
|
||||||
<div class="fdrd-foot">
|
|
||||||
<div class="fdrd-foot-l">
|
|
||||||
<fa-icon icon="info-circle"></fa-icon> Demo entry — quarterly-compounded interest, no real account is
|
|
||||||
created.
|
|
||||||
</div>
|
|
||||||
<div class="fdrd-foot-actions">
|
|
||||||
<button mat-stroked-button class="fdrd-btn-ghost" (click)="resetForm()">
|
|
||||||
<fa-icon icon="undo"></fa-icon> Reset
|
|
||||||
</button>
|
|
||||||
<button mat-stroked-button class="fdrd-btn-save" [disabled]="!isValid" (click)="save()">
|
|
||||||
<fa-icon icon="save"></fa-icon> Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,395 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 {
|
|
||||||
display: block;
|
|
||||||
font-family: 'Hanken Grotesk', Inter, sans-serif;
|
|
||||||
color: #1a2a3e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-dlg {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* HEADER - Matches .ws-header in clerk dashboard */
|
|
||||||
.fdrd-head {
|
|
||||||
padding: 0 12px;
|
|
||||||
height: 32px;
|
|
||||||
background: linear-gradient(90deg, #0d1b2e 0%, #162542 100%);
|
|
||||||
border-bottom: 2px solid #e87722;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 16px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-head-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-head-ic {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: #e87722;
|
|
||||||
color: #fff;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 11px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-title {
|
|
||||||
margin: 0;
|
|
||||||
font-family: Newsreader, Georgia, serif;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #e8f0fb;
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-sub {
|
|
||||||
display: none; /* Hide subtitle in header to match clean style */
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-head-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-meta {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 11px;
|
|
||||||
color: #7a95b0;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
fa-icon {
|
|
||||||
color: #5a7589;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-close {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: #7a95b0;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: all 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgb(255 255 255 / 10%);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BODY - Styled like canvas + card grid */
|
|
||||||
.fdrd-body {
|
|
||||||
padding: 16px 20px;
|
|
||||||
overflow-y: auto;
|
|
||||||
flex: 1;
|
|
||||||
background: #f0f4f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-success {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #dcfce7;
|
|
||||||
color: #15803d;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
fa-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-top: 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-success-sub {
|
|
||||||
margin-top: 2px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 400;
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card container for form */
|
|
||||||
.fdrd-section {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #dce4ef;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 16px 20px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Transaction Entry Style Layout mappings */
|
|
||||||
.ws-group-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 0;
|
|
||||||
|
|
||||||
&--mt {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ws-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&--flex {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__fields {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
height: 100%;
|
|
||||||
align-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ws-caption {
|
|
||||||
font-family: Newsreader, Georgia, serif;
|
|
||||||
font-style: italic;
|
|
||||||
font-size: 11px;
|
|
||||||
color: #7a95b0;
|
|
||||||
padding-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ws-vdivider {
|
|
||||||
width: 1px;
|
|
||||||
background: #dce4ef;
|
|
||||||
margin: 12px 10px 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ws-f {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 3px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&__lbl {
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
color: #7a95b0;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__ctl {
|
|
||||||
height: 26px;
|
|
||||||
padding: 0 7px;
|
|
||||||
border: 1px solid #c8d8eb;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-family: 'Hanken Grotesk', Inter, sans-serif;
|
|
||||||
font-size: 11.5px;
|
|
||||||
color: #1a2a3e;
|
|
||||||
background: #f4f7fc;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
transition:
|
|
||||||
border-color 0.15s,
|
|
||||||
box-shadow 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: #9ab5d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #2563eb;
|
|
||||||
box-shadow: 0 0 0 3px rgb(37 99 235 / 12%);
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--mono {
|
|
||||||
font-family: 'Roboto Mono', monospace;
|
|
||||||
font-size: 11.5px;
|
|
||||||
letter-spacing: 0.03em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-tenure-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.ws-f__ctl {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #7a95b0;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SUMMARY - Styled like info summary block */
|
|
||||||
.fdrd-summary {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: 12px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
background: #f4f7fc;
|
|
||||||
border: 1px solid #c8d8eb;
|
|
||||||
border-radius: 10px;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-stat {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-stat-l {
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #7a95b0;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-stat-v {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1a2a3e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-stat-good .fdrd-stat-v {
|
|
||||||
color: #1a7a4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FOOTER - Styled like action panel */
|
|
||||||
.fdrd-foot {
|
|
||||||
padding: 10px 16px;
|
|
||||||
border-top: 1px solid #dce4ef;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background: #f0f4f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-foot-l {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #7a95b0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-foot-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-btn-ghost {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
height: 28px;
|
|
||||||
padding: 0 12px !important;
|
|
||||||
border-radius: 7px;
|
|
||||||
border: 1px solid #dce4ef !important;
|
|
||||||
background: #fff !important;
|
|
||||||
color: #1a2a3e !important;
|
|
||||||
font-family: 'Hanken Grotesk', sans-serif;
|
|
||||||
font-size: 11.5px !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: none !important;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #f0f4f9 !important;
|
|
||||||
border-color: #a8bdd4 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-btn-save {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
height: 28px;
|
|
||||||
padding: 0 16px !important;
|
|
||||||
border-radius: 7px;
|
|
||||||
border: none !important;
|
|
||||||
background: #e87722 !important;
|
|
||||||
color: #fff !important;
|
|
||||||
font-family: 'Hanken Grotesk', sans-serif;
|
|
||||||
font-size: 11.5px !important;
|
|
||||||
font-weight: 700 !important;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 2px 8px rgb(232 119 34 / 30%) !important;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
filter: brightness(1.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.4;
|
|
||||||
cursor: not-allowed;
|
|
||||||
background: #c8d8eb !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 768px) {
|
|
||||||
.ws-group-row {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ws-vdivider {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ws-group__fields {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fdrd-summary {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright since 2025 Mifos Initiative
|
|
||||||
*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, inject } from '@angular/core';
|
|
||||||
import { CommonModule, DecimalPipe } from '@angular/common';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { MatDialogRef, MatDialogModule } from '@angular/material/dialog';
|
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
||||||
|
|
||||||
type DepositType = 'FD-SINGLE' | 'FD-JOINT' | 'RD-MONTHLY' | 'RD-QUARTERLY';
|
|
||||||
|
|
||||||
interface DemoClient {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-fd-rd-entry-dialog',
|
|
||||||
standalone: true,
|
|
||||||
templateUrl: './fd-rd-entry-dialog.component.html',
|
|
||||||
styleUrls: ['./fd-rd-entry-dialog.component.scss'],
|
|
||||||
imports: [CommonModule, DecimalPipe, FormsModule, MatDialogModule, MatButtonModule, MatTooltipModule, FaIconComponent]
|
|
||||||
})
|
|
||||||
export class FdRdEntryDialogComponent {
|
|
||||||
private dialogRef = inject(MatDialogRef<FdRdEntryDialogComponent>);
|
|
||||||
private snackBar = inject(MatSnackBar);
|
|
||||||
|
|
||||||
bank = {
|
|
||||||
name: 'CBSNexora Bank Ltd',
|
|
||||||
branch: 'Head Office'
|
|
||||||
};
|
|
||||||
|
|
||||||
depositTypes: { code: DepositType; label: string; defaultRate: number }[] = [
|
|
||||||
{ code: 'FD-SINGLE', label: 'Fixed Deposit — Single A/c', defaultRate: 7.25 },
|
|
||||||
{ code: 'FD-JOINT', label: 'Fixed Deposit — Joint A/c', defaultRate: 7.25 },
|
|
||||||
{ code: 'RD-MONTHLY', label: 'Recurring Deposit — Monthly', defaultRate: 7.0 },
|
|
||||||
{ code: 'RD-QUARTERLY', label: 'Recurring Deposit — Quarterly', defaultRate: 7.0 }
|
|
||||||
];
|
|
||||||
|
|
||||||
clients: DemoClient[] = [
|
|
||||||
{ id: 'C-10231', name: 'Ramesh Kumar Sharma' },
|
|
||||||
{ id: 'C-10344', name: 'Priya Enterprises' },
|
|
||||||
{ id: 'C-10412', name: 'Anjali Verma' },
|
|
||||||
{ id: 'C-10567', name: 'Suresh & Sons HUF' },
|
|
||||||
{ id: 'C-10689', name: 'Vikram Traders' }
|
|
||||||
];
|
|
||||||
|
|
||||||
payModes = ['Cash', 'Cheque', 'Transfer from Savings A/c'];
|
|
||||||
interestPayoutOptions = ['Cumulative (Paid on Maturity)', 'Monthly Payout', 'Quarterly Payout'];
|
|
||||||
maturityInstructions = [
|
|
||||||
'Renew Principal & Interest',
|
|
||||||
'Renew Principal Only, Payout Interest',
|
|
||||||
'Payout on Maturity (No Renewal)'
|
|
||||||
];
|
|
||||||
|
|
||||||
depositType: DepositType = 'FD-SINGLE';
|
|
||||||
clientId = '';
|
|
||||||
principal: number | null = null;
|
|
||||||
interestRate = 7.25;
|
|
||||||
tenureYears = 1;
|
|
||||||
tenureMonths = 0;
|
|
||||||
tenureDays = 0;
|
|
||||||
interestPayout = this.interestPayoutOptions[0];
|
|
||||||
payMode = this.payModes[0];
|
|
||||||
nomineeName = '';
|
|
||||||
maturityInstruction = this.maturityInstructions[0];
|
|
||||||
|
|
||||||
saved = false;
|
|
||||||
lastAccountNo = '';
|
|
||||||
lastClientName = '';
|
|
||||||
|
|
||||||
get selectedClient(): DemoClient | undefined {
|
|
||||||
return this.clients.find((c) => c.id === this.clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDepositTypeChange(): void {
|
|
||||||
const dt = this.depositTypes.find((d) => d.code === this.depositType);
|
|
||||||
if (dt) this.interestRate = dt.defaultRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRecurring(): boolean {
|
|
||||||
return this.depositType.startsWith('RD');
|
|
||||||
}
|
|
||||||
|
|
||||||
get tenureInYears(): number {
|
|
||||||
return this.tenureYears + this.tenureMonths / 12 + this.tenureDays / 365;
|
|
||||||
}
|
|
||||||
|
|
||||||
get maturityDate(): Date {
|
|
||||||
const d = new Date();
|
|
||||||
d.setFullYear(d.getFullYear() + this.tenureYears);
|
|
||||||
d.setMonth(d.getMonth() + this.tenureMonths);
|
|
||||||
d.setDate(d.getDate() + this.tenureDays);
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Quarterly-compounded maturity value, the standard FD/RD convention */
|
|
||||||
get maturityAmount(): number {
|
|
||||||
const p = this.principal || 0;
|
|
||||||
const t = this.tenureInYears;
|
|
||||||
if (p <= 0 || t <= 0) return 0;
|
|
||||||
return p * Math.pow(1 + this.interestRate / 100 / 4, 4 * t);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isValid(): boolean {
|
|
||||||
return !!this.clientId && (this.principal ?? 0) > 0 && this.tenureInYears > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
save(): void {
|
|
||||||
if (!this.isValid) return;
|
|
||||||
const prefix = this.isRecurring ? 'RD' : 'FD';
|
|
||||||
this.lastAccountNo = `${prefix}${Math.floor(1000000 + Math.random() * 8999999)}`;
|
|
||||||
this.lastClientName = this.selectedClient?.name ?? '';
|
|
||||||
this.saved = true;
|
|
||||||
this.snackBar.open(`${prefix} account ${this.lastAccountNo} created for ${this.lastClientName}`, 'Close', {
|
|
||||||
duration: 4000
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
resetForm(): void {
|
|
||||||
this.depositType = 'FD-SINGLE';
|
|
||||||
this.clientId = '';
|
|
||||||
this.principal = null;
|
|
||||||
this.interestRate = 7.25;
|
|
||||||
this.tenureYears = 1;
|
|
||||||
this.tenureMonths = 0;
|
|
||||||
this.tenureDays = 0;
|
|
||||||
this.nomineeName = '';
|
|
||||||
this.interestPayout = this.interestPayoutOptions[0];
|
|
||||||
this.payMode = this.payModes[0];
|
|
||||||
this.maturityInstruction = this.maturityInstructions[0];
|
|
||||||
this.saved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
close(): void {
|
|
||||||
this.dialogRef.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -63,7 +63,7 @@ export class PslReportDialogComponent {
|
|||||||
activeTab: 'dashboard' | 'categories' | 'register' | 'returns' = 'dashboard';
|
activeTab: 'dashboard' | 'categories' | 'register' | 'returns' = 'dashboard';
|
||||||
|
|
||||||
bank = {
|
bank = {
|
||||||
name: 'CBSNexora Bank Ltd',
|
name: 'Bharat Urban Bank Ltd',
|
||||||
branch: 'Head Office',
|
branch: 'Head Office',
|
||||||
quarter: 'Q3 FY 2025-26',
|
quarter: 'Q3 FY 2025-26',
|
||||||
asOnDate: '29-01-2026',
|
asOnDate: '29-01-2026',
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,472 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="pg">
|
|
||||||
<header class="pg-head">
|
|
||||||
<div>
|
|
||||||
<div class="pg-eyebrow">TREASURY · INVESTMENT · FIXED DEPOSIT</div>
|
|
||||||
<h1 class="pg-title">FD Investment</h1>
|
|
||||||
<p class="pg-sub">
|
|
||||||
Bank's own Fixed Deposit investments with other banks — Purchase, Interest, Sell/Premature, Write-off and
|
|
||||||
Part-Payment entries.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'holdings'"
|
|
||||||
(click)="setTab('holdings')"
|
|
||||||
>
|
|
||||||
Holdings
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'purchase'"
|
|
||||||
(click)="setTab('purchase')"
|
|
||||||
>
|
|
||||||
01 Purchase Entry
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'interest'"
|
|
||||||
(click)="setTab('interest')"
|
|
||||||
>
|
|
||||||
02 Interest Entry
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'sell'"
|
|
||||||
(click)="setTab('sell')"
|
|
||||||
>
|
|
||||||
03 Sell / Premature Entry
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'writeoff'"
|
|
||||||
(click)="setTab('writeoff')"
|
|
||||||
>
|
|
||||||
04 Write Off Entry
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'partpayment'"
|
|
||||||
(click)="setTab('partpayment')"
|
|
||||||
>
|
|
||||||
05 FD Part Payment Entry
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- HOLDINGS -->
|
|
||||||
@if (activeTab === 'holdings') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">FD Holdings Register</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Party</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
<th class="num">Rate</th>
|
|
||||||
<th>Purchase Date</th>
|
|
||||||
<th>Maturity Date</th>
|
|
||||||
<th class="num col-in">Accrued Interest</th>
|
|
||||||
<th class="num col-net">Maturity Value</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (h of holdings; track h.certNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.party }}</td>
|
|
||||||
<td>{{ h.interestFreq === 'CUMULATIVE' ? 'Cumulative' : 'Payout' }}</td>
|
|
||||||
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">{{ h.interestRate }}%</td>
|
|
||||||
<td>{{ h.purchaseDate }}</td>
|
|
||||||
<td>{{ h.maturityDate }}</td>
|
|
||||||
<td class="num col-in">₹{{ accrued(h) | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-net">₹{{ maturity(h) | number: '1.2-2' }}</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="statusClass(h)">{{ h.status }}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- PURCHASE ENTRY -->
|
|
||||||
@if (activeTab === 'purchase') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment - FD Purchase Entry</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Tran. Type</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.tranType">
|
|
||||||
<option value="FRESH">FRESH</option>
|
|
||||||
<option value="RENEWAL">RENEWAL</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
@if (purchaseForm.tranType === 'RENEWAL') {
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>OLD CertNo</span><input type="text" [(ngModel)]="purchaseForm.oldCertNo"
|
|
||||||
/></label>
|
|
||||||
}
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>FD Type</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.fdType">
|
|
||||||
@for (t of fdTypeOptions; track t) {
|
|
||||||
<option [value]="t">{{ t }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Mature Amt.calc.Type</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.matureAmtCalcType">
|
|
||||||
<option value="GENERAL">GENERAL</option>
|
|
||||||
<option value="DATEWISE">DATEWISE</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Party *</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.party">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
@for (p of partyOptions; track p.partyCode) {
|
|
||||||
<option [value]="p.partyName">{{ p.partyName }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Sub Party</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.subParty">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
@for (p of partyOptions; track p.partyCode) {
|
|
||||||
<option [value]="p.partyName">{{ p.partyName }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>GL ACNO</span><input type="text" [(ngModel)]="purchaseForm.glAcNo"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>GL A/c Name</span><input type="text" [(ngModel)]="purchaseForm.glAcName" readonly
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Certificate No.</span><input type="text" [(ngModel)]="purchaseForm.certNo"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Certificate Date</span><input type="text" [(ngModel)]="purchaseForm.certDate" placeholder="DD-MM-YYYY"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Sub Type</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.subType">
|
|
||||||
@for (s of subTypeOptions; track s.subTypeCode + s.subTypeName) {
|
|
||||||
<option [value]="s.subTypeName">{{ s.subTypeCode }} - {{ s.subTypeName }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Reciept/Advice</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.receiptAdvice">
|
|
||||||
<option value="RECEIPT">RECEIPT</option>
|
|
||||||
<option value="ADVICE">ADVICE</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Transaction Date</span
|
|
||||||
><input type="text" [(ngModel)]="purchaseForm.transactionDate" placeholder="DD-MM-YYYY"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Deposit Date *</span
|
|
||||||
><input type="text" [(ngModel)]="purchaseForm.depositDate" placeholder="DD-MM-YYYY"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Deposit Amount *</span><input type="number" [(ngModel)]="purchaseForm.depositAmount" min="0"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Interest[M/Q/H/Y]</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.interestFreq">
|
|
||||||
@for (i of intTypeOptions; track i.value) {
|
|
||||||
<option [value]="i.value">{{ i.label }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Interest Date</span
|
|
||||||
><input type="text" [(ngModel)]="purchaseForm.interestDate" placeholder="DD-MM-YYYY"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Interest Rate *</span><input type="number" [(ngModel)]="purchaseForm.interestRate" min="0" step="0.01"
|
|
||||||
/></label>
|
|
||||||
@if (purchaseForm.matureAmtCalcType === 'GENERAL') {
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Period Month</span><input type="number" [(ngModel)]="purchaseForm.periodMonth" min="0" max="11"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Year</span><input type="number" [(ngModel)]="purchaseForm.periodYear" min="0"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Days</span><input type="number" [(ngModel)]="purchaseForm.periodDays" min="0"
|
|
||||||
/></label>
|
|
||||||
} @else {
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Maturity Date (manual) *</span
|
|
||||||
><input type="text" [(ngModel)]="purchaseForm.manualMaturityDate" placeholder="DD-MM-YYYY"
|
|
||||||
/></label>
|
|
||||||
}
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Resolution No.</span><input type="text" [(ngModel)]="purchaseForm.resolutionNo"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>CR. Branch Code</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.crBranchCode">
|
|
||||||
@for (b of branchOptions; track b) {
|
|
||||||
<option [value]="b">{{ b }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>CR. To A/c</span><input type="text" [(ngModel)]="purchaseForm.crToAc"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>CR. A/c Name</span><input type="text" [(ngModel)]="purchaseForm.crToAcName" readonly
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Int.Receivable Code</span><input type="text" [(ngModel)]="purchaseForm.intReceivableCode"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>LIEN Y/N</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.lien">
|
|
||||||
<option value="NO">NO</option>
|
|
||||||
<option value="YES">YES</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
@if (purchaseForm.lien === 'YES') {
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>LIEN AMT.</span><input type="number" [(ngModel)]="purchaseForm.lienAmt"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>LIEN Reason</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.lienReason">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
@for (l of lienReasonOptions; track l.reasonCode) {
|
|
||||||
<option [value]="l.reasonDescription">{{ l.reasonDescription }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
}
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Classification</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.classification">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
<option value="STANDARD">STANDARD</option>
|
|
||||||
<option value="SUB-STANDARD">SUB-STANDARD</option>
|
|
||||||
<option value="DOUBTFUL">DOUBTFUL</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Collable</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.collable">
|
|
||||||
<option value="NO">NO</option>
|
|
||||||
<option value="YES">YES</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="pos-section-label">Mature / Premature Details</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>* Maturity Date</span><input type="text" [value]="purchaseMaturityDate" readonly
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>* Maturity Amt</span><input type="text" [value]="purchaseMaturityAmount | number: '1.0-0'" readonly
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Search by Cert No</span><input type="text" [(ngModel)]="searchCertNo" placeholder="Enter Cert No"
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="savePurchase()">Save</button>
|
|
||||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="cancelPurchase()">Cancel</button>
|
|
||||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="searchPurchase()">Search</button>
|
|
||||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="dropPurchase()">Drop</button>
|
|
||||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="exitPurchase()">Exit</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- INTEREST ENTRY -->
|
|
||||||
@if (activeTab === 'interest') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment - FD Interest Entry / Calculation</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Certificate No *</span>
|
|
||||||
<select [(ngModel)]="interestCertNo">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
@for (h of activeHoldings; track h.certNo) {
|
|
||||||
<option [value]="h.certNo">{{ h.certNo }} — {{ h.party }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
@if (interestHolding; as h) {
|
|
||||||
<label class="inv-form-field"><span>Party</span><input type="text" [value]="h.party" readonly /></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Interest Rate</span><input type="text" [value]="h.interestRate + '%'" readonly
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Frequency</span><input type="text" [value]="h.interestFreq" readonly
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Accrued Interest (as on {{ asOnDate }})</span
|
|
||||||
><input type="text" [value]="'₹' + (interestAccrued(h) | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="postInterest()">Process / Post Interest</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- SELL / PREMATURE ENTRY -->
|
|
||||||
@if (activeTab === 'sell') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment - FD Sell / Premature Closure Entry</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Certificate No *</span>
|
|
||||||
<select [(ngModel)]="sellCertNo">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
@for (h of activeHoldings; track h.certNo) {
|
|
||||||
<option [value]="h.certNo">{{ h.certNo }} — {{ h.party }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
@if (sellHolding; as h) {
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Face Value</span><input type="text" [value]="'₹' + (h.faceValue | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Accrued Interest till Sell Date</span
|
|
||||||
><input type="text" [value]="'₹' + (accrued(h) | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
|
||||||
}
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Sell / Premature Date *</span><input type="text" [(ngModel)]="sellDate" placeholder="DD-MM-YYYY"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"><span>Remarks</span><input type="text" [(ngModel)]="sellRemark" /></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="saveSell()">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- WRITE OFF ENTRY -->
|
|
||||||
@if (activeTab === 'writeoff') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment - FD Write Off Entry</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Certificate No *</span>
|
|
||||||
<select [(ngModel)]="writeoffCertNo">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
@for (h of activeHoldings; track h.certNo) {
|
|
||||||
<option [value]="h.certNo">{{ h.certNo }} — {{ h.party }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
@if (writeoffHolding; as h) {
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Face Value to be Written Off</span
|
|
||||||
><input type="text" [value]="'₹' + (h.faceValue | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
|
||||||
}
|
|
||||||
<label class="inv-form-field"><span>Reason *</span><input type="text" [(ngModel)]="writeoffReason" /></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="saveWriteoff()">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- PART PAYMENT ENTRY -->
|
|
||||||
@if (activeTab === 'partpayment') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment - FD Part Payment Entry</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Certificate No *</span>
|
|
||||||
<select [(ngModel)]="partPayCertNo">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
@for (h of activeHoldings; track h.certNo) {
|
|
||||||
<option [value]="h.certNo">{{ h.certNo }} — {{ h.party }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
@if (partPayHolding; as h) {
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Current Face Value</span><input type="text" [value]="'₹' + (h.faceValue | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
|
||||||
}
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Part Payment Amount *</span><input type="number" [(ngModel)]="partPayAmount" min="0"
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="savePartPayment()">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (activeTab !== 'holdings') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">FD Transaction Log</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Voucher No</th>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="num">Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (t of txns; track t.voucherNo) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ t.txnDate }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ t.voucherNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ t.certNo }}</td>
|
|
||||||
<td>{{ t.txnType }}</td>
|
|
||||||
<td>{{ t.particulars }}</td>
|
|
||||||
<td class="num">₹{{ t.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|
|
||||||
@@ -1,461 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { CommonModule } from '@angular/common';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import {
|
|
||||||
FD_HOLDINGS,
|
|
||||||
ALL_TXNS,
|
|
||||||
PARTY_MASTER,
|
|
||||||
SUBTYPE_MASTER,
|
|
||||||
FD_TYPE_OPTIONS,
|
|
||||||
INT_TYPE_OPTIONS,
|
|
||||||
LIEN_REASON_MASTER,
|
|
||||||
InvestmentHolding,
|
|
||||||
InvestmentTxn,
|
|
||||||
InterestFreq,
|
|
||||||
accruedInterest,
|
|
||||||
daysToMaturity,
|
|
||||||
maturityAmount,
|
|
||||||
addPeriod,
|
|
||||||
todayDDMMYYYY
|
|
||||||
} from '../investment-data';
|
|
||||||
|
|
||||||
type FdTab = 'holdings' | 'purchase' | 'interest' | 'sell' | 'writeoff' | 'partpayment';
|
|
||||||
|
|
||||||
interface FdPurchaseForm {
|
|
||||||
tranType: 'FRESH' | 'RENEWAL';
|
|
||||||
oldCertNo: string;
|
|
||||||
fdType: string;
|
|
||||||
/** GENERAL uses the period (Y/M/D) fields; DATEWISE lets the user type the maturity date directly. */
|
|
||||||
matureAmtCalcType: 'GENERAL' | 'DATEWISE';
|
|
||||||
certNo: string;
|
|
||||||
certDate: string;
|
|
||||||
party: string;
|
|
||||||
subParty: string;
|
|
||||||
glAcNo: string;
|
|
||||||
glAcName: string;
|
|
||||||
subType: string;
|
|
||||||
receiptAdvice: 'RECEIPT' | 'ADVICE';
|
|
||||||
transactionDate: string;
|
|
||||||
depositDate: string;
|
|
||||||
depositAmount: number | null;
|
|
||||||
interestFreq: InterestFreq;
|
|
||||||
interestDate: string;
|
|
||||||
interestRate: number | null;
|
|
||||||
periodYear: number | null;
|
|
||||||
periodMonth: number | null;
|
|
||||||
periodDays: number | null;
|
|
||||||
/** Only used when matureAmtCalcType === 'DATEWISE'. */
|
|
||||||
manualMaturityDate: string;
|
|
||||||
resolutionNo: string;
|
|
||||||
crBranchCode: string;
|
|
||||||
crToAc: string;
|
|
||||||
crToAcName: string;
|
|
||||||
intReceivableCode: string;
|
|
||||||
lien: 'YES' | 'NO';
|
|
||||||
lienAmt: number | null;
|
|
||||||
lienReason: string;
|
|
||||||
classification: string;
|
|
||||||
collable: 'YES' | 'NO';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FD (Fixed Deposit) treasury investment screen — Holdings register + Purchase / Interest /
|
|
||||||
* Sell-Premature / Write-off / Part-Payment entry tabs, modelled on the legacy "Investment - FD
|
|
||||||
* Purchase Entry" form. Dummy in-memory data only; Save appends to the local holdings/txns
|
|
||||||
* arrays so the demo feels interactive without any backend.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-fd-investment',
|
|
||||||
templateUrl: './fd-investment.component.html',
|
|
||||||
styleUrls: [
|
|
||||||
'../../cashier/cashier-shared.scss',
|
|
||||||
'../investment-shared.scss'
|
|
||||||
],
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class FdInvestmentComponent {
|
|
||||||
activeTab: FdTab = 'holdings';
|
|
||||||
asOnDate = todayDDMMYYYY();
|
|
||||||
|
|
||||||
holdings: InvestmentHolding[] = [...FD_HOLDINGS];
|
|
||||||
txns: InvestmentTxn[] = ALL_TXNS.filter((t) => t.instrumentType === 'FD');
|
|
||||||
|
|
||||||
readonly partyOptions = PARTY_MASTER;
|
|
||||||
readonly subTypeOptions = SUBTYPE_MASTER;
|
|
||||||
readonly fdTypeOptions = FD_TYPE_OPTIONS;
|
|
||||||
readonly intTypeOptions = INT_TYPE_OPTIONS;
|
|
||||||
readonly lienReasonOptions = LIEN_REASON_MASTER;
|
|
||||||
readonly branchOptions = [
|
|
||||||
'HEAD OFFICE',
|
|
||||||
'BRANCH 1',
|
|
||||||
'BRANCH 2'
|
|
||||||
];
|
|
||||||
|
|
||||||
toastMsg = '';
|
|
||||||
toastVisible = false;
|
|
||||||
private toastTimer: ReturnType<typeof setTimeout> | undefined;
|
|
||||||
|
|
||||||
purchaseForm: FdPurchaseForm = this.blankPurchaseForm();
|
|
||||||
/** Certificate No typed into the Purchase Entry's Search box (legacy "Search" button). */
|
|
||||||
searchCertNo = '';
|
|
||||||
|
|
||||||
interestCertNo = '';
|
|
||||||
sellCertNo = '';
|
|
||||||
sellDate = this.asOnDate;
|
|
||||||
sellRemark = '';
|
|
||||||
writeoffCertNo = '';
|
|
||||||
writeoffReason = '';
|
|
||||||
partPayCertNo = '';
|
|
||||||
partPayAmount: number | null = null;
|
|
||||||
|
|
||||||
setTab(tab: FdTab): void {
|
|
||||||
this.activeTab = tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
get activeHoldings(): InvestmentHolding[] {
|
|
||||||
return this.holdings.filter((h) => h.status === 'ACTIVE');
|
|
||||||
}
|
|
||||||
|
|
||||||
private nextCertNo(): string {
|
|
||||||
const nums = this.holdings.map((h) => parseInt(h.certNo, 10)).filter((n) => !isNaN(n));
|
|
||||||
return String((nums.length ? Math.max(...nums) : 15619) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private blankPurchaseForm(): FdPurchaseForm {
|
|
||||||
return {
|
|
||||||
tranType: 'FRESH',
|
|
||||||
oldCertNo: '',
|
|
||||||
fdType: 'CUMMULATIVE',
|
|
||||||
matureAmtCalcType: 'GENERAL',
|
|
||||||
certNo: this.nextCertNo(),
|
|
||||||
certDate: this.asOnDate,
|
|
||||||
party: '',
|
|
||||||
subParty: '',
|
|
||||||
glAcNo: '320020',
|
|
||||||
glAcName: 'FIXED DEPO. CO-OPER. BANK MAIN',
|
|
||||||
subType: 'NON SLR',
|
|
||||||
receiptAdvice: 'RECEIPT',
|
|
||||||
transactionDate: this.asOnDate,
|
|
||||||
depositDate: this.asOnDate,
|
|
||||||
depositAmount: null,
|
|
||||||
interestFreq: 'CUMULATIVE',
|
|
||||||
interestDate: this.asOnDate,
|
|
||||||
interestRate: null,
|
|
||||||
periodYear: 2,
|
|
||||||
periodMonth: 0,
|
|
||||||
periodDays: 0,
|
|
||||||
manualMaturityDate: '',
|
|
||||||
resolutionNo: '',
|
|
||||||
crBranchCode: 'HEAD OFFICE',
|
|
||||||
crToAc: '310070',
|
|
||||||
crToAcName: 'CURRENT A/C CO-OP. BANK MAIN BR.41',
|
|
||||||
intReceivableCode: '320020',
|
|
||||||
lien: 'NO',
|
|
||||||
lienAmt: null,
|
|
||||||
lienReason: '',
|
|
||||||
classification: '',
|
|
||||||
collable: 'NO'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
get purchaseMaturityDate(): string {
|
|
||||||
const f = this.purchaseForm;
|
|
||||||
if (f.matureAmtCalcType === 'DATEWISE') {
|
|
||||||
return f.manualMaturityDate;
|
|
||||||
}
|
|
||||||
if (!f.depositDate) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const base = addPeriod(f.depositDate, f.periodYear || 0, f.periodMonth || 0);
|
|
||||||
return f.periodDays ? addPeriod(base, 0, 0, f.periodDays) : base;
|
|
||||||
}
|
|
||||||
|
|
||||||
get purchaseMaturityAmount(): number {
|
|
||||||
const f = this.purchaseForm;
|
|
||||||
if (!f.depositAmount || !f.interestRate || !this.purchaseMaturityDate) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return maturityAmount({
|
|
||||||
certNo: f.certNo,
|
|
||||||
instrumentType: 'FD',
|
|
||||||
scriptCode: '',
|
|
||||||
scriptName: '',
|
|
||||||
party: f.party,
|
|
||||||
subType: f.subType,
|
|
||||||
faceValue: f.depositAmount,
|
|
||||||
purchaseRate: 100,
|
|
||||||
units: 1,
|
|
||||||
purchaseDate: f.depositDate,
|
|
||||||
maturityDate: this.purchaseMaturityDate,
|
|
||||||
interestRate: f.interestRate,
|
|
||||||
interestFreq: f.interestFreq,
|
|
||||||
glCode: f.glAcNo,
|
|
||||||
glName: '',
|
|
||||||
crToAc: f.crToAc,
|
|
||||||
brokerCode: '',
|
|
||||||
status: 'ACTIVE'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
savePurchase(): void {
|
|
||||||
const f = this.purchaseForm;
|
|
||||||
if (!f.party || !f.depositAmount || !f.interestRate || !this.purchaseMaturityDate) {
|
|
||||||
this.toast('Please fill Party, Deposit Amount, Interest Rate and Period.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const holding: InvestmentHolding = {
|
|
||||||
certNo: f.certNo,
|
|
||||||
instrumentType: 'FD',
|
|
||||||
scriptCode: f.interestFreq === 'CUMULATIVE' ? 'FD-CUM' : 'FD-MIP',
|
|
||||||
scriptName: f.fdType ? `${f.fdType} Fixed Deposit` : 'Fixed Deposit',
|
|
||||||
party: f.party,
|
|
||||||
subType: f.subType,
|
|
||||||
faceValue: f.depositAmount,
|
|
||||||
purchaseRate: 100,
|
|
||||||
units: 1,
|
|
||||||
purchaseDate: f.depositDate,
|
|
||||||
maturityDate: this.purchaseMaturityDate,
|
|
||||||
interestRate: f.interestRate,
|
|
||||||
interestFreq: f.interestFreq,
|
|
||||||
glCode: f.glAcNo,
|
|
||||||
glName: f.glAcName,
|
|
||||||
crToAc: f.crToAc,
|
|
||||||
brokerCode: '',
|
|
||||||
status: 'ACTIVE'
|
|
||||||
};
|
|
||||||
this.holdings = [
|
|
||||||
holding,
|
|
||||||
...this.holdings
|
|
||||||
];
|
|
||||||
this.txns = [
|
|
||||||
{
|
|
||||||
certNo: holding.certNo,
|
|
||||||
instrumentType: 'FD',
|
|
||||||
txnType: 'PURCHASE',
|
|
||||||
voucherNo: `INV/PUR/FD-${holding.certNo}`,
|
|
||||||
txnDate: f.transactionDate,
|
|
||||||
amount: holding.faceValue,
|
|
||||||
particulars: `${f.tranType === 'RENEWAL' ? 'Renewal' : 'Fresh'} FD Purchase - ${holding.party}`
|
|
||||||
},
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(`FD Certificate No. ${holding.certNo} saved successfully.`);
|
|
||||||
this.purchaseForm = this.blankPurchaseForm();
|
|
||||||
this.activeTab = 'holdings';
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelPurchase(): void {
|
|
||||||
this.purchaseForm = this.blankPurchaseForm();
|
|
||||||
this.toast('Entry cancelled — form reset.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Legacy "Search" button — loads an existing certificate's values back into the form. */
|
|
||||||
searchPurchase(): void {
|
|
||||||
const h = this.holdings.find((x) => x.certNo === this.searchCertNo.trim());
|
|
||||||
if (!h) {
|
|
||||||
this.toast(`Certificate No. ${this.searchCertNo} not found.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.purchaseForm = {
|
|
||||||
...this.blankPurchaseForm(),
|
|
||||||
tranType: 'FRESH',
|
|
||||||
certNo: h.certNo,
|
|
||||||
certDate: h.purchaseDate,
|
|
||||||
party: h.party,
|
|
||||||
subType: h.subType,
|
|
||||||
glAcNo: h.glCode,
|
|
||||||
glAcName: h.glName,
|
|
||||||
transactionDate: h.purchaseDate,
|
|
||||||
depositDate: h.purchaseDate,
|
|
||||||
depositAmount: h.faceValue,
|
|
||||||
interestFreq: h.interestFreq,
|
|
||||||
interestDate: h.purchaseDate,
|
|
||||||
interestRate: h.interestRate,
|
|
||||||
matureAmtCalcType: 'DATEWISE',
|
|
||||||
manualMaturityDate: h.maturityDate,
|
|
||||||
crToAc: h.crToAc
|
|
||||||
};
|
|
||||||
this.toast(`Certificate ${h.certNo} loaded.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Legacy "Drop" button — removes the loaded certificate from the register. */
|
|
||||||
dropPurchase(): void {
|
|
||||||
const certNo = this.purchaseForm.certNo;
|
|
||||||
const exists = this.holdings.some((h) => h.certNo === certNo);
|
|
||||||
if (!exists) {
|
|
||||||
this.toast('Nothing to drop — this certificate is not yet saved.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.holdings = this.holdings.filter((h) => h.certNo !== certNo);
|
|
||||||
this.txns = this.txns.filter((t) => t.certNo !== certNo);
|
|
||||||
this.purchaseForm = this.blankPurchaseForm();
|
|
||||||
this.toast(`Certificate ${certNo} dropped.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Legacy "Exit" button — closes the entry form and returns to the register. */
|
|
||||||
exitPurchase(): void {
|
|
||||||
this.purchaseForm = this.blankPurchaseForm();
|
|
||||||
this.activeTab = 'holdings';
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Interest --------------------
|
|
||||||
get interestHolding(): InvestmentHolding | undefined {
|
|
||||||
return this.holdings.find((h) => h.certNo === this.interestCertNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
interestAccrued(h: InvestmentHolding): number {
|
|
||||||
return accruedInterest(h, this.asOnDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
postInterest(): void {
|
|
||||||
const h = this.interestHolding;
|
|
||||||
if (!h) {
|
|
||||||
this.toast('Select a certificate first.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const amt = this.interestAccrued(h);
|
|
||||||
this.txns = [
|
|
||||||
{
|
|
||||||
certNo: h.certNo,
|
|
||||||
instrumentType: 'FD',
|
|
||||||
txnType: 'INTEREST',
|
|
||||||
voucherNo: `INV/INT/FD-${h.certNo}-${this.txns.length + 1}`,
|
|
||||||
txnDate: this.asOnDate,
|
|
||||||
amount: amt,
|
|
||||||
particulars: `Interest posted - ${h.party} (${h.interestFreq})`
|
|
||||||
},
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(`Interest of ₹${amt.toLocaleString('en-IN')} posted for Certificate ${h.certNo}.`);
|
|
||||||
this.interestCertNo = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Sell / Premature --------------------
|
|
||||||
get sellHolding(): InvestmentHolding | undefined {
|
|
||||||
return this.holdings.find((h) => h.certNo === this.sellCertNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveSell(): void {
|
|
||||||
const h = this.sellHolding;
|
|
||||||
if (!h || !this.sellDate) {
|
|
||||||
this.toast('Select a certificate and sell/premature date.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const payout = h.faceValue + accruedInterest(h, this.sellDate);
|
|
||||||
h.status = 'SOLD';
|
|
||||||
this.txns = [
|
|
||||||
{
|
|
||||||
certNo: h.certNo,
|
|
||||||
instrumentType: 'FD',
|
|
||||||
txnType: 'SELL',
|
|
||||||
voucherNo: `INV/SEL/FD-${h.certNo}`,
|
|
||||||
txnDate: this.sellDate,
|
|
||||||
amount: payout,
|
|
||||||
particulars: `Premature closure - ${h.party}${this.sellRemark ? ' - ' + this.sellRemark : ''}`
|
|
||||||
},
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(`Certificate ${h.certNo} closed. Payout ₹${payout.toLocaleString('en-IN')}.`);
|
|
||||||
this.sellCertNo = '';
|
|
||||||
this.sellRemark = '';
|
|
||||||
this.activeTab = 'holdings';
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Write-off --------------------
|
|
||||||
get writeoffHolding(): InvestmentHolding | undefined {
|
|
||||||
return this.holdings.find((h) => h.certNo === this.writeoffCertNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveWriteoff(): void {
|
|
||||||
const h = this.writeoffHolding;
|
|
||||||
if (!h || !this.writeoffReason) {
|
|
||||||
this.toast('Select a certificate and enter a write-off reason.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
h.status = 'WRITTEN_OFF';
|
|
||||||
this.txns = [
|
|
||||||
{
|
|
||||||
certNo: h.certNo,
|
|
||||||
instrumentType: 'FD',
|
|
||||||
txnType: 'WRITE_OFF',
|
|
||||||
voucherNo: `INV/WOF/FD-${h.certNo}`,
|
|
||||||
txnDate: this.asOnDate,
|
|
||||||
amount: h.faceValue,
|
|
||||||
particulars: `Written off - ${this.writeoffReason}`
|
|
||||||
},
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(`Certificate ${h.certNo} written off.`);
|
|
||||||
this.writeoffCertNo = '';
|
|
||||||
this.writeoffReason = '';
|
|
||||||
this.activeTab = 'holdings';
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Part Payment --------------------
|
|
||||||
get partPayHolding(): InvestmentHolding | undefined {
|
|
||||||
return this.holdings.find((h) => h.certNo === this.partPayCertNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
savePartPayment(): void {
|
|
||||||
const h = this.partPayHolding;
|
|
||||||
if (!h || !this.partPayAmount || this.partPayAmount <= 0 || this.partPayAmount >= h.faceValue) {
|
|
||||||
this.toast('Select a certificate and enter a valid part-payment amount (less than face value).');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
h.faceValue -= this.partPayAmount;
|
|
||||||
this.txns = [
|
|
||||||
{
|
|
||||||
certNo: h.certNo,
|
|
||||||
instrumentType: 'FD',
|
|
||||||
txnType: 'PART_PAYMENT',
|
|
||||||
voucherNo: `INV/PP/FD-${h.certNo}-${this.txns.length + 1}`,
|
|
||||||
txnDate: this.asOnDate,
|
|
||||||
amount: this.partPayAmount,
|
|
||||||
particulars: `Part payment withdrawn - ${h.party}, balance ₹${h.faceValue.toLocaleString('en-IN')}`
|
|
||||||
},
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(`Part payment of ₹${this.partPayAmount.toLocaleString('en-IN')} recorded for Certificate ${h.certNo}.`);
|
|
||||||
this.partPayCertNo = '';
|
|
||||||
this.partPayAmount = null;
|
|
||||||
this.activeTab = 'holdings';
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Helpers --------------------
|
|
||||||
accrued(h: InvestmentHolding): number {
|
|
||||||
return accruedInterest(h, this.asOnDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
maturity(h: InvestmentHolding): number {
|
|
||||||
return maturityAmount(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
daysLeft(h: InvestmentHolding): number {
|
|
||||||
return daysToMaturity(h, this.asOnDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
statusClass(h: InvestmentHolding): string {
|
|
||||||
return `inv-badge inv-badge--${h.status.toLowerCase()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private toast(msg: string): void {
|
|
||||||
this.toastMsg = msg;
|
|
||||||
this.toastVisible = true;
|
|
||||||
if (this.toastTimer) {
|
|
||||||
clearTimeout(this.toastTimer);
|
|
||||||
}
|
|
||||||
this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="pg">
|
|
||||||
<header class="pg-head">
|
|
||||||
<div>
|
|
||||||
<div class="pg-eyebrow">TREASURY · INVESTMENT</div>
|
|
||||||
<h1 class="pg-title">Investment Portfolio Dashboard</h1>
|
|
||||||
<p class="pg-sub">
|
|
||||||
Consolidated position across FD, Govt Security, Non-SLR, Bonds, Mutual Fund and Share holdings as on
|
|
||||||
{{ asOnDate }}.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="inv-kpis">
|
|
||||||
<div class="inv-kpi">
|
|
||||||
<span class="inv-kpi__label">Total Holdings</span>
|
|
||||||
<span class="inv-kpi__value">{{ totalHoldings }}</span>
|
|
||||||
<span class="inv-kpi__sub">{{ activeCount }} active · {{ maturedCount }} matured</span>
|
|
||||||
</div>
|
|
||||||
<div class="inv-kpi">
|
|
||||||
<span class="inv-kpi__label">Total Invested (Face Value)</span>
|
|
||||||
<span class="inv-kpi__value">₹{{ totalFaceValue | number: '1.2-2' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="inv-kpi">
|
|
||||||
<span class="inv-kpi__label">Accrued Interest / Income</span>
|
|
||||||
<span class="inv-kpi__value col-in">₹{{ totalAccruedInterest | number: '1.2-2' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="inv-kpi">
|
|
||||||
<span class="inv-kpi__label">Projected Value at Maturity</span>
|
|
||||||
<span class="inv-kpi__value">₹{{ totalMaturityValue | number: '1.2-2' }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Instrument-wise Break-up</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Instrument Type</th>
|
|
||||||
<th class="num">Holdings</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
<th class="num col-in">Accrued Interest</th>
|
|
||||||
<th class="num col-net">Maturity Value</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (s of summaries; track s.type) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ s.label }}</td>
|
|
||||||
<td class="num">{{ s.count }}</td>
|
|
||||||
<td class="num">₹{{ s.faceValue | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-in">₹{{ s.accruedInterest | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-net">₹{{ s.maturityValue | number: '1.2-2' }}</td>
|
|
||||||
<td>
|
|
||||||
<a class="pg-btn pg-btn--ghost" [routerLink]="s.route">Open</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td>Total</td>
|
|
||||||
<td class="num">{{ totalHoldings }}</td>
|
|
||||||
<td class="num">₹{{ totalFaceValue | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ totalAccruedInterest | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ totalMaturityValue | number: '1.2-2' }}</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Due-Date Diary — Maturing Within 90 Days</div>
|
|
||||||
@if (maturingSoon.length === 0) {
|
|
||||||
<div class="pg-empty">No holdings maturing in the next 90 days.</div>
|
|
||||||
} @else {
|
|
||||||
<div>
|
|
||||||
@for (h of maturingSoon; track h.certNo) {
|
|
||||||
<div class="inv-maturity-row">
|
|
||||||
<span>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
{{ h.scriptName }} — {{ h.party }} ({{ instrumentLabel(h.instrumentType) }})
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
Matures {{ h.maturityDate }} ·
|
|
||||||
<span
|
|
||||||
class="inv-maturity-days"
|
|
||||||
[class.inv-maturity-days--soon]="daysLeft(h) <= 30"
|
|
||||||
[class.inv-maturity-days--ok]="daysLeft(h) > 30"
|
|
||||||
>
|
|
||||||
{{ daysLeft(h) }} days
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Quick Entry</div>
|
|
||||||
<div class="inv-nav-grid" style="padding: 16px">
|
|
||||||
<a class="inv-nav-card" routerLink="/investment/fd">
|
|
||||||
<span class="inv-nav-card__label">Fixed Deposit</span>
|
|
||||||
<span class="inv-nav-card__count">{{ summaries[0].count }}</span>
|
|
||||||
<span class="inv-nav-card__value">Purchase / Interest / Sell / Write-off</span>
|
|
||||||
</a>
|
|
||||||
<a class="inv-nav-card" routerLink="/investment/bonds">
|
|
||||||
<span class="inv-nav-card__label">Govt Security / Non-SLR / Bonds / MF</span>
|
|
||||||
<span class="inv-nav-card__count">{{
|
|
||||||
summaries[1].count + summaries[2].count + summaries[3].count + summaries[4].count
|
|
||||||
}}</span>
|
|
||||||
<span class="inv-nav-card__value">Purchase / Interest / Sell / Amortize</span>
|
|
||||||
</a>
|
|
||||||
<a class="inv-nav-card" routerLink="/investment/share">
|
|
||||||
<span class="inv-nav-card__label">Share Entry</span>
|
|
||||||
<span class="inv-nav-card__count">{{ summaries[5].count }}</span>
|
|
||||||
<span class="inv-nav-card__value">Purchase / Dividend / Sell / Write-off</span>
|
|
||||||
</a>
|
|
||||||
<a class="inv-nav-card" routerLink="/investment/interest">
|
|
||||||
<span class="inv-nav-card__label">Interest</span>
|
|
||||||
<span class="inv-nav-card__count">5</span>
|
|
||||||
<span class="inv-nav-card__value">FD / Bond Calculation & Registers</span>
|
|
||||||
</a>
|
|
||||||
<a class="inv-nav-card" routerLink="/investment/masters">
|
|
||||||
<span class="inv-nav-card__label">Masters</span>
|
|
||||||
<span class="inv-nav-card__count">10</span>
|
|
||||||
<span class="inv-nav-card__value">Script / Broker / Party / Control Params</span>
|
|
||||||
</a>
|
|
||||||
<a class="inv-nav-card" routerLink="/investment/reports">
|
|
||||||
<span class="inv-nav-card__label">Reports</span>
|
|
||||||
<span class="inv-nav-card__count">20+</span>
|
|
||||||
<span class="inv-nav-card__value">Balance / Ledger / Maturity / Portfolio</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { CommonModule } from '@angular/common';
|
|
||||||
import { RouterLink } from '@angular/router';
|
|
||||||
import {
|
|
||||||
ALL_HOLDINGS,
|
|
||||||
INSTRUMENT_LABELS,
|
|
||||||
InstrumentType,
|
|
||||||
InvestmentHolding,
|
|
||||||
accruedInterest,
|
|
||||||
daysToMaturity,
|
|
||||||
maturityAmount
|
|
||||||
} from '../investment-data';
|
|
||||||
|
|
||||||
interface InstrumentSummary {
|
|
||||||
type: InstrumentType;
|
|
||||||
label: string;
|
|
||||||
route: string;
|
|
||||||
count: number;
|
|
||||||
faceValue: number;
|
|
||||||
accruedInterest: number;
|
|
||||||
maturityValue: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Cross-instrument portfolio dashboard — every figure is derived from ALL_HOLDINGS, never
|
|
||||||
* hand-duplicated, so it can never drift from the per-instrument screens or Reports. */
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-investment-dashboard',
|
|
||||||
templateUrl: './investment-dashboard.component.html',
|
|
||||||
styleUrls: [
|
|
||||||
'../../cashier/cashier-shared.scss',
|
|
||||||
'../investment-shared.scss'
|
|
||||||
],
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
RouterLink
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class InvestmentDashboardComponent {
|
|
||||||
asOnDate = '';
|
|
||||||
private readonly types: Array<{ type: InstrumentType; route: string }> = [
|
|
||||||
{ type: 'FD', route: '/investment/fd' },
|
|
||||||
{ type: 'GOVT_SECURITY', route: '/investment/bonds' },
|
|
||||||
{ type: 'NON_SLR', route: '/investment/bonds' },
|
|
||||||
{ type: 'BOND', route: '/investment/bonds' },
|
|
||||||
{ type: 'MUTUAL_FUND', route: '/investment/bonds' },
|
|
||||||
{ type: 'SHARE', route: '/investment/share' }
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
const d = new Date();
|
|
||||||
this.asOnDate = `${String(d.getDate()).padStart(2, '0')}-${String(d.getMonth() + 1).padStart(2, '0')}-${d.getFullYear()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private holdingsOf(type: InstrumentType): InvestmentHolding[] {
|
|
||||||
return ALL_HOLDINGS.filter((h) => h.instrumentType === type);
|
|
||||||
}
|
|
||||||
|
|
||||||
get summaries(): InstrumentSummary[] {
|
|
||||||
return this.types.map(({ type, route }) => {
|
|
||||||
const holdings = this.holdingsOf(type);
|
|
||||||
return {
|
|
||||||
type,
|
|
||||||
label: INSTRUMENT_LABELS[type],
|
|
||||||
route,
|
|
||||||
count: holdings.length,
|
|
||||||
faceValue: holdings.reduce((s, h) => s + h.faceValue, 0),
|
|
||||||
accruedInterest: holdings.reduce((s, h) => s + accruedInterest(h, this.asOnDate), 0),
|
|
||||||
maturityValue: holdings.reduce((s, h) => s + maturityAmount(h), 0)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get totalHoldings(): number {
|
|
||||||
return ALL_HOLDINGS.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
get totalFaceValue(): number {
|
|
||||||
return ALL_HOLDINGS.reduce((s, h) => s + h.faceValue, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get totalAccruedInterest(): number {
|
|
||||||
return ALL_HOLDINGS.reduce((s, h) => s + accruedInterest(h, this.asOnDate), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get totalMaturityValue(): number {
|
|
||||||
return ALL_HOLDINGS.reduce((s, h) => s + maturityAmount(h), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get activeCount(): number {
|
|
||||||
return ALL_HOLDINGS.filter((h) => h.status === 'ACTIVE').length;
|
|
||||||
}
|
|
||||||
|
|
||||||
get maturedCount(): number {
|
|
||||||
return ALL_HOLDINGS.filter((h) => h.status === 'MATURED').length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Holdings maturing within the next 90 days — a "due-date diary" style widget. */
|
|
||||||
get maturingSoon(): InvestmentHolding[] {
|
|
||||||
return ALL_HOLDINGS.filter((h) => h.status === 'ACTIVE' && daysToMaturity(h, this.asOnDate) <= 90).sort(
|
|
||||||
(a, b) => daysToMaturity(a, this.asOnDate) - daysToMaturity(b, this.asOnDate)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
daysLeft(h: InvestmentHolding): number {
|
|
||||||
return daysToMaturity(h, this.asOnDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
instrumentLabel(type: InstrumentType): string {
|
|
||||||
return INSTRUMENT_LABELS[type];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,352 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="pg">
|
|
||||||
<header class="pg-head">
|
|
||||||
<div>
|
|
||||||
<div class="pg-eyebrow">TREASURY · INVESTMENT · INTEREST</div>
|
|
||||||
<h1 class="pg-title">Investment Interest</h1>
|
|
||||||
<p class="pg-sub">
|
|
||||||
FD and Govt./Bond interest calculation batch runs, plus the Government Bond Interest, FD Interest and FD
|
|
||||||
registers.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'fdCalc'"
|
|
||||||
(click)="setTab('fdCalc')"
|
|
||||||
>
|
|
||||||
01 FD Interest Calculation
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'bondCalc'"
|
|
||||||
(click)="setTab('bondCalc')"
|
|
||||||
>
|
|
||||||
02 Govt./Bond Int. Calculation
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'bondIntRegister'"
|
|
||||||
(click)="setTab('bondIntRegister')"
|
|
||||||
>
|
|
||||||
03 Government Bond Int Register
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'fdIntRegister'"
|
|
||||||
(click)="setTab('fdIntRegister')"
|
|
||||||
>
|
|
||||||
04 FD Interest Register
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'fdRegister'"
|
|
||||||
(click)="setTab('fdRegister')"
|
|
||||||
>
|
|
||||||
05 FD Register
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 01 FD INTEREST CALCULATION -->
|
|
||||||
@if (activeTab === 'fdCalc') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment - FD Interest Calculation</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Date</span><input type="text" [(ngModel)]="fdCalcDate" placeholder="DD-MM-YYYY"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Certificate No</span>
|
|
||||||
<select [(ngModel)]="fdCalcCertNo">
|
|
||||||
<option value="">-- All Certificates --</option>
|
|
||||||
@for (h of fdHoldings; track h.certNo) {
|
|
||||||
<option [value]="h.certNo">{{ h.certNo }} — {{ h.party }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>ALL</span><input type="checkbox" [(ngModel)]="fdCalcAll" style="height: 18px; width: 18px"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Total Interest to Post</span
|
|
||||||
><input type="text" [value]="'₹' + (fdCalcTotal | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="processFdInterest()">Process</button>
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Party</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
<th class="num">Rate</th>
|
|
||||||
<th>Frequency</th>
|
|
||||||
<th class="num col-in">Interest</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (h of fdCalcTargets; track h.certNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.party }}</td>
|
|
||||||
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">{{ h.interestRate }}%</td>
|
|
||||||
<td>{{ h.interestFreq }}</td>
|
|
||||||
<td class="num col-in">₹{{ fdInterest(h) | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="pg-empty">No FD certificates match the filter.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td colspan="5">Total</td>
|
|
||||||
<td class="num">₹{{ fdCalcTotal | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- 02 GOVT./BOND INT. CALCULATION -->
|
|
||||||
@if (activeTab === 'bondCalc') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment - Govt./Bond Interest Calculation</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Date</span><input type="text" [(ngModel)]="bondCalcDate" placeholder="DD-MM-YYYY"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Type</span>
|
|
||||||
<select [(ngModel)]="bondCalcType">
|
|
||||||
<option value="">-- All Types --</option>
|
|
||||||
<option value="GOVT_SECURITY">GOVT SECURITY</option>
|
|
||||||
<option value="NON_SLR">NON SLR</option>
|
|
||||||
<option value="BOND">BOND</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Script</span>
|
|
||||||
<select [(ngModel)]="bondCalcScript">
|
|
||||||
<option value="">-- All Scripts --</option>
|
|
||||||
@for (s of scriptOptions; track s.scriptCode) {
|
|
||||||
<option [value]="s.scriptCode">{{ s.scriptCode }} — {{ s.scriptName }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field"><span>Deal No</span><input type="text" [(ngModel)]="bondCalcDealNo" /></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>ALL</span><input type="checkbox" [(ngModel)]="bondCalcAll" style="height: 18px; width: 18px"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Total Interest to Post</span
|
|
||||||
><input type="text" [value]="'₹' + (bondCalcTotal | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="processBondInterest()">Process</button>
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Script</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
<th class="num">Rate</th>
|
|
||||||
<th>Frequency</th>
|
|
||||||
<th class="num col-in">Interest</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (h of bondCalcTargets; track h.certNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.scriptName }}</td>
|
|
||||||
<td>{{ instrumentLabel(h.instrumentType) }}</td>
|
|
||||||
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">{{ h.interestRate }}%</td>
|
|
||||||
<td>{{ h.interestFreq }}</td>
|
|
||||||
<td class="num col-in">₹{{ bondInterest(h) | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="7" class="pg-empty">No holdings match the filter.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td colspan="6">Total</td>
|
|
||||||
<td class="num">₹{{ bondCalcTotal | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- 03 GOVERNMENT BOND INT REGISTER -->
|
|
||||||
@if (activeTab === 'bondIntRegister') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Government Bond Interest Register</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Voucher No</th>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Instrument</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="num col-in">Interest Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (t of bondIntRegister; track t.voucherNo) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ t.txnDate }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ t.voucherNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ t.certNo }}</td>
|
|
||||||
<td>{{ instrumentLabel(t.instrumentType) }}</td>
|
|
||||||
<td>{{ t.particulars }}</td>
|
|
||||||
<td class="num col-in">₹{{ t.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="pg-empty">No bond interest entries recorded yet.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td colspan="5">Total</td>
|
|
||||||
<td class="num">₹{{ bondIntRegisterTotal | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- 04 FD INTEREST REGISTER -->
|
|
||||||
@if (activeTab === 'fdIntRegister') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">FD Interest Register</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Voucher No</th>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="num col-in">Interest Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (t of fdIntRegister; track t.voucherNo) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ t.txnDate }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ t.voucherNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ t.certNo }}</td>
|
|
||||||
<td>{{ t.particulars }}</td>
|
|
||||||
<td class="num col-in">₹{{ t.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="5" class="pg-empty">No FD interest entries recorded yet.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td colspan="4">Total</td>
|
|
||||||
<td class="num">₹{{ fdIntRegisterTotal | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- 05 FD REGISTER -->
|
|
||||||
@if (activeTab === 'fdRegister') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">FD Register</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Party</th>
|
|
||||||
<th>Sub Type</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
<th class="num">Rate</th>
|
|
||||||
<th>Frequency</th>
|
|
||||||
<th>Deposit Date</th>
|
|
||||||
<th>Maturity Date</th>
|
|
||||||
<th class="num">Days Left</th>
|
|
||||||
<th class="num col-in">Accrued Interest</th>
|
|
||||||
<th class="num col-net">Maturity Value</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (h of fdRegister; track h.certNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.party }}</td>
|
|
||||||
<td>{{ h.subType }}</td>
|
|
||||||
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">{{ h.interestRate }}%</td>
|
|
||||||
<td>{{ h.interestFreq }}</td>
|
|
||||||
<td>{{ h.purchaseDate }}</td>
|
|
||||||
<td>{{ h.maturityDate }}</td>
|
|
||||||
<td class="num">{{ daysLeft(h) }}</td>
|
|
||||||
<td class="num col-in">₹{{ accrued(h) | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-net">₹{{ maturity(h) | number: '1.2-2' }}</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="statusClass(h)">{{ h.status }}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td colspan="3">Total</td>
|
|
||||||
<td class="num">₹{{ fdRegisterFaceTotal | number: '1.2-2' }}</td>
|
|
||||||
<td colspan="5"></td>
|
|
||||||
<td class="num">₹{{ fdRegisterAccruedTotal | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ fdRegisterMaturityTotal | number: '1.2-2' }}</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { CommonModule } from '@angular/common';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import {
|
|
||||||
FD_HOLDINGS,
|
|
||||||
GOVT_SECURITY_HOLDINGS,
|
|
||||||
NON_SLR_HOLDINGS,
|
|
||||||
BOND_HOLDINGS,
|
|
||||||
ALL_TXNS,
|
|
||||||
SCRIPT_MASTER,
|
|
||||||
InvestmentHolding,
|
|
||||||
InvestmentTxn,
|
|
||||||
InstrumentType,
|
|
||||||
INSTRUMENT_LABELS,
|
|
||||||
accruedInterest,
|
|
||||||
daysToMaturity,
|
|
||||||
maturityAmount,
|
|
||||||
todayDDMMYYYY
|
|
||||||
} from '../investment-data';
|
|
||||||
|
|
||||||
type InterestTab = 'fdCalc' | 'bondCalc' | 'bondIntRegister' | 'fdIntRegister' | 'fdRegister';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The legacy "Interest" top-menu for Investment — 5 screens:
|
|
||||||
* 01 FD Interest Calculation, 02 Govt./Bond Int. Calculation (both batch "Process" runs),
|
|
||||||
* 03 Government Bond Int Register, 04 FD Interest Register, 05 FD Register (listings).
|
|
||||||
*
|
|
||||||
* Interest figures are derived from the same holdings used by the Entry screens and Reports,
|
|
||||||
* so a register here always agrees with what a Purchase/Interest entry recorded.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-investment-interest',
|
|
||||||
templateUrl: './investment-interest.component.html',
|
|
||||||
styleUrls: [
|
|
||||||
'../../cashier/cashier-shared.scss',
|
|
||||||
'../investment-shared.scss'
|
|
||||||
],
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class InvestmentInterestComponent {
|
|
||||||
activeTab: InterestTab = 'fdCalc';
|
|
||||||
asOnDate = todayDDMMYYYY();
|
|
||||||
|
|
||||||
fdHoldings: InvestmentHolding[] = [...FD_HOLDINGS];
|
|
||||||
bondHoldings: InvestmentHolding[] = [
|
|
||||||
...GOVT_SECURITY_HOLDINGS,
|
|
||||||
...NON_SLR_HOLDINGS,
|
|
||||||
...BOND_HOLDINGS
|
|
||||||
];
|
|
||||||
txns: InvestmentTxn[] = [...ALL_TXNS];
|
|
||||||
|
|
||||||
readonly scriptOptions = SCRIPT_MASTER;
|
|
||||||
|
|
||||||
toastMsg = '';
|
|
||||||
toastVisible = false;
|
|
||||||
private toastTimer: ReturnType<typeof setTimeout> | undefined;
|
|
||||||
|
|
||||||
// 01 FD Interest Calculation
|
|
||||||
fdCalcDate = this.asOnDate;
|
|
||||||
fdCalcCertNo = '';
|
|
||||||
fdCalcAll = false;
|
|
||||||
|
|
||||||
// 02 Govt./Bond Int. Calculation
|
|
||||||
bondCalcDate = this.asOnDate;
|
|
||||||
bondCalcType: InstrumentType | '' = '';
|
|
||||||
bondCalcScript = '';
|
|
||||||
bondCalcDealNo = '';
|
|
||||||
bondCalcAll = false;
|
|
||||||
|
|
||||||
// Register filters
|
|
||||||
registerFromDate = '';
|
|
||||||
registerToDate = '';
|
|
||||||
|
|
||||||
setTab(tab: InterestTab): void {
|
|
||||||
this.activeTab = tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
instrumentLabel(type: InstrumentType): string {
|
|
||||||
return INSTRUMENT_LABELS[type];
|
|
||||||
}
|
|
||||||
|
|
||||||
statusClass(h: InvestmentHolding): string {
|
|
||||||
return `inv-badge inv-badge--${h.status.toLowerCase()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
maturity(h: InvestmentHolding): number {
|
|
||||||
return maturityAmount(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
daysLeft(h: InvestmentHolding): number {
|
|
||||||
return daysToMaturity(h, this.asOnDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- 01 FD Interest Calculation --------------------
|
|
||||||
get fdCalcTargets(): InvestmentHolding[] {
|
|
||||||
if (this.fdCalcAll) {
|
|
||||||
return this.fdHoldings.filter((h) => h.status === 'ACTIVE');
|
|
||||||
}
|
|
||||||
return this.fdHoldings.filter(
|
|
||||||
(h) => h.status === 'ACTIVE' && (!this.fdCalcCertNo || h.certNo === this.fdCalcCertNo)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fdInterest(h: InvestmentHolding): number {
|
|
||||||
return accruedInterest(h, this.fdCalcDate || this.asOnDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
get fdCalcTotal(): number {
|
|
||||||
return this.fdCalcTargets.reduce((s, h) => s + this.fdInterest(h), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
processFdInterest(): void {
|
|
||||||
const targets = this.fdCalcTargets;
|
|
||||||
if (targets.length === 0) {
|
|
||||||
this.toast('No FD holdings match the selected filters.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newTxns: InvestmentTxn[] = targets.map((h, i) => ({
|
|
||||||
certNo: h.certNo,
|
|
||||||
instrumentType: 'FD',
|
|
||||||
txnType: 'INTEREST',
|
|
||||||
voucherNo: `INV/FD-INT/${(this.fdCalcDate || this.asOnDate).replace(/-/g, '')}-${i + 1}`,
|
|
||||||
txnDate: this.fdCalcDate || this.asOnDate,
|
|
||||||
amount: this.fdInterest(h),
|
|
||||||
particulars: `FD interest calculated - ${h.party}`
|
|
||||||
}));
|
|
||||||
this.txns = [
|
|
||||||
...newTxns,
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(
|
|
||||||
`FD interest processed for ${targets.length} certificate(s). Total ₹${this.fdCalcTotal.toLocaleString('en-IN')}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- 02 Govt./Bond Int. Calculation --------------------
|
|
||||||
get bondCalcTargets(): InvestmentHolding[] {
|
|
||||||
if (this.bondCalcAll) {
|
|
||||||
return this.bondHoldings.filter((h) => h.status === 'ACTIVE');
|
|
||||||
}
|
|
||||||
return this.bondHoldings.filter((h) => {
|
|
||||||
if (h.status !== 'ACTIVE') return false;
|
|
||||||
if (this.bondCalcType && h.instrumentType !== this.bondCalcType) return false;
|
|
||||||
if (this.bondCalcScript && h.scriptCode !== this.bondCalcScript) return false;
|
|
||||||
if (this.bondCalcDealNo && h.certNo !== this.bondCalcDealNo.trim()) return false;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bondInterest(h: InvestmentHolding): number {
|
|
||||||
return accruedInterest(h, this.bondCalcDate || this.asOnDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
get bondCalcTotal(): number {
|
|
||||||
return this.bondCalcTargets.reduce((s, h) => s + this.bondInterest(h), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
processBondInterest(): void {
|
|
||||||
const targets = this.bondCalcTargets;
|
|
||||||
if (targets.length === 0) {
|
|
||||||
this.toast('No Govt/Bond holdings match the selected filters.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newTxns: InvestmentTxn[] = targets.map((h, i) => ({
|
|
||||||
certNo: h.certNo,
|
|
||||||
instrumentType: h.instrumentType,
|
|
||||||
txnType: 'INTEREST',
|
|
||||||
voucherNo: `INV/BOND-INT/${(this.bondCalcDate || this.asOnDate).replace(/-/g, '')}-${i + 1}`,
|
|
||||||
txnDate: this.bondCalcDate || this.asOnDate,
|
|
||||||
amount: this.bondInterest(h),
|
|
||||||
particulars: `Coupon/interest calculated - ${h.scriptName}`
|
|
||||||
}));
|
|
||||||
this.txns = [
|
|
||||||
...newTxns,
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(
|
|
||||||
`Interest processed for ${targets.length} holding(s). Total ₹${this.bondCalcTotal.toLocaleString('en-IN')}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- 03 Government Bond Int Register --------------------
|
|
||||||
get bondIntRegister(): InvestmentTxn[] {
|
|
||||||
return this.txns.filter(
|
|
||||||
(t) => t.txnType === 'INTEREST' && ([
|
|
||||||
'GOVT_SECURITY',
|
|
||||||
'NON_SLR',
|
|
||||||
'BOND'
|
|
||||||
] as InstrumentType[]).includes(t.instrumentType)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get bondIntRegisterTotal(): number {
|
|
||||||
return this.bondIntRegister.reduce((s, t) => s + t.amount, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- 04 FD Interest Register --------------------
|
|
||||||
get fdIntRegister(): InvestmentTxn[] {
|
|
||||||
return this.txns.filter((t) => t.txnType === 'INTEREST' && t.instrumentType === 'FD');
|
|
||||||
}
|
|
||||||
|
|
||||||
get fdIntRegisterTotal(): number {
|
|
||||||
return this.fdIntRegister.reduce((s, t) => s + t.amount, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- 05 FD Register --------------------
|
|
||||||
get fdRegister(): InvestmentHolding[] {
|
|
||||||
return this.fdHoldings;
|
|
||||||
}
|
|
||||||
|
|
||||||
get fdRegisterFaceTotal(): number {
|
|
||||||
return this.fdRegister.reduce((s, h) => s + h.faceValue, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get fdRegisterMaturityTotal(): number {
|
|
||||||
return this.fdRegister.reduce((s, h) => s + this.maturity(h), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
accrued(h: InvestmentHolding): number {
|
|
||||||
return accruedInterest(h, this.asOnDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
get fdRegisterAccruedTotal(): number {
|
|
||||||
return this.fdRegister.reduce((s, h) => s + this.accrued(h), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private toast(msg: string): void {
|
|
||||||
this.toastMsg = msg;
|
|
||||||
this.toastVisible = true;
|
|
||||||
if (this.toastTimer) {
|
|
||||||
clearTimeout(this.toastTimer);
|
|
||||||
}
|
|
||||||
this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,557 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="pg">
|
|
||||||
<header class="pg-head">
|
|
||||||
<div>
|
|
||||||
<div class="pg-eyebrow">TREASURY · INVESTMENT · MASTERS</div>
|
|
||||||
<h1 class="pg-title">Investment Masters</h1>
|
|
||||||
<p class="pg-sub">
|
|
||||||
Bank/Party, Script, SubType, Broker, Control Parameter, LIEN Reason, Mutual Fund Product, SGL Bank and Script
|
|
||||||
Market Detail masters.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'party'"
|
|
||||||
(click)="setTab('party')"
|
|
||||||
>
|
|
||||||
04 Bank / Party Master
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'script'"
|
|
||||||
(click)="setTab('script')"
|
|
||||||
>
|
|
||||||
05 Script Master
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'subtype'"
|
|
||||||
(click)="setTab('subtype')"
|
|
||||||
>
|
|
||||||
06 SubType Master
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'broker'"
|
|
||||||
(click)="setTab('broker')"
|
|
||||||
>
|
|
||||||
07 Broker Master
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'control'"
|
|
||||||
(click)="setTab('control')"
|
|
||||||
>
|
|
||||||
08 Control Parameter
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'dailyscript'"
|
|
||||||
(click)="setTab('dailyscript')"
|
|
||||||
>
|
|
||||||
09 Daily Script Master
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'lien'"
|
|
||||||
(click)="setTab('lien')"
|
|
||||||
>
|
|
||||||
10 LIEN Reason Master
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'mfproduct'"
|
|
||||||
(click)="setTab('mfproduct')"
|
|
||||||
>
|
|
||||||
11 Mutual Fund Product Master
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'sglbank'"
|
|
||||||
(click)="setTab('sglbank')"
|
|
||||||
>
|
|
||||||
12 SGL Bank Master
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'marketdetail'"
|
|
||||||
(click)="setTab('marketdetail')"
|
|
||||||
>
|
|
||||||
13 Script Market Detail
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- PARTY MASTER -->
|
|
||||||
@if (activeTab === 'party') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Add Bank / Party</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Party Name *</span><input type="text" [(ngModel)]="newParty.partyName"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Party Type *</span>
|
|
||||||
<select [(ngModel)]="newParty.partyType">
|
|
||||||
<option [ngValue]="undefined">-- Select --</option>
|
|
||||||
<option value="BANK">BANK</option>
|
|
||||||
<option value="CORPORATE">CORPORATE</option>
|
|
||||||
<option value="GOVT">GOVT</option>
|
|
||||||
<option value="AMC">AMC</option>
|
|
||||||
<option value="EXCHANGE">EXCHANGE</option>
|
|
||||||
<option value="COOP_SOCIETY">COOP SOCIETY</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field"><span>PAN No</span><input type="text" [(ngModel)]="newParty.panNo" /></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="addParty()">Save</button>
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Party Code</th>
|
|
||||||
<th>Party Name</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>PAN No</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (p of parties; track p.partyCode) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ p.partyCode }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ p.partyName }}</td>
|
|
||||||
<td>{{ p.partyType }}</td>
|
|
||||||
<td>{{ p.panNo }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- SCRIPT MASTER -->
|
|
||||||
@if (activeTab === 'script') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Add Script</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Script Code</span><input type="text" [(ngModel)]="newScript.scriptCode"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Script Name *</span><input type="text" [(ngModel)]="newScript.scriptName"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Instrument Type *</span>
|
|
||||||
<select [(ngModel)]="newScript.instrumentType">
|
|
||||||
<option [ngValue]="undefined">-- Select --</option>
|
|
||||||
@for (t of instrumentTypes; track t) {
|
|
||||||
<option [value]="t">{{ t }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field"><span>ISIN</span><input type="text" [(ngModel)]="newScript.isin" /></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Face Value / Unit</span><input type="number" [(ngModel)]="newScript.faceValuePerUnit"
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="addScript()">Save</button>
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Script Code</th>
|
|
||||||
<th>Script Name</th>
|
|
||||||
<th>Instrument</th>
|
|
||||||
<th>ISIN</th>
|
|
||||||
<th class="num">Face Value/Unit</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (s of scripts; track s.scriptCode) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ s.scriptCode }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ s.scriptName }}</td>
|
|
||||||
<td>{{ s.instrumentType }}</td>
|
|
||||||
<td>{{ s.isin || '—' }}</td>
|
|
||||||
<td class="num">{{ s.faceValuePerUnit }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- SUBTYPE MASTER -->
|
|
||||||
@if (activeTab === 'subtype') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Add Sub Type</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Sub Type Name *</span><input type="text" [(ngModel)]="newSubType.subTypeName"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Instrument Type *</span>
|
|
||||||
<select [(ngModel)]="newSubType.instrumentType">
|
|
||||||
<option [ngValue]="undefined">-- Select --</option>
|
|
||||||
@for (t of instrumentTypes; track t) {
|
|
||||||
<option [value]="t">{{ t }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="addSubType()">Save</button>
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Code</th>
|
|
||||||
<th>Sub Type</th>
|
|
||||||
<th>Instrument</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (s of subTypes; track s.subTypeCode + s.subTypeName) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ s.subTypeCode }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ s.subTypeName }}</td>
|
|
||||||
<td>{{ s.instrumentType }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- BROKER MASTER -->
|
|
||||||
@if (activeTab === 'broker') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Add Broker</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Broker Name *</span><input type="text" [(ngModel)]="newBroker.brokerName"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Contact No</span><input type="text" [(ngModel)]="newBroker.contactNo"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Empanelled On</span><input type="text" [(ngModel)]="newBroker.empanelledOn" placeholder="DD-MM-YYYY"
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="addBroker()">Save</button>
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Broker Code</th>
|
|
||||||
<th>Broker Name</th>
|
|
||||||
<th>Contact</th>
|
|
||||||
<th>Empanelled On</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (b of brokers; track b.brokerCode) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ b.brokerCode }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ b.brokerName }}</td>
|
|
||||||
<td>{{ b.contactNo }}</td>
|
|
||||||
<td>{{ b.empanelledOn }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="inv-badge inv-badge--active">{{ b.status }}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- CONTROL PARAMETER -->
|
|
||||||
@if (activeTab === 'control') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Add Control Parameter</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Parameter Name *</span><input type="text" [(ngModel)]="newControlParam.paramName"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Value *</span><input type="text" [(ngModel)]="newControlParam.paramValue"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Description</span><input type="text" [(ngModel)]="newControlParam.description"
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="addControlParam()">Save</button>
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Parameter</th>
|
|
||||||
<th>Value</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (c of controlParams; track c.paramName) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ c.paramName }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="num">{{ c.paramValue }}</td>
|
|
||||||
<td>{{ c.description }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- DAILY SCRIPT MASTER -->
|
|
||||||
@if (activeTab === 'dailyscript') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Add Daily Script Rate</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Rate Date *</span><input type="text" [(ngModel)]="newDailyRate.rateDate" placeholder="DD-MM-YYYY"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Script Code *</span>
|
|
||||||
<select [(ngModel)]="newDailyRate.scriptCode">
|
|
||||||
<option [ngValue]="undefined">-- Select --</option>
|
|
||||||
@for (s of scripts; track s.scriptCode) {
|
|
||||||
<option [value]="s.scriptCode">{{ s.scriptCode }} — {{ s.scriptName }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Open Rate</span><input type="number" [(ngModel)]="newDailyRate.openRate" step="0.01"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Close Rate *</span><input type="number" [(ngModel)]="newDailyRate.closeRate" step="0.01"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>High Rate</span><input type="number" [(ngModel)]="newDailyRate.highRate" step="0.01"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Low Rate</span><input type="number" [(ngModel)]="newDailyRate.lowRate" step="0.01"
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="addDailyRate()">Save</button>
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Rate Date</th>
|
|
||||||
<th>Script Code</th>
|
|
||||||
<th>Script Name</th>
|
|
||||||
<th class="num">Open</th>
|
|
||||||
<th class="num">High</th>
|
|
||||||
<th class="num">Low</th>
|
|
||||||
<th class="num">Close</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of dailyScriptRates; track r.rateDate + r.scriptCode) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ r.rateDate }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ r.scriptCode }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ r.scriptName }}</td>
|
|
||||||
<td class="num">{{ r.openRate | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">{{ r.highRate | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">{{ r.lowRate | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">{{ r.closeRate | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- LIEN REASON MASTER -->
|
|
||||||
@if (activeTab === 'lien') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Add LIEN Reason</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Reason Description *</span><input type="text" [(ngModel)]="newLienReason.reasonDescription"
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="addLienReason()">Save</button>
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Reason Code</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (l of lienReasons; track l.reasonCode) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ l.reasonCode }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ l.reasonDescription }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- MUTUAL FUND PRODUCT MASTER -->
|
|
||||||
@if (activeTab === 'mfproduct') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Add Mutual Fund Product</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Product Name *</span><input type="text" [(ngModel)]="newMfProduct.productName"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>AMC Name *</span><input type="text" [(ngModel)]="newMfProduct.amcName"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Category</span><input type="text" [(ngModel)]="newMfProduct.category"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Risk Level</span>
|
|
||||||
<select [(ngModel)]="newMfProduct.riskLevel">
|
|
||||||
<option value="LOW">LOW</option>
|
|
||||||
<option value="MODERATE">MODERATE</option>
|
|
||||||
<option value="HIGH">HIGH</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="addMfProduct()">Save</button>
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Product Code</th>
|
|
||||||
<th>Product Name</th>
|
|
||||||
<th>AMC</th>
|
|
||||||
<th>Category</th>
|
|
||||||
<th>Risk</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (m of mfProducts; track m.productCode) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ m.productCode }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ m.productName }}</td>
|
|
||||||
<td>{{ m.amcName }}</td>
|
|
||||||
<td>{{ m.category }}</td>
|
|
||||||
<td>{{ m.riskLevel }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- SGL BANK MASTER -->
|
|
||||||
@if (activeTab === 'sglbank') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Add SGL Bank</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Bank Name *</span><input type="text" [(ngModel)]="newSglBank.bankName"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"><span>Branch</span><input type="text" [(ngModel)]="newSglBank.branch" /></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="addSglBank()">Save</button>
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>SGL Code</th>
|
|
||||||
<th>Bank Name</th>
|
|
||||||
<th>Branch</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (s of sglBanks; track s.sglCode) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ s.sglCode }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ s.bankName }}</td>
|
|
||||||
<td>{{ s.branch }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- SCRIPT MARKET DETAIL (also covers "09 Daily Script Master") -->
|
|
||||||
@if (activeTab === 'marketdetail') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Script Market Detail (Daily Script Rates)</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Script Code</th>
|
|
||||||
<th class="num">Market Price</th>
|
|
||||||
<th>As On</th>
|
|
||||||
<th class="num">YTM (%)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (m of marketDetails; track m.scriptCode) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ m.scriptCode }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="num">₹{{ m.marketPrice | number: '1.2-2' }}</td>
|
|
||||||
<td>{{ m.asOnDate }}</td>
|
|
||||||
<td class="num">{{ m.ytm }}%</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { CommonModule } from '@angular/common';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import {
|
|
||||||
SCRIPT_MASTER,
|
|
||||||
BROKER_MASTER,
|
|
||||||
PARTY_MASTER,
|
|
||||||
SUBTYPE_MASTER,
|
|
||||||
CONTROL_PARAMETERS,
|
|
||||||
MUTUAL_FUND_PRODUCTS,
|
|
||||||
SGL_BANK_MASTER,
|
|
||||||
SCRIPT_MARKET_DETAIL,
|
|
||||||
DAILY_SCRIPT_RATES,
|
|
||||||
LIEN_REASON_MASTER,
|
|
||||||
ScriptMaster,
|
|
||||||
BrokerMaster,
|
|
||||||
PartyMaster,
|
|
||||||
SubTypeMaster,
|
|
||||||
ControlParameter,
|
|
||||||
MutualFundProductMaster,
|
|
||||||
SglBankMaster,
|
|
||||||
ScriptMarketDetail,
|
|
||||||
DailyScriptRate,
|
|
||||||
LienReason,
|
|
||||||
InstrumentType
|
|
||||||
} from '../investment-data';
|
|
||||||
|
|
||||||
type MasterTab =
|
|
||||||
| 'party'
|
|
||||||
| 'script'
|
|
||||||
| 'subtype'
|
|
||||||
| 'broker'
|
|
||||||
| 'control'
|
|
||||||
| 'dailyscript'
|
|
||||||
| 'lien'
|
|
||||||
| 'mfproduct'
|
|
||||||
| 'sglbank'
|
|
||||||
| 'marketdetail';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Investment master-data screens (legacy "04-13" Entry submenu masters) — 9 simple dummy
|
|
||||||
* lists, each with an inline Add-row form so the demo stays interactive without a backend.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-investment-masters',
|
|
||||||
templateUrl: './investment-masters.component.html',
|
|
||||||
styleUrls: [
|
|
||||||
'../../cashier/cashier-shared.scss',
|
|
||||||
'../investment-shared.scss'
|
|
||||||
],
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class InvestmentMastersComponent {
|
|
||||||
activeTab: MasterTab = 'party';
|
|
||||||
|
|
||||||
scripts: ScriptMaster[] = [...SCRIPT_MASTER];
|
|
||||||
brokers: BrokerMaster[] = [...BROKER_MASTER];
|
|
||||||
parties: PartyMaster[] = [...PARTY_MASTER];
|
|
||||||
subTypes: SubTypeMaster[] = [...SUBTYPE_MASTER];
|
|
||||||
controlParams: ControlParameter[] = [...CONTROL_PARAMETERS];
|
|
||||||
mfProducts: MutualFundProductMaster[] = [...MUTUAL_FUND_PRODUCTS];
|
|
||||||
sglBanks: SglBankMaster[] = [...SGL_BANK_MASTER];
|
|
||||||
marketDetails: ScriptMarketDetail[] = [...SCRIPT_MARKET_DETAIL];
|
|
||||||
dailyScriptRates: DailyScriptRate[] = [...DAILY_SCRIPT_RATES];
|
|
||||||
lienReasons: LienReason[] = [...LIEN_REASON_MASTER];
|
|
||||||
|
|
||||||
readonly instrumentTypes: InstrumentType[] = [
|
|
||||||
'FD',
|
|
||||||
'GOVT_SECURITY',
|
|
||||||
'NON_SLR',
|
|
||||||
'BOND',
|
|
||||||
'MUTUAL_FUND',
|
|
||||||
'SHARE'
|
|
||||||
];
|
|
||||||
|
|
||||||
toastMsg = '';
|
|
||||||
toastVisible = false;
|
|
||||||
private toastTimer: ReturnType<typeof setTimeout> | undefined;
|
|
||||||
|
|
||||||
newParty: Partial<PartyMaster> = {};
|
|
||||||
newScript: Partial<ScriptMaster> = {};
|
|
||||||
newSubType: Partial<SubTypeMaster> = {};
|
|
||||||
newBroker: Partial<BrokerMaster> = {};
|
|
||||||
newControlParam: Partial<ControlParameter> = {};
|
|
||||||
newLienReason: Partial<LienReason> = {};
|
|
||||||
newMfProduct: Partial<MutualFundProductMaster> = {};
|
|
||||||
newSglBank: Partial<SglBankMaster> = {};
|
|
||||||
newDailyRate: Partial<DailyScriptRate> = {};
|
|
||||||
|
|
||||||
setTab(tab: MasterTab): void {
|
|
||||||
this.activeTab = tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
addParty(): void {
|
|
||||||
if (!this.newParty.partyName || !this.newParty.partyType) {
|
|
||||||
this.toast('Enter Party Name and Type.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.parties = [
|
|
||||||
{
|
|
||||||
partyCode: `PTY-${String(this.parties.length + 1).padStart(3, '0')}`,
|
|
||||||
panNo: '',
|
|
||||||
...this.newParty
|
|
||||||
} as PartyMaster,
|
|
||||||
...this.parties
|
|
||||||
];
|
|
||||||
this.newParty = {};
|
|
||||||
this.toast('Party/Bank master added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
addScript(): void {
|
|
||||||
if (!this.newScript.scriptName || !this.newScript.instrumentType) {
|
|
||||||
this.toast('Enter Script Name and Instrument Type.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.scripts = [
|
|
||||||
{
|
|
||||||
scriptCode: this.newScript.scriptCode || `SCR-${this.scripts.length + 1}`,
|
|
||||||
isin: '',
|
|
||||||
faceValuePerUnit: 100,
|
|
||||||
...this.newScript
|
|
||||||
} as ScriptMaster,
|
|
||||||
...this.scripts
|
|
||||||
];
|
|
||||||
this.newScript = {};
|
|
||||||
this.toast('Script master added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
addSubType(): void {
|
|
||||||
if (!this.newSubType.subTypeName || !this.newSubType.instrumentType) {
|
|
||||||
this.toast('Enter Sub Type Name and Instrument Type.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.subTypes = [
|
|
||||||
{ subTypeCode: String(this.subTypes.length + 1), ...this.newSubType } as SubTypeMaster,
|
|
||||||
...this.subTypes
|
|
||||||
];
|
|
||||||
this.newSubType = {};
|
|
||||||
this.toast('Sub Type master added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
addBroker(): void {
|
|
||||||
if (!this.newBroker.brokerName) {
|
|
||||||
this.toast('Enter Broker Name.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.brokers = [
|
|
||||||
{
|
|
||||||
brokerCode: `BRK-${String(this.brokers.length + 1).padStart(3, '0')}`,
|
|
||||||
contactNo: '',
|
|
||||||
empanelledOn: '',
|
|
||||||
status: 'ACTIVE',
|
|
||||||
...this.newBroker
|
|
||||||
} as BrokerMaster,
|
|
||||||
...this.brokers
|
|
||||||
];
|
|
||||||
this.newBroker = {};
|
|
||||||
this.toast('Broker master added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
addControlParam(): void {
|
|
||||||
if (!this.newControlParam.paramName || !this.newControlParam.paramValue) {
|
|
||||||
this.toast('Enter Parameter Name and Value.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.controlParams = [
|
|
||||||
{ description: '', ...this.newControlParam } as ControlParameter,
|
|
||||||
...this.controlParams
|
|
||||||
];
|
|
||||||
this.newControlParam = {};
|
|
||||||
this.toast('Control parameter added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
addLienReason(): void {
|
|
||||||
if (!this.newLienReason.reasonDescription) {
|
|
||||||
this.toast('Enter Reason Description.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.lienReasons = [
|
|
||||||
{ reasonCode: `LR-${String(this.lienReasons.length + 1).padStart(2, '0')}`, ...this.newLienReason } as LienReason,
|
|
||||||
...this.lienReasons
|
|
||||||
];
|
|
||||||
this.newLienReason = {};
|
|
||||||
this.toast('LIEN reason added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
addMfProduct(): void {
|
|
||||||
if (!this.newMfProduct.productName || !this.newMfProduct.amcName) {
|
|
||||||
this.toast('Enter Product Name and AMC Name.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.mfProducts = [
|
|
||||||
{
|
|
||||||
productCode: `MFP-${String(this.mfProducts.length + 1).padStart(2, '0')}`,
|
|
||||||
category: '',
|
|
||||||
riskLevel: 'MODERATE',
|
|
||||||
...this.newMfProduct
|
|
||||||
} as MutualFundProductMaster,
|
|
||||||
...this.mfProducts
|
|
||||||
];
|
|
||||||
this.newMfProduct = {};
|
|
||||||
this.toast('Mutual Fund product added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
addSglBank(): void {
|
|
||||||
if (!this.newSglBank.bankName) {
|
|
||||||
this.toast('Enter Bank Name.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.sglBanks = [
|
|
||||||
{
|
|
||||||
sglCode: `SGL-${String(this.sglBanks.length + 1).padStart(2, '0')}`,
|
|
||||||
branch: '',
|
|
||||||
...this.newSglBank
|
|
||||||
} as SglBankMaster,
|
|
||||||
...this.sglBanks
|
|
||||||
];
|
|
||||||
this.newSglBank = {};
|
|
||||||
this.toast('SGL Bank master added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
addDailyRate(): void {
|
|
||||||
if (!this.newDailyRate.scriptCode || !this.newDailyRate.rateDate || this.newDailyRate.closeRate == null) {
|
|
||||||
this.toast('Enter Rate Date, Script Code and Close Rate.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const script = this.scripts.find((s) => s.scriptCode === this.newDailyRate.scriptCode);
|
|
||||||
this.dailyScriptRates = [
|
|
||||||
{
|
|
||||||
scriptName: script?.scriptName || '',
|
|
||||||
openRate: this.newDailyRate.closeRate,
|
|
||||||
highRate: this.newDailyRate.closeRate,
|
|
||||||
lowRate: this.newDailyRate.closeRate,
|
|
||||||
...this.newDailyRate
|
|
||||||
} as DailyScriptRate,
|
|
||||||
...this.dailyScriptRates
|
|
||||||
];
|
|
||||||
this.newDailyRate = {};
|
|
||||||
this.toast('Daily script rate added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
private toast(msg: string): void {
|
|
||||||
this.toastMsg = msg;
|
|
||||||
this.toastVisible = true;
|
|
||||||
if (this.toastTimer) {
|
|
||||||
clearTimeout(this.toastTimer);
|
|
||||||
}
|
|
||||||
this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,694 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="pg">
|
|
||||||
<header class="pg-head">
|
|
||||||
<div>
|
|
||||||
<div class="pg-eyebrow">TREASURY · INVESTMENT · REPORTS</div>
|
|
||||||
<h1 class="pg-title">Investment Reports</h1>
|
|
||||||
<p class="pg-sub">
|
|
||||||
Balance reports, ledgers, registers, maturity lists and the consolidated portfolio — all derived live from the
|
|
||||||
same holdings/transactions used by the Entry screens.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeGroup === 'balance'"
|
|
||||||
(click)="setGroup('balance', 'govtSecBalance')"
|
|
||||||
>
|
|
||||||
Balance Reports
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeGroup === 'ledger'"
|
|
||||||
(click)="setGroup('ledger', 'fdLedger')"
|
|
||||||
>
|
|
||||||
Ledgers & Registers
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeGroup === 'maturity'"
|
|
||||||
(click)="setGroup('maturity', 'maturedFd')"
|
|
||||||
>
|
|
||||||
Maturity & Lists
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeGroup === 'portfolio'"
|
|
||||||
(click)="setGroup('portfolio', 'portfolio')"
|
|
||||||
>
|
|
||||||
Portfolio
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ===================== BALANCE REPORTS ===================== -->
|
|
||||||
@if (activeGroup === 'balance') {
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'govtSecBalance'"
|
|
||||||
(click)="setReport('govtSecBalance')"
|
|
||||||
>
|
|
||||||
Govt Security Balance
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'shareBalance'"
|
|
||||||
(click)="setReport('shareBalance')"
|
|
||||||
>
|
|
||||||
Share Balance
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'fdrBalance'"
|
|
||||||
(click)="setReport('fdrBalance')"
|
|
||||||
>
|
|
||||||
FDR Balance
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'bankInvList'"
|
|
||||||
(click)="setReport('bankInvList')"
|
|
||||||
>
|
|
||||||
Bank Investment List
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (activeReport === 'govtSecBalance' || activeReport === 'shareBalance' || activeReport === 'fdrBalance') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">
|
|
||||||
{{
|
|
||||||
activeReport === 'govtSecBalance'
|
|
||||||
? 'Govt Security Balance Report'
|
|
||||||
: activeReport === 'shareBalance'
|
|
||||||
? 'Share Balance Report'
|
|
||||||
: 'FDR Balance Report'
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Script / Party</th>
|
|
||||||
<th class="num">Units</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
<th class="num">Market/Purchase Rate</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (
|
|
||||||
h of activeReport === 'govtSecBalance'
|
|
||||||
? govtSecHoldings
|
|
||||||
: activeReport === 'shareBalance'
|
|
||||||
? shareHoldings
|
|
||||||
: fdHoldings;
|
|
||||||
track h.certNo
|
|
||||||
) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.scriptName }} — {{ h.party }}</td>
|
|
||||||
<td class="num">{{ h.units | number }}</td>
|
|
||||||
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">{{ marketPrice(h.scriptCode) || h.purchaseRate }}</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="statusClass(h)">{{ h.status }}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (activeReport === 'bankInvList') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Bank Investment List</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Party / Bank</th>
|
|
||||||
<th class="num">No. of Holdings</th>
|
|
||||||
<th class="num">Total Face Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of bankInvestmentList; track r.party) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ r.party }}</td>
|
|
||||||
<td class="num">{{ r.count }}</td>
|
|
||||||
<td class="num">₹{{ r.totalFaceValue | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ===================== LEDGERS & REGISTERS ===================== -->
|
|
||||||
@if (activeGroup === 'ledger') {
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'fdLedger'"
|
|
||||||
(click)="setReport('fdLedger')"
|
|
||||||
>
|
|
||||||
FD Ledger
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'bondLedger'"
|
|
||||||
(click)="setReport('bondLedger')"
|
|
||||||
>
|
|
||||||
Govt/Bond Ledger
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'shareLedger'"
|
|
||||||
(click)="setReport('shareLedger')"
|
|
||||||
>
|
|
||||||
Share Ledger
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'intDueRegister'"
|
|
||||||
(click)="setReport('intDueRegister')"
|
|
||||||
>
|
|
||||||
Interest Due Register
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'slrRegister'"
|
|
||||||
(click)="setReport('slrRegister')"
|
|
||||||
>
|
|
||||||
SLR Bond Register
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'pendingReceived'"
|
|
||||||
(click)="setReport('pendingReceived')"
|
|
||||||
>
|
|
||||||
Pending/Received Interest
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'amortizeLedger'"
|
|
||||||
(click)="setReport('amortizeLedger')"
|
|
||||||
>
|
|
||||||
Amortize Ledger
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'dealSlip'"
|
|
||||||
(click)="setReport('dealSlip')"
|
|
||||||
>
|
|
||||||
Deal Slip
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (
|
|
||||||
activeReport === 'fdLedger' ||
|
|
||||||
activeReport === 'bondLedger' ||
|
|
||||||
activeReport === 'shareLedger' ||
|
|
||||||
activeReport === 'amortizeLedger' ||
|
|
||||||
activeReport === 'dealSlip'
|
|
||||||
) {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">
|
|
||||||
{{
|
|
||||||
activeReport === 'fdLedger'
|
|
||||||
? 'FD Ledger'
|
|
||||||
: activeReport === 'bondLedger'
|
|
||||||
? 'Govt/Bond Ledger'
|
|
||||||
: activeReport === 'shareLedger'
|
|
||||||
? 'Share Ledger'
|
|
||||||
: activeReport === 'amortizeLedger'
|
|
||||||
? 'Amortize Ledger'
|
|
||||||
: 'Deal Slip Register'
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Voucher No</th>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="num">Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (
|
|
||||||
t of activeReport === 'fdLedger'
|
|
||||||
? fdTxns
|
|
||||||
: activeReport === 'bondLedger'
|
|
||||||
? bondTxns
|
|
||||||
: activeReport === 'shareLedger'
|
|
||||||
? shareTxns
|
|
||||||
: activeReport === 'amortizeLedger'
|
|
||||||
? amortizeTxns
|
|
||||||
: dealSlips;
|
|
||||||
track t.voucherNo
|
|
||||||
) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ t.txnDate }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ t.voucherNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ t.certNo }}</td>
|
|
||||||
<td>{{ t.txnType }}</td>
|
|
||||||
<td>{{ t.particulars }}</td>
|
|
||||||
<td class="num">₹{{ t.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="pg-empty">No entries.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (activeReport === 'intDueRegister') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Interest Due Register (Non-Cumulative Holdings)</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Script / Party</th>
|
|
||||||
<th>Frequency</th>
|
|
||||||
<th class="num">Rate</th>
|
|
||||||
<th class="num col-in">Due Amount (as on {{ asOnDate }})</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (h of interestDueRegister; track h.certNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.scriptName }} — {{ h.party }}</td>
|
|
||||||
<td>{{ h.interestFreq }}</td>
|
|
||||||
<td class="num">{{ h.interestRate }}%</td>
|
|
||||||
<td class="num col-in">₹{{ h.dueAmount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (activeReport === 'slrRegister') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">SLR Bond Register</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Script</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
<th class="num">Rate</th>
|
|
||||||
<th>Maturity Date</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (h of slrBondRegister; track h.certNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.scriptName }}</td>
|
|
||||||
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">{{ h.interestRate }}%</td>
|
|
||||||
<td>{{ h.maturityDate }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (activeReport === 'pendingReceived') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Received Interest / Dividend</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="num col-in">Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (t of receivedInterestTxns; track t.voucherNo) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ t.txnDate }}</td>
|
|
||||||
<td>{{ t.certNo }}</td>
|
|
||||||
<td>{{ t.particulars }}</td>
|
|
||||||
<td class="num col-in">₹{{ t.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Pending Interest (No Interest Posted Yet)</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Script / Party</th>
|
|
||||||
<th class="num col-out">Accrued (Pending)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (h of pendingInterestHoldings; track h.certNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.scriptName }} — {{ h.party }}</td>
|
|
||||||
<td class="num col-out">₹{{ accrued(h) | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ===================== MATURITY & LISTS ===================== -->
|
|
||||||
@if (activeGroup === 'maturity') {
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'maturedFd'"
|
|
||||||
(click)="setReport('maturedFd')"
|
|
||||||
>
|
|
||||||
List of Matured FD
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'maturingList'"
|
|
||||||
(click)="setReport('maturingList')"
|
|
||||||
>
|
|
||||||
Govt/FD Mature List
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'maturityPattern'"
|
|
||||||
(click)="setReport('maturityPattern')"
|
|
||||||
>
|
|
||||||
Maturity Pattern Register
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'govtSecList'"
|
|
||||||
(click)="setReport('govtSecList')"
|
|
||||||
>
|
|
||||||
Govt Security List
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'fdSecList'"
|
|
||||||
(click)="setReport('fdSecList')"
|
|
||||||
>
|
|
||||||
FD Security List
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'shareSecList'"
|
|
||||||
(click)="setReport('shareSecList')"
|
|
||||||
>
|
|
||||||
Share Security List
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'mfList'"
|
|
||||||
(click)="setReport('mfList')"
|
|
||||||
>
|
|
||||||
Mutual Fund List
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === 'shiftingReport'"
|
|
||||||
(click)="setReport('shiftingReport')"
|
|
||||||
>
|
|
||||||
Shifting Report
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (activeReport === 'maturedFd') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">List of Matured FD</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Party</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
<th>Matured On</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (h of maturedFdList; track h.certNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.party }}</td>
|
|
||||||
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
|
|
||||||
<td>{{ h.maturityDate }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" class="pg-empty">No matured FDs.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (activeReport === 'maturingList') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Govt Security / FD Maturity List (sorted by days-to-maturity)</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Instrument</th>
|
|
||||||
<th>Script / Party</th>
|
|
||||||
<th>Maturity Date</th>
|
|
||||||
<th class="num">Days Left</th>
|
|
||||||
<th class="num col-net">Maturity Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (h of maturingList; track h.certNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ instrumentLabel(h.instrumentType) }}</td>
|
|
||||||
<td>{{ h.scriptName }} — {{ h.party }}</td>
|
|
||||||
<td>{{ h.maturityDate }}</td>
|
|
||||||
<td class="num">{{ daysLeft(h) }}</td>
|
|
||||||
<td class="num col-net">₹{{ maturity(h) | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (activeReport === 'maturityPattern') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Maturity Pattern Register</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Bucket</th>
|
|
||||||
<th class="num">Holdings</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (b of maturityBuckets; track b.label) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ b.label }}</td>
|
|
||||||
<td class="num">{{ b.count }}</td>
|
|
||||||
<td class="num">₹{{ b.faceValue | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (
|
|
||||||
activeReport === 'govtSecList' ||
|
|
||||||
activeReport === 'fdSecList' ||
|
|
||||||
activeReport === 'shareSecList' ||
|
|
||||||
activeReport === 'mfList'
|
|
||||||
) {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">
|
|
||||||
{{
|
|
||||||
activeReport === 'govtSecList'
|
|
||||||
? 'Govt Security List'
|
|
||||||
: activeReport === 'fdSecList'
|
|
||||||
? 'FD Security List'
|
|
||||||
: activeReport === 'shareSecList'
|
|
||||||
? 'Share Security List'
|
|
||||||
: 'Mutual Fund List'
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Script</th>
|
|
||||||
<th>Party</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
<th class="num">Rate</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (
|
|
||||||
h of activeReport === 'govtSecList'
|
|
||||||
? govtSecList
|
|
||||||
: activeReport === 'fdSecList'
|
|
||||||
? fdSecList
|
|
||||||
: activeReport === 'shareSecList'
|
|
||||||
? shareSecList
|
|
||||||
: mutualFundList;
|
|
||||||
track h.certNo
|
|
||||||
) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.scriptName }}</td>
|
|
||||||
<td>{{ h.party }}</td>
|
|
||||||
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">{{ h.interestRate }}%</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="statusClass(h)">{{ h.status }}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (activeReport === 'shiftingReport') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">
|
|
||||||
Shifting Report — SLR Securities with Market Deviation ≥ 1% (AFS/HTM review candidates)
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Script</th>
|
|
||||||
<th class="num">Purchase Rate</th>
|
|
||||||
<th class="num">Market Price</th>
|
|
||||||
<th class="num">Deviation %</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (h of shiftingCandidates; track h.certNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.scriptName }}</td>
|
|
||||||
<td class="num">{{ h.purchaseRate }}</td>
|
|
||||||
<td class="num">{{ h.marketPrice }}</td>
|
|
||||||
<td class="num" [class.col-in]="h.deviationPct > 0" [class.col-out]="h.deviationPct < 0">
|
|
||||||
{{ h.deviationPct }}%
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="5" class="pg-empty">No securities crossing the 1% deviation threshold today.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ===================== PORTFOLIO ===================== -->
|
|
||||||
@if (activeGroup === 'portfolio') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment Portfolio — Consolidated Summary</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Instrument Type</th>
|
|
||||||
<th class="num">Holdings</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
<th class="num col-in">Accrued Interest</th>
|
|
||||||
<th class="num col-net">Maturity Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of portfolioSummary; track r.label) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ r.label }}</td>
|
|
||||||
<td class="num">{{ r.count }}</td>
|
|
||||||
<td class="num">₹{{ r.faceValue | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-in">₹{{ r.accrued | number: '1.2-2' }}</td>
|
|
||||||
<td class="num col-net">₹{{ r.maturityValue | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td>Total</td>
|
|
||||||
<td class="num"></td>
|
|
||||||
<td class="num">₹{{ portfolioTotalFaceValue | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ portfolioTotalAccrued | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ portfolioTotalMaturity | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { CommonModule } from '@angular/common';
|
|
||||||
import {
|
|
||||||
ALL_HOLDINGS,
|
|
||||||
ALL_TXNS,
|
|
||||||
FD_HOLDINGS,
|
|
||||||
GOVT_SECURITY_HOLDINGS,
|
|
||||||
NON_SLR_HOLDINGS,
|
|
||||||
BOND_HOLDINGS,
|
|
||||||
MUTUAL_FUND_HOLDINGS,
|
|
||||||
SHARE_HOLDINGS,
|
|
||||||
SCRIPT_MARKET_DETAIL,
|
|
||||||
InstrumentType,
|
|
||||||
InvestmentHolding,
|
|
||||||
InvestmentTxn,
|
|
||||||
INSTRUMENT_LABELS,
|
|
||||||
accruedInterest,
|
|
||||||
maturityAmount,
|
|
||||||
daysToMaturity,
|
|
||||||
todayDDMMYYYY
|
|
||||||
} from '../investment-data';
|
|
||||||
|
|
||||||
type ReportGroup = 'balance' | 'ledger' | 'maturity' | 'portfolio';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ~20 legacy Investment reports, grouped into 4 tabs. Every figure here is derived from the
|
|
||||||
* same ALL_HOLDINGS/ALL_TXNS arrays used by the Entry screens and Dashboard — so a report can
|
|
||||||
* never drift out of sync with what a Purchase/Sell/Interest entry actually recorded.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-investment-reports',
|
|
||||||
templateUrl: './investment-reports.component.html',
|
|
||||||
styleUrls: [
|
|
||||||
'../../cashier/cashier-shared.scss',
|
|
||||||
'../investment-shared.scss'
|
|
||||||
],
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule]
|
|
||||||
})
|
|
||||||
export class InvestmentReportsComponent {
|
|
||||||
activeGroup: ReportGroup = 'balance';
|
|
||||||
activeReport = 'govtSecBalance';
|
|
||||||
asOnDate = todayDDMMYYYY();
|
|
||||||
|
|
||||||
setGroup(group: ReportGroup, defaultReport: string): void {
|
|
||||||
this.activeGroup = group;
|
|
||||||
this.activeReport = defaultReport;
|
|
||||||
}
|
|
||||||
|
|
||||||
setReport(report: string): void {
|
|
||||||
this.activeReport = report;
|
|
||||||
}
|
|
||||||
|
|
||||||
instrumentLabel(type: InstrumentType): string {
|
|
||||||
return INSTRUMENT_LABELS[type];
|
|
||||||
}
|
|
||||||
|
|
||||||
accrued(h: InvestmentHolding): number {
|
|
||||||
return accruedInterest(h, this.asOnDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
maturity(h: InvestmentHolding): number {
|
|
||||||
return maturityAmount(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
daysLeft(h: InvestmentHolding): number {
|
|
||||||
return daysToMaturity(h, this.asOnDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
statusClass(h: InvestmentHolding): string {
|
|
||||||
return `inv-badge inv-badge--${h.status.toLowerCase()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
marketPrice(scriptCode: string): number | null {
|
|
||||||
return SCRIPT_MARKET_DETAIL.find((m) => m.scriptCode === scriptCode)?.marketPrice ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Balance Reports --------------------
|
|
||||||
get govtSecHoldings(): InvestmentHolding[] {
|
|
||||||
return GOVT_SECURITY_HOLDINGS;
|
|
||||||
}
|
|
||||||
get shareHoldings(): InvestmentHolding[] {
|
|
||||||
return SHARE_HOLDINGS;
|
|
||||||
}
|
|
||||||
get fdHoldings(): InvestmentHolding[] {
|
|
||||||
return FD_HOLDINGS;
|
|
||||||
}
|
|
||||||
get bankInvestmentList(): Array<{ party: string; count: number; totalFaceValue: number }> {
|
|
||||||
const map = new Map<string, { party: string; count: number; totalFaceValue: number }>();
|
|
||||||
for (const h of ALL_HOLDINGS) {
|
|
||||||
const row = map.get(h.party) || { party: h.party, count: 0, totalFaceValue: 0 };
|
|
||||||
row.count += 1;
|
|
||||||
row.totalFaceValue += h.faceValue;
|
|
||||||
map.set(h.party, row);
|
|
||||||
}
|
|
||||||
return [...map.values()].sort((a, b) => b.totalFaceValue - a.totalFaceValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Ledgers & Registers --------------------
|
|
||||||
get fdTxns(): InvestmentTxn[] {
|
|
||||||
return ALL_TXNS.filter((t) => t.instrumentType === 'FD');
|
|
||||||
}
|
|
||||||
get bondTxns(): InvestmentTxn[] {
|
|
||||||
return ALL_TXNS.filter((t) => ([
|
|
||||||
'GOVT_SECURITY',
|
|
||||||
'NON_SLR',
|
|
||||||
'BOND'
|
|
||||||
] as InstrumentType[]).includes(t.instrumentType));
|
|
||||||
}
|
|
||||||
get shareTxns(): InvestmentTxn[] {
|
|
||||||
return ALL_TXNS.filter((t) => t.instrumentType === 'SHARE');
|
|
||||||
}
|
|
||||||
get amortizeTxns(): InvestmentTxn[] {
|
|
||||||
return ALL_TXNS.filter((t) => t.txnType === 'AMORTIZE');
|
|
||||||
}
|
|
||||||
get dealSlips(): InvestmentTxn[] {
|
|
||||||
return ALL_TXNS.filter((t) => t.txnType === 'PURCHASE');
|
|
||||||
}
|
|
||||||
get slrBondRegister(): InvestmentHolding[] {
|
|
||||||
return GOVT_SECURITY_HOLDINGS.filter((h) => h.subType === 'SLR');
|
|
||||||
}
|
|
||||||
get interestDueRegister(): Array<InvestmentHolding & { dueAmount: number }> {
|
|
||||||
return ALL_HOLDINGS.filter((h) => h.status === 'ACTIVE' && h.interestFreq !== 'CUMULATIVE').map((h) => ({
|
|
||||||
...h,
|
|
||||||
dueAmount: this.accrued(h)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
get receivedInterestTxns(): InvestmentTxn[] {
|
|
||||||
return ALL_TXNS.filter((t) => t.txnType === 'INTEREST' || t.txnType === 'DIVIDEND');
|
|
||||||
}
|
|
||||||
get pendingInterestHoldings(): InvestmentHolding[] {
|
|
||||||
const receivedCerts = new Set(this.receivedInterestTxns.map((t) => t.certNo));
|
|
||||||
return ALL_HOLDINGS.filter(
|
|
||||||
(h) => h.status === 'ACTIVE' && h.interestFreq !== 'CUMULATIVE' && !receivedCerts.has(h.certNo)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Maturity & Lists --------------------
|
|
||||||
get maturedFdList(): InvestmentHolding[] {
|
|
||||||
return FD_HOLDINGS.filter((h) => h.status === 'MATURED');
|
|
||||||
}
|
|
||||||
get maturingList(): InvestmentHolding[] {
|
|
||||||
return [
|
|
||||||
...FD_HOLDINGS,
|
|
||||||
...GOVT_SECURITY_HOLDINGS
|
|
||||||
]
|
|
||||||
.filter((h) => h.status === 'ACTIVE')
|
|
||||||
.sort((a, b) => this.daysLeft(a) - this.daysLeft(b));
|
|
||||||
}
|
|
||||||
get maturityBuckets(): Array<{ label: string; count: number; faceValue: number }> {
|
|
||||||
const buckets = [
|
|
||||||
{ label: '0-1 Year', min: -99999, max: 365, count: 0, faceValue: 0 },
|
|
||||||
{ label: '1-3 Years', min: 366, max: 1095, count: 0, faceValue: 0 },
|
|
||||||
{ label: '3-5 Years', min: 1096, max: 1825, count: 0, faceValue: 0 },
|
|
||||||
{ label: 'Above 5 Years', min: 1826, max: 999999, count: 0, faceValue: 0 }
|
|
||||||
];
|
|
||||||
for (const h of ALL_HOLDINGS.filter((x) => x.status === 'ACTIVE')) {
|
|
||||||
const days = this.daysLeft(h);
|
|
||||||
const bucket = buckets.find((b) => days >= b.min && days <= b.max);
|
|
||||||
if (bucket) {
|
|
||||||
bucket.count += 1;
|
|
||||||
bucket.faceValue += h.faceValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buckets;
|
|
||||||
}
|
|
||||||
get govtSecList(): InvestmentHolding[] {
|
|
||||||
return GOVT_SECURITY_HOLDINGS;
|
|
||||||
}
|
|
||||||
get fdSecList(): InvestmentHolding[] {
|
|
||||||
return FD_HOLDINGS;
|
|
||||||
}
|
|
||||||
get shareSecList(): InvestmentHolding[] {
|
|
||||||
return SHARE_HOLDINGS;
|
|
||||||
}
|
|
||||||
get mutualFundList(): InvestmentHolding[] {
|
|
||||||
return MUTUAL_FUND_HOLDINGS;
|
|
||||||
}
|
|
||||||
get nonSlrList(): InvestmentHolding[] {
|
|
||||||
return NON_SLR_HOLDINGS;
|
|
||||||
}
|
|
||||||
get bondList(): InvestmentHolding[] {
|
|
||||||
return BOND_HOLDINGS;
|
|
||||||
}
|
|
||||||
/** Shifting Report — SLR securities whose market value has moved away from book value by
|
|
||||||
* more than 1%, a proxy for "category shift" candidates (AFS/HTM reclassification review). */
|
|
||||||
get shiftingCandidates(): Array<InvestmentHolding & { marketPrice: number; deviationPct: number }> {
|
|
||||||
return GOVT_SECURITY_HOLDINGS.map((h) => {
|
|
||||||
const mp = this.marketPrice(h.scriptCode);
|
|
||||||
if (mp === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const deviationPct = +(((mp - h.purchaseRate) / h.purchaseRate) * 100).toFixed(2);
|
|
||||||
return { ...h, marketPrice: mp, deviationPct };
|
|
||||||
})
|
|
||||||
.filter(
|
|
||||||
(x): x is InvestmentHolding & { marketPrice: number; deviationPct: number } =>
|
|
||||||
x !== null && Math.abs(x.deviationPct) >= 1
|
|
||||||
)
|
|
||||||
.sort((a, b) => Math.abs(b.deviationPct) - Math.abs(a.deviationPct));
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Portfolio --------------------
|
|
||||||
private readonly portfolioTypes: InstrumentType[] = [
|
|
||||||
'FD',
|
|
||||||
'GOVT_SECURITY',
|
|
||||||
'NON_SLR',
|
|
||||||
'BOND',
|
|
||||||
'MUTUAL_FUND',
|
|
||||||
'SHARE'
|
|
||||||
];
|
|
||||||
get portfolioSummary(): Array<{
|
|
||||||
label: string;
|
|
||||||
count: number;
|
|
||||||
faceValue: number;
|
|
||||||
accrued: number;
|
|
||||||
maturityValue: number;
|
|
||||||
}> {
|
|
||||||
return this.portfolioTypes.map((type) => {
|
|
||||||
const holdings = ALL_HOLDINGS.filter((h) => h.instrumentType === type);
|
|
||||||
return {
|
|
||||||
label: INSTRUMENT_LABELS[type],
|
|
||||||
count: holdings.length,
|
|
||||||
faceValue: holdings.reduce((s, h) => s + h.faceValue, 0),
|
|
||||||
accrued: holdings.reduce((s, h) => s + this.accrued(h), 0),
|
|
||||||
maturityValue: holdings.reduce((s, h) => s + this.maturity(h), 0)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
get portfolioTotalFaceValue(): number {
|
|
||||||
return this.portfolioSummary.reduce((s, r) => s + r.faceValue, 0);
|
|
||||||
}
|
|
||||||
get portfolioTotalAccrued(): number {
|
|
||||||
return this.portfolioSummary.reduce((s, r) => s + r.accrued, 0);
|
|
||||||
}
|
|
||||||
get portfolioTotalMaturity(): number {
|
|
||||||
return this.portfolioSummary.reduce((s, r) => s + r.maturityValue, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright since 2025 Mifos Initiative
|
|
||||||
*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
|
||||||
|
|
||||||
import { Route } from '../core/route/route.service';
|
|
||||||
import { InvestmentDashboardComponent } from './investment-dashboard/investment-dashboard.component';
|
|
||||||
import { FdInvestmentComponent } from './fd-investment/fd-investment.component';
|
|
||||||
import { BondInvestmentComponent } from './bond-investment/bond-investment.component';
|
|
||||||
import { ShareInvestmentComponent } from './share-investment/share-investment.component';
|
|
||||||
import { InvestmentMastersComponent } from './investment-masters/investment-masters.component';
|
|
||||||
import { InvestmentReportsComponent } from './investment-reports/investment-reports.component';
|
|
||||||
import { InvestmentInterestComponent } from './investment-interest/investment-interest.component';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
Route.withShell([
|
|
||||||
{
|
|
||||||
path: 'investment',
|
|
||||||
data: { title: 'Investment', breadcrumb: 'Investment' },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'dashboard',
|
|
||||||
component: InvestmentDashboardComponent,
|
|
||||||
data: { title: 'Investment Dashboard', breadcrumb: 'Investment Dashboard' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'fd',
|
|
||||||
component: FdInvestmentComponent,
|
|
||||||
data: { title: 'FD Investment', breadcrumb: 'FD Investment' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'bonds',
|
|
||||||
component: BondInvestmentComponent,
|
|
||||||
data: { title: 'Govt Security / Bond Investment', breadcrumb: 'Govt Security / Bond Investment' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'share',
|
|
||||||
component: ShareInvestmentComponent,
|
|
||||||
data: { title: 'Share Investment', breadcrumb: 'Share Investment' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'masters',
|
|
||||||
component: InvestmentMastersComponent,
|
|
||||||
data: { title: 'Investment Masters', breadcrumb: 'Investment Masters' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'interest',
|
|
||||||
component: InvestmentInterestComponent,
|
|
||||||
data: { title: 'Investment Interest', breadcrumb: 'Investment Interest' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'reports',
|
|
||||||
component: InvestmentReportsComponent,
|
|
||||||
data: { title: 'Investment Reports', breadcrumb: 'Investment Reports' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [RouterModule.forChild(routes)],
|
|
||||||
exports: [RouterModule]
|
|
||||||
})
|
|
||||||
export class InvestmentRoutingModule {}
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Extra styles layered on top of ../cashier/cashier-shared.scss (pg-*, cctr-*) for the
|
|
||||||
// Investment module — instrument nav cards, status badges, KPI tiles, entry-form layout.
|
|
||||||
|
|
||||||
.inv-kpis {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
@media (width <= 1100px) {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inv-kpi {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 14px 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.07em;
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__value {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 800;
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
color: #0f172a;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__sub {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #94a3b8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inv-nav-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inv-nav-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 16px;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 12px;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
transition:
|
|
||||||
border-color 0.15s,
|
|
||||||
box-shadow 0.15s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: #0f4f3c;
|
|
||||||
box-shadow: 0 2px 10px rgb(15 79 60 / 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__count {
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 800;
|
|
||||||
color: #0f172a;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__value {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #0f4f3c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inv-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 3px 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
|
|
||||||
&--active {
|
|
||||||
background: #f0fdf4;
|
|
||||||
color: #15803d;
|
|
||||||
border: 1px solid #bbf7d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--matured {
|
|
||||||
background: #eff6ff;
|
|
||||||
color: #1d4ed8;
|
|
||||||
border: 1px solid #bfdbfe;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--sold {
|
|
||||||
background: #fefce8;
|
|
||||||
color: #a16207;
|
|
||||||
border: 1px solid #fde68a;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--written_off {
|
|
||||||
background: #fef2f2;
|
|
||||||
color: #b91c1c;
|
|
||||||
border: 1px solid #fecaca;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inv-form-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
||||||
gap: 14px 20px;
|
|
||||||
padding: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inv-form-field {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #64748b;
|
|
||||||
|
|
||||||
input,
|
|
||||||
select {
|
|
||||||
width: 100%;
|
|
||||||
height: 36px;
|
|
||||||
padding: 0 10px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #0d1b2a;
|
|
||||||
font-family: inherit;
|
|
||||||
background: #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Long "code — name (branch)" style option text must never run under the native dropdown
|
|
||||||
// arrow — truncate it with an ellipsis instead of letting the browser clip it mid-word.
|
|
||||||
select {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computed / lookup-filled fields are rendered read-only and greyed out so they read as
|
|
||||||
// derived values rather than something the user is expected to type into.
|
|
||||||
/* stylelint-disable-next-line selector-no-qualifying-type -- [readonly] only applies to inputs */
|
|
||||||
input[readonly] {
|
|
||||||
background: #f8fafc;
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inv-form-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 0 18px 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inv-maturity-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 10px 14px;
|
|
||||||
border-bottom: 1px dotted #eef2f7;
|
|
||||||
font-size: 13px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inv-maturity-days {
|
|
||||||
font-weight: 800;
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
|
|
||||||
&--soon {
|
|
||||||
color: #dc2626;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--ok {
|
|
||||||
color: #16a34a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inv-toast {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 24px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%) translateY(20px);
|
|
||||||
background: #0f172a;
|
|
||||||
color: #fff;
|
|
||||||
padding: 12px 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
box-shadow: 0 8px 24px rgb(0 0 0 / 25%);
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
transition:
|
|
||||||
opacity 0.2s,
|
|
||||||
transform 0.2s;
|
|
||||||
z-index: 2000;
|
|
||||||
|
|
||||||
&.show {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(-50%) translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright since 2025 Mifos Initiative
|
|
||||||
*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
|
|
||||||
import { SharedModule } from '../shared/shared.module';
|
|
||||||
import { InvestmentRoutingModule } from './investment-routing.module';
|
|
||||||
import { InvestmentDashboardComponent } from './investment-dashboard/investment-dashboard.component';
|
|
||||||
import { FdInvestmentComponent } from './fd-investment/fd-investment.component';
|
|
||||||
import { BondInvestmentComponent } from './bond-investment/bond-investment.component';
|
|
||||||
import { ShareInvestmentComponent } from './share-investment/share-investment.component';
|
|
||||||
import { InvestmentMastersComponent } from './investment-masters/investment-masters.component';
|
|
||||||
import { InvestmentReportsComponent } from './investment-reports/investment-reports.component';
|
|
||||||
import { InvestmentInterestComponent } from './investment-interest/investment-interest.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
SharedModule,
|
|
||||||
InvestmentRoutingModule,
|
|
||||||
InvestmentDashboardComponent,
|
|
||||||
FdInvestmentComponent,
|
|
||||||
BondInvestmentComponent,
|
|
||||||
ShareInvestmentComponent,
|
|
||||||
InvestmentMastersComponent,
|
|
||||||
InvestmentReportsComponent,
|
|
||||||
InvestmentInterestComponent
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class InvestmentModule {}
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="pg">
|
|
||||||
<header class="pg-head">
|
|
||||||
<div>
|
|
||||||
<div class="pg-eyebrow">TREASURY · INVESTMENT · SHARE</div>
|
|
||||||
<h1 class="pg-title">Share Entry</h1>
|
|
||||||
<p class="pg-sub">
|
|
||||||
Equity and cooperative-society share holdings — Purchase, Dividend, Sell and Write-off entries.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'holdings'"
|
|
||||||
(click)="setTab('holdings')"
|
|
||||||
>
|
|
||||||
Holdings
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'purchase'"
|
|
||||||
(click)="setTab('purchase')"
|
|
||||||
>
|
|
||||||
01 Share Purchase Entry
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'dividend'"
|
|
||||||
(click)="setTab('dividend')"
|
|
||||||
>
|
|
||||||
02 Share Interest / Dividend Entry
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'sell'"
|
|
||||||
(click)="setTab('sell')"
|
|
||||||
>
|
|
||||||
03 Share Sell Entry
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeTab === 'writeoff'"
|
|
||||||
(click)="setTab('writeoff')"
|
|
||||||
>
|
|
||||||
04 Share Write Off Entry
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- HOLDINGS -->
|
|
||||||
@if (activeTab === 'holdings') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Share Holdings Register</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Script</th>
|
|
||||||
<th>Party</th>
|
|
||||||
<th class="num">Units</th>
|
|
||||||
<th class="num">Rate</th>
|
|
||||||
<th class="num">Face Value</th>
|
|
||||||
<th>Purchase Date</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (h of holdings; track h.certNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ h.scriptName }}</td>
|
|
||||||
<td>{{ h.party }}</td>
|
|
||||||
<td class="num">{{ h.units | number }}</td>
|
|
||||||
<td class="num">₹{{ h.purchaseRate | number: '1.2-2' }}</td>
|
|
||||||
<td class="num">₹{{ h.faceValue | number: '1.2-2' }}</td>
|
|
||||||
<td>{{ h.purchaseDate }}</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="statusClass(h)">{{ h.status }}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- PURCHASE ENTRY -->
|
|
||||||
@if (activeTab === 'purchase') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment - Share Purchase Entry</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Cert No</span><input type="text" [(ngModel)]="purchaseForm.certNo" readonly
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Script Code</span><input type="text" [(ngModel)]="purchaseForm.scriptCode"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Script/Company Name *</span><input type="text" [(ngModel)]="purchaseForm.scriptName"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Party / Exchange</span><input type="text" [(ngModel)]="purchaseForm.party"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Sub Type</span>
|
|
||||||
<select [(ngModel)]="purchaseForm.subType">
|
|
||||||
<option value="EQUITY">EQUITY</option>
|
|
||||||
<option value="CO-OP SHARE">CO-OP SHARE</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>No. of Units *</span><input type="number" [(ngModel)]="purchaseForm.units" min="0"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Purchase Rate *</span><input type="number" [(ngModel)]="purchaseForm.purchaseRate" min="0" step="0.01"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Purchase Date *</span
|
|
||||||
><input type="text" [(ngModel)]="purchaseForm.purchaseDate" placeholder="DD-MM-YYYY"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Broker Code</span><input type="text" [(ngModel)]="purchaseForm.brokerCode"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>CR To A/c</span><input type="text" [(ngModel)]="purchaseForm.crToAc"
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="cancelPurchase()">Cancel</button>
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="savePurchase()">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- DIVIDEND ENTRY -->
|
|
||||||
@if (activeTab === 'dividend') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment - Share Interest / Dividend Entry</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Cert No *</span>
|
|
||||||
<select [(ngModel)]="dividendCertNo">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
@for (h of activeHoldings; track h.certNo) {
|
|
||||||
<option [value]="h.certNo">{{ h.certNo }} — {{ h.scriptName }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
@if (dividendHolding; as h) {
|
|
||||||
<label class="inv-form-field"><span>Units Held</span><input type="text" [value]="h.units" readonly /></label>
|
|
||||||
}
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Dividend per Share *</span><input type="number" [(ngModel)]="dividendPerShare" min="0" step="0.01"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Total Dividend (computed)</span
|
|
||||||
><input type="text" [value]="'₹' + (dividendAmount | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="saveDividend()">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- SELL ENTRY -->
|
|
||||||
@if (activeTab === 'sell') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment - Share Sell Entry</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Cert No *</span>
|
|
||||||
<select [(ngModel)]="sellCertNo">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
@for (h of activeHoldings; track h.certNo) {
|
|
||||||
<option [value]="h.certNo">{{ h.certNo }} — {{ h.scriptName }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
@if (sellHolding; as h) {
|
|
||||||
<label class="inv-form-field"><span>Units Held</span><input type="text" [value]="h.units" readonly /></label>
|
|
||||||
}
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Units to Sell *</span><input type="number" [(ngModel)]="sellUnits" min="0"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Sell Rate *</span><input type="number" [(ngModel)]="sellRate" min="0" step="0.01"
|
|
||||||
/></label>
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Consideration (computed)</span
|
|
||||||
><input type="text" [value]="'₹' + (sellConsideration | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="saveSell()">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- WRITE OFF ENTRY -->
|
|
||||||
@if (activeTab === 'writeoff') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Investment - Share Write Off Entry</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Cert No *</span>
|
|
||||||
<select [(ngModel)]="writeoffCertNo">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
@for (h of activeHoldings; track h.certNo) {
|
|
||||||
<option [value]="h.certNo">{{ h.certNo }} — {{ h.scriptName }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
@if (writeoffHolding; as h) {
|
|
||||||
<label class="inv-form-field"
|
|
||||||
><span>Face Value to be Written Off</span
|
|
||||||
><input type="text" [value]="'₹' + (h.faceValue | number: '1.2-2')" readonly
|
|
||||||
/></label>
|
|
||||||
}
|
|
||||||
<label class="inv-form-field"><span>Reason *</span><input type="text" [(ngModel)]="writeoffReason" /></label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="saveWriteoff()">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (activeTab !== 'holdings') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Share Transaction Log</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Voucher No</th>
|
|
||||||
<th>Cert No</th>
|
|
||||||
<th>Type</th>
|
|
||||||
<th>Particulars</th>
|
|
||||||
<th class="num">Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (t of txns; track t.voucherNo) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ t.txnDate }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ t.voucherNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ t.certNo }}</td>
|
|
||||||
<td>{{ t.txnType }}</td>
|
|
||||||
<td>{{ t.particulars }}</td>
|
|
||||||
<td class="num">₹{{ t.amount | number: '1.2-2' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|
|
||||||
@@ -1,263 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { CommonModule } from '@angular/common';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { SHARE_HOLDINGS, ALL_TXNS, InvestmentHolding, InvestmentTxn, todayDDMMYYYY } from '../investment-data';
|
|
||||||
|
|
||||||
type ShareTab = 'holdings' | 'purchase' | 'dividend' | 'sell' | 'writeoff';
|
|
||||||
|
|
||||||
interface SharePurchaseForm {
|
|
||||||
certNo: string;
|
|
||||||
scriptCode: string;
|
|
||||||
scriptName: string;
|
|
||||||
party: string;
|
|
||||||
subType: string;
|
|
||||||
units: number | null;
|
|
||||||
purchaseRate: number | null;
|
|
||||||
purchaseDate: string;
|
|
||||||
brokerCode: string;
|
|
||||||
crToAc: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Share Entry treasury screen — Holdings register + Purchase / Dividend / Sell / Write-off
|
|
||||||
* entry tabs (legacy "02 Share Entry" menu). Dummy in-memory data; Save appends locally.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-share-investment',
|
|
||||||
templateUrl: './share-investment.component.html',
|
|
||||||
styleUrls: [
|
|
||||||
'../../cashier/cashier-shared.scss',
|
|
||||||
'../investment-shared.scss'
|
|
||||||
],
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class ShareInvestmentComponent {
|
|
||||||
activeTab: ShareTab = 'holdings';
|
|
||||||
asOnDate = todayDDMMYYYY();
|
|
||||||
|
|
||||||
holdings: InvestmentHolding[] = [...SHARE_HOLDINGS];
|
|
||||||
txns: InvestmentTxn[] = ALL_TXNS.filter((t) => t.instrumentType === 'SHARE');
|
|
||||||
|
|
||||||
toastMsg = '';
|
|
||||||
toastVisible = false;
|
|
||||||
private toastTimer: ReturnType<typeof setTimeout> | undefined;
|
|
||||||
|
|
||||||
purchaseForm: SharePurchaseForm = this.blankPurchaseForm();
|
|
||||||
dividendCertNo = '';
|
|
||||||
dividendPerShare: number | null = null;
|
|
||||||
sellCertNo = '';
|
|
||||||
sellUnits: number | null = null;
|
|
||||||
sellRate: number | null = null;
|
|
||||||
writeoffCertNo = '';
|
|
||||||
writeoffReason = '';
|
|
||||||
|
|
||||||
setTab(tab: ShareTab): void {
|
|
||||||
this.activeTab = tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
get activeHoldings(): InvestmentHolding[] {
|
|
||||||
return this.holdings.filter((h) => h.status === 'ACTIVE');
|
|
||||||
}
|
|
||||||
|
|
||||||
private blankPurchaseForm(): SharePurchaseForm {
|
|
||||||
return {
|
|
||||||
certNo: `SH-${6000 + this.holdings.length + 1}`,
|
|
||||||
scriptCode: '',
|
|
||||||
scriptName: '',
|
|
||||||
party: 'NSE/BSE',
|
|
||||||
subType: 'EQUITY',
|
|
||||||
units: null,
|
|
||||||
purchaseRate: null,
|
|
||||||
purchaseDate: this.asOnDate,
|
|
||||||
brokerCode: '',
|
|
||||||
crToAc: '310070'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
savePurchase(): void {
|
|
||||||
const f = this.purchaseForm;
|
|
||||||
if (!f.scriptName || !f.units || !f.purchaseRate) {
|
|
||||||
this.toast('Please fill Script Name, No. of Units and Purchase Rate.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const faceValue = f.units * f.purchaseRate;
|
|
||||||
const holding: InvestmentHolding = {
|
|
||||||
certNo: f.certNo,
|
|
||||||
instrumentType: 'SHARE',
|
|
||||||
scriptCode: f.scriptCode || f.scriptName,
|
|
||||||
scriptName: f.scriptName,
|
|
||||||
party: f.party,
|
|
||||||
subType: f.subType,
|
|
||||||
faceValue,
|
|
||||||
purchaseRate: f.purchaseRate,
|
|
||||||
units: f.units,
|
|
||||||
purchaseDate: f.purchaseDate,
|
|
||||||
maturityDate: f.purchaseDate,
|
|
||||||
interestRate: 0,
|
|
||||||
interestFreq: 'YEARLY',
|
|
||||||
glCode: '310060',
|
|
||||||
glName: 'INVESTMENT IN SHARES',
|
|
||||||
crToAc: f.crToAc,
|
|
||||||
brokerCode: f.brokerCode,
|
|
||||||
status: 'ACTIVE'
|
|
||||||
};
|
|
||||||
this.holdings = [
|
|
||||||
holding,
|
|
||||||
...this.holdings
|
|
||||||
];
|
|
||||||
this.txns = [
|
|
||||||
{
|
|
||||||
certNo: holding.certNo,
|
|
||||||
instrumentType: 'SHARE',
|
|
||||||
txnType: 'PURCHASE',
|
|
||||||
voucherNo: `INV/PUR/${holding.certNo}`,
|
|
||||||
txnDate: f.purchaseDate,
|
|
||||||
amount: faceValue,
|
|
||||||
particulars: `Purchase of ${f.units} shares - ${holding.scriptName}`
|
|
||||||
},
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(`Share holding ${holding.certNo} saved successfully.`);
|
|
||||||
this.purchaseForm = this.blankPurchaseForm();
|
|
||||||
this.activeTab = 'holdings';
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelPurchase(): void {
|
|
||||||
this.purchaseForm = this.blankPurchaseForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Dividend --------------------
|
|
||||||
get dividendHolding(): InvestmentHolding | undefined {
|
|
||||||
return this.holdings.find((h) => h.certNo === this.dividendCertNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
get dividendAmount(): number {
|
|
||||||
const h = this.dividendHolding;
|
|
||||||
if (!h || !this.dividendPerShare) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return Math.round(h.units * this.dividendPerShare);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveDividend(): void {
|
|
||||||
const h = this.dividendHolding;
|
|
||||||
if (!h || !this.dividendPerShare) {
|
|
||||||
this.toast('Select a holding and enter dividend per share.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const amt = this.dividendAmount;
|
|
||||||
this.txns = [
|
|
||||||
{
|
|
||||||
certNo: h.certNo,
|
|
||||||
instrumentType: 'SHARE',
|
|
||||||
txnType: 'DIVIDEND',
|
|
||||||
voucherNo: `INV/DIV/${h.certNo}-${this.txns.length + 1}`,
|
|
||||||
txnDate: this.asOnDate,
|
|
||||||
amount: amt,
|
|
||||||
particulars: `Dividend received - ${h.scriptName} (₹${this.dividendPerShare}/share)`
|
|
||||||
},
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(`Dividend of ₹${amt.toLocaleString('en-IN')} recorded for ${h.certNo}.`);
|
|
||||||
this.dividendCertNo = '';
|
|
||||||
this.dividendPerShare = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Sell --------------------
|
|
||||||
get sellHolding(): InvestmentHolding | undefined {
|
|
||||||
return this.holdings.find((h) => h.certNo === this.sellCertNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
get sellConsideration(): number {
|
|
||||||
if (!this.sellUnits || !this.sellRate) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return Math.round(this.sellUnits * this.sellRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveSell(): void {
|
|
||||||
const h = this.sellHolding;
|
|
||||||
if (!h || !this.sellUnits || !this.sellRate || this.sellUnits > h.units) {
|
|
||||||
this.toast('Select a holding and enter valid units (≤ held) and sell rate.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const consideration = this.sellConsideration;
|
|
||||||
h.units -= this.sellUnits;
|
|
||||||
h.faceValue = Math.round(h.units * h.purchaseRate);
|
|
||||||
if (h.units === 0) {
|
|
||||||
h.status = 'SOLD';
|
|
||||||
}
|
|
||||||
this.txns = [
|
|
||||||
{
|
|
||||||
certNo: h.certNo,
|
|
||||||
instrumentType: 'SHARE',
|
|
||||||
txnType: 'SELL',
|
|
||||||
voucherNo: `INV/SEL/${h.certNo}-${this.txns.length + 1}`,
|
|
||||||
txnDate: this.asOnDate,
|
|
||||||
amount: consideration,
|
|
||||||
particulars: `Sold ${this.sellUnits} shares of ${h.scriptName} @ ${this.sellRate}`
|
|
||||||
},
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(`Sold ${this.sellUnits} shares. Consideration ₹${consideration.toLocaleString('en-IN')}.`);
|
|
||||||
this.sellCertNo = '';
|
|
||||||
this.sellUnits = null;
|
|
||||||
this.sellRate = null;
|
|
||||||
this.activeTab = 'holdings';
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Write-off --------------------
|
|
||||||
get writeoffHolding(): InvestmentHolding | undefined {
|
|
||||||
return this.holdings.find((h) => h.certNo === this.writeoffCertNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveWriteoff(): void {
|
|
||||||
const h = this.writeoffHolding;
|
|
||||||
if (!h || !this.writeoffReason) {
|
|
||||||
this.toast('Select a holding and enter a write-off reason.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
h.status = 'WRITTEN_OFF';
|
|
||||||
this.txns = [
|
|
||||||
{
|
|
||||||
certNo: h.certNo,
|
|
||||||
instrumentType: 'SHARE',
|
|
||||||
txnType: 'WRITE_OFF',
|
|
||||||
voucherNo: `INV/WOF/${h.certNo}`,
|
|
||||||
txnDate: this.asOnDate,
|
|
||||||
amount: h.faceValue,
|
|
||||||
particulars: `Written off - ${this.writeoffReason}`
|
|
||||||
},
|
|
||||||
...this.txns
|
|
||||||
];
|
|
||||||
this.toast(`${h.certNo} written off.`);
|
|
||||||
this.writeoffCertNo = '';
|
|
||||||
this.writeoffReason = '';
|
|
||||||
this.activeTab = 'holdings';
|
|
||||||
}
|
|
||||||
|
|
||||||
statusClass(h: InvestmentHolding): string {
|
|
||||||
return `inv-badge inv-badge--${h.status.toLowerCase()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private toast(msg: string): void {
|
|
||||||
this.toastMsg = msg;
|
|
||||||
this.toastVisible = true;
|
|
||||||
if (this.toastTimer) {
|
|
||||||
clearTimeout(this.toastTimer);
|
|
||||||
}
|
|
||||||
this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,619 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Dummy NPA (Non-Performing Asset) data layer for the NPA Reports / Analysis / Classification
|
|
||||||
// screens. Everything downstream — classification, provisioning, interest reversal, GNPA/NNPA —
|
|
||||||
// is *derived* from LOAN_ACCOUNTS via pure functions below, so no two reports can ever disagree
|
|
||||||
// on the same account's numbers.
|
|
||||||
//
|
|
||||||
// The book is generated with a seeded RNG (not Math.random()) so the "large, real-world-CBS"
|
|
||||||
// dataset stays identical across reloads instead of reshuffling every time the page opens.
|
|
||||||
|
|
||||||
export type NpaStage =
|
|
||||||
| 'STANDARD'
|
|
||||||
| 'SMA0'
|
|
||||||
| 'SMA1'
|
|
||||||
| 'SMA2'
|
|
||||||
| 'SUBSTANDARD'
|
|
||||||
| 'DOUBTFUL1'
|
|
||||||
| 'DOUBTFUL2'
|
|
||||||
| 'DOUBTFUL3'
|
|
||||||
| 'LOSS';
|
|
||||||
|
|
||||||
export const STAGE_ORDER: NpaStage[] = [
|
|
||||||
'STANDARD',
|
|
||||||
'SMA0',
|
|
||||||
'SMA1',
|
|
||||||
'SMA2',
|
|
||||||
'SUBSTANDARD',
|
|
||||||
'DOUBTFUL1',
|
|
||||||
'DOUBTFUL2',
|
|
||||||
'DOUBTFUL3',
|
|
||||||
'LOSS'
|
|
||||||
];
|
|
||||||
|
|
||||||
export const STAGE_LABELS: Record<NpaStage, string> = {
|
|
||||||
STANDARD: 'Standard',
|
|
||||||
SMA0: 'SMA-0',
|
|
||||||
SMA1: 'SMA-1',
|
|
||||||
SMA2: 'SMA-2',
|
|
||||||
SUBSTANDARD: 'Sub-Standard',
|
|
||||||
DOUBTFUL1: 'Doubtful-1',
|
|
||||||
DOUBTFUL2: 'Doubtful-2',
|
|
||||||
DOUBTFUL3: 'Doubtful-3',
|
|
||||||
LOSS: 'Loss Asset'
|
|
||||||
};
|
|
||||||
|
|
||||||
export function isNpaStage(stage: NpaStage): boolean {
|
|
||||||
return stage !== 'STANDARD' && stage !== 'SMA0' && stage !== 'SMA1' && stage !== 'SMA2';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSmaStage(stage: NpaStage): boolean {
|
|
||||||
return stage === 'SMA0' || stage === 'SMA1' || stage === 'SMA2';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stageBadgeClass(stage: NpaStage): string {
|
|
||||||
return `npa-badge npa-badge--${stage.toLowerCase()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Recovery-workflow stage tracked separately from IRAC classification — a Doubtful/Loss
|
|
||||||
* account can sit at any of these while its classification stays driven purely by overdueDays. */
|
|
||||||
export type LegalActionStatus = 'NONE' | 'NOTICE_ISSUED' | 'SARFAESI_INITIATED' | 'SUIT_FILED' | 'OTS_NEGOTIATION';
|
|
||||||
|
|
||||||
export const LEGAL_ACTION_LABELS: Record<LegalActionStatus, string> = {
|
|
||||||
NONE: 'No Action Yet',
|
|
||||||
NOTICE_ISSUED: 'Demand Notice Issued',
|
|
||||||
SARFAESI_INITIATED: 'SARFAESI Action Initiated',
|
|
||||||
SUIT_FILED: 'Recovery Suit Filed',
|
|
||||||
OTS_NEGOTIATION: 'One-Time Settlement Under Negotiation'
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface RecoveryTxn {
|
|
||||||
date: string;
|
|
||||||
amount: number;
|
|
||||||
mode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoanAccount {
|
|
||||||
accountNo: string;
|
|
||||||
customerName: string;
|
|
||||||
guarantorName: string;
|
|
||||||
branchCode: string;
|
|
||||||
branchName: string;
|
|
||||||
productCode: string;
|
|
||||||
productName: string;
|
|
||||||
secured: boolean;
|
|
||||||
sanctionedAmount: number;
|
|
||||||
disbursementDate: string;
|
|
||||||
interestRate: number;
|
|
||||||
outstandingBalance: number;
|
|
||||||
overdueAmount: number;
|
|
||||||
overdueDays: number;
|
|
||||||
overdueDaysQuarterAgo: number;
|
|
||||||
collateralValue: number;
|
|
||||||
lastPaymentDate: string;
|
|
||||||
isLossAsset: boolean;
|
|
||||||
isRestructured: boolean;
|
|
||||||
provisionCoverageRatio: number;
|
|
||||||
recoveries: RecoveryTxn[];
|
|
||||||
/** '—' for Standard/SMA accounts — only NPA accounts are actually assigned a reason. */
|
|
||||||
npaReason: string;
|
|
||||||
/** '—' for Standard accounts — SMA and NPA accounts get a recovery officer assigned for follow-up. */
|
|
||||||
recoveryOfficer: string;
|
|
||||||
legalActionStatus: LegalActionStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Date helpers
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
function fmt(d: Date): string {
|
|
||||||
return `${String(d.getDate()).padStart(2, '0')}-${String(d.getMonth() + 1).padStart(2, '0')}-${d.getFullYear()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addDays(d: Date, days: number): Date {
|
|
||||||
const r = new Date(d);
|
|
||||||
r.setDate(r.getDate() + days);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Parses a "DD-MM-YYYY" string back into a Date, so generated dates can be compared/ordered
|
|
||||||
* against each other (e.g. "is this recovery more recent than the last-payment estimate?"). */
|
|
||||||
function parseDMY(s: string): Date {
|
|
||||||
const [
|
|
||||||
d,
|
|
||||||
m,
|
|
||||||
y
|
|
||||||
] = s.split('-').map(Number);
|
|
||||||
return new Date(y, m - 1, d);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function todayDDMMYYYY(): string {
|
|
||||||
return fmt(new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Seeded RNG — keeps the generated book stable across reloads
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
function mulberry32(seed: number): () => number {
|
|
||||||
return function random(): number {
|
|
||||||
seed |= 0;
|
|
||||||
seed = (seed + 0x6d2b79f5) | 0;
|
|
||||||
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
||||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
||||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const rng = mulberry32(19870614);
|
|
||||||
const pick = <T>(arr: T[]): T => arr[Math.floor(rng() * arr.length)];
|
|
||||||
const between = (min: number, max: number): number => min + rng() * (max - min);
|
|
||||||
const round = (n: number): number => Math.round(n);
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Masters
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
export interface BranchMaster {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
// BR01/BR02 are deliberately named "Branch 1"/"Branch 2" — identical to the two branches in
|
|
||||||
// cash-clearing-transfer-summary.component.ts — so a single pair of real office names in the DB
|
|
||||||
// gets recognised by BOTH reports instead of each module expecting a different branch name.
|
|
||||||
export const BRANCHES: BranchMaster[] = [
|
|
||||||
{ code: 'BR01', name: 'Branch 1' },
|
|
||||||
{ code: 'BR02', name: 'Branch 2' },
|
|
||||||
{ code: 'BR03', name: 'Borivali Branch' },
|
|
||||||
{ code: 'BR04', name: 'Thane Branch' },
|
|
||||||
{ code: 'BR05', name: 'Pune Branch' },
|
|
||||||
{ code: 'BR06', name: 'Nashik Branch' },
|
|
||||||
{ code: 'BR07', name: 'Nagpur Branch' },
|
|
||||||
{ code: 'BR08', name: 'Kolhapur Branch' }
|
|
||||||
];
|
|
||||||
|
|
||||||
interface ProductMaster {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
secured: boolean;
|
|
||||||
minAmt: number;
|
|
||||||
maxAmt: number;
|
|
||||||
minRate: number;
|
|
||||||
maxRate: number;
|
|
||||||
}
|
|
||||||
export const LOAN_PRODUCTS: ProductMaster[] = [
|
|
||||||
{
|
|
||||||
code: '01',
|
|
||||||
name: 'Cash Credit (MSME)',
|
|
||||||
secured: true,
|
|
||||||
minAmt: 500000,
|
|
||||||
maxAmt: 5000000,
|
|
||||||
minRate: 11,
|
|
||||||
maxRate: 13.5
|
|
||||||
},
|
|
||||||
{ code: '02', name: 'Term Loan - MSME', secured: true, minAmt: 300000, maxAmt: 3000000, minRate: 11, maxRate: 14 },
|
|
||||||
{ code: '03', name: 'Housing Loan', secured: true, minAmt: 1000000, maxAmt: 4000000, minRate: 8.5, maxRate: 10.5 },
|
|
||||||
{ code: '04', name: 'Vehicle Loan', secured: true, minAmt: 300000, maxAmt: 1500000, minRate: 9, maxRate: 11.5 },
|
|
||||||
{ code: '05', name: 'Gold Loan', secured: true, minAmt: 50000, maxAmt: 1000000, minRate: 9, maxRate: 12 },
|
|
||||||
{ code: '06', name: 'Kisan Credit Card', secured: true, minAmt: 100000, maxAmt: 500000, minRate: 7, maxRate: 9 },
|
|
||||||
{ code: '07', name: 'Personal Loan', secured: false, minAmt: 50000, maxAmt: 500000, minRate: 13, maxRate: 16 },
|
|
||||||
{ code: '08', name: 'Education Loan', secured: false, minAmt: 200000, maxAmt: 1500000, minRate: 10, maxRate: 12 },
|
|
||||||
{
|
|
||||||
code: '09',
|
|
||||||
name: 'SHG / Micro Finance Loan',
|
|
||||||
secured: false,
|
|
||||||
minAmt: 20000,
|
|
||||||
maxAmt: 200000,
|
|
||||||
minRate: 12,
|
|
||||||
maxRate: 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: '10',
|
|
||||||
name: 'Overdraft Against Property',
|
|
||||||
secured: true,
|
|
||||||
minAmt: 500000,
|
|
||||||
maxAmt: 2500000,
|
|
||||||
minRate: 11,
|
|
||||||
maxRate: 13
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const FIRST_NAMES = [
|
|
||||||
'Ramesh',
|
|
||||||
'Suresh',
|
|
||||||
'Mahesh',
|
|
||||||
'Ganesh',
|
|
||||||
'Dinesh',
|
|
||||||
'Vijay',
|
|
||||||
'Sanjay',
|
|
||||||
'Ajay',
|
|
||||||
'Prakash',
|
|
||||||
'Vinod',
|
|
||||||
'Sunita',
|
|
||||||
'Anita',
|
|
||||||
'Kavita',
|
|
||||||
'Savita',
|
|
||||||
'Rekha',
|
|
||||||
'Meena',
|
|
||||||
'Pooja',
|
|
||||||
'Sangita',
|
|
||||||
'Nirmala',
|
|
||||||
'Usha',
|
|
||||||
'Ravindra',
|
|
||||||
'Mahendra',
|
|
||||||
'Narendra',
|
|
||||||
'Jitendra',
|
|
||||||
'Rajendra',
|
|
||||||
'Bhagwan',
|
|
||||||
'Balasaheb',
|
|
||||||
'Vasant',
|
|
||||||
'Shivaji',
|
|
||||||
'Yogesh',
|
|
||||||
'Kiran',
|
|
||||||
'Manisha',
|
|
||||||
'Deepak',
|
|
||||||
'Sandeep',
|
|
||||||
'Pradeep',
|
|
||||||
'Ashok',
|
|
||||||
'Vishal',
|
|
||||||
'Nitin',
|
|
||||||
'Amol',
|
|
||||||
'Santosh'
|
|
||||||
];
|
|
||||||
const LAST_NAMES = [
|
|
||||||
'Patil',
|
|
||||||
'More',
|
|
||||||
'Jadhav',
|
|
||||||
'Deshmukh',
|
|
||||||
'Pawar',
|
|
||||||
'Kulkarni',
|
|
||||||
'Shinde',
|
|
||||||
'Gaikwad',
|
|
||||||
'Chavan',
|
|
||||||
'Bhosale',
|
|
||||||
'Kale',
|
|
||||||
'Salunkhe',
|
|
||||||
'Sawant',
|
|
||||||
'Kadam',
|
|
||||||
'Joshi',
|
|
||||||
'Wagh',
|
|
||||||
'Thorat',
|
|
||||||
'Waghmare',
|
|
||||||
'Mane',
|
|
||||||
'Shaikh'
|
|
||||||
];
|
|
||||||
|
|
||||||
const NPA_REASONS = [
|
|
||||||
'Crop Failure / Agri Distress',
|
|
||||||
'Business Loss',
|
|
||||||
'Health Issue / Death of Borrower',
|
|
||||||
'Diversion of Funds',
|
|
||||||
'Wilful Default',
|
|
||||||
'Natural Calamity',
|
|
||||||
'Loss of Employment',
|
|
||||||
'Family Dispute / Litigation',
|
|
||||||
'Delayed Receivables from Debtors',
|
|
||||||
'Unit Closure'
|
|
||||||
];
|
|
||||||
|
|
||||||
/** Recovery officers are bank staff, kept distinct from the FIRST_NAMES/LAST_NAMES pool used for
|
|
||||||
* customers so a name never accidentally reads as "the officer is also the borrower". */
|
|
||||||
const RECOVERY_OFFICERS = [
|
|
||||||
'Anil Deshpande',
|
|
||||||
'Sunil Kadam',
|
|
||||||
'Vikas Pathak',
|
|
||||||
'Rajendra More',
|
|
||||||
'Sachin Bhagat',
|
|
||||||
'Prashant Jagtap',
|
|
||||||
'Milind Kulkarni',
|
|
||||||
'Ravi Ghadge'
|
|
||||||
];
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Weighted stage buckets — used only to seed a realistic distribution of
|
|
||||||
// overdue-day counts; the actual classification is always re-derived from
|
|
||||||
// `overdueDays` via classifyDays(), never stored directly.
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
interface StageBucket {
|
|
||||||
weight: number;
|
|
||||||
minDays: number;
|
|
||||||
maxDays: number;
|
|
||||||
isLossPool: boolean;
|
|
||||||
}
|
|
||||||
const BUCKETS: StageBucket[] = [
|
|
||||||
{ weight: 60, minDays: 0, maxDays: 0, isLossPool: false },
|
|
||||||
{ weight: 10, minDays: 1, maxDays: 30, isLossPool: false },
|
|
||||||
{ weight: 7, minDays: 31, maxDays: 60, isLossPool: false },
|
|
||||||
{ weight: 6, minDays: 61, maxDays: 90, isLossPool: false },
|
|
||||||
{ weight: 9, minDays: 91, maxDays: 365, isLossPool: false },
|
|
||||||
{ weight: 4, minDays: 366, maxDays: 730, isLossPool: false },
|
|
||||||
{ weight: 2.5, minDays: 731, maxDays: 1095, isLossPool: false },
|
|
||||||
{ weight: 1.5, minDays: 1096, maxDays: 1900, isLossPool: true }
|
|
||||||
];
|
|
||||||
const TOTAL_WEIGHT = BUCKETS.reduce((s, b) => s + b.weight, 0);
|
|
||||||
|
|
||||||
function pickBucket(): StageBucket {
|
|
||||||
let r = rng() * TOTAL_WEIGHT;
|
|
||||||
for (const b of BUCKETS) {
|
|
||||||
if (r < b.weight) {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
r -= b.weight;
|
|
||||||
}
|
|
||||||
return BUCKETS[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Recovery-workflow progression — only NPA accounts get any legal action, and the more
|
|
||||||
* advanced/stressed the classification, the more likely the bank has already escalated. */
|
|
||||||
function legalActionFor(stage: NpaStage): LegalActionStatus {
|
|
||||||
if (!isNpaStage(stage)) {
|
|
||||||
return 'NONE';
|
|
||||||
}
|
|
||||||
const r = rng();
|
|
||||||
if (stage === 'SUBSTANDARD') {
|
|
||||||
return r < 0.2 ? 'NOTICE_ISSUED' : 'NONE';
|
|
||||||
}
|
|
||||||
if (stage === 'DOUBTFUL1') {
|
|
||||||
if (r < 0.15) return 'OTS_NEGOTIATION';
|
|
||||||
if (r < 0.45) return 'SARFAESI_INITIATED';
|
|
||||||
if (r < 0.65) return 'NOTICE_ISSUED';
|
|
||||||
return 'NONE';
|
|
||||||
}
|
|
||||||
// DOUBTFUL2, DOUBTFUL3, LOSS
|
|
||||||
if (r < 0.1) return 'OTS_NEGOTIATION';
|
|
||||||
if (r < 0.55) return 'SUIT_FILED';
|
|
||||||
if (r < 0.8) return 'SARFAESI_INITIATED';
|
|
||||||
if (r < 0.9) return 'NOTICE_ISSUED';
|
|
||||||
return 'NONE';
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateAccounts(count: number): LoanAccount[] {
|
|
||||||
const today = new Date();
|
|
||||||
const accounts: LoanAccount[] = [];
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
const branch = pick(BRANCHES);
|
|
||||||
const product = pick(LOAN_PRODUCTS);
|
|
||||||
const bucket = pickBucket();
|
|
||||||
const overdueDays = bucket.maxDays === 0 ? 0 : round(between(bucket.minDays, bucket.maxDays));
|
|
||||||
const isLossAsset = bucket.isLossPool && rng() < 0.4;
|
|
||||||
// Restructured accounts skew towards the already-stressed buckets (SMA-2 and worse),
|
|
||||||
// matching how RBI restructuring relief is actually used in practice.
|
|
||||||
const stressed = classifyDays(overdueDays, isLossAsset);
|
|
||||||
const isRestructured =
|
|
||||||
rng() < (stressed === 'STANDARD' || stressed === 'SMA0' || stressed === 'SMA1' ? 0.04 : 0.18);
|
|
||||||
|
|
||||||
const sanctionedAmount = round(between(product.minAmt, product.maxAmt) / 1000) * 1000;
|
|
||||||
// The loan must be old enough to have actually accrued `overdueDays` of delinquency, plus a
|
|
||||||
// minimum "good" performing stretch beforehand — otherwise a badly-overdue account could end
|
|
||||||
// up with a disbursement date that falls AFTER its own last-payment/NPA date, which would look
|
|
||||||
// wrong no matter what day this report is viewed on.
|
|
||||||
const minGoodPeriodDays = round(between(30, 180));
|
|
||||||
const loanAgeDays = Math.max(round(between(0.5, 8) * 365), overdueDays + minGoodPeriodDays);
|
|
||||||
const disbursementDate = fmt(addDays(today, -loanAgeDays));
|
|
||||||
const interestRate = +between(product.minRate, product.maxRate).toFixed(2);
|
|
||||||
const outstandingBalance = round(sanctionedAmount * between(0.25, 0.9));
|
|
||||||
const overdueFrac = overdueDays === 0 ? 0 : Math.min(0.85, overdueDays / 1200 + between(0, 0.15));
|
|
||||||
const overdueAmount = round(outstandingBalance * overdueFrac);
|
|
||||||
const collateralValue = product.secured ? round(sanctionedAmount * between(0.85, 1.25)) : 0;
|
|
||||||
|
|
||||||
const npaReason = isNpaStage(stressed) ? pick(NPA_REASONS) : '—';
|
|
||||||
const recoveryOfficer = isNpaStage(stressed) || isSmaStage(stressed) ? pick(RECOVERY_OFFICERS) : '—';
|
|
||||||
const legalActionStatus = legalActionFor(stressed);
|
|
||||||
|
|
||||||
// Quarter-ago overdue count is capped at how old the loan actually was 90 days back — a loan
|
|
||||||
// can't have been overdue for longer than it had existed at that point in time.
|
|
||||||
const drift = round((rng() - 0.4) * 130);
|
|
||||||
const maxQuarterAgoOverdueDays = Math.max(0, loanAgeDays - 90);
|
|
||||||
const overdueDaysQuarterAgo = Math.min(Math.max(0, overdueDays - drift), maxQuarterAgoOverdueDays);
|
|
||||||
|
|
||||||
const recoveries: RecoveryTxn[] = [];
|
|
||||||
if (overdueDays > 90 && rng() < 0.5) {
|
|
||||||
const numRecoveries = 1 + Math.floor(rng() * 3);
|
|
||||||
for (let r = 0; r < numRecoveries; r++) {
|
|
||||||
recoveries.push({
|
|
||||||
date: fmt(addDays(today, -round(between(10, Math.min(overdueDays, 400))))),
|
|
||||||
amount: round(outstandingBalance * between(0.02, 0.12)),
|
|
||||||
mode: pick([
|
|
||||||
'Cash Deposit',
|
|
||||||
'Cheque',
|
|
||||||
'NEFT/RTGS',
|
|
||||||
'Auction / SARFAESI',
|
|
||||||
'One-Time Settlement'
|
|
||||||
])
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A recorded recovery IS a payment — if one landed more recently than the formula-based
|
|
||||||
// last-payment estimate, the account's "last payment" must reflect that, otherwise the two
|
|
||||||
// fields on the Account Detail screen would visibly contradict each other.
|
|
||||||
const formulaLastPaymentDate = fmt(addDays(today, -Math.max(5, overdueDays - round(between(0, 20)))));
|
|
||||||
const latestRecoveryDate = recoveries.reduce<Date | null>((latest, r) => {
|
|
||||||
const d = parseDMY(r.date);
|
|
||||||
return !latest || d > latest ? d : latest;
|
|
||||||
}, null);
|
|
||||||
const lastPaymentDate =
|
|
||||||
latestRecoveryDate && latestRecoveryDate > parseDMY(formulaLastPaymentDate)
|
|
||||||
? fmt(latestRecoveryDate)
|
|
||||||
: formulaLastPaymentDate;
|
|
||||||
|
|
||||||
const seq = String(1000 + i).padStart(6, '0');
|
|
||||||
accounts.push({
|
|
||||||
accountNo: `1${branch.code.slice(2)}${product.code}${seq}`,
|
|
||||||
customerName: `${pick(FIRST_NAMES)} ${pick(LAST_NAMES)}`,
|
|
||||||
guarantorName: `${pick(FIRST_NAMES)} ${pick(LAST_NAMES)}`,
|
|
||||||
branchCode: branch.code,
|
|
||||||
branchName: branch.name,
|
|
||||||
productCode: product.code,
|
|
||||||
productName: product.name,
|
|
||||||
secured: product.secured,
|
|
||||||
sanctionedAmount,
|
|
||||||
disbursementDate,
|
|
||||||
interestRate,
|
|
||||||
outstandingBalance,
|
|
||||||
overdueAmount,
|
|
||||||
overdueDays,
|
|
||||||
overdueDaysQuarterAgo,
|
|
||||||
collateralValue,
|
|
||||||
lastPaymentDate,
|
|
||||||
isLossAsset,
|
|
||||||
isRestructured,
|
|
||||||
provisionCoverageRatio: +between(0.75, 1.1).toFixed(2),
|
|
||||||
recoveries,
|
|
||||||
npaReason,
|
|
||||||
recoveryOfficer,
|
|
||||||
legalActionStatus
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return accounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8 branches x ~80 accounts/branch on average — sized to look like a real cooperative bank's
|
|
||||||
// loan book rather than a token demo sample. Pagination in the reports UI is what makes a book
|
|
||||||
// this size usable, not a smaller dataset.
|
|
||||||
export const LOAN_ACCOUNTS: LoanAccount[] = generateAccounts(640);
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Classification & provisioning (RBI IRAC norms, simplified for a demo book)
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
export function classifyDays(days: number, isLossAsset: boolean): NpaStage {
|
|
||||||
if (isLossAsset && days > 1095) {
|
|
||||||
return 'LOSS';
|
|
||||||
}
|
|
||||||
if (days <= 0) {
|
|
||||||
return 'STANDARD';
|
|
||||||
}
|
|
||||||
if (days <= 30) {
|
|
||||||
return 'SMA0';
|
|
||||||
}
|
|
||||||
if (days <= 60) {
|
|
||||||
return 'SMA1';
|
|
||||||
}
|
|
||||||
if (days <= 90) {
|
|
||||||
return 'SMA2';
|
|
||||||
}
|
|
||||||
if (days <= 365) {
|
|
||||||
return 'SUBSTANDARD';
|
|
||||||
}
|
|
||||||
if (days <= 730) {
|
|
||||||
return 'DOUBTFUL1';
|
|
||||||
}
|
|
||||||
if (days <= 1095) {
|
|
||||||
return 'DOUBTFUL2';
|
|
||||||
}
|
|
||||||
return 'DOUBTFUL3';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function currentStage(a: LoanAccount): NpaStage {
|
|
||||||
return classifyDays(a.overdueDays, a.isLossAsset);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function previousStage(a: LoanAccount): NpaStage {
|
|
||||||
return classifyDays(a.overdueDaysQuarterAgo, a.isLossAsset && a.overdueDaysQuarterAgo > 1095);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function provisionRatePct(stage: NpaStage, secured: boolean): number {
|
|
||||||
switch (stage) {
|
|
||||||
case 'STANDARD':
|
|
||||||
case 'SMA0':
|
|
||||||
case 'SMA1':
|
|
||||||
return 0.4;
|
|
||||||
case 'SMA2':
|
|
||||||
return 0.75;
|
|
||||||
case 'SUBSTANDARD':
|
|
||||||
return secured ? 15 : 25;
|
|
||||||
case 'DOUBTFUL1':
|
|
||||||
return secured ? 25 : 100;
|
|
||||||
case 'DOUBTFUL2':
|
|
||||||
return secured ? 40 : 100;
|
|
||||||
case 'DOUBTFUL3':
|
|
||||||
case 'LOSS':
|
|
||||||
return 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function provisionRequired(a: LoanAccount): number {
|
|
||||||
const stage = currentStage(a);
|
|
||||||
if (!isNpaStage(stage)) {
|
|
||||||
return round(a.outstandingBalance * (provisionRatePct(stage, a.secured) / 100));
|
|
||||||
}
|
|
||||||
const securedPortion = Math.min(a.outstandingBalance, a.collateralValue);
|
|
||||||
const unsecuredPortion = Math.max(0, a.outstandingBalance - a.collateralValue);
|
|
||||||
const securedRate = provisionRatePct(stage, true) / 100;
|
|
||||||
const unsecuredRate = provisionRatePct(stage, false) / 100;
|
|
||||||
return round(securedPortion * securedRate + unsecuredPortion * unsecuredRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Provision held can run a little above or below what's required (over/under-provisioning
|
|
||||||
* happens in real banks) — but it can never exceed the account's own outstanding balance, since
|
|
||||||
* a bank cannot hold more provision than the money actually at risk on that one loan. */
|
|
||||||
export function provisionHeld(a: LoanAccount): number {
|
|
||||||
return Math.min(a.outstandingBalance, round(provisionRequired(a) * a.provisionCoverageRatio));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Interest income de-recognised because the account has been NPA for longer than 90 days. */
|
|
||||||
export function interestReversed(a: LoanAccount): number {
|
|
||||||
const stage = currentStage(a);
|
|
||||||
if (!isNpaStage(stage)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const daysSinceNpa = Math.max(0, a.overdueDays - 90);
|
|
||||||
return round(a.outstandingBalance * (a.interestRate / 100) * (daysSinceNpa / 365));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Date the account first crossed 90 days overdue (i.e. became NPA) — derived from overdueDays,
|
|
||||||
* never stored separately, so it always agrees with the account's current classification. */
|
|
||||||
export function npaSinceDate(a: LoanAccount): string {
|
|
||||||
if (!isNpaStage(currentStage(a))) {
|
|
||||||
return '—';
|
|
||||||
}
|
|
||||||
const daysSinceNpa = Math.max(0, a.overdueDays - 90);
|
|
||||||
return fmt(addDays(new Date(), -daysSinceNpa));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function totalRecovered(a: LoanAccount): number {
|
|
||||||
return a.recoveries.reduce((s, r) => s + r.amount, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function movementLabel(prev: NpaStage, curr: NpaStage): string {
|
|
||||||
if (prev === curr) {
|
|
||||||
return 'No Change';
|
|
||||||
}
|
|
||||||
const prevIdx = STAGE_ORDER.indexOf(prev);
|
|
||||||
const currIdx = STAGE_ORDER.indexOf(curr);
|
|
||||||
if (currIdx > prevIdx) {
|
|
||||||
return !isNpaStage(prev) && isNpaStage(curr) ? 'Fresh Slippage to NPA' : 'Downgraded';
|
|
||||||
}
|
|
||||||
return isNpaStage(prev) && !isNpaStage(curr) ? 'Upgraded to Standard' : 'Improved';
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Report menu metadata — drives both the shell mega-menu routerLinks and the
|
|
||||||
// in-page group/report tab rows, so a menu item and its screen can never
|
|
||||||
// point at different labels.
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
export type ReportGroupId = 'reports' | 'analysis' | 'classification';
|
|
||||||
export interface ReportMeta {
|
|
||||||
id: string;
|
|
||||||
group: ReportGroupId;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
export const REPORT_META: ReportMeta[] = [
|
|
||||||
{ id: 'interest', group: 'reports', label: 'NPA Interest' },
|
|
||||||
{ id: 'status', group: 'reports', label: 'NPA Status Report' },
|
|
||||||
{ id: 'recovery', group: 'reports', label: 'NPA Recovery Report' },
|
|
||||||
{ id: 'summary', group: 'reports', label: 'NPA Summary Report' },
|
|
||||||
{ id: 'account-detail', group: 'analysis', label: 'NPA Account Detail Report' },
|
|
||||||
{ id: 'movement', group: 'analysis', label: 'NPA Movement Report' },
|
|
||||||
{ id: 'provision', group: 'analysis', label: 'NPA with Provision and Required Provision' },
|
|
||||||
{ id: 'probable', group: 'classification', label: 'Probable NPA' },
|
|
||||||
{ id: 'sma', group: 'classification', label: 'SMA Report' }
|
|
||||||
];
|
|
||||||
export const GROUP_LABELS: Record<ReportGroupId, string> = {
|
|
||||||
reports: 'NPA Reports',
|
|
||||||
analysis: 'NPA Analysis',
|
|
||||||
classification: 'NPA Classification'
|
|
||||||
};
|
|
||||||
export const DEFAULT_REPORT_ID = 'summary';
|
|
||||||
@@ -1,949 +0,0 @@
|
|||||||
<!--
|
|
||||||
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="pg">
|
|
||||||
<header class="pg-head">
|
|
||||||
<div>
|
|
||||||
<div class="pg-eyebrow">CREDIT · NON-PERFORMING ASSETS · {{ scopeName | uppercase }}</div>
|
|
||||||
<h1 class="pg-title">{{ reportLabel(activeReport) }}</h1>
|
|
||||||
<p class="pg-sub">
|
|
||||||
RBI IRAC-norm classification (Standard → SMA-0/1/2 → Sub-Standard → Doubtful-1/2/3 → Loss) across
|
|
||||||
{{ allAccounts.length }} of the bank's {{ bankWideTotalAccounts }} loan accounts, as on {{ asOnDate }}.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="pg-toolbar">
|
|
||||||
@if (isHeadOffice) {
|
|
||||||
<label class="pg-field">
|
|
||||||
<span>Branch</span>
|
|
||||||
<select class="pg-select" [ngModel]="branchFilter" (ngModelChange)="setBranch($event)">
|
|
||||||
<option value="">-- All Branches --</option>
|
|
||||||
@for (b of branches; track b.code) {
|
|
||||||
<option [value]="b.code">{{ b.name }}</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
} @else {
|
|
||||||
<label class="pg-field">
|
|
||||||
<span>Branch</span>
|
|
||||||
<span class="pg-chip">{{ scopeName }}</span>
|
|
||||||
</label>
|
|
||||||
}
|
|
||||||
<label class="pg-field">
|
|
||||||
<span>As On</span>
|
|
||||||
<input type="text" [(ngModel)]="asOnDate" placeholder="DD-MM-YYYY" />
|
|
||||||
</label>
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="generate()">Generate</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="inv-kpis npa-kpis--ratio">
|
|
||||||
<div class="inv-kpi">
|
|
||||||
<span class="inv-kpi__label">Total Advances</span>
|
|
||||||
<span class="inv-kpi__value">₹{{ totalAdvances | number: '1.0-0' }}</span>
|
|
||||||
<span class="inv-kpi__sub">{{ allAccounts.length }} accounts</span>
|
|
||||||
</div>
|
|
||||||
<div class="inv-kpi">
|
|
||||||
<span class="inv-kpi__label">Gross NPA (GNPA)</span>
|
|
||||||
<span class="inv-kpi__value npa-warn">₹{{ gnpaAmount | number: '1.0-0' }}</span>
|
|
||||||
<span class="inv-kpi__sub"
|
|
||||||
>{{ npaAccounts.length }} accounts · {{ gnpaRatio | number: '1.2-2' }}% of advances</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="inv-kpi">
|
|
||||||
<span class="inv-kpi__label">Net NPA (NNPA)</span>
|
|
||||||
<span class="inv-kpi__value npa-warn">₹{{ netNpaAmount | number: '1.0-0' }}</span>
|
|
||||||
<span class="inv-kpi__sub">{{ nnpaRatio | number: '1.2-2' }}% of advances</span>
|
|
||||||
</div>
|
|
||||||
<div class="inv-kpi">
|
|
||||||
<span class="inv-kpi__label">Provision Held (NPA)</span>
|
|
||||||
<span class="inv-kpi__value">₹{{ totalNpaProvisionHeld | number: '1.0-0' }}</span>
|
|
||||||
<span class="inv-kpi__sub">Recovered to date ₹{{ totalRecoveredAmount | number: '1.0-0' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="inv-kpi">
|
|
||||||
<span class="inv-kpi__label">Restructured Accounts</span>
|
|
||||||
<span class="inv-kpi__value">{{ restructuredAccounts.length }}</span>
|
|
||||||
<span class="inv-kpi__sub">₹{{ restructuredOutstanding | number: '1.0-0' }} outstanding</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
@for (g of ['reports', 'analysis', 'classification']; track g) {
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeGroup === g"
|
|
||||||
(click)="goToReport(groupOf(g)[0].id)"
|
|
||||||
>
|
|
||||||
{{ groupLabels[g] }}
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cctr-tabs">
|
|
||||||
@for (m of groupOf(activeGroup); track m.id) {
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[class.pg-btn--active]="activeReport === m.id"
|
|
||||||
(click)="goToReport(m.id)"
|
|
||||||
>
|
|
||||||
{{ m.label }}
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ===================== 01 NPA INTEREST ===================== -->
|
|
||||||
@if (activeReport === 'interest') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">NPA Interest — Income Reversed on Non-Performing Accounts</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Account No</th>
|
|
||||||
<th>Customer</th>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th>Product</th>
|
|
||||||
<th>Stage</th>
|
|
||||||
<th class="num">Outstanding</th>
|
|
||||||
<th class="num">Rate</th>
|
|
||||||
<th class="num">Overdue Days</th>
|
|
||||||
<th class="num col-out">Interest Reversed</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (a of paged(npaAccounts); track a.accountNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ a.accountNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ a.customerName }}</td>
|
|
||||||
<td>{{ a.branchName }}</td>
|
|
||||||
<td>{{ a.productName }}</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="badgeClass(stage(a))">{{ stageLabel(stage(a)) }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="num">₹{{ a.outstandingBalance | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">{{ a.interestRate }}%</td>
|
|
||||||
<td class="num">{{ a.overdueDays }}</td>
|
|
||||||
<td class="num col-out">₹{{ interestRev(a) | number: '1.0-0' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="9" class="pg-empty">No NPA accounts in this branch.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td colspan="8">Total Interest Reversed ({{ npaAccounts.length }} accounts)</td>
|
|
||||||
<td class="num">₹{{ sumInterest(npaAccounts) | number: '1.0-0' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
@if (npaAccounts.length > pageSize) {
|
|
||||||
<div class="pg-pagination">
|
|
||||||
<span class="pg-pagination__info"
|
|
||||||
>Showing {{ rangeStart(npaAccounts) }}–{{ rangeEnd(npaAccounts) }} of {{ npaAccounts.length }}</span
|
|
||||||
>
|
|
||||||
<div class="pg-pagination__controls">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === 1"
|
|
||||||
(click)="goToPage(currentPage - 1, npaAccounts)"
|
|
||||||
>
|
|
||||||
‹ Prev
|
|
||||||
</button>
|
|
||||||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(npaAccounts) }}</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === pageCount(npaAccounts)"
|
|
||||||
(click)="goToPage(currentPage + 1, npaAccounts)"
|
|
||||||
>
|
|
||||||
Next ›
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ===================== 02 NPA STATUS REPORT ===================== -->
|
|
||||||
@if (activeReport === 'status') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">NPA Status Report — Full Non-Performing Book</div>
|
|
||||||
<div class="pg-table-scroll">
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Account No</th>
|
|
||||||
<th>Customer</th>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th>Product</th>
|
|
||||||
<th>Secured</th>
|
|
||||||
<th class="num">Outstanding</th>
|
|
||||||
<th class="num">Overdue Amount</th>
|
|
||||||
<th class="num">Overdue Days</th>
|
|
||||||
<th>NPA Since</th>
|
|
||||||
<th>Stage</th>
|
|
||||||
<th>Restructured</th>
|
|
||||||
<th>Legal Action</th>
|
|
||||||
<th class="num col-out">Provision Required</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (a of paged(npaAccounts); track a.accountNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ a.accountNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ a.customerName }}</td>
|
|
||||||
<td>{{ a.branchName }}</td>
|
|
||||||
<td>{{ a.productName }}</td>
|
|
||||||
<td>{{ a.secured ? 'Yes' : 'No' }}</td>
|
|
||||||
<td class="num">₹{{ a.outstandingBalance | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">₹{{ a.overdueAmount | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">{{ a.overdueDays }}</td>
|
|
||||||
<td>{{ npaSince(a) }}</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="badgeClass(stage(a))">{{ stageLabel(stage(a)) }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ a.isRestructured ? 'Yes' : 'No' }}</td>
|
|
||||||
<td [class]="legalActionClass(a.legalActionStatus)">{{ legalActionLabel(a.legalActionStatus) }}</td>
|
|
||||||
<td class="num col-out">₹{{ provisionReq(a) | number: '1.0-0' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="13" class="pg-empty">No NPA accounts in this branch.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
@if (npaAccounts.length > pageSize) {
|
|
||||||
<div class="pg-pagination">
|
|
||||||
<span class="pg-pagination__info"
|
|
||||||
>Showing {{ rangeStart(npaAccounts) }}–{{ rangeEnd(npaAccounts) }} of {{ npaAccounts.length }}</span
|
|
||||||
>
|
|
||||||
<div class="pg-pagination__controls">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === 1"
|
|
||||||
(click)="goToPage(currentPage - 1, npaAccounts)"
|
|
||||||
>
|
|
||||||
‹ Prev
|
|
||||||
</button>
|
|
||||||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(npaAccounts) }}</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === pageCount(npaAccounts)"
|
|
||||||
(click)="goToPage(currentPage + 1, npaAccounts)"
|
|
||||||
>
|
|
||||||
Next ›
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ===================== 03 NPA RECOVERY REPORT ===================== -->
|
|
||||||
@if (activeReport === 'recovery') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Record a Recovery Against an NPA Account</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>* NPA Account</span>
|
|
||||||
<select [(ngModel)]="newRecovery.accountNo">
|
|
||||||
<option value="">-- Select --</option>
|
|
||||||
@for (a of recoveryOptions; track a.accountNo) {
|
|
||||||
<option [value]="a.accountNo">
|
|
||||||
{{ a.accountNo }} — {{ a.customerName }} ({{ stageLabel(stage(a)) }})
|
|
||||||
</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>* Recovery Date</span>
|
|
||||||
<input type="text" [(ngModel)]="newRecovery.date" placeholder="DD-MM-YYYY" />
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>* Recovery Amount</span>
|
|
||||||
<input type="number" [(ngModel)]="newRecovery.amount" />
|
|
||||||
</label>
|
|
||||||
<label class="inv-form-field">
|
|
||||||
<span>Mode</span>
|
|
||||||
<select [(ngModel)]="newRecovery.mode">
|
|
||||||
<option>Cash Deposit</option>
|
|
||||||
<option>Cheque</option>
|
|
||||||
<option>NEFT/RTGS</option>
|
|
||||||
<option>Auction / SARFAESI</option>
|
|
||||||
<option>One-Time Settlement</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="addRecovery()">Save Recovery Entry</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">
|
|
||||||
NPA Recovery Register — ₹{{ totalRecoveredAmount | number: '1.0-0' }} recovered against ₹{{
|
|
||||||
gnpaAmount | number: '1.0-0'
|
|
||||||
}}
|
|
||||||
GNPA outstanding
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Account No</th>
|
|
||||||
<th>Customer</th>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th>Mode</th>
|
|
||||||
<th class="num col-in">Recovered Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of paged(recoveryRows); track r.accountNo + r.date + r.amount) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ r.date }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ r.accountNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ r.customerName }}</td>
|
|
||||||
<td>{{ r.branchName }}</td>
|
|
||||||
<td>{{ r.mode }}</td>
|
|
||||||
<td class="num col-in">₹{{ r.amount | number: '1.0-0' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="pg-empty">No recoveries recorded yet.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td colspan="5">Total Recovered</td>
|
|
||||||
<td class="num">₹{{ totalRecoveredAmount | number: '1.0-0' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
@if (recoveryRows.length > pageSize) {
|
|
||||||
<div class="pg-pagination">
|
|
||||||
<span class="pg-pagination__info"
|
|
||||||
>Showing {{ rangeStart(recoveryRows) }}–{{ rangeEnd(recoveryRows) }} of {{ recoveryRows.length }}</span
|
|
||||||
>
|
|
||||||
<div class="pg-pagination__controls">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === 1"
|
|
||||||
(click)="goToPage(currentPage - 1, recoveryRows)"
|
|
||||||
>
|
|
||||||
‹ Prev
|
|
||||||
</button>
|
|
||||||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(recoveryRows) }}</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === pageCount(recoveryRows)"
|
|
||||||
(click)="goToPage(currentPage + 1, recoveryRows)"
|
|
||||||
>
|
|
||||||
Next ›
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ===================== 04 NPA SUMMARY REPORT ===================== -->
|
|
||||||
@if (activeReport === 'summary') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Stage-wise Summary — {{ scopeName }}</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Classification</th>
|
|
||||||
<th class="num">Accounts</th>
|
|
||||||
<th class="num">Outstanding</th>
|
|
||||||
<th class="num col-out">Provision Required</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (row of stageSummary; track row.stage) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span [class]="badgeClass(row.stage)">{{ row.label }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="num">{{ row.count }}</td>
|
|
||||||
<td class="num">₹{{ row.outstanding | number: '1.0-0' }}</td>
|
|
||||||
<td class="num col-out">₹{{ row.provisionReq | number: '1.0-0' }}</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td>Total</td>
|
|
||||||
<td class="num">{{ grandTotalAccounts }}</td>
|
|
||||||
<td class="num">₹{{ grandTotalOutstanding | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">₹{{ grandTotalProvision | number: '1.0-0' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (isHeadOffice && !branchFilter) {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Branch-wise NPA Summary — All Branches</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th class="num">Total Accounts</th>
|
|
||||||
<th class="num">Total Advances</th>
|
|
||||||
<th class="num">NPA Accounts</th>
|
|
||||||
<th class="num col-out">GNPA Outstanding</th>
|
|
||||||
<th class="num col-out">Provision Required</th>
|
|
||||||
<th class="num">GNPA %</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (row of branchSummary; track row.branchCode) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="javascript:void(0)" (click)="setBranch(row.branchCode)">{{ row.branchName }}</a>
|
|
||||||
</td>
|
|
||||||
<td class="num">{{ row.totalAccounts }}</td>
|
|
||||||
<td class="num">₹{{ row.totalAdvances | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">{{ row.npaCount }}</td>
|
|
||||||
<td class="num col-out">₹{{ row.npaOutstanding | number: '1.0-0' }}</td>
|
|
||||||
<td class="num col-out">₹{{ row.provisionReq | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">{{ row.gnpaRatio | number: '1.2-2' }}%</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ===================== 05 NPA ACCOUNT DETAIL REPORT ===================== -->
|
|
||||||
@if (activeReport === 'account-detail') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Search NPA Account</div>
|
|
||||||
<div class="inv-form-grid">
|
|
||||||
<label class="inv-form-field npa-form-field--full">
|
|
||||||
<span>Account No / Customer</span>
|
|
||||||
<select [(ngModel)]="selectedAccountNo">
|
|
||||||
@for (a of npaAccounts; track a.accountNo) {
|
|
||||||
<option [value]="a.accountNo">{{ a.accountNo }} — {{ a.customerName }} ({{ a.branchName }})</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="searchAccount()">View Details</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (selectedAccount; as a) {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">
|
|
||||||
{{ a.accountNo }} — {{ a.customerName }}
|
|
||||||
<span [class]="badgeClass(stage(a))">{{ stageLabel(stage(a)) }}</span>
|
|
||||||
</div>
|
|
||||||
<dl class="npa-detail-grid">
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Branch</dt>
|
|
||||||
<dd>{{ a.branchName }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Product</dt>
|
|
||||||
<dd>{{ a.productName }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Secured</dt>
|
|
||||||
<dd>{{ a.secured ? 'Yes' : 'No' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Guarantor</dt>
|
|
||||||
<dd>{{ a.guarantorName }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Sanctioned Amount</dt>
|
|
||||||
<dd>₹{{ a.sanctionedAmount | number: '1.0-0' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Disbursement Date</dt>
|
|
||||||
<dd>{{ a.disbursementDate }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Interest Rate</dt>
|
|
||||||
<dd>{{ a.interestRate }}%</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Outstanding Balance</dt>
|
|
||||||
<dd>₹{{ a.outstandingBalance | number: '1.0-0' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Overdue Amount</dt>
|
|
||||||
<dd>₹{{ a.overdueAmount | number: '1.0-0' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Overdue Days</dt>
|
|
||||||
<dd>{{ a.overdueDays }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>NPA Since</dt>
|
|
||||||
<dd>{{ npaSince(a) }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Restructured</dt>
|
|
||||||
<dd>{{ a.isRestructured ? 'Yes' : 'No' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Last Payment Date</dt>
|
|
||||||
<dd>{{ a.lastPaymentDate }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Collateral Value</dt>
|
|
||||||
<dd>₹{{ a.collateralValue | number: '1.0-0' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Provisioning Rate</dt>
|
|
||||||
<dd>{{ provisionRate(a) }}%</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Provision Required</dt>
|
|
||||||
<dd>₹{{ provisionReq(a) | number: '1.0-0' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Provision Held</dt>
|
|
||||||
<dd>₹{{ provisionHeldAmt(a) | number: '1.0-0' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Interest Reversed</dt>
|
|
||||||
<dd>₹{{ interestRev(a) | number: '1.0-0' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Total Recovered</dt>
|
|
||||||
<dd>₹{{ recovered(a) | number: '1.0-0' }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Movement (vs Last Quarter)</dt>
|
|
||||||
<dd>{{ movement(a) }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>NPA Reason</dt>
|
|
||||||
<dd>{{ a.npaReason }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Recovery Officer</dt>
|
|
||||||
<dd>{{ a.recoveryOfficer }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="npa-detail-field">
|
|
||||||
<dt>Legal Action Status</dt>
|
|
||||||
<dd [class]="legalActionClass(a.legalActionStatus)">{{ legalActionLabel(a.legalActionStatus) }}</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (isNpaStageOf(a)) {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Recovery Action — Update Legal Action Status</div>
|
|
||||||
<div class="inv-form-actions">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="a.legalActionStatus === 'NOTICE_ISSUED'"
|
|
||||||
(click)="updateLegalAction(a, 'NOTICE_ISSUED')"
|
|
||||||
>
|
|
||||||
Issue Demand Notice
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="a.legalActionStatus === 'SARFAESI_INITIATED'"
|
|
||||||
(click)="updateLegalAction(a, 'SARFAESI_INITIATED')"
|
|
||||||
>
|
|
||||||
Initiate SARFAESI Action
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="a.legalActionStatus === 'SUIT_FILED'"
|
|
||||||
(click)="updateLegalAction(a, 'SUIT_FILED')"
|
|
||||||
>
|
|
||||||
File Recovery Suit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="a.legalActionStatus === 'OTS_NEGOTIATION'"
|
|
||||||
(click)="updateLegalAction(a, 'OTS_NEGOTIATION')"
|
|
||||||
>
|
|
||||||
Mark OTS Negotiation
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="a.legalActionStatus === 'NONE'"
|
|
||||||
(click)="updateLegalAction(a, 'NONE')"
|
|
||||||
>
|
|
||||||
Reset to No Action
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Recovery History</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Mode</th>
|
|
||||||
<th class="num col-in">Amount</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (r of a.recoveries; track r.date + r.amount) {
|
|
||||||
<tr>
|
|
||||||
<td>{{ r.date }}</td>
|
|
||||||
<td>{{ r.mode }}</td>
|
|
||||||
<td class="num col-in">₹{{ r.amount | number: '1.0-0' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="3" class="pg-empty">No recoveries recorded against this account.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ===================== 06 NPA MOVEMENT REPORT ===================== -->
|
|
||||||
@if (activeReport === 'movement') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Classification Movement — Current Quarter vs Previous Quarter</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Account No</th>
|
|
||||||
<th>Customer</th>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th>Previous Stage</th>
|
|
||||||
<th>Current Stage</th>
|
|
||||||
<th>Movement</th>
|
|
||||||
<th class="num">Outstanding</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (a of paged(movedAccounts); track a.accountNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ a.accountNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ a.customerName }}</td>
|
|
||||||
<td>{{ a.branchName }}</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="badgeClass(prevStage(a))">{{ stageLabel(prevStage(a)) }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="badgeClass(stage(a))">{{ stageLabel(stage(a)) }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ movement(a) }}</td>
|
|
||||||
<td class="num">₹{{ a.outstandingBalance | number: '1.0-0' }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="7" class="pg-empty">No classification movement in this branch.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
@if (movedAccounts.length > pageSize) {
|
|
||||||
<div class="pg-pagination">
|
|
||||||
<span class="pg-pagination__info"
|
|
||||||
>Showing {{ rangeStart(movedAccounts) }}–{{ rangeEnd(movedAccounts) }} of {{ movedAccounts.length }}</span
|
|
||||||
>
|
|
||||||
<div class="pg-pagination__controls">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === 1"
|
|
||||||
(click)="goToPage(currentPage - 1, movedAccounts)"
|
|
||||||
>
|
|
||||||
‹ Prev
|
|
||||||
</button>
|
|
||||||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(movedAccounts) }}</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === pageCount(movedAccounts)"
|
|
||||||
(click)="goToPage(currentPage + 1, movedAccounts)"
|
|
||||||
>
|
|
||||||
Next ›
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ===================== 07 NPA WITH PROVISION AND REQUIRED PROVISION ===================== -->
|
|
||||||
@if (activeReport === 'provision') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Provision Held vs Required — Full Loan Book</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Account No</th>
|
|
||||||
<th>Customer</th>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th>Stage</th>
|
|
||||||
<th class="num">Outstanding</th>
|
|
||||||
<th class="num">Rate</th>
|
|
||||||
<th class="num">Provision Required</th>
|
|
||||||
<th class="num">Provision Held</th>
|
|
||||||
<th class="num">Shortfall / (Excess)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (a of paged(provisionRows); track a.accountNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ a.accountNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ a.customerName }}</td>
|
|
||||||
<td>{{ a.branchName }}</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="badgeClass(stage(a))">{{ stageLabel(stage(a)) }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="num">₹{{ a.outstandingBalance | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">{{ provisionRate(a) }}%</td>
|
|
||||||
<td class="num">₹{{ provisionReq(a) | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">₹{{ provisionHeldAmt(a) | number: '1.0-0' }}</td>
|
|
||||||
<td class="num" [class.col-out]="shortfall(a) > 0" [class.col-in]="shortfall(a) < 0">
|
|
||||||
₹{{ shortfall(a) | number: '1.0-0' }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td colspan="6">Total ({{ provisionRows.length }} accounts)</td>
|
|
||||||
<td class="num">₹{{ provisionRequiredTotal | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">₹{{ provisionHeldTotal | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">₹{{ provisionShortfallTotal | number: '1.0-0' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
@if (provisionRows.length > pageSize) {
|
|
||||||
<div class="pg-pagination">
|
|
||||||
<span class="pg-pagination__info"
|
|
||||||
>Showing {{ rangeStart(provisionRows) }}–{{ rangeEnd(provisionRows) }} of {{ provisionRows.length }}</span
|
|
||||||
>
|
|
||||||
<div class="pg-pagination__controls">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === 1"
|
|
||||||
(click)="goToPage(currentPage - 1, provisionRows)"
|
|
||||||
>
|
|
||||||
‹ Prev
|
|
||||||
</button>
|
|
||||||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(provisionRows) }}</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === pageCount(provisionRows)"
|
|
||||||
(click)="goToPage(currentPage + 1, provisionRows)"
|
|
||||||
>
|
|
||||||
Next ›
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ===================== 08 PROBABLE NPA ===================== -->
|
|
||||||
@if (activeReport === 'probable') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">
|
|
||||||
Probable NPA — SMA-2 Accounts Likely to Slip Next Quarter (61–90 Days Overdue)
|
|
||||||
</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Account No</th>
|
|
||||||
<th>Customer</th>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th>Product</th>
|
|
||||||
<th class="num">Outstanding</th>
|
|
||||||
<th class="num">Overdue Amount</th>
|
|
||||||
<th class="num">Overdue Days</th>
|
|
||||||
<th>Last Payment</th>
|
|
||||||
<th>Follow-up</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (a of paged(probableNpaAccounts); track a.accountNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ a.accountNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ a.customerName }}</td>
|
|
||||||
<td>{{ a.branchName }}</td>
|
|
||||||
<td>{{ a.productName }}</td>
|
|
||||||
<td class="num">₹{{ a.outstandingBalance | number: '1.0-0' }}</td>
|
|
||||||
<td class="num col-out">₹{{ a.overdueAmount | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">{{ a.overdueDays }}</td>
|
|
||||||
<td>{{ a.lastPaymentDate }}</td>
|
|
||||||
<td>
|
|
||||||
<span
|
|
||||||
class="npa-badge"
|
|
||||||
[class.npa-badge--sma2]="a.overdueDays > 75"
|
|
||||||
[class.npa-badge--sma0]="a.overdueDays <= 75"
|
|
||||||
>
|
|
||||||
{{ a.overdueDays > 75 ? 'Urgent' : 'Monitor' }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="9" class="pg-empty">No SMA-2 accounts in this branch.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td colspan="4">Total ({{ probableNpaAccounts.length }} accounts)</td>
|
|
||||||
<td class="num">₹{{ sumOutstanding(probableNpaAccounts) | number: '1.0-0' }}</td>
|
|
||||||
<td colspan="4"></td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
@if (probableNpaAccounts.length > pageSize) {
|
|
||||||
<div class="pg-pagination">
|
|
||||||
<span class="pg-pagination__info"
|
|
||||||
>Showing {{ rangeStart(probableNpaAccounts) }}–{{ rangeEnd(probableNpaAccounts) }} of
|
|
||||||
{{ probableNpaAccounts.length }}</span
|
|
||||||
>
|
|
||||||
<div class="pg-pagination__controls">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === 1"
|
|
||||||
(click)="goToPage(currentPage - 1, probableNpaAccounts)"
|
|
||||||
>
|
|
||||||
‹ Prev
|
|
||||||
</button>
|
|
||||||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(probableNpaAccounts) }}</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === pageCount(probableNpaAccounts)"
|
|
||||||
(click)="goToPage(currentPage + 1, probableNpaAccounts)"
|
|
||||||
>
|
|
||||||
Next ›
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- ===================== 09 SMA REPORT ===================== -->
|
|
||||||
@if (activeReport === 'sma') {
|
|
||||||
<div class="pg-card">
|
|
||||||
<div class="pos-section-label">Special Mention Accounts — SMA-0 (1–30d) / SMA-1 (31–60d) / SMA-2 (61–90d)</div>
|
|
||||||
<table class="pg-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Account No</th>
|
|
||||||
<th>Customer</th>
|
|
||||||
<th>Branch</th>
|
|
||||||
<th>Product</th>
|
|
||||||
<th>Stage</th>
|
|
||||||
<th class="num">Outstanding</th>
|
|
||||||
<th class="num">Overdue Amount</th>
|
|
||||||
<th class="num">Overdue Days</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (a of paged(smaAccounts); track a.accountNo) {
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<span class="pg-chip">{{ a.accountNo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ a.customerName }}</td>
|
|
||||||
<td>{{ a.branchName }}</td>
|
|
||||||
<td>{{ a.productName }}</td>
|
|
||||||
<td>
|
|
||||||
<span [class]="badgeClass(stage(a))">{{ stageLabel(stage(a)) }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="num">₹{{ a.outstandingBalance | number: '1.0-0' }}</td>
|
|
||||||
<td class="num col-out">₹{{ a.overdueAmount | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">{{ a.overdueDays }}</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td colspan="8" class="pg-empty">No SMA accounts in this branch.</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="cctr-total-row">
|
|
||||||
<td colspan="5">Total ({{ smaAccounts.length }} accounts)</td>
|
|
||||||
<td class="num">₹{{ sumOutstanding(smaAccounts) | number: '1.0-0' }}</td>
|
|
||||||
<td class="num">₹{{ sumOverdue(smaAccounts) | number: '1.0-0' }}</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
@if (smaAccounts.length > pageSize) {
|
|
||||||
<div class="pg-pagination">
|
|
||||||
<span class="pg-pagination__info"
|
|
||||||
>Showing {{ rangeStart(smaAccounts) }}–{{ rangeEnd(smaAccounts) }} of {{ smaAccounts.length }}</span
|
|
||||||
>
|
|
||||||
<div class="pg-pagination__controls">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === 1"
|
|
||||||
(click)="goToPage(currentPage - 1, smaAccounts)"
|
|
||||||
>
|
|
||||||
‹ Prev
|
|
||||||
</button>
|
|
||||||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(smaAccounts) }}</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="pg-btn pg-btn--ghost"
|
|
||||||
[disabled]="currentPage === pageCount(smaAccounts)"
|
|
||||||
(click)="goToPage(currentPage + 1, smaAccounts)"
|
|
||||||
>
|
|
||||||
Next ›
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|
|
||||||
@@ -1,490 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright since 2025 Mifos Initiative
|
|
||||||
*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
import { AuthenticationService } from '../../core/authentication/authentication.service';
|
|
||||||
import {
|
|
||||||
LOAN_ACCOUNTS,
|
|
||||||
BRANCHES,
|
|
||||||
REPORT_META,
|
|
||||||
GROUP_LABELS,
|
|
||||||
DEFAULT_REPORT_ID,
|
|
||||||
LoanAccount,
|
|
||||||
NpaStage,
|
|
||||||
ReportGroupId,
|
|
||||||
STAGE_ORDER,
|
|
||||||
STAGE_LABELS,
|
|
||||||
currentStage,
|
|
||||||
previousStage,
|
|
||||||
isNpaStage,
|
|
||||||
isSmaStage,
|
|
||||||
provisionRatePct,
|
|
||||||
provisionRequired,
|
|
||||||
provisionHeld,
|
|
||||||
interestReversed,
|
|
||||||
totalRecovered,
|
|
||||||
movementLabel,
|
|
||||||
stageBadgeClass,
|
|
||||||
npaSinceDate,
|
|
||||||
todayDDMMYYYY,
|
|
||||||
LegalActionStatus,
|
|
||||||
LEGAL_ACTION_LABELS
|
|
||||||
} from '../npa-data';
|
|
||||||
|
|
||||||
interface StageSummaryRow {
|
|
||||||
stage: NpaStage;
|
|
||||||
label: string;
|
|
||||||
count: number;
|
|
||||||
outstanding: number;
|
|
||||||
provisionReq: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BranchSummaryRow {
|
|
||||||
branchCode: string;
|
|
||||||
branchName: string;
|
|
||||||
totalAccounts: number;
|
|
||||||
totalAdvances: number;
|
|
||||||
npaCount: number;
|
|
||||||
npaOutstanding: number;
|
|
||||||
provisionReq: number;
|
|
||||||
gnpaRatio: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RecoveryRow {
|
|
||||||
accountNo: string;
|
|
||||||
customerName: string;
|
|
||||||
branchName: string;
|
|
||||||
date: string;
|
|
||||||
amount: number;
|
|
||||||
mode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The legacy "NPA" mega-menu — 9 screens across NPA Reports / NPA Analysis / NPA Classification —
|
|
||||||
* collapsed into one route-param-driven component so every menu item lands on its own URL
|
|
||||||
* (/npa/reports/:reportId) with nothing left unwired. Every figure is derived from the single
|
|
||||||
* LOAN_ACCOUNTS book in npa-data.ts, so a count on one report always agrees with another.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'mifosx-npa-reports',
|
|
||||||
templateUrl: './npa-reports.component.html',
|
|
||||||
styleUrls: [
|
|
||||||
'../../cashier/cashier-shared.scss',
|
|
||||||
'../../investment/investment-shared.scss',
|
|
||||||
'../npa-shared.scss'
|
|
||||||
],
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
|
||||||
RouterModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class NpaReportsComponent implements OnInit, OnDestroy {
|
|
||||||
readonly reportMeta = REPORT_META;
|
|
||||||
readonly groupLabels = GROUP_LABELS;
|
|
||||||
readonly branches = BRANCHES;
|
|
||||||
readonly stageOrder = STAGE_ORDER;
|
|
||||||
readonly stageLabels = STAGE_LABELS;
|
|
||||||
asOnDate = todayDDMMYYYY();
|
|
||||||
|
|
||||||
activeGroup: ReportGroupId = 'reports';
|
|
||||||
activeReport = DEFAULT_REPORT_ID;
|
|
||||||
|
|
||||||
/** True for a Head Office login — sees a branch selector + the consolidated "All Branches"
|
|
||||||
* view with a branch-wise break-up. A branch-level login is locked to its own branch, the
|
|
||||||
* same split used by the Cash/Clearing/Transfer Summary report. */
|
|
||||||
isHeadOffice = false;
|
|
||||||
/** The logged-in user's own branch/office name, from real session credentials. */
|
|
||||||
currentOfficeName = 'Branch Office';
|
|
||||||
/** Set when a branch-level login's officeName matches one of the demo branches below. */
|
|
||||||
private ownBranchCode: string | null = null;
|
|
||||||
|
|
||||||
/** '' = All Branches (Head Office consolidated view); otherwise a BranchMaster.code. */
|
|
||||||
branchFilter = '';
|
|
||||||
|
|
||||||
/** Pagination — shared across whichever report is currently on screen, since only one
|
|
||||||
* report's table is ever visible at a time. Reset to page 1 whenever the underlying list
|
|
||||||
* can change size (report switch, branch switch, Generate). */
|
|
||||||
readonly pageSize = 20;
|
|
||||||
currentPage = 1;
|
|
||||||
|
|
||||||
selectedAccountNo = '';
|
|
||||||
|
|
||||||
newRecovery = {
|
|
||||||
accountNo: '',
|
|
||||||
date: this.asOnDate,
|
|
||||||
amount: null as number | null,
|
|
||||||
mode: 'Cash Deposit'
|
|
||||||
};
|
|
||||||
|
|
||||||
toastMsg = '';
|
|
||||||
toastVisible = false;
|
|
||||||
private toastTimer: ReturnType<typeof setTimeout> | undefined;
|
|
||||||
private sub?: Subscription;
|
|
||||||
|
|
||||||
private readonly route = inject(ActivatedRoute);
|
|
||||||
private readonly router = inject(Router);
|
|
||||||
private readonly auth = inject(AuthenticationService);
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.sub = this.route.paramMap.subscribe((params) => {
|
|
||||||
const id = params.get('reportId') || DEFAULT_REPORT_ID;
|
|
||||||
const meta = this.reportMeta.find((m) => m.id === id) || this.reportMeta[3];
|
|
||||||
this.activeReport = meta.id;
|
|
||||||
this.activeGroup = meta.group;
|
|
||||||
this.currentPage = 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
const creds = this.auth.getCredentials();
|
|
||||||
this.isHeadOffice = this.auth.isHeadOfficeUser();
|
|
||||||
this.currentOfficeName = creds?.officeName || 'Branch Office';
|
|
||||||
if (this.isHeadOffice) {
|
|
||||||
// Head Office starts on the consolidated view and can drill into any branch.
|
|
||||||
this.branchFilter = '';
|
|
||||||
} else {
|
|
||||||
// Match the logged-in office's real name to one of the demo branches, so each branch's
|
|
||||||
// login sees only its own book. Falls back to the first demo branch (still labelled with
|
|
||||||
// the user's real office name) if no branch name matches.
|
|
||||||
this.ownBranchCode =
|
|
||||||
this.branches.find((b) => this.currentOfficeName.toLowerCase().includes(b.name.toLowerCase()))?.code || null;
|
|
||||||
this.branchFilter = this.ownBranchCode || this.branches[0].code;
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstNpa = this.npaAccounts[0];
|
|
||||||
this.selectedAccountNo = firstNpa ? firstNpa.accountNo : this.allAccounts[0]?.accountNo || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.sub?.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Name of the branch/scope currently being viewed, for display in the header. */
|
|
||||||
get scopeName(): string {
|
|
||||||
if (!this.isHeadOffice) {
|
|
||||||
return this.currentOfficeName;
|
|
||||||
}
|
|
||||||
return this.branchFilter
|
|
||||||
? this.branches.find((b) => b.code === this.branchFilter)?.name || ''
|
|
||||||
: 'All Branches (Consolidated)';
|
|
||||||
}
|
|
||||||
|
|
||||||
setBranch(code: string): void {
|
|
||||||
this.branchFilter = code;
|
|
||||||
this.currentPage = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Pagination (shared by every report's table) --------------------
|
|
||||||
paged<T>(items: T[]): T[] {
|
|
||||||
const start = (this.currentPage - 1) * this.pageSize;
|
|
||||||
return items.slice(start, start + this.pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
pageCount(items: unknown[]): number {
|
|
||||||
return Math.max(1, Math.ceil(items.length / this.pageSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
rangeStart(items: unknown[]): number {
|
|
||||||
return items.length === 0 ? 0 : (this.currentPage - 1) * this.pageSize + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
rangeEnd(items: unknown[]): number {
|
|
||||||
return Math.min(this.currentPage * this.pageSize, items.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
goToPage(page: number, items: unknown[]): void {
|
|
||||||
this.currentPage = Math.min(Math.max(1, page), this.pageCount(items));
|
|
||||||
}
|
|
||||||
|
|
||||||
goToReport(reportId: string): void {
|
|
||||||
this.router.navigate([
|
|
||||||
'/npa/reports',
|
|
||||||
reportId
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
groupOf(group: string) {
|
|
||||||
return this.reportMeta.filter((m) => m.group === group);
|
|
||||||
}
|
|
||||||
|
|
||||||
reportLabel(id: string): string {
|
|
||||||
return this.reportMeta.find((m) => m.id === id)?.label || id;
|
|
||||||
}
|
|
||||||
|
|
||||||
stage(a: LoanAccount): NpaStage {
|
|
||||||
return currentStage(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
prevStage(a: LoanAccount): NpaStage {
|
|
||||||
return previousStage(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
stageLabel(stage: NpaStage): string {
|
|
||||||
return STAGE_LABELS[stage];
|
|
||||||
}
|
|
||||||
|
|
||||||
badgeClass(stage: NpaStage): string {
|
|
||||||
return stageBadgeClass(stage);
|
|
||||||
}
|
|
||||||
|
|
||||||
provisionRate(a: LoanAccount): number {
|
|
||||||
return provisionRatePct(this.stage(a), a.secured);
|
|
||||||
}
|
|
||||||
|
|
||||||
provisionReq(a: LoanAccount): number {
|
|
||||||
return provisionRequired(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
provisionHeldAmt(a: LoanAccount): number {
|
|
||||||
return provisionHeld(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
interestRev(a: LoanAccount): number {
|
|
||||||
return interestReversed(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
recovered(a: LoanAccount): number {
|
|
||||||
return totalRecovered(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
movement(a: LoanAccount): string {
|
|
||||||
return movementLabel(this.prevStage(a), this.stage(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
npaSince(a: LoanAccount): string {
|
|
||||||
return npaSinceDate(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
legalActionLabel(status: LegalActionStatus): string {
|
|
||||||
return LEGAL_ACTION_LABELS[status];
|
|
||||||
}
|
|
||||||
|
|
||||||
legalActionClass(status: LegalActionStatus): string {
|
|
||||||
return status === 'SARFAESI_INITIATED' || status === 'SUIT_FILED' ? 'col-out' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
updateLegalAction(a: LoanAccount, status: LegalActionStatus): void {
|
|
||||||
a.legalActionStatus = status;
|
|
||||||
this.toast(`${this.legalActionLabel(status)} — recorded for ${a.accountNo}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
isNpaStageOf(a: LoanAccount): boolean {
|
|
||||||
return isNpaStage(this.stage(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
get allAccounts(): LoanAccount[] {
|
|
||||||
return this.branchFilter ? LOAN_ACCOUNTS.filter((a) => a.branchCode === this.branchFilter) : LOAN_ACCOUNTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
get npaAccounts(): LoanAccount[] {
|
|
||||||
return this.allAccounts.filter((a) => isNpaStage(this.stage(a)));
|
|
||||||
}
|
|
||||||
|
|
||||||
get smaAccounts(): LoanAccount[] {
|
|
||||||
return this.allAccounts.filter((a) => isSmaStage(this.stage(a)));
|
|
||||||
}
|
|
||||||
|
|
||||||
get probableNpaAccounts(): LoanAccount[] {
|
|
||||||
return this.allAccounts.filter((a) => this.stage(a) === 'SMA2').sort((a, b) => b.overdueDays - a.overdueDays);
|
|
||||||
}
|
|
||||||
|
|
||||||
get movedAccounts(): LoanAccount[] {
|
|
||||||
return this.allAccounts
|
|
||||||
.filter((a) => this.prevStage(a) !== this.stage(a))
|
|
||||||
.sort((a, b) => STAGE_ORDER.indexOf(this.stage(b)) - STAGE_ORDER.indexOf(this.stage(a)));
|
|
||||||
}
|
|
||||||
|
|
||||||
get recoveryAccounts(): LoanAccount[] {
|
|
||||||
return this.npaAccounts.filter((a) => a.recoveries.length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get recoveryOptions(): LoanAccount[] {
|
|
||||||
return this.npaAccounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
get recoveryRows(): RecoveryRow[] {
|
|
||||||
return this.recoveryAccounts
|
|
||||||
.flatMap((a) =>
|
|
||||||
a.recoveries.map((r) => ({
|
|
||||||
accountNo: a.accountNo,
|
|
||||||
customerName: a.customerName,
|
|
||||||
branchName: a.branchName,
|
|
||||||
date: r.date,
|
|
||||||
amount: r.amount,
|
|
||||||
mode: r.mode
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
.sort((a, b) => b.amount - a.amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- Portfolio-wide KPIs (shown on every screen) --------------------
|
|
||||||
get totalAdvances(): number {
|
|
||||||
return this.allAccounts.reduce((s, a) => s + a.outstandingBalance, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get gnpaAmount(): number {
|
|
||||||
return this.npaAccounts.reduce((s, a) => s + a.outstandingBalance, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get gnpaRatio(): number {
|
|
||||||
return this.totalAdvances ? (this.gnpaAmount / this.totalAdvances) * 100 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get totalNpaProvisionHeld(): number {
|
|
||||||
return this.npaAccounts.reduce((s, a) => s + this.provisionHeldAmt(a), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get netNpaAmount(): number {
|
|
||||||
return Math.max(0, this.gnpaAmount - this.totalNpaProvisionHeld);
|
|
||||||
}
|
|
||||||
|
|
||||||
get nnpaRatio(): number {
|
|
||||||
return this.totalAdvances ? (this.netNpaAmount / this.totalAdvances) * 100 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get totalRecoveredAmount(): number {
|
|
||||||
return this.npaAccounts.reduce((s, a) => s + this.recovered(a), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get restructuredAccounts(): LoanAccount[] {
|
|
||||||
return this.allAccounts.filter((a) => a.isRestructured);
|
|
||||||
}
|
|
||||||
|
|
||||||
get restructuredOutstanding(): number {
|
|
||||||
return this.restructuredAccounts.reduce((s, a) => s + a.outstandingBalance, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- NPA Summary Report: stage-wise + branch-wise cuts --------------------
|
|
||||||
get stageSummary(): StageSummaryRow[] {
|
|
||||||
return STAGE_ORDER.map((stage) => {
|
|
||||||
const accts = this.allAccounts.filter((a) => this.stage(a) === stage);
|
|
||||||
return {
|
|
||||||
stage,
|
|
||||||
label: STAGE_LABELS[stage],
|
|
||||||
count: accts.length,
|
|
||||||
outstanding: accts.reduce((s, a) => s + a.outstandingBalance, 0),
|
|
||||||
provisionReq: accts.reduce((s, a) => s + this.provisionReq(a), 0)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get branchSummary(): BranchSummaryRow[] {
|
|
||||||
return this.branches.map((b) => {
|
|
||||||
const all = LOAN_ACCOUNTS.filter((a) => a.branchCode === b.code);
|
|
||||||
const npa = all.filter((a) => isNpaStage(this.stage(a)));
|
|
||||||
const totalAdvances = all.reduce((s, a) => s + a.outstandingBalance, 0);
|
|
||||||
const npaOutstanding = npa.reduce((s, a) => s + a.outstandingBalance, 0);
|
|
||||||
return {
|
|
||||||
branchCode: b.code,
|
|
||||||
branchName: b.name,
|
|
||||||
totalAccounts: all.length,
|
|
||||||
totalAdvances,
|
|
||||||
npaCount: npa.length,
|
|
||||||
npaOutstanding,
|
|
||||||
provisionReq: npa.reduce((s, a) => s + this.provisionReq(a), 0),
|
|
||||||
gnpaRatio: totalAdvances ? (npaOutstanding / totalAdvances) * 100 : 0
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get grandTotalAccounts(): number {
|
|
||||||
return this.allAccounts.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
get grandTotalOutstanding(): number {
|
|
||||||
return this.allAccounts.reduce((s, a) => s + a.outstandingBalance, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get grandTotalProvision(): number {
|
|
||||||
return this.allAccounts.reduce((s, a) => s + this.provisionReq(a), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get bankWideTotalAccounts(): number {
|
|
||||||
return LOAN_ACCOUNTS.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- NPA with Provision and Required Provision --------------------
|
|
||||||
get provisionRows(): LoanAccount[] {
|
|
||||||
return [...this.allAccounts].sort((a, b) => this.provisionReq(b) - this.provisionReq(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
get provisionRequiredTotal(): number {
|
|
||||||
return this.allAccounts.reduce((s, a) => s + this.provisionReq(a), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get provisionHeldTotal(): number {
|
|
||||||
return this.allAccounts.reduce((s, a) => s + this.provisionHeldAmt(a), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get provisionShortfallTotal(): number {
|
|
||||||
return this.provisionRequiredTotal - this.provisionHeldTotal;
|
|
||||||
}
|
|
||||||
|
|
||||||
shortfall(a: LoanAccount): number {
|
|
||||||
return this.provisionReq(a) - this.provisionHeldAmt(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- NPA Account Detail Report --------------------
|
|
||||||
get selectedAccount(): LoanAccount | undefined {
|
|
||||||
return LOAN_ACCOUNTS.find((a) => a.accountNo === this.selectedAccountNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
searchAccount(): void {
|
|
||||||
const a = this.selectedAccount;
|
|
||||||
if (!a) {
|
|
||||||
this.toast('No account found with that number.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.toast(`Loaded full NPA details for ${a.accountNo} — ${a.customerName}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------- NPA Recovery Report: add recovery entry --------------------
|
|
||||||
addRecovery(): void {
|
|
||||||
const acc = LOAN_ACCOUNTS.find((a) => a.accountNo === this.newRecovery.accountNo);
|
|
||||||
if (!acc || !this.newRecovery.amount || this.newRecovery.amount <= 0) {
|
|
||||||
this.toast('Select an NPA account and enter a valid recovery amount.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
acc.recoveries = [
|
|
||||||
{ date: this.newRecovery.date || this.asOnDate, amount: this.newRecovery.amount, mode: this.newRecovery.mode },
|
|
||||||
...acc.recoveries
|
|
||||||
];
|
|
||||||
this.toast(`Recovery of ₹${this.newRecovery.amount.toLocaleString('en-IN')} recorded against ${acc.accountNo}.`);
|
|
||||||
this.newRecovery = { accountNo: '', date: this.asOnDate, amount: null, mode: 'Cash Deposit' };
|
|
||||||
}
|
|
||||||
|
|
||||||
sumOutstanding(accounts: LoanAccount[]): number {
|
|
||||||
return accounts.reduce((s, a) => s + a.outstandingBalance, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
sumOverdue(accounts: LoanAccount[]): number {
|
|
||||||
return accounts.reduce((s, a) => s + a.overdueAmount, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
sumInterest(accounts: LoanAccount[]): number {
|
|
||||||
return accounts.reduce((s, a) => s + this.interestRev(a), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
generate(): void {
|
|
||||||
this.currentPage = 1;
|
|
||||||
this.toast(`${this.reportLabel(this.activeReport)} generated as on ${this.asOnDate}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private toast(msg: string): void {
|
|
||||||
this.toastMsg = msg;
|
|
||||||
this.toastVisible = true;
|
|
||||||
if (this.toastTimer) {
|
|
||||||
clearTimeout(this.toastTimer);
|
|
||||||
}
|
|
||||||
this.toastTimer = setTimeout(() => (this.toastVisible = false), 3200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright since 2025 Mifos Initiative
|
|
||||||
*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
|
||||||
|
|
||||||
import { Route } from '../core/route/route.service';
|
|
||||||
import { NpaReportsComponent } from './npa-reports/npa-reports.component';
|
|
||||||
import { DEFAULT_REPORT_ID } from './npa-data';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
Route.withShell([
|
|
||||||
{
|
|
||||||
path: 'npa',
|
|
||||||
data: { title: 'NPA', breadcrumb: 'NPA' },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'reports',
|
|
||||||
redirectTo: `reports/${DEFAULT_REPORT_ID}`,
|
|
||||||
pathMatch: 'full'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'reports/:reportId',
|
|
||||||
component: NpaReportsComponent,
|
|
||||||
data: { title: 'NPA Reports', breadcrumb: 'NPA Reports' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [RouterModule.forChild(routes)],
|
|
||||||
exports: [RouterModule]
|
|
||||||
})
|
|
||||||
export class NpaRoutingModule {}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// NPA-specific status colouring layered on top of ../cashier/cashier-shared.scss (pg-*, cctr-*)
|
|
||||||
// and ../investment/investment-shared.scss (inv-kpis, inv-form-*, inv-toast — reused as-is since
|
|
||||||
// they are the same generic "KPI tile / entry form / toast" primitives, not Investment-specific).
|
|
||||||
|
|
||||||
.npa-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 3px 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&--standard {
|
|
||||||
background: #f0fdf4;
|
|
||||||
color: #15803d;
|
|
||||||
border: 1px solid #bbf7d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--sma0 {
|
|
||||||
background: #fefce8;
|
|
||||||
color: #854d0e;
|
|
||||||
border: 1px solid #fde68a;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--sma1 {
|
|
||||||
background: #fff7ed;
|
|
||||||
color: #9a3412;
|
|
||||||
border: 1px solid #fed7aa;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--sma2 {
|
|
||||||
background: #fff1f0;
|
|
||||||
color: #b91c1c;
|
|
||||||
border: 1px solid #fecdd3;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--substandard {
|
|
||||||
background: #fef2f2;
|
|
||||||
color: #dc2626;
|
|
||||||
border: 1px solid #fecaca;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--doubtful1 {
|
|
||||||
background: #fef2f2;
|
|
||||||
color: #b91c1c;
|
|
||||||
border: 1px solid #fca5a5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--doubtful2 {
|
|
||||||
background: #fdf2f8;
|
|
||||||
color: #9d174d;
|
|
||||||
border: 1px solid #fbcfe8;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--doubtful3 {
|
|
||||||
background: #f5f3ff;
|
|
||||||
color: #5b21b6;
|
|
||||||
border: 1px solid #ddd6fe;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--loss {
|
|
||||||
background: #1f2937;
|
|
||||||
color: #f9fafb;
|
|
||||||
border: 1px solid #1f2937;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.npa-kpis--ratio {
|
|
||||||
.inv-kpi__value {
|
|
||||||
&.npa-warn {
|
|
||||||
color: #dc2626;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.npa-detail-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
||||||
gap: 12px 20px;
|
|
||||||
padding: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.npa-detail-field {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 3px;
|
|
||||||
|
|
||||||
dt {
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #0d1b2a;
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wraps tables with more columns than comfortably fit — .pg-card clips overflow with
|
|
||||||
// border-radius, so the scroll container has to live one level in, around the <table> only.
|
|
||||||
.pg-table-scroll {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lets a single field span the whole .inv-form-grid row instead of being squeezed into one
|
|
||||||
// ~220px column — needed for the Account Detail search box, whose "code — name (branch)"
|
|
||||||
// option text is long enough to overflow a narrow column even with ellipsis truncation.
|
|
||||||
.npa-form-field--full {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pg-pagination {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px 16px;
|
|
||||||
border-top: 1px solid #e2e8f0;
|
|
||||||
background: #f8fafc;
|
|
||||||
font-size: 12.5px;
|
|
||||||
color: #64748b;
|
|
||||||
|
|
||||||
&__controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__page {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #0d1b2a;
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright since 2025 Mifos Initiative
|
|
||||||
*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
|
|
||||||
import { SharedModule } from '../shared/shared.module';
|
|
||||||
import { NpaRoutingModule } from './npa-routing.module';
|
|
||||||
import { NpaReportsComponent } from './npa-reports/npa-reports.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
SharedModule,
|
|
||||||
NpaRoutingModule,
|
|
||||||
NpaReportsComponent
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class NpaModule {}
|
|
||||||
@@ -45,12 +45,6 @@
|
|||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</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 -->
|
<!-- Reason Column -->
|
||||||
<ng-container matColumnDef="reason">
|
<ng-container matColumnDef="reason">
|
||||||
<th mat-header-cell *matHeaderCellDef>Rule Reason / Criteria</th>
|
<th mat-header-cell *matHeaderCellDef>Rule Reason / Criteria</th>
|
||||||
|
|||||||
@@ -114,15 +114,6 @@
|
|||||||
max-width: 400px;
|
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 */
|
/* Badges for Risk Category */
|
||||||
.risk-badge {
|
.risk-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ export class AmlRulesComponent implements OnInit {
|
|||||||
'orderPosition',
|
'orderPosition',
|
||||||
'ruleName',
|
'ruleName',
|
||||||
'riskCategory',
|
'riskCategory',
|
||||||
'incomeLimit',
|
|
||||||
'reason',
|
'reason',
|
||||||
'amlCheckStatus',
|
'amlCheckStatus',
|
||||||
'bannedEntityMatch'
|
'bannedEntityMatch'
|
||||||
@@ -85,39 +84,4 @@ export class AmlRulesComponent implements OnInit {
|
|||||||
return '';
|
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';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -1,417 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
@@ -1,431 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@@ -1,422 +0,0 @@
|
|||||||
<!--
|
|
||||||
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>
|
|
||||||
@@ -1,423 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@@ -1,278 +0,0 @@
|
|||||||
<!--
|
|
||||||
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>
|
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
@@ -1,372 +0,0 @@
|
|||||||
<!--
|
|
||||||
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>
|
|
||||||
@@ -1,470 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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,22 +24,6 @@ const routes: Routes = [
|
|||||||
path: 'rbi/aml',
|
path: 'rbi/aml',
|
||||||
component: AmlRulesComponent,
|
component: AmlRulesComponent,
|
||||||
data: { title: 'AML Rules Master', breadcrumb: 'AML Rules Master' }
|
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)' }
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
|
|||||||
+11
-11
@@ -141,11 +141,11 @@ export interface SftInterestDividendRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SFT_BANK = {
|
export const SFT_BANK = {
|
||||||
name: 'CBSNexora Bank Ltd',
|
name: 'Bharat Urban Bank Ltd',
|
||||||
branch: 'Head Office',
|
branch: 'Head Office',
|
||||||
filerPan: 'AABCB1100M',
|
filerPan: 'AABCB1100M',
|
||||||
filerTan: 'MUMR99001E',
|
filerTan: 'MUMR99001E',
|
||||||
itdReportingPortalId: 'CBSNEXORA-285BA',
|
itdReportingPortalId: 'BHARATURBAN-285BA',
|
||||||
fy: '2025-26',
|
fy: '2025-26',
|
||||||
filingDueDate: '31-05-2026',
|
filingDueDate: '31-05-2026',
|
||||||
principalOfficer: 'Karthik Kamath',
|
principalOfficer: 'Karthik Kamath',
|
||||||
@@ -159,7 +159,7 @@ export const SFT_BANK = {
|
|||||||
branchStdCode: '022',
|
branchStdCode: '022',
|
||||||
branchPhone: '24938000',
|
branchPhone: '24938000',
|
||||||
branchMobile: '9892000100',
|
branchMobile: '9892000100',
|
||||||
branchEmail: 'compliance@cbsnexorabank.example'
|
branchEmail: 'compliance@bharaturbanbank.example'
|
||||||
};
|
};
|
||||||
|
|
||||||
/** ── SFT-001 / 002 / 004 — Person-wise records ── */
|
/** ── SFT-001 / 002 / 004 — Person-wise records ── */
|
||||||
@@ -202,7 +202,7 @@ export const SFT_PERSON_RECORDS: SftPersonRecord[] = [
|
|||||||
cashAmountReceivedFromPerson: 1500000,
|
cashAmountReceivedFromPerson: 1500000,
|
||||||
amountPaidToPerson: 0,
|
amountPaidToPerson: 0,
|
||||||
relatedAccountNumber: '110001101000838',
|
relatedAccountNumber: '110001101000838',
|
||||||
relatedInstituteName: 'CBSNEXORA BANK',
|
relatedInstituteName: 'BHARAT URBAN BANK',
|
||||||
relatedInstituteRefNumber: 'YESB0BHRT',
|
relatedInstituteRefNumber: 'YESB0BHRT',
|
||||||
sftCode: 'SFT-001'
|
sftCode: 'SFT-001'
|
||||||
},
|
},
|
||||||
@@ -243,7 +243,7 @@ export const SFT_PERSON_RECORDS: SftPersonRecord[] = [
|
|||||||
cashAmountReceivedFromPerson: 1100000,
|
cashAmountReceivedFromPerson: 1100000,
|
||||||
amountPaidToPerson: 0,
|
amountPaidToPerson: 0,
|
||||||
relatedAccountNumber: '110001352000357',
|
relatedAccountNumber: '110001352000357',
|
||||||
relatedInstituteName: 'CBSNEXORA BANK',
|
relatedInstituteName: 'BHARAT URBAN BANK',
|
||||||
relatedInstituteRefNumber: 'YESB0BHRT',
|
relatedInstituteRefNumber: 'YESB0BHRT',
|
||||||
sftCode: 'SFT-001'
|
sftCode: 'SFT-001'
|
||||||
},
|
},
|
||||||
@@ -285,7 +285,7 @@ export const SFT_PERSON_RECORDS: SftPersonRecord[] = [
|
|||||||
cashAmountReceivedFromPerson: 2500000,
|
cashAmountReceivedFromPerson: 2500000,
|
||||||
amountPaidToPerson: 0,
|
amountPaidToPerson: 0,
|
||||||
relatedAccountNumber: '110001352000420',
|
relatedAccountNumber: '110001352000420',
|
||||||
relatedInstituteName: 'CBSNEXORA BANK',
|
relatedInstituteName: 'BHARAT URBAN BANK',
|
||||||
relatedInstituteRefNumber: 'YESB0BHRT',
|
relatedInstituteRefNumber: 'YESB0BHRT',
|
||||||
sftCode: 'SFT-002'
|
sftCode: 'SFT-002'
|
||||||
},
|
},
|
||||||
@@ -326,7 +326,7 @@ export const SFT_PERSON_RECORDS: SftPersonRecord[] = [
|
|||||||
cashAmountReceivedFromPerson: 1800000,
|
cashAmountReceivedFromPerson: 1800000,
|
||||||
amountPaidToPerson: 0,
|
amountPaidToPerson: 0,
|
||||||
relatedAccountNumber: '110001352000511',
|
relatedAccountNumber: '110001352000511',
|
||||||
relatedInstituteName: 'CBSNEXORA BANK',
|
relatedInstituteName: 'BHARAT URBAN BANK',
|
||||||
relatedInstituteRefNumber: 'YESB0BHRT',
|
relatedInstituteRefNumber: 'YESB0BHRT',
|
||||||
sftCode: 'SFT-002'
|
sftCode: 'SFT-002'
|
||||||
},
|
},
|
||||||
@@ -368,7 +368,7 @@ export const SFT_PERSON_RECORDS: SftPersonRecord[] = [
|
|||||||
cashAmountReceivedFromPerson: 1250000,
|
cashAmountReceivedFromPerson: 1250000,
|
||||||
amountPaidToPerson: 0,
|
amountPaidToPerson: 0,
|
||||||
relatedAccountNumber: '110001101001145',
|
relatedAccountNumber: '110001101001145',
|
||||||
relatedInstituteName: 'CBSNEXORA BANK',
|
relatedInstituteName: 'BHARAT URBAN BANK',
|
||||||
relatedInstituteRefNumber: 'YESB0BHRT',
|
relatedInstituteRefNumber: 'YESB0BHRT',
|
||||||
sftCode: 'SFT-004'
|
sftCode: 'SFT-004'
|
||||||
},
|
},
|
||||||
@@ -409,7 +409,7 @@ export const SFT_PERSON_RECORDS: SftPersonRecord[] = [
|
|||||||
cashAmountReceivedFromPerson: 2150000,
|
cashAmountReceivedFromPerson: 2150000,
|
||||||
amountPaidToPerson: 0,
|
amountPaidToPerson: 0,
|
||||||
relatedAccountNumber: '110001101001458',
|
relatedAccountNumber: '110001101001458',
|
||||||
relatedInstituteName: 'CBSNEXORA BANK',
|
relatedInstituteName: 'BHARAT URBAN BANK',
|
||||||
relatedInstituteRefNumber: 'YESB0BHRT',
|
relatedInstituteRefNumber: 'YESB0BHRT',
|
||||||
sftCode: 'SFT-004'
|
sftCode: 'SFT-004'
|
||||||
}
|
}
|
||||||
@@ -436,7 +436,7 @@ export const SFT_ACCOUNT_RECORDS: SftAccountRecord[] = [
|
|||||||
branchMobile: '9892000100',
|
branchMobile: '9892000100',
|
||||||
branchFaxStd: '022',
|
branchFaxStd: '022',
|
||||||
branchFaxPhone: '24938011',
|
branchFaxPhone: '24938011',
|
||||||
branchEmail: 'compliance@cbsnexorabank.example',
|
branchEmail: 'compliance@bharaturbanbank.example',
|
||||||
aggGrossCashCredited: 6500000,
|
aggGrossCashCredited: 6500000,
|
||||||
aggGrossCashDebited: 4200000,
|
aggGrossCashDebited: 4200000,
|
||||||
amountCreditedBeforeDemonetization: 0,
|
amountCreditedBeforeDemonetization: 0,
|
||||||
@@ -485,7 +485,7 @@ export const SFT_ACCOUNT_RECORDS: SftAccountRecord[] = [
|
|||||||
branchMobile: '9892000100',
|
branchMobile: '9892000100',
|
||||||
branchFaxStd: '022',
|
branchFaxStd: '022',
|
||||||
branchFaxPhone: '24938011',
|
branchFaxPhone: '24938011',
|
||||||
branchEmail: 'compliance@cbsnexorabank.example',
|
branchEmail: 'compliance@bharaturbanbank.example',
|
||||||
aggGrossCashCredited: 5800000,
|
aggGrossCashCredited: 5800000,
|
||||||
aggGrossCashDebited: 3450000,
|
aggGrossCashDebited: 3450000,
|
||||||
amountCreditedBeforeDemonetization: 0,
|
amountCreditedBeforeDemonetization: 0,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
export default {
|
export default {
|
||||||
'mifos_x': {
|
'mifos_x': {
|
||||||
'version': '260801',
|
'version': '260731',
|
||||||
'hash': 'bd753f49'
|
'hash': 'ca85bae4'
|
||||||
},
|
},
|
||||||
'allow_switching_backend_instance': true
|
'allow_switching_backend_instance': true
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1017,22 +1017,6 @@ mat-card {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Full-Screen Dialog Panel (e.g. FD/RD Entry) — edge-to-edge, no rounded corners ─── */
|
|
||||||
.fdrd-fullscreen-panel {
|
|
||||||
max-width: 100vw !important;
|
|
||||||
width: 100vw !important;
|
|
||||||
height: 100vh !important;
|
|
||||||
max-height: 100vh !important;
|
|
||||||
|
|
||||||
.mat-mdc-dialog-container {
|
|
||||||
border-radius: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-mdc-dialog-surface {
|
|
||||||
border-radius: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── CIC DQR Dialog Backdrop — ensure outside clicks register ─── */
|
/* ─── CIC DQR Dialog Backdrop — ensure outside clicks register ─── */
|
||||||
.cic-dlg-backdrop {
|
.cic-dlg-backdrop {
|
||||||
background: rgb(15 23 42 / 45%);
|
background: rgb(15 23 42 / 45%);
|
||||||
|
|||||||
Reference in New Issue
Block a user