feat(npa): add Undebited Interest Recovery Report, fix note text width
Adds a 10th NPA report tracking memorandum "undebited interest" per IRAC norms (accrued vs cash-recovered vs balance), wired into Export to Excel and the NPA mega-menu. Also fixes a narrow-width text wrap on the report's description note (new .npa-report-note class overriding pg-sub's 560px cap).
This commit is contained in:
@@ -784,7 +784,97 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ===================== 08 PROBABLE NPA ===================== -->
|
||||
<!-- ===================== 08 UNDEBITED INTEREST RECOVERY REPORT ===================== -->
|
||||
@if (activeReport === 'undebited-interest') {
|
||||
<div class="pg-card">
|
||||
<div class="pos-section-label">
|
||||
Undebited Interest Recovery Report — Memorandum Interest Held Outside the Loan Account (IRAC Norms)
|
||||
</div>
|
||||
<p class="pg-sub npa-report-note">
|
||||
Once an account turns NPA, interest is no longer debited to the borrower's loan account or recognised as income
|
||||
— it is tracked here as memorandum "undebited interest" and recognised only when actually recovered in cash.
|
||||
Cash recovered is appropriated to this bucket first; any balance beyond it reduces principal.
|
||||
</p>
|
||||
<div class="pg-table-scroll">
|
||||
<table class="pg-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account No</th>
|
||||
<th>Customer</th>
|
||||
<th>Branch</th>
|
||||
<th>Stage</th>
|
||||
<th>NPA Since</th>
|
||||
<th class="num">Days NPA</th>
|
||||
<th class="num">Rate</th>
|
||||
<th class="num col-out">Undebited Interest Accrued</th>
|
||||
<th class="num col-in">Interest Recovered (Cash)</th>
|
||||
<th class="num">Undebited Interest Balance</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>
|
||||
<span [class]="badgeClass(stage(a))">{{ stageLabel(stage(a)) }}</span>
|
||||
</td>
|
||||
<td>{{ npaSince(a) }}</td>
|
||||
<td class="num">{{ daysSinceNpaOf(a) }}</td>
|
||||
<td class="num">{{ a.interestRate }}%</td>
|
||||
<td class="num col-out">₹{{ interestRev(a) | number: '1.0-0' }}</td>
|
||||
<td class="num col-in">₹{{ interestRecoveredCashOf(a) | number: '1.0-0' }}</td>
|
||||
<td class="num">₹{{ undebitedInterestBalanceOf(a) | number: '1.0-0' }}</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="10" class="pg-empty">No NPA accounts in this branch.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="cctr-total-row">
|
||||
<td colspan="7">Total ({{ npaAccounts.length }} accounts)</td>
|
||||
<td class="num">₹{{ undebitedInterestAccruedTotal | number: '1.0-0' }}</td>
|
||||
<td class="num">₹{{ interestRecoveredCashTotal | number: '1.0-0' }}</td>
|
||||
<td class="num">₹{{ undebitedInterestBalanceTotal | number: '1.0-0' }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</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>
|
||||
}
|
||||
|
||||
<!-- ===================== 09 PROBABLE NPA ===================== -->
|
||||
@if (activeReport === 'probable') {
|
||||
<div class="pg-card">
|
||||
<div class="pos-section-label">
|
||||
@@ -871,7 +961,7 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ===================== 09 SMA REPORT ===================== -->
|
||||
<!-- ===================== 10 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>
|
||||
|
||||
@@ -36,6 +36,9 @@ import {
|
||||
movementLabel,
|
||||
stageBadgeClass,
|
||||
npaSinceDate,
|
||||
daysSinceNpa,
|
||||
interestRecoveredCash,
|
||||
undebitedInterestBalance,
|
||||
todayDDMMYYYY,
|
||||
LegalActionStatus,
|
||||
LEGAL_ACTION_LABELS
|
||||
@@ -268,6 +271,18 @@ export class NpaReportsComponent implements OnInit, OnDestroy {
|
||||
return npaSinceDate(a);
|
||||
}
|
||||
|
||||
daysSinceNpaOf(a: LoanAccount): number {
|
||||
return daysSinceNpa(a);
|
||||
}
|
||||
|
||||
interestRecoveredCashOf(a: LoanAccount): number {
|
||||
return interestRecoveredCash(a);
|
||||
}
|
||||
|
||||
undebitedInterestBalanceOf(a: LoanAccount): number {
|
||||
return undebitedInterestBalance(a);
|
||||
}
|
||||
|
||||
legalActionLabel(status: LegalActionStatus): string {
|
||||
return LEGAL_ACTION_LABELS[status];
|
||||
}
|
||||
@@ -359,6 +374,19 @@ export class NpaReportsComponent implements OnInit, OnDestroy {
|
||||
return this.npaAccounts.reduce((s, a) => s + this.recovered(a), 0);
|
||||
}
|
||||
|
||||
// -------------------- Undebited Interest Recovery Report --------------------
|
||||
get undebitedInterestAccruedTotal(): number {
|
||||
return this.npaAccounts.reduce((s, a) => s + this.interestRev(a), 0);
|
||||
}
|
||||
|
||||
get interestRecoveredCashTotal(): number {
|
||||
return this.npaAccounts.reduce((s, a) => s + this.interestRecoveredCashOf(a), 0);
|
||||
}
|
||||
|
||||
get undebitedInterestBalanceTotal(): number {
|
||||
return this.npaAccounts.reduce((s, a) => s + this.undebitedInterestBalanceOf(a), 0);
|
||||
}
|
||||
|
||||
get restructuredAccounts(): LoanAccount[] {
|
||||
return this.allAccounts.filter((a) => a.isRestructured);
|
||||
}
|
||||
@@ -964,6 +992,65 @@ export class NpaReportsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
];
|
||||
|
||||
case 'undebited-interest':
|
||||
return [
|
||||
{
|
||||
sheetName: 'Undebited Interest',
|
||||
title: 'Undebited Interest Recovery Report — Memorandum Interest (IRAC Norms)',
|
||||
subtitle,
|
||||
headers: [
|
||||
'Account No',
|
||||
'Customer',
|
||||
'Branch',
|
||||
'Stage',
|
||||
'NPA Since',
|
||||
'Days NPA',
|
||||
'Rate %',
|
||||
'Undebited Interest Accrued',
|
||||
'Interest Recovered (Cash)',
|
||||
'Undebited Interest Balance'
|
||||
],
|
||||
widths: [
|
||||
16,
|
||||
22,
|
||||
16,
|
||||
14,
|
||||
12,
|
||||
10,
|
||||
10,
|
||||
22,
|
||||
20,
|
||||
22
|
||||
],
|
||||
rows: [
|
||||
...this.npaAccounts.map((a) => [
|
||||
a.accountNo,
|
||||
a.customerName,
|
||||
a.branchName,
|
||||
this.stageLabel(this.stage(a)),
|
||||
this.npaSince(a),
|
||||
this.daysSinceNpaOf(a),
|
||||
a.interestRate,
|
||||
this.interestRev(a),
|
||||
this.interestRecoveredCashOf(a),
|
||||
this.undebitedInterestBalanceOf(a)
|
||||
]),
|
||||
[
|
||||
`Total (${this.npaAccounts.length} accounts)`,
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
this.undebitedInterestAccruedTotal,
|
||||
this.interestRecoveredCashTotal,
|
||||
this.undebitedInterestBalanceTotal
|
||||
]
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
case 'probable':
|
||||
return [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user