- Correct Investment portfolio allocation and Loan Origination product-mix targets against RBI's UCB SLR/Non-SLR framework and PSL circulars, with a Target Basis column distinguishing RBI-mandated vs board-policy rows (Excel export included). - Expand Investment holdings dataset with more near-term maturities so the Due-Date Diary reflects a realistic spread across FD/Govt Security/Non-SLR/Bond. - Add From/To period-based Interest Calculation & Post (with GL selection for both posting and receiving) across the Investment Interest module, FD Entry and Bond Entry screens; fix a bug where a posted period kept re-appearing as due since accrual wasn't tracked per-holding. - Add a reusable DD-MM-YYYY date-picker component and roll it out across all Investment date fields. - NPA Summary Report: drop SMA-0/1/2 from the stage-wise table (kept in Day-wise Overdue only). - NPA Recovery Report: add Account Type / NPA Category / Target-vs-Actual columns, a shared Account Type filter driving both the account picker and the register, and a searchable (fixed-position, not clipped by card overflow) account select for long lists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1617 lines
61 KiB
HTML
1617 lines
61 KiB
HTML
<!--
|
||
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>
|
||
<button type="button" class="pg-btn pg-btn--ghost" [disabled]="generating" (click)="exportExcel()">
|
||
{{ generating ? 'Exporting…' : 'Export to Excel' }}
|
||
</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', 'recovery-mgmt']; 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>Account Type</span>
|
||
<select [ngModel]="recoveryProductFilter" (ngModelChange)="setRecoveryProductFilter($event)">
|
||
<option value="">-- All Account Types --</option>
|
||
@for (p of recoveryProductOptions; track p) {
|
||
<option [value]="p">{{ p }}</option>
|
||
}
|
||
</select>
|
||
</label>
|
||
<label class="inv-form-field">
|
||
<span>* NPA Account</span>
|
||
<mifosx-search-select
|
||
[options]="recoveryAccountSearchOptions"
|
||
[(ngModel)]="newRecovery.accountNo"
|
||
placeholder="Search account no. or customer name…"
|
||
></mifosx-search-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"
|
||
style="display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap"
|
||
>
|
||
<span>
|
||
NPA Recovery Register — ₹{{ totalRecoveredAmount | number: '1.0-0' }} recovered against ₹{{
|
||
gnpaAmount | number: '1.0-0'
|
||
}}
|
||
GNPA outstanding
|
||
</span>
|
||
@if (recoveryProductFilter) {
|
||
<span class="pg-chip">Filtered: {{ recoveryProductFilter }}</span>
|
||
}
|
||
</div>
|
||
<p class="pg-sub" style="padding: 8px 16px 12px">
|
||
Target Recovery is the account's contractual due this month (EMI / interest-servicing / bullet amount) — same
|
||
basis as the Recovery Management Dashboard.
|
||
</p>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Date</th>
|
||
<th>Account Type</th>
|
||
<th>NPA Category</th>
|
||
<th>Account No</th>
|
||
<th>Customer</th>
|
||
<th>Branch</th>
|
||
<th>Mode</th>
|
||
<th class="num">Target Recovery</th>
|
||
<th class="num col-in">Recovered Amount (Actual)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (r of paged(recoveryRows); track r.accountNo + r.date + r.amount) {
|
||
<tr>
|
||
<td>{{ r.date }}</td>
|
||
<td>{{ r.productName }}</td>
|
||
<td>
|
||
<span [class]="r.npaCategoryClass">{{ r.npaCategory }}</span>
|
||
</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">₹{{ r.targetRecovery | number: '1.0-0' }}</td>
|
||
<td class="num col-in">₹{{ r.amount | number: '1.0-0' }}</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="9" class="pg-empty">No recoveries recorded yet.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr class="cctr-total-row">
|
||
<td colspan="7">Total (Target once per account · Actual across all entries shown)</td>
|
||
<td class="num">₹{{ recoveryTargetTotal | number: '1.0-0' }}</td>
|
||
<td class="num">₹{{ recoveryActualTotal | 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 npa-detail-header">
|
||
<span
|
||
>{{ a.accountNo }} — {{ a.customerName }}
|
||
<span [class]="badgeClass(stage(a))">{{ stageLabel(stage(a)) }}</span></span
|
||
>
|
||
<button type="button" class="pg-btn npa-timeline-trigger" (click)="openTimelineModal()">
|
||
<fa-icon icon="history"></fa-icon> Recovery Journey
|
||
</button>
|
||
</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 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">
|
||
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>
|
||
}
|
||
|
||
<!-- ===================== 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>
|
||
<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>
|
||
}
|
||
|
||
<!-- ===================== 11 RECOVERY MANAGEMENT DASHBOARD ===================== -->
|
||
@if (activeReport === 'recovery-dashboard') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Recovery Management Dashboard — Loan-Type Overdue Check</div>
|
||
<p class="pg-sub npa-report-note">
|
||
Every loan is checked for "is this cycle's dues fully recovered" the way it's actually due — a Term Loan (EMI)
|
||
is checked against its instalment, an Overdrawn (CC/OD/KCC) account against its drawing power and interest
|
||
servicing, and a Bullet loan (Gold) is checked once past maturity.
|
||
</p>
|
||
<div class="inv-kpis npa-kpis--three">
|
||
@for (row of recoveryTypeSummary; track row.type) {
|
||
<div class="inv-kpi">
|
||
<span class="inv-kpi__label">{{ row.label }}</span>
|
||
<span class="inv-kpi__value">{{ row.accounts }} accounts</span>
|
||
<span class="inv-kpi__sub" [class.npa-warn]="row.overdueCount > 0"
|
||
>{{ row.overdueCount }} overdue this cycle</span
|
||
>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Expected vs Actual Recovery — This Cycle, by Loan Type</div>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Loan Type</th>
|
||
<th class="num">Accounts</th>
|
||
<th class="num">Overdue Now</th>
|
||
<th class="num">Expected Recovery</th>
|
||
<th class="num col-in">Actual Recovery</th>
|
||
<th class="num">Variance</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (row of recoveryTypeSummary; track row.type) {
|
||
<tr>
|
||
<td>{{ row.label }}</td>
|
||
<td class="num">{{ row.accounts }}</td>
|
||
<td class="num">{{ row.overdueCount }}</td>
|
||
<td class="num">₹{{ row.expected | number: '1.0-0' }}</td>
|
||
<td class="num col-in">₹{{ row.actual | number: '1.0-0' }}</td>
|
||
<td class="num" [class.col-out]="row.actual - row.expected < 0">
|
||
₹{{ row.actual - row.expected | number: '1.0-0' }}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr class="cctr-total-row">
|
||
<td>Total</td>
|
||
<td class="num">{{ allAccounts.length }}</td>
|
||
<td class="num">
|
||
{{
|
||
recoveryTypeSummary[0].overdueCount +
|
||
recoveryTypeSummary[1].overdueCount +
|
||
recoveryTypeSummary[2].overdueCount
|
||
}}
|
||
</td>
|
||
<td class="num">₹{{ expectedRecoveryTotal | number: '1.0-0' }}</td>
|
||
<td class="num">₹{{ actualRecoveryTotal | number: '1.0-0' }}</td>
|
||
<td class="num" [class.col-out]="recoveryVarianceTotal < 0">
|
||
₹{{ recoveryVarianceTotal | number: '1.0-0' }}
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
<p class="pg-sub npa-report-note">
|
||
Overall recovery rate: {{ recoveryPctTotal | number: '1.1-1' }}% of this cycle's expected dues.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Loan-wise Expected vs Actual Recovery</div>
|
||
<div class="pg-table-scroll">
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Account No</th>
|
||
<th>Customer</th>
|
||
<th>Branch</th>
|
||
<th>Loan Type</th>
|
||
<th class="num">Expected</th>
|
||
<th class="num col-in">Actual</th>
|
||
<th class="num">Variance</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (a of paged(loanWiseRecoveryAccounts); track a.accountNo) {
|
||
<tr>
|
||
<td>
|
||
<span class="pg-chip">{{ a.accountNo }}</span>
|
||
</td>
|
||
<td>{{ a.customerName }}</td>
|
||
<td>{{ a.branchName }}</td>
|
||
<td>{{ repaymentTypeLabel(a.repaymentType) }}</td>
|
||
<td class="num">₹{{ expectedThisMonth(a) | number: '1.0-0' }}</td>
|
||
<td class="num col-in">₹{{ actualThisMonth(a) | number: '1.0-0' }}</td>
|
||
<td class="num" [class.col-out]="recoveryVariance(a) < 0">
|
||
₹{{ recoveryVariance(a) | number: '1.0-0' }}
|
||
</td>
|
||
<td>
|
||
<span
|
||
class="npa-badge"
|
||
[class.npa-badge--standard]="recoveryVariance(a) >= 0"
|
||
[class.npa-badge--substandard]="recoveryVariance(a) < 0"
|
||
>
|
||
{{ recoveryVariance(a) >= 0 ? 'Recovered' : 'Shortfall' }}
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="8" class="pg-empty">No accounts in this branch.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if (loanWiseRecoveryAccounts.length > pageSize) {
|
||
<div class="pg-pagination">
|
||
<span class="pg-pagination__info"
|
||
>Showing {{ rangeStart(loanWiseRecoveryAccounts) }}–{{ rangeEnd(loanWiseRecoveryAccounts) }} of
|
||
{{ loanWiseRecoveryAccounts.length }}</span
|
||
>
|
||
<div class="pg-pagination__controls">
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === 1"
|
||
(click)="goToPage(currentPage - 1, loanWiseRecoveryAccounts)"
|
||
>
|
||
‹ Prev
|
||
</button>
|
||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(loanWiseRecoveryAccounts) }}</span>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === pageCount(loanWiseRecoveryAccounts)"
|
||
(click)="goToPage(currentPage + 1, loanWiseRecoveryAccounts)"
|
||
>
|
||
Next ›
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- ===================== 12 DAY-WISE OVERDUE REPORT ===================== -->
|
||
@if (activeReport === 'daywise-overdue') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Day-wise Overdue Report — Ageing Buckets</div>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Overdue Bucket</th>
|
||
<th class="num">Accounts</th>
|
||
<th class="num">Outstanding</th>
|
||
<th class="num col-out">Overdue Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (b of overdueAgeingBuckets; track b.label) {
|
||
<tr>
|
||
<td>{{ b.label }}</td>
|
||
<td class="num">{{ b.count }}</td>
|
||
<td class="num">₹{{ b.outstanding | number: '1.0-0' }}</td>
|
||
<td class="num col-out">₹{{ b.overdueAmt | number: '1.0-0' }}</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Overdue Accounts — Day-wise, Marked by SMA Stage</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>Stage</th>
|
||
<th class="num">Overdue Days</th>
|
||
<th class="num">Outstanding</th>
|
||
<th class="num col-out">Overdue Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (a of paged(overdueDaysAccounts); 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.overdueDays }}</td>
|
||
<td class="num">₹{{ a.outstandingBalance | number: '1.0-0' }}</td>
|
||
<td class="num col-out">₹{{ a.overdueAmount | number: '1.0-0' }}</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="8" class="pg-empty">No overdue accounts in this branch.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if (overdueDaysAccounts.length > pageSize) {
|
||
<div class="pg-pagination">
|
||
<span class="pg-pagination__info"
|
||
>Showing {{ rangeStart(overdueDaysAccounts) }}–{{ rangeEnd(overdueDaysAccounts) }} of
|
||
{{ overdueDaysAccounts.length }}</span
|
||
>
|
||
<div class="pg-pagination__controls">
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === 1"
|
||
(click)="goToPage(currentPage - 1, overdueDaysAccounts)"
|
||
>
|
||
‹ Prev
|
||
</button>
|
||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(overdueDaysAccounts) }}</span>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === pageCount(overdueDaysAccounts)"
|
||
(click)="goToPage(currentPage + 1, overdueDaysAccounts)"
|
||
>
|
||
Next ›
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- ===================== 13 DAY-WISE OVERDRAWN REPORT ===================== -->
|
||
@if (activeReport === 'daywise-overdrawn') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">
|
||
Day-wise Overdrawn Report — Cash Credit / Overdraft / KCC Accounts ({{ overdrawnBreachCount }} of
|
||
{{ overdrawnAccounts.length }} breaching Drawing Power)
|
||
</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 class="num">Drawing Power</th>
|
||
<th class="num">Outstanding</th>
|
||
<th class="num col-out">Excess Over DP</th>
|
||
<th class="num">Days Overdrawn</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (a of paged(overdrawnAccounts); 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.drawingPower | number: '1.0-0' }}</td>
|
||
<td class="num">₹{{ a.outstandingBalance | number: '1.0-0' }}</td>
|
||
<td class="num col-out">₹{{ excessOverDp(a) | number: '1.0-0' }}</td>
|
||
<td class="num">{{ overdrawnBreachOf(a) ? a.overdueDays : 0 }}</td>
|
||
<td>
|
||
<span
|
||
class="npa-badge"
|
||
[class.npa-badge--standard]="!overdrawnBreachOf(a)"
|
||
[class.npa-badge--substandard]="overdrawnBreachOf(a)"
|
||
>
|
||
{{ overdrawnBreachOf(a) ? 'DP Breached' : 'Within Limit' }}
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="9" class="pg-empty">No Overdrawn accounts in this branch.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if (overdrawnAccounts.length > pageSize) {
|
||
<div class="pg-pagination">
|
||
<span class="pg-pagination__info"
|
||
>Showing {{ rangeStart(overdrawnAccounts) }}–{{ rangeEnd(overdrawnAccounts) }} of
|
||
{{ overdrawnAccounts.length }}</span
|
||
>
|
||
<div class="pg-pagination__controls">
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === 1"
|
||
(click)="goToPage(currentPage - 1, overdrawnAccounts)"
|
||
>
|
||
‹ Prev
|
||
</button>
|
||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(overdrawnAccounts) }}</span>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === pageCount(overdrawnAccounts)"
|
||
(click)="goToPage(currentPage + 1, overdrawnAccounts)"
|
||
>
|
||
Next ›
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- ===================== 14 AGRICULTURE / GOLD MARKET VALUE REPORT ===================== -->
|
||
@if (activeReport === 'agri-gold-value') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Agriculture / Gold Market Value Report — Current Security Value vs Loan</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 class="num">Outstanding</th>
|
||
<th class="num">Collateral Value (At Sanction)</th>
|
||
<th class="num">Market Value (Now)</th>
|
||
<th class="num">Coverage %</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (a of paged(agriGoldAccounts); 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">₹{{ a.collateralValue | number: '1.0-0' }}</td>
|
||
<td
|
||
class="num"
|
||
[class.col-in]="a.collateralMarketValue >= a.collateralValue"
|
||
[class.col-out]="a.collateralMarketValue < a.collateralValue"
|
||
>
|
||
₹{{ a.collateralMarketValue | number: '1.0-0' }}
|
||
</td>
|
||
<td class="num">{{ coverageRatio(a) | number: '1.0-0' }}%</td>
|
||
<td>
|
||
<span
|
||
class="npa-badge"
|
||
[class.npa-badge--standard]="!undersecuredOf(a)"
|
||
[class.npa-badge--substandard]="undersecuredOf(a)"
|
||
>
|
||
{{ undersecuredOf(a) ? 'Undersecured' : 'Adequate' }}
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="9" class="pg-empty">No Agriculture / Gold accounts in this branch.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if (agriGoldAccounts.length > pageSize) {
|
||
<div class="pg-pagination">
|
||
<span class="pg-pagination__info"
|
||
>Showing {{ rangeStart(agriGoldAccounts) }}–{{ rangeEnd(agriGoldAccounts) }} of
|
||
{{ agriGoldAccounts.length }}</span
|
||
>
|
||
<div class="pg-pagination__controls">
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === 1"
|
||
(click)="goToPage(currentPage - 1, agriGoldAccounts)"
|
||
>
|
||
‹ Prev
|
||
</button>
|
||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(agriGoldAccounts) }}</span>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === pageCount(agriGoldAccounts)"
|
||
(click)="goToPage(currentPage + 1, agriGoldAccounts)"
|
||
>
|
||
Next ›
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- ===================== 15 EXCEPTION CHECK REPORT ===================== -->
|
||
@if (activeReport === 'exception-check') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">
|
||
Exception Check — Security Value vs Loan ({{ exceptionAccounts.length }} accounts undersecured)
|
||
</div>
|
||
<p class="pg-sub npa-report-note">
|
||
Every secured loan's current security market value is expected to stay above its outstanding balance. This lists
|
||
the exceptions — where it doesn't — so recovery/credit can review before the next classification review.
|
||
</p>
|
||
<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>Stage</th>
|
||
<th class="num">Outstanding</th>
|
||
<th class="num">Market Value</th>
|
||
<th class="num col-out">Shortfall</th>
|
||
<th class="num">Coverage %</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (a of paged(exceptionAccounts); 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.collateralMarketValue | number: '1.0-0' }}</td>
|
||
<td class="num col-out">₹{{ securityShortfall(a) | number: '1.0-0' }}</td>
|
||
<td class="num">{{ coverageRatio(a) | number: '1.0-0' }}%</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="9" class="pg-empty">No undersecured accounts in this branch.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if (exceptionAccounts.length > pageSize) {
|
||
<div class="pg-pagination">
|
||
<span class="pg-pagination__info"
|
||
>Showing {{ rangeStart(exceptionAccounts) }}–{{ rangeEnd(exceptionAccounts) }} of
|
||
{{ exceptionAccounts.length }}</span
|
||
>
|
||
<div class="pg-pagination__controls">
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === 1"
|
||
(click)="goToPage(currentPage - 1, exceptionAccounts)"
|
||
>
|
||
‹ Prev
|
||
</button>
|
||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(exceptionAccounts) }}</span>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === pageCount(exceptionAccounts)"
|
||
(click)="goToPage(currentPage + 1, exceptionAccounts)"
|
||
>
|
||
Next ›
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
<!-- ===================== Recovery / Collection Journey popup ===================== -->
|
||
@if (showTimelineModal && selectedAccount; as a) {
|
||
<div class="npa-timeline-backdrop" (click)="closeTimelineModal()">
|
||
<div class="npa-timeline-modal" (click)="$event.stopPropagation()">
|
||
<header class="npa-timeline-modal__head">
|
||
<div class="npa-timeline-modal__title">
|
||
<span class="npa-timeline-modal__eyebrow">Recovery / Collection Journey</span>
|
||
<h2>{{ a.accountNo }} — {{ a.customerName }}</h2>
|
||
<div class="npa-timeline-modal__meta">
|
||
<span [class]="badgeClass(stage(a))">{{ stageLabel(stage(a)) }}</span>
|
||
<span class="npa-timeline-modal__branch">{{ a.branchName }}</span>
|
||
<span class="npa-timeline-modal__count">{{ a.collectionTimeline.length }} touchpoints</span>
|
||
</div>
|
||
</div>
|
||
<button type="button" class="npa-timeline-modal__close" (click)="closeTimelineModal()" aria-label="Close">
|
||
<fa-icon icon="times"></fa-icon>
|
||
</button>
|
||
</header>
|
||
<div class="npa-timeline-modal__body">
|
||
@if (a.collectionTimeline.length) {
|
||
<ol class="npa-timeline">
|
||
@for (e of a.collectionTimeline; track e.date + e.type) {
|
||
<li [class]="'npa-timeline-item npa-timeline-item--' + timelineEventTone(e.type)">
|
||
<span class="npa-timeline-item__icon"><fa-icon [icon]="timelineEventIcon(e.type)"></fa-icon></span>
|
||
<div class="npa-timeline-item__body">
|
||
<div class="npa-timeline-item__row">
|
||
<span class="npa-timeline-item__type">{{ e.type }}</span>
|
||
<span class="npa-timeline-item__date">{{ e.date }}</span>
|
||
</div>
|
||
<p class="npa-timeline-item__remarks">{{ e.remarks }}</p>
|
||
<span class="npa-timeline-item__channel">{{ e.channel }}</span>
|
||
</div>
|
||
</li>
|
||
}
|
||
</ol>
|
||
} @else {
|
||
<div class="npa-timeline-empty">
|
||
<fa-icon icon="check-circle"></fa-icon>
|
||
<p>No collection activity — this account has never gone overdue.</p>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|