feat(npa): add Recovery Management System reports + Recovery Journey popup
Adds loan-type-aware recovery tracking (EMI/Overdrawn/Bullet) with a Recovery Management Dashboard, Day-wise Overdue/Overdrawn reports, an Agriculture/Gold market-value report, and a security-value exception check — all derived from new repaymentType/emiAmount/drawingPower/maturityDate/collateralMarketValue fields on LoanAccount, wired into Export to Excel and the NPA mega-menu. Also replaces the always-visible Recovery/Collection Journey card on the Account Detail Report with a "Recovery Journey" popup: a date-led, icon-coded timeline of every SMS/call/visit/notice and payment run against an account.
This commit is contained in:
+349
-13
@@ -79,6 +79,26 @@ export interface RecoveryTxn {
|
||||
mode: string;
|
||||
}
|
||||
|
||||
/** One entry in an account's collection/recovery journey — the real-world touchpoints a CBS
|
||||
* Recovery Management System logs against an overdue loan, in the order they actually happened. */
|
||||
export type RecoveryEventType =
|
||||
| 'SMS Reminder'
|
||||
| 'IVR / Auto Call'
|
||||
| 'Tele-calling'
|
||||
| 'Overdue Call'
|
||||
| 'Field Visit'
|
||||
| 'Formal Overdue Letter'
|
||||
| 'Recovery Agent Assigned'
|
||||
| 'Demand Notice'
|
||||
| 'Payment Received';
|
||||
|
||||
export interface RecoveryEvent {
|
||||
date: string;
|
||||
type: RecoveryEventType;
|
||||
channel: string;
|
||||
remarks: string;
|
||||
}
|
||||
|
||||
export interface LoanAccount {
|
||||
accountNo: string;
|
||||
customerName: string;
|
||||
@@ -88,6 +108,7 @@ export interface LoanAccount {
|
||||
productCode: string;
|
||||
productName: string;
|
||||
secured: boolean;
|
||||
repaymentType: RepaymentType;
|
||||
sanctionedAmount: number;
|
||||
disbursementDate: string;
|
||||
interestRate: number;
|
||||
@@ -96,11 +117,27 @@ export interface LoanAccount {
|
||||
overdueDays: number;
|
||||
overdueDaysQuarterAgo: number;
|
||||
collateralValue: number;
|
||||
/** Current estimated market value of the pledged security — can run above or below
|
||||
* `collateralValue` (the value recorded at sanction/last valuation), which is exactly what the
|
||||
* Exception Check / Agriculture-Gold Market Value reports exist to catch. 0 for unsecured loans. */
|
||||
collateralMarketValue: number;
|
||||
/** Fixed EMI for an EMI-repayment loan (standard reducing-balance formula on the sanctioned
|
||||
* amount and tenure) — 0 for OVERDRAWN/BULLET loans, which aren't due on a fixed instalment. */
|
||||
emiAmount: number;
|
||||
/** Drawing limit for an OVERDRAWN (CC/OD/KCC) account — kept below the sanctioned amount by a
|
||||
* margin, same as a real cash-credit facility. 0 for EMI/BULLET loans. */
|
||||
drawingPower: number;
|
||||
/** Bullet-repayment due date (principal falls due in full on this date) — '—' for EMI/OVERDRAWN
|
||||
* loans, which don't have a single bullet maturity. */
|
||||
maturityDate: string;
|
||||
lastPaymentDate: string;
|
||||
isLossAsset: boolean;
|
||||
isRestructured: boolean;
|
||||
provisionCoverageRatio: number;
|
||||
recoveries: RecoveryTxn[];
|
||||
/** Chronological log of collection touchpoints (SMS/call/visit/notice) plus actual recoveries —
|
||||
* empty for an account that has never gone overdue. */
|
||||
collectionTimeline: RecoveryEvent[];
|
||||
/** '—' for Standard/SMA accounts — only NPA accounts are actually assigned a reason. */
|
||||
npaReason: string;
|
||||
/** '—' for Standard accounts — SMA and NPA accounts get a recovery officer assigned for follow-up. */
|
||||
@@ -174,6 +211,17 @@ export const BRANCHES: BranchMaster[] = [
|
||||
{ code: 'BR08', name: 'Kolhapur Branch' }
|
||||
];
|
||||
|
||||
/** How the loan is contractually repaid — drives the Recovery Management System's per-type
|
||||
* overdue check (EMI shortfall / drawing-power breach / bullet-maturity lapse) and the
|
||||
* Expected-vs-Actual Recovery figures, since each type is "due" on a different basis. */
|
||||
export type RepaymentType = 'EMI' | 'OVERDRAWN' | 'BULLET';
|
||||
|
||||
export const REPAYMENT_TYPE_LABELS: Record<RepaymentType, string> = {
|
||||
EMI: 'Term Loan (EMI)',
|
||||
OVERDRAWN: 'Overdrawn (CC / OD)',
|
||||
BULLET: 'Bullet Repayment'
|
||||
};
|
||||
|
||||
interface ProductMaster {
|
||||
code: string;
|
||||
name: string;
|
||||
@@ -182,6 +230,11 @@ interface ProductMaster {
|
||||
maxAmt: number;
|
||||
minRate: number;
|
||||
maxRate: number;
|
||||
repaymentType: RepaymentType;
|
||||
/** Sanctioned tenure in months — drives the EMI formula for EMI-type products and the
|
||||
* bullet maturity date for BULLET-type products. Renewal cycle (not a repayment schedule)
|
||||
* for OVERDRAWN/revolving products. */
|
||||
tenureMonths: number;
|
||||
}
|
||||
export const LOAN_PRODUCTS: ProductMaster[] = [
|
||||
{
|
||||
@@ -191,15 +244,87 @@ export const LOAN_PRODUCTS: ProductMaster[] = [
|
||||
minAmt: 500000,
|
||||
maxAmt: 5000000,
|
||||
minRate: 11,
|
||||
maxRate: 13.5
|
||||
maxRate: 13.5,
|
||||
repaymentType: 'OVERDRAWN',
|
||||
tenureMonths: 12
|
||||
},
|
||||
{
|
||||
code: '02',
|
||||
name: 'Term Loan - MSME',
|
||||
secured: true,
|
||||
minAmt: 300000,
|
||||
maxAmt: 3000000,
|
||||
minRate: 11,
|
||||
maxRate: 14,
|
||||
repaymentType: 'EMI',
|
||||
tenureMonths: 60
|
||||
},
|
||||
{
|
||||
code: '03',
|
||||
name: 'Housing Loan',
|
||||
secured: true,
|
||||
minAmt: 1000000,
|
||||
maxAmt: 4000000,
|
||||
minRate: 8.5,
|
||||
maxRate: 10.5,
|
||||
repaymentType: 'EMI',
|
||||
tenureMonths: 180
|
||||
},
|
||||
{
|
||||
code: '04',
|
||||
name: 'Vehicle Loan',
|
||||
secured: true,
|
||||
minAmt: 300000,
|
||||
maxAmt: 1500000,
|
||||
minRate: 9,
|
||||
maxRate: 11.5,
|
||||
repaymentType: 'EMI',
|
||||
tenureMonths: 84
|
||||
},
|
||||
{
|
||||
code: '05',
|
||||
name: 'Gold Loan',
|
||||
secured: true,
|
||||
minAmt: 50000,
|
||||
maxAmt: 1000000,
|
||||
minRate: 9,
|
||||
maxRate: 12,
|
||||
repaymentType: 'BULLET',
|
||||
tenureMonths: 12
|
||||
},
|
||||
{
|
||||
code: '06',
|
||||
name: 'Kisan Credit Card',
|
||||
secured: true,
|
||||
minAmt: 100000,
|
||||
maxAmt: 500000,
|
||||
minRate: 7,
|
||||
maxRate: 9,
|
||||
repaymentType: 'OVERDRAWN',
|
||||
tenureMonths: 12
|
||||
},
|
||||
{
|
||||
code: '07',
|
||||
name: 'Personal Loan',
|
||||
secured: false,
|
||||
minAmt: 50000,
|
||||
maxAmt: 500000,
|
||||
minRate: 13,
|
||||
maxRate: 16,
|
||||
repaymentType: 'EMI',
|
||||
tenureMonths: 36
|
||||
},
|
||||
{
|
||||
code: '08',
|
||||
name: 'Education Loan',
|
||||
secured: false,
|
||||
minAmt: 200000,
|
||||
maxAmt: 1500000,
|
||||
minRate: 10,
|
||||
maxRate: 12,
|
||||
repaymentType: 'EMI',
|
||||
tenureMonths: 84
|
||||
},
|
||||
{ code: '02', name: 'Term Loan - MSME', secured: true, minAmt: 300000, maxAmt: 3000000, minRate: 11, maxRate: 14 },
|
||||
{ code: '03', name: 'Housing Loan', secured: true, minAmt: 1000000, maxAmt: 4000000, minRate: 8.5, maxRate: 10.5 },
|
||||
{ code: '04', name: 'Vehicle Loan', secured: true, minAmt: 300000, maxAmt: 1500000, minRate: 9, maxRate: 11.5 },
|
||||
{ code: '05', name: 'Gold Loan', secured: true, minAmt: 50000, maxAmt: 1000000, minRate: 9, maxRate: 12 },
|
||||
{ code: '06', name: 'Kisan Credit Card', secured: true, minAmt: 100000, maxAmt: 500000, minRate: 7, maxRate: 9 },
|
||||
{ code: '07', name: 'Personal Loan', secured: false, minAmt: 50000, maxAmt: 500000, minRate: 13, maxRate: 16 },
|
||||
{ code: '08', name: 'Education Loan', secured: false, minAmt: 200000, maxAmt: 1500000, minRate: 10, maxRate: 12 },
|
||||
{
|
||||
code: '09',
|
||||
name: 'SHG / Micro Finance Loan',
|
||||
@@ -207,7 +332,9 @@ export const LOAN_PRODUCTS: ProductMaster[] = [
|
||||
minAmt: 20000,
|
||||
maxAmt: 200000,
|
||||
minRate: 12,
|
||||
maxRate: 14
|
||||
maxRate: 14,
|
||||
repaymentType: 'EMI',
|
||||
tenureMonths: 24
|
||||
},
|
||||
{
|
||||
code: '10',
|
||||
@@ -216,7 +343,9 @@ export const LOAN_PRODUCTS: ProductMaster[] = [
|
||||
minAmt: 500000,
|
||||
maxAmt: 2500000,
|
||||
minRate: 11,
|
||||
maxRate: 13
|
||||
maxRate: 13,
|
||||
repaymentType: 'OVERDRAWN',
|
||||
tenureMonths: 12
|
||||
}
|
||||
];
|
||||
|
||||
@@ -345,6 +474,113 @@ function pickBucket(): StageBucket {
|
||||
return BUCKETS[0];
|
||||
}
|
||||
|
||||
/** Standard reducing-balance EMI formula on the original sanctioned amount and tenure — the same
|
||||
* fixed instalment the loan was actually sanctioned with, not something recomputed off the
|
||||
* current (partly-repaid) outstanding balance. */
|
||||
function calcEmi(principal: number, annualRatePct: number, tenureMonths: number): number {
|
||||
const r = annualRatePct / 12 / 100;
|
||||
if (r === 0) {
|
||||
return round(principal / tenureMonths);
|
||||
}
|
||||
const factor = Math.pow(1 + r, tenureMonths);
|
||||
return round((principal * r * factor) / (factor - 1));
|
||||
}
|
||||
|
||||
interface TimelineCheckpoint {
|
||||
atDay: number;
|
||||
type: RecoveryEventType;
|
||||
channel: string;
|
||||
remarks: string;
|
||||
}
|
||||
|
||||
/** The real-world escalation ladder a CBS Recovery Management System runs an overdue account
|
||||
* through — each checkpoint only fires once the account has actually been overdue that long, so
|
||||
* a 10-day-overdue account shows just the SMS/call steps while a 200-day NPA shows the full
|
||||
* ladder up to the demand notice. */
|
||||
const TIMELINE_CHECKPOINTS: TimelineCheckpoint[] = [
|
||||
{
|
||||
atDay: 3,
|
||||
type: 'SMS Reminder',
|
||||
channel: 'SMS Gateway',
|
||||
remarks: 'Auto SMS reminder sent for the overdue instalment/interest.'
|
||||
},
|
||||
{
|
||||
atDay: 7,
|
||||
type: 'IVR / Auto Call',
|
||||
channel: 'IVR System',
|
||||
remarks: 'Automated voice-call reminder placed to the registered mobile number.'
|
||||
},
|
||||
{
|
||||
atDay: 15,
|
||||
type: 'Tele-calling',
|
||||
channel: 'Branch Tele-calling Desk',
|
||||
remarks: 'Branch staff called the customer to follow up on the continuing overdue.'
|
||||
},
|
||||
{
|
||||
atDay: 25,
|
||||
type: 'Overdue Call',
|
||||
channel: 'Recovery Officer',
|
||||
remarks: 'Recovery officer placed a personal follow-up call.'
|
||||
},
|
||||
{
|
||||
atDay: 30,
|
||||
type: 'Field Visit',
|
||||
channel: 'Branch Staff',
|
||||
remarks: 'Field visit conducted at the registered address — account under SMA monitoring.'
|
||||
},
|
||||
{
|
||||
atDay: 45,
|
||||
type: 'SMS Reminder',
|
||||
channel: 'SMS Gateway',
|
||||
remarks: 'Second reminder SMS sent — account still overdue.'
|
||||
},
|
||||
{
|
||||
atDay: 60,
|
||||
type: 'Formal Overdue Letter',
|
||||
channel: 'Registered Post',
|
||||
remarks: 'Formal overdue letter dispatched — account crossed into SMA-2 monitoring.'
|
||||
},
|
||||
{
|
||||
atDay: 75,
|
||||
type: 'Recovery Agent Assigned',
|
||||
channel: 'External Recovery Agency',
|
||||
remarks: 'Case assigned to a recovery agent for intensified follow-up.'
|
||||
},
|
||||
{
|
||||
atDay: 91,
|
||||
type: 'Demand Notice',
|
||||
channel: 'Recovery Officer',
|
||||
remarks: 'Account classified NPA — formal demand notice process initiated.'
|
||||
}
|
||||
];
|
||||
|
||||
/** Builds the account's full collection journey — every escalation checkpoint the account has
|
||||
* actually reached, interleaved date-wise with its real cash recoveries — so the Account Detail
|
||||
* screen can show one combined, chronological timeline instead of two disconnected lists. */
|
||||
function buildCollectionTimeline(overdueDays: number, recoveries: RecoveryTxn[], today: Date): RecoveryEvent[] {
|
||||
const events: RecoveryEvent[] = [];
|
||||
for (const cp of TIMELINE_CHECKPOINTS) {
|
||||
if (cp.atDay <= overdueDays) {
|
||||
events.push({
|
||||
date: fmt(addDays(today, -(overdueDays - cp.atDay))),
|
||||
type: cp.type,
|
||||
channel: cp.channel,
|
||||
remarks: cp.remarks
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const r of recoveries) {
|
||||
events.push({
|
||||
date: r.date,
|
||||
type: 'Payment Received',
|
||||
channel: r.mode,
|
||||
remarks: `Recovery of ₹${r.amount.toLocaleString('en-IN')} received via ${r.mode}.`
|
||||
});
|
||||
}
|
||||
events.sort((a, b) => parseDMY(a.date).getTime() - parseDMY(b.date).getTime());
|
||||
return events;
|
||||
}
|
||||
|
||||
/** Recovery-workflow progression — only NPA accounts get any legal action, and the more
|
||||
* advanced/stressed the classification, the more likely the bank has already escalated. */
|
||||
function legalActionFor(stage: NpaStage): LegalActionStatus {
|
||||
@@ -398,6 +634,26 @@ function generateAccounts(count: number): LoanAccount[] {
|
||||
const overdueAmount = round(outstandingBalance * overdueFrac);
|
||||
const collateralValue = product.secured ? round(sanctionedAmount * between(0.85, 1.25)) : 0;
|
||||
|
||||
// Current market value of the pledged security vs its value at sanction: gold trends upward
|
||||
// over time (real-world gold-price appreciation); crop/agri collateral (KCC) is volatile in
|
||||
// both directions, matching how "Crop Failure" shows up as an NPA reason; other secured
|
||||
// collateral (property/vehicle) moves in a narrower band. Unsecured loans have none.
|
||||
let marketValueFactor = 0;
|
||||
if (product.code === '05') {
|
||||
marketValueFactor = between(1.05, 1.35);
|
||||
} else if (product.code === '06') {
|
||||
marketValueFactor = between(0.55, 1.2);
|
||||
} else if (product.secured) {
|
||||
marketValueFactor = between(0.85, 1.15);
|
||||
}
|
||||
const collateralMarketValue = product.secured ? round(collateralValue * marketValueFactor) : 0;
|
||||
|
||||
const emiAmount =
|
||||
product.repaymentType === 'EMI' ? calcEmi(sanctionedAmount, interestRate, product.tenureMonths) : 0;
|
||||
const drawingPower = product.repaymentType === 'OVERDRAWN' ? round(sanctionedAmount * between(0.75, 0.9)) : 0;
|
||||
const maturityDate =
|
||||
product.repaymentType === 'BULLET' ? fmt(addDays(parseDMY(disbursementDate), product.tenureMonths * 30)) : '—';
|
||||
|
||||
const npaReason = isNpaStage(stressed) ? pick(NPA_REASONS) : '—';
|
||||
const recoveryOfficer = isNpaStage(stressed) || isSmaStage(stressed) ? pick(RECOVERY_OFFICERS) : '—';
|
||||
const legalActionStatus = legalActionFor(stressed);
|
||||
@@ -439,6 +695,8 @@ function generateAccounts(count: number): LoanAccount[] {
|
||||
? fmt(latestRecoveryDate)
|
||||
: formulaLastPaymentDate;
|
||||
|
||||
const collectionTimeline = buildCollectionTimeline(overdueDays, recoveries, today);
|
||||
|
||||
const seq = String(1000 + i).padStart(6, '0');
|
||||
accounts.push({
|
||||
accountNo: `1${branch.code.slice(2)}${product.code}${seq}`,
|
||||
@@ -449,6 +707,7 @@ function generateAccounts(count: number): LoanAccount[] {
|
||||
productCode: product.code,
|
||||
productName: product.name,
|
||||
secured: product.secured,
|
||||
repaymentType: product.repaymentType,
|
||||
sanctionedAmount,
|
||||
disbursementDate,
|
||||
interestRate,
|
||||
@@ -457,11 +716,16 @@ function generateAccounts(count: number): LoanAccount[] {
|
||||
overdueDays,
|
||||
overdueDaysQuarterAgo,
|
||||
collateralValue,
|
||||
collateralMarketValue,
|
||||
emiAmount,
|
||||
drawingPower,
|
||||
maturityDate,
|
||||
lastPaymentDate,
|
||||
isLossAsset,
|
||||
isRestructured,
|
||||
provisionCoverageRatio: +between(0.75, 1.1).toFixed(2),
|
||||
recoveries,
|
||||
collectionTimeline,
|
||||
npaReason,
|
||||
recoveryOfficer,
|
||||
legalActionStatus
|
||||
@@ -602,6 +866,72 @@ export function undebitedInterestBalance(a: LoanAccount): number {
|
||||
return interestReversed(a) - interestRecoveredCash(a);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Recovery Management System — per-repayment-type overdue check, Expected vs
|
||||
// Actual Recovery, and the collateral-adequacy exception check. `overdueDays`
|
||||
// stays the single universal "how overdue is this account" signal (so IRAC
|
||||
// classification never needs a second, type-specific day-counter) — only its
|
||||
// PLAIN-ENGLISH INTERPRETATION differs by repayment type: an EMI account
|
||||
// missed its instalment, an OVERDRAWN account has been over its drawing power,
|
||||
// a BULLET account is past maturity unpaid.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** True once a bullet loan's maturity date has passed — the point at which the bank checks
|
||||
* whether the account "self-closed" (fully repaid) or is now overdue. */
|
||||
export function isBulletMatured(a: LoanAccount): boolean {
|
||||
return a.repaymentType === 'BULLET' && a.maturityDate !== '—' && parseDMY(a.maturityDate).getTime() <= Date.now();
|
||||
}
|
||||
|
||||
/** A bullet loan past maturity that's still carrying an overdue balance. */
|
||||
export function isBulletOverdue(a: LoanAccount): boolean {
|
||||
return isBulletMatured(a) && a.overdueDays > 0;
|
||||
}
|
||||
|
||||
/** A cash-credit/overdraft-style account currently drawn beyond its sanctioned drawing power. */
|
||||
export function isOverdrawnBreach(a: LoanAccount): boolean {
|
||||
return a.repaymentType === 'OVERDRAWN' && a.outstandingBalance > a.drawingPower;
|
||||
}
|
||||
|
||||
/** What ought to be recovered this month, by repayment type: the fixed EMI for a term loan, one
|
||||
* month's interest-servicing for an overdrawn CC/OD/KCC account, or the full outstanding once a
|
||||
* bullet loan has matured (0 before maturity — nothing is contractually due yet). */
|
||||
export function expectedRecoveryThisMonth(a: LoanAccount): number {
|
||||
switch (a.repaymentType) {
|
||||
case 'EMI':
|
||||
return a.emiAmount;
|
||||
case 'OVERDRAWN':
|
||||
return round((a.outstandingBalance * a.interestRate) / 100 / 12);
|
||||
case 'BULLET':
|
||||
return isBulletMatured(a) ? a.outstandingBalance : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** What was actually recovered this month — `overdueDays === 0` means the account is fully
|
||||
* current, i.e. this cycle's EMI/interest/bullet-repayment landed in full; any overdue days at
|
||||
* all means it didn't, exactly the binary "full amount or mark Overdue" check a real recovery
|
||||
* desk runs (this demo book doesn't model partial-instalment part-payments). */
|
||||
export function actualRecoveryThisMonth(a: LoanAccount): number {
|
||||
if (a.repaymentType === 'BULLET') {
|
||||
return isBulletMatured(a) && a.overdueDays === 0 ? a.outstandingBalance : 0;
|
||||
}
|
||||
return a.overdueDays === 0 ? expectedRecoveryThisMonth(a) : 0;
|
||||
}
|
||||
|
||||
/** Current collateral cover — market value of security as a % of outstanding balance. 0 for an
|
||||
* unsecured loan (no collateral to speak of). */
|
||||
export function securityCoverageRatio(a: LoanAccount): number {
|
||||
if (!a.secured || a.outstandingBalance <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return (a.collateralMarketValue / a.outstandingBalance) * 100;
|
||||
}
|
||||
|
||||
/** True when a secured loan's CURRENT security market value has fallen below what's actually
|
||||
* outstanding — the "security value is above loan" exception check every recovery review runs. */
|
||||
export function isUndersecured(a: LoanAccount): boolean {
|
||||
return a.secured && a.collateralMarketValue < a.outstandingBalance;
|
||||
}
|
||||
|
||||
export function movementLabel(prev: NpaStage, curr: NpaStage): string {
|
||||
if (prev === curr) {
|
||||
return 'No Change';
|
||||
@@ -619,7 +949,7 @@ export function movementLabel(prev: NpaStage, curr: NpaStage): string {
|
||||
// in-page group/report tab rows, so a menu item and its screen can never
|
||||
// point at different labels.
|
||||
// -------------------------------------------------------------------------
|
||||
export type ReportGroupId = 'reports' | 'analysis' | 'classification';
|
||||
export type ReportGroupId = 'reports' | 'analysis' | 'classification' | 'recovery-mgmt';
|
||||
export interface ReportMeta {
|
||||
id: string;
|
||||
group: ReportGroupId;
|
||||
@@ -635,11 +965,17 @@ export const REPORT_META: ReportMeta[] = [
|
||||
{ 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' }
|
||||
{ id: 'sma', group: 'classification', label: 'SMA Report' },
|
||||
{ id: 'recovery-dashboard', group: 'recovery-mgmt', label: 'Recovery Management Dashboard' },
|
||||
{ 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' },
|
||||
{ id: 'exception-check', group: 'recovery-mgmt', label: 'Exception Check — Security Value vs Loan' }
|
||||
];
|
||||
export const GROUP_LABELS: Record<ReportGroupId, string> = {
|
||||
reports: 'NPA Reports',
|
||||
analysis: 'NPA Analysis',
|
||||
classification: 'NPA Classification'
|
||||
classification: 'NPA Classification',
|
||||
'recovery-mgmt': 'Recovery Management'
|
||||
};
|
||||
export const DEFAULT_REPORT_ID = 'summary';
|
||||
|
||||
Reference in New Issue
Block a user