From 1bf441114e8ec0150bfdd8bc50e394884d5a93b9 Mon Sep 17 00:00:00 2001 From: Sonali Likhitkar Date: Mon, 19 Oct 2026 15:38:03 +0530 Subject: [PATCH] feat(npa): add Cash Credit (CC) Recovery Management tab New tab under NPA > Reports > Recovery Management, dedicated to Cash Credit accounts: Drawing Power vs outstanding, interest due/paid this cycle, lifetime recovery, and a Days-Overdrawn/Status view (Regular / Overdrawn / NPA) in place of the generic EMI-style SMA labels, matching how a CC/OD book is actually monitored in real banks. Status now checks NPA history first so an account doesn't show "Regular" just because it's briefly back within DP (RBI upgrade norms require full clearance, not a one-off snapshot). Co-Authored-By: Claude Sonnet 5 --- src/app/npa/npa-data.ts | 1 + .../npa-reports/npa-reports.component.html | 178 ++++++++++++++++++ .../npa/npa-reports/npa-reports.component.ts | 120 ++++++++++++ src/app/npa/npa-shared.scss | 159 ++++++++++++++++ src/environments/.env.ts | 4 +- 5 files changed, 460 insertions(+), 2 deletions(-) diff --git a/src/app/npa/npa-data.ts b/src/app/npa/npa-data.ts index 60dc67d56..0f0a79921 100644 --- a/src/app/npa/npa-data.ts +++ b/src/app/npa/npa-data.ts @@ -967,6 +967,7 @@ export const REPORT_META: ReportMeta[] = [ { id: 'probable', group: 'classification', label: 'Probable NPA' }, { id: 'sma', group: 'classification', label: 'SMA Report' }, { id: 'recovery-dashboard', group: 'recovery-mgmt', label: 'Recovery Management Dashboard' }, + { id: 'cc-recovery', group: 'recovery-mgmt', label: 'Cash Credit (CC) Recovery Management' }, { id: 'daywise-overdue', group: 'recovery-mgmt', label: 'Day-wise Overdue Report' }, { id: 'daywise-overdrawn', group: 'recovery-mgmt', label: 'Day-wise Overdrawn Report' }, { id: 'agri-gold-value', group: 'recovery-mgmt', label: 'Agriculture / Gold Market Value Report' }, diff --git a/src/app/npa/npa-reports/npa-reports.component.html b/src/app/npa/npa-reports/npa-reports.component.html index 9211bbdc8..9ad416d85 100644 --- a/src/app/npa/npa-reports/npa-reports.component.html +++ b/src/app/npa/npa-reports/npa-reports.component.html @@ -1400,6 +1400,184 @@ } + + @if (activeReport === 'cc-recovery') { +
+ +

+ Cash Credit is a revolving working-capital facility secured against stock / receivables. Unlike a Term Loan, it + isn't checked against a fixed instalment — it is checked against its Drawing Power (DP), and + tracked by how many continuous Days Overdrawn ("Out of Order") it has run — crossing 90 days + turns it NPA. +

+
+
+ +
Drawing Power (DP)
+

+ The maximum amount the borrower may actually draw against the sanctioned CC limit — fixed from the value of + stock / book-debts declared in the latest stock statement. Can be lower than the sanctioned limit. +

+
+
+ +
Out of Order
+

+ A CC account turns "Out of Order" once its outstanding continuously stays above the limit/DP, or adequate + credits stop coming in, for 90 days — the CC-specific overdue trigger, in place of a missed EMI. +

+
+
+ +
Days Overdrawn
+

+ The running count of continuous days an account has stayed Out of Order — the number a recovery desk + actually watches day to day, instead of a classification label. A rising count is the early-warning sign. +

+
+
+ +
NPA (Non-Performing Asset)
+

+ Once Out of Order crosses 90 days, the CC account slips to NPA — Sub-Standard, then Doubtful-1/2/3, then + Loss Asset as it ages further. +

+
+
+
+ +
+
+
+ + Total CC Accounts + {{ ccAccounts.length }} + ₹{{ ccTotalSanctioned | number: '1.0-0' }} sanctioned +
+
+ + Overdrawn (1–90 Days) + {{ ccOverdrawnCount }} + Out of Order, not yet NPA +
+
+ + NPA (90+ Days Overdrawn) + {{ ccNpaCount }} + ₹{{ ccTotalOutstanding | number: '1.0-0' }} total outstanding +
+
+ + Interest Paid (This Cycle) + ₹{{ ccInterestPaidThisCycle | number: '1.0-0' }} + of ₹{{ ccInterestDueThisCycle | number: '1.0-0' }} due +
+
+ + Total Recovered + ₹{{ ccTotalRecovered | number: '1.0-0' }} + {{ ccRecoveryPct | number: '1.1-1' }}% of ₹{{ ccTotalSanctioned | number: '1.0-0' }} sanctioned +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + @for (a of paged(ccAccounts); track a.accountNo) { + + + + + + + + + + + + + + + + + } @empty { + + + + } + +
Account NoCustomerBranchSanctioned LimitDrawing Power (DP)OutstandingPending to RecoverExcess Over DPInterest Due (This Cycle)Interest Paid (This Cycle)Total RecoveredDays OverdrawnStatusRecovery Officer
+ {{ a.accountNo }} + {{ a.customerName }}{{ a.branchName }}₹{{ a.sanctionedAmount | number: '1.0-0' }}₹{{ a.drawingPower | number: '1.0-0' }}₹{{ a.outstandingBalance | number: '1.0-0' }}₹{{ a.overdueAmount | number: '1.0-0' }}₹{{ excessOverDp(a) | number: '1.0-0' }}₹{{ expectedThisMonth(a) | number: '1.0-0' }} + ₹{{ actualThisMonth(a) | number: '1.0-0' }} + ₹{{ recovered(a) | number: '1.0-0' }}{{ isNpaStageOf(a) || overdrawnBreachOf(a) ? a.overdueDays : 0 }} + + {{ isNpaStageOf(a) ? 'NPA' : overdrawnBreachOf(a) ? 'Overdrawn' : 'Regular' }} + + {{ a.recoveryOfficer }}
No Cash Credit accounts in this branch.
+
+ @if (ccAccounts.length > pageSize) { +
+ Showing {{ rangeStart(ccAccounts) }}–{{ rangeEnd(ccAccounts) }} of {{ ccAccounts.length }} +
+ + Page {{ currentPage }} of {{ pageCount(ccAccounts) }} + +
+
+ } +
+ } + @if (activeReport === 'agri-gold-value') {
diff --git a/src/app/npa/npa-reports/npa-reports.component.ts b/src/app/npa/npa-reports/npa-reports.component.ts index f9f62f9cd..678eac397 100644 --- a/src/app/npa/npa-reports/npa-reports.component.ts +++ b/src/app/npa/npa-reports/npa-reports.component.ts @@ -660,6 +660,69 @@ export class NpaReportsComponent implements OnInit, OnDestroy { return this.overdrawnAccounts.filter((a) => this.overdrawnBreachOf(a)).length; } + // -------------------- Cash Credit (CC) Recovery Management -------------------- + /** Pure Cash Credit (MSME) accounts only — product code '01' — kept separate from the combined + * Overdrawn (CC/OD/KCC) bucket so CC's own Drawing Power / Out-of-Order / SMA-NPA position can + * be reviewed on its own, the way a recovery desk actually works a CC book. */ + get ccAccounts(): LoanAccount[] { + return this.allAccounts + .filter((a) => a.productCode === '01') + .sort((a, b) => this.excessOverDp(b) - this.excessOverDp(a)); + } + + /** Any CC account that's irregular — either drawn beyond its Drawing Power right now, or already + * NPA-staged from its overdrawn history (an NPA account doesn't "go Regular" just because it's + * briefly back within DP — RBI upgrade norms require full clearance first). Covers the same + * population the 1–90-day Overdrawn and 90+-day NPA buckets below split in two. */ + get ccOutOfOrderCount(): number { + return this.ccAccounts.filter((a) => this.overdrawnBreachOf(a) || this.isNpaStageOf(a)).length; + } + + /** Out of Order for 1–90 continuous days — the pre-NPA monitoring window a recovery desk tracks + * by plain day-count, not by an SMA-0/1/2 label (that split only matters for regulatory + * reporting, not the day-to-day collection screen). */ + get ccOverdrawnCount(): number { + return this.ccAccounts.filter((a) => this.overdrawnBreachOf(a) && !this.isNpaStageOf(a)).length; + } + + /** Out of Order for more than 90 continuous days — the point RBI norms classify a CC/OD account + * as NPA. */ + get ccNpaCount(): number { + return this.ccAccounts.filter((a) => this.isNpaStageOf(a)).length; + } + + get ccTotalSanctioned(): number { + return this.ccAccounts.reduce((s, a) => s + a.sanctionedAmount, 0); + } + + get ccTotalOutstanding(): number { + return this.ccAccounts.reduce((s, a) => s + a.outstandingBalance, 0); + } + + /** A Cash Credit account has no fixed EMI — each cycle's "due" amount is interest servicing on + * the drawn balance, so this total doubles as "how much interest was CC customers due to pay + * this cycle". */ + get ccInterestDueThisCycle(): number { + return this.ccAccounts.reduce((s, a) => s + this.expectedThisMonth(a), 0); + } + + /** How much of that interest the CC book has actually paid this cycle — the answer to "which + * customers have paid how much interest", summed across the Cash Credit book. */ + get ccInterestPaidThisCycle(): number { + return this.ccAccounts.reduce((s, a) => s + this.actualThisMonth(a), 0); + } + + /** Lifetime cash recovered against the CC book (recorded recovery transactions — cash deposits, + * cheques, NEFT/RTGS, auction/SARFAESI, OTS) — the answer to "loan diya kitna, recover kitna + * hua", measured against what was actually sanctioned. */ + get ccTotalRecovered(): number { + return this.ccAccounts.reduce((s, a) => s + this.recovered(a), 0); + } + + get ccRecoveryPct(): number { + return this.ccTotalSanctioned ? (this.ccTotalRecovered / this.ccTotalSanctioned) * 100 : 0; + } + // -------------------- Agriculture / Gold Market Value Report -------------------- get agriGoldAccounts(): LoanAccount[] { return this.allAccounts @@ -1617,6 +1680,63 @@ export class NpaReportsComponent implements OnInit, OnDestroy { } ]; + case 'cc-recovery': + return [ + { + sheetName: 'Cash Credit Recovery', + title: 'Cash Credit (CC) Recovery Management — Drawing Power & Overdrawn-Day Tracking', + subtitle, + headers: [ + 'Account No', + 'Customer', + 'Branch', + 'Sanctioned Limit', + 'Drawing Power', + 'Outstanding', + 'Pending to Recover', + 'Excess Over DP', + 'Interest Due (This Cycle)', + 'Interest Paid (This Cycle)', + 'Total Recovered', + 'Days Overdrawn', + 'Status', + 'Recovery Officer' + ], + widths: [ + 16, + 22, + 16, + 16, + 16, + 16, + 16, + 16, + 18, + 18, + 18, + 14, + 14, + 20 + ], + rows: this.ccAccounts.map((a) => [ + a.accountNo, + a.customerName, + a.branchName, + a.sanctionedAmount, + a.drawingPower, + a.outstandingBalance, + a.overdueAmount, + this.excessOverDp(a), + this.expectedThisMonth(a), + this.actualThisMonth(a), + this.recovered(a), + this.isNpaStageOf(a) || this.overdrawnBreachOf(a) ? a.overdueDays : 0, + this.isNpaStageOf(a) ? 'NPA' : this.overdrawnBreachOf(a) ? 'Overdrawn' : 'Regular', + a.recoveryOfficer + ]) + } + ]; + case 'daywise-overdrawn': return [ { diff --git a/src/app/npa/npa-shared.scss b/src/app/npa/npa-shared.scss index 419e908f0..f66d106c2 100644 --- a/src/app/npa/npa-shared.scss +++ b/src/app/npa/npa-shared.scss @@ -94,6 +94,165 @@ grid-template-columns: repeat(3, 1fr); } +// ----------------------------------------------------------------------- +// Cash Credit Recovery Management — a glossary strip explaining DP / Out of +// Order / SMA / NPA to a reader meeting the terms for the first time (e.g. a +// stakeholder demo), plus tone-coloured accents for that screen's KPI row. +// ----------------------------------------------------------------------- +.npa-glossary { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); + gap: 14px; + margin-top: 18px; +} + +.npa-glossary-card { + padding: 16px 16px 16px 18px; + border-radius: 12px; + background: #fff; + border: 1px solid #e2e8f0; + border-left: 4px solid #64748b; + transition: box-shadow 0.15s ease; + + &:hover { + box-shadow: 0 4px 14px rgb(15 23 42 / 8%); + } + + &__icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 999px; + background: #f1f5f9; + color: #475569; + font-size: 14px; + margin-bottom: 10px; + } + + &__term { + font-size: 12px; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #0f172a; + margin-bottom: 6px; + } + + &__desc { + font-size: 12.5px; + line-height: 1.55; + color: #475569; + margin: 0; + } + + &--dp { + border-left-color: #2563eb; + + .npa-glossary-card__icon { + background: #dbeafe; + color: #2563eb; + } + } + + &--ooo { + border-left-color: #d97706; + + .npa-glossary-card__icon { + background: #fef3c7; + color: #b45309; + } + } + + &--sma { + border-left-color: #ca8a04; + + .npa-glossary-card__icon { + background: #fef9c3; + color: #854d0e; + } + } + + &--npa { + border-left-color: #dc2626; + + .npa-glossary-card__icon { + background: #fee2e2; + color: #dc2626; + } + } +} + +// Compact gap variant for a KPI row that otherwise uses npa-kpis--ratio's 4-wide grid — used by +// the CC Recovery Management summary row, which packs more (smaller) tiles than the main header. +.npa-kpis--compact { + gap: 8px; +} + +// Tone-coloured KPI tile for the CC summary row — same .inv-kpi tile, with a left accent + icon +// chip so "Out of Order"/"SMA"/"NPA" read as a severity strip rather than four identical boxes. +// Sized smaller than the default .inv-kpi tile since this row packs 6 of them. +.npa-kpi--accent { + padding: 10px 12px 10px 14px; + border-left: 4px solid var(--npa-kpi-accent, #64748b); + + .inv-kpi__label { + font-size: 9px; + } + + .inv-kpi__value { + font-size: 16px; + } + + .inv-kpi__sub { + font-size: 10px; + } + + .npa-kpi__icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border-radius: 999px; + background: var(--npa-kpi-accent-soft, #f1f5f9); + color: var(--npa-kpi-accent, #475569); + font-size: 10px; + margin-bottom: 4px; + } + + &--neutral { + --npa-kpi-accent: #0f4f3c; + --npa-kpi-accent-soft: #dcfce7; + } + + &--warn { + --npa-kpi-accent: #d97706; + --npa-kpi-accent-soft: #fef3c7; + } + + &--sma { + --npa-kpi-accent: #ca8a04; + --npa-kpi-accent-soft: #fef9c3; + } + + &--npa { + --npa-kpi-accent: #dc2626; + --npa-kpi-accent-soft: #fee2e2; + } + + &--info { + --npa-kpi-accent: #2563eb; + --npa-kpi-accent-soft: #dbeafe; + } + + &--positive { + --npa-kpi-accent: #15803d; + --npa-kpi-accent-soft: #dcfce7; + } +} + // Account Detail header — title/badge on the left, the Recovery Journey trigger pinned right. .npa-detail-header { display: flex; diff --git a/src/environments/.env.ts b/src/environments/.env.ts index 5cf0d7b56..c7a2f2020 100644 --- a/src/environments/.env.ts +++ b/src/environments/.env.ts @@ -9,8 +9,8 @@ /* tslint:disable */ export default { 'mifos_x': { - 'version': '261012', - 'hash': '6b0f218b' + 'version': '261019', + 'hash': '1b70fb453' }, 'allow_switching_backend_instance': true };