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:
+34
-8
@@ -553,14 +553,23 @@ 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)) {
|
||||
/** Days elapsed since the account first crossed 90 days overdue (i.e. became NPA) — 0 for any
|
||||
* account that isn't currently NPA. The single source both interest-reversal and NPA-since-date
|
||||
* are built from, so neither can ever quietly drift out of step with the other. */
|
||||
export function daysSinceNpa(a: LoanAccount): number {
|
||||
if (!isNpaStage(currentStage(a))) {
|
||||
return 0;
|
||||
}
|
||||
const daysSinceNpa = Math.max(0, a.overdueDays - 90);
|
||||
return round(a.outstandingBalance * (a.interestRate / 100) * (daysSinceNpa / 365));
|
||||
return Math.max(0, a.overdueDays - 90);
|
||||
}
|
||||
|
||||
/** Interest income de-recognised because the account has been NPA for longer than 90 days. */
|
||||
export function interestReversed(a: LoanAccount): number {
|
||||
const days = daysSinceNpa(a);
|
||||
if (days <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return round(a.outstandingBalance * (a.interestRate / 100) * (days / 365));
|
||||
}
|
||||
|
||||
/** Date the account first crossed 90 days overdue (i.e. became NPA) — derived from overdueDays,
|
||||
@@ -569,14 +578,30 @@ 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));
|
||||
return fmt(addDays(new Date(), -daysSinceNpa(a)));
|
||||
}
|
||||
|
||||
export function totalRecovered(a: LoanAccount): number {
|
||||
return a.recoveries.reduce((s, r) => s + r.amount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cash recovered against an NPA account is appropriated to the memorandum "undebited interest"
|
||||
* bucket first — consistent with IRAC norms recognising NPA interest income only on actual cash
|
||||
* receipt, never on accrual — and only the balance beyond that reduces principal. Capped at the
|
||||
* interest actually accrued, so this can never show more interest "recovered" than ever accrued.
|
||||
*/
|
||||
export function interestRecoveredCash(a: LoanAccount): number {
|
||||
return Math.min(totalRecovered(a), interestReversed(a));
|
||||
}
|
||||
|
||||
/** Memorandum interest still un-recovered — the "undebited interest" carried outside the
|
||||
* borrower's loan account (per IRAC norms), less whatever cash has actually been appropriated
|
||||
* against it so far. */
|
||||
export function undebitedInterestBalance(a: LoanAccount): number {
|
||||
return interestReversed(a) - interestRecoveredCash(a);
|
||||
}
|
||||
|
||||
export function movementLabel(prev: NpaStage, curr: NpaStage): string {
|
||||
if (prev === curr) {
|
||||
return 'No Change';
|
||||
@@ -608,6 +633,7 @@ export const REPORT_META: ReportMeta[] = [
|
||||
{ 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: 'undebited-interest', group: 'analysis', label: 'Undebited Interest Recovery Report' },
|
||||
{ id: 'probable', group: 'classification', label: 'Probable NPA' },
|
||||
{ id: 'sma', group: 'classification', label: 'SMA Report' }
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user