feat(investment,npa): RBI-grounded targets, GL-tracked interest posting, date/search pickers

- 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>
This commit is contained in:
2026-08-02 19:26:19 +05:30
co-authored by Claude Sonnet 5
parent d7f5bfe5f5
commit ebe67cc410
20 changed files with 1401 additions and 226 deletions
@@ -1470,16 +1470,30 @@ export function branchTargetAmount(branchCode: string): number {
return BRANCH_DISBURSEMENT_TARGET[branchCode] || 0;
}
/** Target product mix (% of total disbursement) — weighted towards secured Housing/Vehicle
* lending plus the priority-sector KCC/Cash Credit book, adding up to 100%. */
/**
* Target product mix (% of total disbursement). Two of these are genuinely RBI-mandated
* Priority Sector Lending sub-targets (RBI Master Direction — Priority Sector Lending: Targets
* and Classification, FIDD.CO.Plan.BC.13/04.09.01/2020-21, as extended to UCBs) —
* • Agriculture: 18% of Adjusted Net Bank Credit (ANBC) — mapped here to Kisan Credit Card.
* • Micro Enterprises: 7.5% of ANBC — mapped here to Cash Credit (MSME).
* RBI sets these as a % of the BANK'S WHOLE loan book (ANBC), not of any one product line's own
* disbursement — shown here against this module's own mix as the closest available proxy, since
* this demo has no bank-wide ANBC figure to check against. The remaining five products (Housing,
* Vehicle, Gold, Personal, Education) have no RBI-mandated minimum share of their own — Housing
* and Education are Priority-Sector-*eligible* within RBI's loan-amount ceilings but aren't
* separately targeted, and Vehicle/Gold/Personal aren't Priority Sector categories at all — so
* their split below is an internal business-plan assumption, not a regulatory requirement.
* (RBI's other PSL sub-targets — Weaker Sections 12%, overall PSL 40% — cut across products by
* borrower category rather than by loan type, so they aren't represented as their own row here.)
*/
export const PRODUCT_TARGET_PCT: Record<string, number> = {
PL: 12,
HL: 28,
HL: 30,
VL: 15,
GL: 10,
CC: 15,
KCC: 12,
EL: 8
GL: 12,
CC: 7.5, // RBI Micro Enterprises PSL sub-target
KCC: 18, // RBI Agriculture PSL sub-target
EL: 5.5
};
/** Target channel mix (% of disbursement) — the bank's push towards BC/Digital origination and
@@ -483,10 +483,17 @@
<div class="pg-card">
<div class="pos-section-label">Product Mix — Target % vs Actual % (of Disbursed Amount)</div>
<p class="pg-sub npa-report-note">
KCC and Cash Credit (MSME) targets are RBI's actual Priority Sector Lending sub-targets — Agriculture 18% and
Micro Enterprises 7.5% of Adjusted Net Bank Credit (shown here against this module's own mix, since RBI sets
them against the bank's whole loan book, not one product line). The rest are internal business-plan targets, not
RBI-mandated.
</p>
<table class="pg-table">
<thead>
<tr>
<th>Product</th>
<th>Target Basis</th>
<th class="num">Target %</th>
<th class="num col-in">Actual %</th>
<th class="num">Variance</th>
@@ -497,7 +504,12 @@
@for (row of productTargetRows; track row.code) {
<tr>
<td>{{ row.label }}</td>
<td class="num">{{ row.targetPct | number: '1.0-0' }}%</td>
<td>
<span class="loan-badge" [class.loan-badge--progress]="row.isRbiMandated">
{{ row.isRbiMandated ? 'RBI PSL Sub-Target' : 'Internal Target' }}
</span>
</td>
<td class="num">{{ row.targetPct | number: '1.0-1' }}%</td>
<td class="num col-in">{{ row.actualPct | number: '1.1-1' }}%</td>
<td class="num" [class.col-out]="row.variancePct < 0" [class.col-in]="row.variancePct > 0">
{{ row.variancePct > 0 ? '+' : '' }}{{ row.variancePct | number: '1.1-1' }} pp
@@ -533,7 +545,7 @@
@for (row of channelTargetRows; track row.code) {
<tr>
<td>{{ row.label }}</td>
<td class="num">{{ row.targetPct | number: '1.0-0' }}%</td>
<td class="num">{{ row.targetPct | number: '1.0-1' }}%</td>
<td class="num col-in">{{ row.actualPct | number: '1.1-1' }}%</td>
<td class="num" [class.col-out]="row.variancePct < 0" [class.col-in]="row.variancePct > 0">
{{ row.variancePct > 0 ? '+' : '' }}{{ row.variancePct | number: '1.1-1' }} pp
@@ -71,6 +71,9 @@ interface MixTargetRow {
actualPct: number;
variancePct: number;
status: TargetStatus;
/** True only for the two rows whose target is an actual RBI PSL sub-target (Agriculture 18% /
* Micro Enterprises 7.5% of ANBC) rather than an internal business-plan assumption. */
isRbiMandated: boolean;
}
type ViewId = 'pipeline' | 'new' | 'channel' | 'bc' | 'share' | 'target';
@@ -389,7 +392,8 @@ export class LoanPipelineComponent implements OnInit {
targetPct,
actualPct,
variancePct: actualPct - targetPct,
status: targetStatus(actualPct, targetPct, 3)
status: targetStatus(actualPct, targetPct, 3),
isRbiMandated: p.code === 'KCC' || p.code === 'CC'
};
});
}
@@ -413,7 +417,8 @@ export class LoanPipelineComponent implements OnInit {
targetPct,
actualPct,
variancePct: actualPct - targetPct,
status: targetStatus(actualPct, targetPct, 3)
status: targetStatus(actualPct, targetPct, 3),
isRbiMandated: false
};
});
}
@@ -626,6 +631,7 @@ export class LoanPipelineComponent implements OnInit {
subtitle,
headers: [
'Mix',
'Target Basis',
'Target %',
'Actual %',
'Variance (pp)',
@@ -633,6 +639,7 @@ export class LoanPipelineComponent implements OnInit {
],
widths: [
26,
18,
12,
12,
14,
@@ -641,6 +648,7 @@ export class LoanPipelineComponent implements OnInit {
rows: [
...this.productTargetRows.map((r) => [
`Product — ${r.label}`,
r.isRbiMandated ? 'RBI PSL Sub-Target' : 'Internal Target',
r.targetPct,
Number(r.actualPct.toFixed(1)),
Number(r.variancePct.toFixed(1)),
@@ -648,6 +656,7 @@ export class LoanPipelineComponent implements OnInit {
]),
...this.channelTargetRows.map((r) => [
`Channel — ${r.label}`,
'Internal Target',
r.targetPct,
Number(r.actualPct.toFixed(1)),
Number(r.variancePct.toFixed(1)),