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
+361 -17
View File
@@ -53,16 +53,45 @@ export const PROVISION_STATUS_LABELS: Record<InvestmentProvisionStatus, string>
LOSS: 'Loss (NPI)'
};
/** Board-approved portfolio-policy target — % of the total investment corpus each instrument
* type should hold. Compared against actual holdings on the Dashboard so a treasury desk can
* see at a glance where the book has drifted from policy. */
/**
* Portfolio-policy target — % of the total investment corpus each instrument type should hold.
* Compared against actual holdings on the Dashboard so a treasury desk can see at a glance where
* the book has drifted from policy. Four of these six trace back to real RBI rules for Urban
* Co-operative Banks (RBI Master Direction/Circular — Investment Portfolio of Primary (Urban)
* Co-operative Banks):
* • SLR: UCBs must hold 18% of NDTL in approved securities — predominantly Central/State Govt
* securities (and SLR-eligible State Development Loan bonds). A well-run treasury holds a
* safety buffer above the bare floor, so Govt Security dominates the book far beyond the 18%
* minimum — it isn't itself an upper target, just the reason this bucket is the largest.
* • Non-SLR ceiling: a UCB's Non-SLR investments must not exceed 10% of its total deposits as
* on 31 March of the previous year — a hard regulatory cap, not a board choice.
* • Mutual Funds: RBI permits UCBs to invest only in debt-oriented schemes of SEBI-registered
* mutual funds, in strictly limited amounts — equity/balanced schemes are not permitted.
* • Shares: RBI does not permit UCBs to invest in equity shares of companies as a general
* investment class; any "Share" holding is essentially the (small, largely static) shares a
* UCB holds in affiliated co-operative institutions, not a discretionary trading book.
* Fixed Deposit (treasury placements with other banks) and Bond (a mix of SLR-eligible SDL and
* Non-SLR PSU/FI bonds) have no RBI-mandated percentage of their own — their split here is a
* board-approved policy choice within the room RBI's SLR/Non-SLR framework leaves.
*/
export const TARGET_ALLOCATION_PCT: Record<InstrumentType, number> = {
GOVT_SECURITY: 55,
BOND: 18,
FD: 15,
GOVT_SECURITY: 35,
NON_SLR: 10,
BOND: 20,
MUTUAL_FUND: 12,
SHARE: 8
NON_SLR: 7,
MUTUAL_FUND: 3,
SHARE: 2
};
/** True for the instrument types whose target traces to an actual RBI rule (SLR floor / Non-SLR
* ceiling) rather than a board-only policy choice — see TARGET_ALLOCATION_PCT's doc comment. */
export const ALLOCATION_TARGET_IS_RBI_LINKED: Record<InstrumentType, boolean> = {
GOVT_SECURITY: true,
BOND: false,
FD: false,
NON_SLR: true,
MUTUAL_FUND: true,
SHARE: true
};
export interface InvestmentHolding {
@@ -90,6 +119,10 @@ export interface InvestmentHolding {
interestPosted?: number;
/** Cumulative interest actually received in cash to date. */
interestReceived?: number;
/** DD-MM-YYYY through which interest has already been calculated & posted. accruedInterestBetween()
* never re-counts days up to this date, so re-running a Calculation & Post for an overlapping
* or repeated period always yields 0 instead of double-posting the same interest. */
interestPostedTillDate?: string;
}
export interface InvestmentTxn {
@@ -100,6 +133,10 @@ export interface InvestmentTxn {
txnDate: string;
amount: number;
particulars: string;
/** GL account this entry was posted/received against — the Interest Income GL for a Post,
* the Cash/Bank GL for a Receive. Absent on older/seeded entries that predate GL selection. */
glCode?: string;
glName?: string;
}
// -------------------- Date / interest helpers --------------------
@@ -175,15 +212,6 @@ export function canAccrueInterest(h: InvestmentHolding): boolean {
return !isNpi(h);
}
/** Interest still to be calculated & posted this cycle. Always 0 for an NPI holding — posting
* an accrual on a non-performing investment would overstate income. */
export function interestToPost(h: InvestmentHolding, asOnStr: string): number {
if (!canAccrueInterest(h)) {
return 0;
}
return Math.max(0, accruedInterest(h, asOnStr) - (h.interestPosted ?? 0));
}
/** Outstanding "Interest Receivable" GL balance — interest already posted (booked as income)
* but not yet actually received in cash. Always 0 for an NPI holding, since nothing is ever
* posted as a receivable on one. */
@@ -203,6 +231,23 @@ export function suspenseInterest(h: InvestmentHolding, asOnStr: string): number
return Math.max(0, accruedInterest(h, asOnStr) - (h.interestReceived ?? 0));
}
/** Interest accrued strictly within [fromStr, toStr], clipped to the holding's own
* purchase→maturity window — the basis for a period-based Interest Calculation & Post run
* (as opposed to accruedInterest(), which is always measured from purchase date). Returns 0
* for a Non-Performing holding, same accrual-basis rule as accruedInterest(). */
export function accruedInterestBetween(h: InvestmentHolding, fromStr: string, toStr: string): number {
if (!canAccrueInterest(h)) {
return 0;
}
const purchase = parseDDMMYYYY(h.purchaseDate).getTime();
const maturity = parseDDMMYYYY(h.maturityDate).getTime();
const postedTill = h.interestPostedTillDate ? parseDDMMYYYY(h.interestPostedTillDate).getTime() : purchase;
const from = Math.max(parseDDMMYYYY(fromStr).getTime(), purchase, postedTill);
const to = Math.min(parseDDMMYYYY(toStr).getTime(), maturity);
const days = Math.max(0, (to - from) / (24 * 3600 * 1000));
return Math.round(h.faceValue * (h.interestRate / 100) * (days / 365.25));
}
export function formatDDMMYYYY(d: Date): string {
return `${String(d.getDate()).padStart(2, '0')}-${String(d.getMonth() + 1).padStart(2, '0')}-${d.getFullYear()}`;
}
@@ -388,6 +433,68 @@ export const FD_HOLDINGS: InvestmentHolding[] = [
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
},
{
certNo: '15628',
instrumentType: 'FD',
scriptCode: 'FD-CUM',
scriptName: 'Cumulative Fixed Deposit',
party: 'Zilla Sahakari Bank Ltd',
subType: 'NON SLR',
faceValue: 1800000,
purchaseRate: 100,
units: 1,
purchaseDate: '20-08-2025',
maturityDate: '20-08-2026',
interestRate: 6.8,
interestFreq: 'CUMULATIVE',
glCode: '320020',
glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
},
{
certNo: '15629',
instrumentType: 'FD',
scriptCode: 'FD-MIP',
scriptName: 'Monthly Interest Payout FD',
party: 'Vidarbha Cooperative Bank',
subType: 'NON SLR',
faceValue: 2200000,
purchaseRate: 100,
units: 1,
purchaseDate: '25-09-2025',
maturityDate: '25-09-2026',
interestRate: 7.15,
interestFreq: 'MONTHLY',
glCode: '320020',
glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE',
interestPosted: 143220,
interestReceived: 130800
},
{
certNo: '15630',
instrumentType: 'FD',
scriptCode: 'FD-CUM',
scriptName: 'Cumulative Fixed Deposit',
party: 'Konkan Cooperative Bank Ltd',
subType: 'NON SLR',
faceValue: 900000,
purchaseRate: 100,
units: 1,
purchaseDate: '28-10-2024',
maturityDate: '28-10-2026',
interestRate: 6.75,
interestFreq: 'YEARLY',
glCode: '320020',
glName: 'FIXED DEPO. CO-OPER. BANK MAIN',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
}
];
@@ -515,6 +622,46 @@ export const GOVT_SECURITY_HOLDINGS: InvestmentHolding[] = [
crToAc: '310070',
brokerCode: 'BRK-002',
status: 'ACTIVE'
},
{
certNo: 'GSEC-2007',
instrumentType: 'GOVT_SECURITY',
scriptCode: 'TBILL-91D',
scriptName: '91 Days Treasury Bill',
party: 'RBI - SGL',
subType: 'SLR',
faceValue: 2000000,
purchaseRate: 98.2,
units: 20000,
purchaseDate: '15-05-2026',
maturityDate: '13-08-2026',
interestRate: 6.2,
interestFreq: 'CUMULATIVE',
glCode: '310020',
glName: 'INVESTMENT IN GOVT. SECURITIES',
crToAc: '310070',
brokerCode: 'BRK-003',
status: 'ACTIVE'
},
{
certNo: 'GSEC-2008',
instrumentType: 'GOVT_SECURITY',
scriptCode: 'GJ-SDL-2026',
scriptName: '7.85% Gujarat SDL 2026',
party: 'RBI - SGL',
subType: 'SLR',
faceValue: 1800000,
purchaseRate: 100.2,
units: 18000,
purchaseDate: '18-09-2025',
maturityDate: '18-09-2026',
interestRate: 7.85,
interestFreq: 'HALF_YEARLY',
glCode: '310020',
glName: 'INVESTMENT IN GOVT. SECURITIES',
crToAc: '310070',
brokerCode: 'BRK-001',
status: 'ACTIVE'
}
];
@@ -620,6 +767,46 @@ export const NON_SLR_HOLDINGS: InvestmentHolding[] = [
status: 'ACTIVE',
interestPosted: 89400,
interestReceived: 60000
},
{
certNo: 'NSLR-3006',
instrumentType: 'NON_SLR',
scriptCode: 'IRFC-7.55-2026',
scriptName: 'IRFC Bond 7.55% 2026',
party: 'Indian Railway Finance Corp',
subType: 'NON SLR',
faceValue: 1600000,
purchaseRate: 100,
units: 16000,
purchaseDate: '25-08-2025',
maturityDate: '25-08-2026',
interestRate: 7.55,
interestFreq: 'YEARLY',
glCode: '310030',
glName: 'INVESTMENT IN NON SLR SECURITIES',
crToAc: '310070',
brokerCode: 'BRK-004',
status: 'ACTIVE'
},
{
certNo: 'NSLR-3007',
instrumentType: 'NON_SLR',
scriptCode: 'NHB-7.3-2026',
scriptName: 'NHB Bond 7.30% 2026',
party: 'National Housing Bank',
subType: 'NON SLR',
faceValue: 1000000,
purchaseRate: 100,
units: 10000,
purchaseDate: '10-10-2025',
maturityDate: '10-10-2026',
interestRate: 7.3,
interestFreq: 'YEARLY',
glCode: '310030',
glName: 'INVESTMENT IN NON SLR SECURITIES',
crToAc: '310070',
brokerCode: 'BRK-003',
status: 'ACTIVE'
}
];
@@ -726,6 +913,46 @@ export const BOND_HOLDINGS: InvestmentHolding[] = [
crToAc: '310070',
brokerCode: 'BRK-005',
status: 'ACTIVE'
},
{
certNo: 'BOND-4006',
instrumentType: 'BOND',
scriptCode: 'CHOLA-8.35-2026',
scriptName: 'Cholamandalam Finance NCD 8.35% 2026',
party: 'Cholamandalam Investment & Finance',
subType: 'CORPORATE BOND',
faceValue: 1400000,
purchaseRate: 100,
units: 14000,
purchaseDate: '15-09-2024',
maturityDate: '15-09-2026',
interestRate: 8.35,
interestFreq: 'YEARLY',
glCode: '310040',
glName: 'INVESTMENT IN CORPORATE BONDS',
crToAc: '310070',
brokerCode: 'BRK-006',
status: 'ACTIVE'
},
{
certNo: 'BOND-4007',
instrumentType: 'BOND',
scriptCode: 'PEL-8.4-2026',
scriptName: 'Piramal Enterprises NCD 8.40% 2026',
party: 'Piramal Enterprises Ltd',
subType: 'CORPORATE BOND',
faceValue: 900000,
purchaseRate: 100,
units: 9000,
purchaseDate: '28-10-2025',
maturityDate: '28-10-2026',
interestRate: 8.4,
interestFreq: 'YEARLY',
glCode: '310040',
glName: 'INVESTMENT IN CORPORATE BONDS',
crToAc: '310070',
brokerCode: 'BRK-005',
status: 'ACTIVE'
}
];
@@ -829,6 +1056,46 @@ export const MUTUAL_FUND_HOLDINGS: InvestmentHolding[] = [
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
},
{
certNo: 'MF-5006',
instrumentType: 'MUTUAL_FUND',
scriptCode: 'SBI-BPSU',
scriptName: 'SBI Banking & PSU Fund',
party: 'SBI Mutual Fund',
subType: 'DEBT - BANKING & PSU',
faceValue: 700000,
purchaseRate: 18.6,
units: 37634.4,
purchaseDate: '10-09-2025',
maturityDate: '10-09-2026',
interestRate: 7.1,
interestFreq: 'CUMULATIVE',
glCode: '310050',
glName: 'INVESTMENT IN MUTUAL FUNDS',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
},
{
certNo: 'MF-5007',
instrumentType: 'MUTUAL_FUND',
scriptCode: 'ICICI-CB',
scriptName: 'ICICI Pru Corporate Bond Fund',
party: 'ICICI Prudential Mutual Fund',
subType: 'DEBT - CORPORATE BOND',
faceValue: 1100000,
purchaseRate: 24.8,
units: 44354.84,
purchaseDate: '14-07-2026',
maturityDate: '14-07-2027',
interestRate: 7.05,
interestFreq: 'CUMULATIVE',
glCode: '310050',
glName: 'INVESTMENT IN MUTUAL FUNDS',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
}
];
@@ -932,6 +1199,46 @@ export const SHARE_HOLDINGS: InvestmentHolding[] = [
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
},
{
certNo: 'SH-6006',
instrumentType: 'SHARE',
scriptCode: 'ICICIBANK',
scriptName: 'ICICI Bank Ltd',
party: 'NSE/BSE',
subType: 'EQUITY',
faceValue: 420000,
purchaseRate: 1050,
units: 400,
purchaseDate: '05-03-2026',
maturityDate: '05-03-2036',
interestRate: 2.0,
interestFreq: 'YEARLY',
glCode: '310060',
glName: 'INVESTMENT IN SHARES',
crToAc: '310070',
brokerCode: 'BRK-007',
status: 'ACTIVE'
},
{
certNo: 'SH-6007',
instrumentType: 'SHARE',
scriptCode: 'PDCC',
scriptName: 'Pune District Central Coop Bank Shares',
party: 'Pune District Central Cooperative Bank',
subType: 'CO-OP SHARE',
faceValue: 60000,
purchaseRate: 50,
units: 1200,
purchaseDate: '18-07-2025',
maturityDate: '18-07-2035',
interestRate: 5.0,
interestFreq: 'YEARLY',
glCode: '310060',
glName: 'INVESTMENT IN SHARES',
crToAc: '310070',
brokerCode: '',
status: 'ACTIVE'
}
];
@@ -1060,6 +1367,43 @@ export const ALL_TXNS: InvestmentTxn[] = [
// -------------------- Masters --------------------
export interface InvestmentGlOption {
glCode: string;
glName: string;
}
/** Interest Income (Cr) GL per instrument type — the GL an Interest Calculation & Post run
* credits. Selectable on the FD/Govt./Bond Interest Calculation screens instead of being
* hard-wired, so a period's posting can be routed to a different income head when needed. */
export const INTEREST_INCOME_GL_MASTER: InvestmentGlOption[] = [
{ glCode: '410010', glName: 'INTEREST ON GOVT. SECURITIES' },
{ glCode: '410020', glName: 'INTEREST ON NON SLR SECURITIES' },
{ glCode: '410030', glName: 'INTEREST ON CORPORATE BONDS' },
{ glCode: '410040', glName: 'INTEREST ON FIXED DEPOSITS' },
{ glCode: '410050', glName: 'INCOME FROM MUTUAL FUNDS' },
{ glCode: '410060', glName: 'DIVIDEND ON SHARES' }
];
/** Default Interest Income GL suggested per instrument type — pre-fills the selector above,
* which the user can still override before posting. */
export const INTEREST_INCOME_GL_BY_TYPE: Record<InstrumentType, string> = {
GOVT_SECURITY: '410010',
NON_SLR: '410020',
BOND: '410030',
FD: '410040',
MUTUAL_FUND: '410050',
SHARE: '410060'
};
/** Cash/Bank (Dr) GL credited when an Interest Receive entry is booked — the account the cash
* actually lands in. Selectable on the Interest Receive screen. */
export const CASH_BANK_GL_MASTER: InvestmentGlOption[] = [
{ glCode: '100010', glName: 'CASH IN HAND' },
{ glCode: '110010', glName: 'RBI CURRENT ACCOUNT' },
{ glCode: '110020', glName: 'SBI CURRENT ACCOUNT' },
{ glCode: '110030', glName: 'HDFC BANK CURRENT ACCOUNT' }
];
export interface ScriptMaster {
scriptCode: string;
scriptName: string;