+ {{ intCalcExcludedNpi.length }} holding(s) excluded — classified Non-Performing, interest is not posted on + accrual basis (collectible on cash basis only, via Interest Receive in the Investment Interest module). +
+ }| Type | Particulars | +GL Posted | Amount | {{ t.certNo }} | {{ t.txnType }} | {{ t.particulars }} | +{{ t.glCode ? t.glCode + ' — ' + t.glName : '—' }} | ₹{{ t.amount | number: '1.2-2' }} | } @empty {||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| No transactions yet. | +No transactions yet. | Cert No | Type | Particulars | +GL Posted | Amount | @@ -467,6 +482,7 @@{{ t.certNo }} | {{ t.txnType }} | {{ t.particulars }} | +{{ t.glCode ? t.glCode + ' — ' + t.glName : '—' }} | ₹{{ t.amount | number: '1.2-2' }} | } diff --git a/src/app/investment/fd-investment/fd-investment.component.ts b/src/app/investment/fd-investment/fd-investment.component.ts index 0d3c10226..04643d0c0 100644 --- a/src/app/investment/fd-investment/fd-investment.component.ts +++ b/src/app/investment/fd-investment/fd-investment.component.ts @@ -9,6 +9,7 @@ import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; +import { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component'; import { FD_HOLDINGS, ALL_TXNS, @@ -17,10 +18,14 @@ import { FD_TYPE_OPTIONS, INT_TYPE_OPTIONS, LIEN_REASON_MASTER, + INTEREST_INCOME_GL_MASTER, + INTEREST_INCOME_GL_BY_TYPE, InvestmentHolding, InvestmentTxn, InterestFreq, accruedInterest, + accruedInterestBetween, + canAccrueInterest, daysToMaturity, maturityAmount, addPeriod, @@ -83,7 +88,8 @@ interface FdPurchaseForm { standalone: true, imports: [ CommonModule, - FormsModule + FormsModule, + DateDdmmyyyyPickerComponent ] }) export class FdInvestmentComponent { @@ -98,6 +104,7 @@ export class FdInvestmentComponent { readonly fdTypeOptions = FD_TYPE_OPTIONS; readonly intTypeOptions = INT_TYPE_OPTIONS; readonly lienReasonOptions = LIEN_REASON_MASTER; + readonly interestIncomeGlOptions = INTEREST_INCOME_GL_MASTER; readonly branchOptions = [ 'HEAD OFFICE', 'BRANCH 1', @@ -114,6 +121,9 @@ export class FdInvestmentComponent { searchCertNo = ''; interestCertNo = ''; + interestFromDate = addPeriod(this.asOnDate, 0, -3); + interestToDate = this.asOnDate; + interestGlCode = INTEREST_INCOME_GL_BY_TYPE.FD; sellCertNo = ''; sellDate = this.asOnDate; sellRemark = ''; @@ -313,12 +323,24 @@ export class FdInvestmentComponent { } // -------------------- Interest -------------------- + /** Certificates eligible for accrual-basis posting — same rule as the Investment Interest + * module: a Non-Performing holding never appears here, only through Interest Receive. */ + get interestEligibleHoldings(): InvestmentHolding[] { + return this.activeHoldings.filter((h) => canAccrueInterest(h)); + } + get interestHolding(): InvestmentHolding | undefined { return this.holdings.find((h) => h.certNo === this.interestCertNo); } + /** Interest accrued strictly within [interestFromDate, interestToDate] — a period-based + * Interest Calculation & Post run, same basis as the Investment Interest module. */ interestAccrued(h: InvestmentHolding): number { - return accruedInterest(h, this.asOnDate); + return accruedInterestBetween(h, this.interestFromDate || h.purchaseDate, this.interestToDate || this.asOnDate); + } + + glName(glCode: string): string { + return this.interestIncomeGlOptions.find((g) => g.glCode === glCode)?.glName ?? glCode; } postInterest(): void { @@ -328,19 +350,34 @@ export class FdInvestmentComponent { return; } const amt = this.interestAccrued(h); + if (amt <= 0) { + this.toast('No interest due to be posted for this period.'); + return; + } + const toDate = this.interestToDate || this.asOnDate; + const fromDate = this.interestFromDate || toDate; + const glCode = this.interestGlCode; + const glName = this.glName(glCode); + h.interestPosted = (h.interestPosted ?? 0) + amt; + h.interestPostedTillDate = toDate; + this.interestFromDate = toDate; this.txns = [ { certNo: h.certNo, instrumentType: 'FD', txnType: 'INTEREST', voucherNo: `INV/INT/FD-${h.certNo}-${this.txns.length + 1}`, - txnDate: this.asOnDate, + txnDate: toDate, amount: amt, - particulars: `Interest posted - ${h.party} (${h.interestFreq})` + particulars: `Interest posted (accrual) for period ${fromDate} to ${toDate} - ${h.party} (${h.interestFreq})`, + glCode, + glName }, ...this.txns ]; - this.toast(`Interest of ₹${amt.toLocaleString('en-IN')} posted for Certificate ${h.certNo}.`); + this.toast( + `Interest of ₹${amt.toLocaleString('en-IN')} posted to ${glCode} - ${glName} for Certificate ${h.certNo}.` + ); this.interestCertNo = ''; } diff --git a/src/app/investment/investment-dashboard/investment-dashboard.component.html b/src/app/investment/investment-dashboard/investment-dashboard.component.html index 36daffc84..51673f91b 100644 --- a/src/app/investment/investment-dashboard/investment-dashboard.component.html +++ b/src/app/investment/investment-dashboard/investment-dashboard.component.html @@ -85,10 +85,16 @@|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Instrument Type | +Target Basis | Target % | Actual % | Actual Value | @@ -100,6 +106,11 @@ @for (a of allocationVsTarget; track a.type) {||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ a.label }} | ++ + {{ a.isRbiLinked ? 'RBI SLR / Non-SLR Norm' : 'Board Policy' }} + + | {{ a.targetPct }}% | {{ a.actualPct }}% | ₹{{ a.actualValue | number: '1.2-2' }} | diff --git a/src/app/investment/investment-dashboard/investment-dashboard.component.ts b/src/app/investment/investment-dashboard/investment-dashboard.component.ts index 5c6166a82..15d6adb79 100644 --- a/src/app/investment/investment-dashboard/investment-dashboard.component.ts +++ b/src/app/investment/investment-dashboard/investment-dashboard.component.ts @@ -11,6 +11,7 @@ import { CommonModule } from '@angular/common'; import { RouterLink } from '@angular/router'; import { ALL_HOLDINGS, + ALLOCATION_TARGET_IS_RBI_LINKED, INSTRUMENT_LABELS, InstrumentType, InvestmentHolding, @@ -39,6 +40,9 @@ interface AllocationRow { actualValue: number; variancePct: number; statusLabel: 'Within Target' | 'Over-Allocated' | 'Under-Allocated'; + /** True when the target itself traces to an RBI SLR/Non-SLR rule rather than being a + * board-only policy choice — see TARGET_ALLOCATION_PCT's doc comment in investment-data.ts. */ + isRbiLinked: boolean; } /** Cross-instrument portfolio dashboard — every figure is derived from ALL_HOLDINGS, never @@ -114,7 +118,8 @@ export class InvestmentDashboardComponent { actualPct: +actualPct.toFixed(1), actualValue: value, variancePct, - statusLabel + statusLabel, + isRbiLinked: ALLOCATION_TARGET_IS_RBI_LINKED[type] }; }); } @@ -195,6 +200,7 @@ export class InvestmentDashboardComponent { subtitle: `As on ${this.asOnDate}`, headers: [ 'Instrument Type', + 'Target Basis', 'Target %', 'Actual %', 'Actual Value', @@ -203,6 +209,7 @@ export class InvestmentDashboardComponent { ], widths: [ 26, + 20, 12, 12, 18, @@ -211,6 +218,7 @@ export class InvestmentDashboardComponent { ], rows: this.allocationVsTarget.map((a) => [ a.label, + a.isRbiLinked ? 'RBI SLR / Non-SLR Norm' : 'Board Policy', a.targetPct, a.actualPct, a.actualValue, diff --git a/src/app/investment/investment-data.ts b/src/app/investment/investment-data.ts index 980093c86..0ea88ce1a 100644 --- a/src/app/investment/investment-data.ts +++ b/src/app/investment/investment-data.ts @@ -53,16 +53,45 @@ export const PROVISION_STATUS_LABELS: Record||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Voucher No | Cert No | Particulars | +GL Credited | Amount Received | {{ t.certNo }} | {{ t.particulars }} | +{{ t.glCode ? t.glCode + ' — ' + t.glName : '—' }} | ₹{{ t.amount | number: '1.2-2' }} | } @empty {|||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| No interest received yet. | +No interest received yet. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Total Received | +Total Received | ₹{{ receivedLogTotal | number: '1.2-2' }} | Cert No | Instrument | Particulars | +GL Posted | Interest Amount | @@ -451,17 +486,18 @@{{ t.certNo }} | {{ instrumentLabel(t.instrumentType) }} | {{ t.particulars }} | +{{ t.glCode ? t.glCode + ' — ' + t.glName : '—' }} | ₹{{ t.amount | number: '1.2-2' }} | } @empty {|||||||||||||||||||||||||||||||||||||||||
| No bond interest entries recorded yet. | +No bond interest entries recorded yet. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Total | +Total | ₹{{ bondIntRegisterTotal | number: '1.2-2' }} | Voucher No | Cert No | Particulars | +GL Posted | Interest Amount | @@ -492,17 +529,18 @@{{ t.certNo }} | {{ t.particulars }} | +{{ t.glCode ? t.glCode + ' — ' + t.glName : '—' }} | ₹{{ t.amount | number: '1.2-2' }} | } @empty {||||||||||||||||||||||||||||||||||||||||||
| No FD interest entries recorded yet. | +No FD interest entries recorded yet. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Total | +Total | ₹{{ fdIntRegisterTotal | number: '1.2-2' }} | |||||||||||||||||||||||||||||||||||||||||||||||||||
| Product | +Target Basis | Target % | Actual % | Variance | @@ -497,7 +504,12 @@ @for (row of productTargetRows; track row.code) {|||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{ row.label }} | -{{ row.targetPct | number: '1.0-0' }}% | ++ + {{ row.isRbiMandated ? 'RBI PSL Sub-Target' : 'Internal Target' }} + + | +{{ row.targetPct | number: '1.0-1' }}% | {{ row.actualPct | number: '1.1-1' }}% | 0"> {{ row.variancePct > 0 ? '+' : '' }}{{ row.variancePct | number: '1.1-1' }} pp @@ -533,7 +545,7 @@ @for (row of channelTargetRows; track row.code) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| {{ row.label }} | -{{ row.targetPct | number: '1.0-0' }}% | +{{ row.targetPct | number: '1.0-1' }}% | {{ row.actualPct | number: '1.1-1' }}% | 0">
{{ row.variancePct > 0 ? '+' : '' }}{{ row.variancePct | number: '1.1-1' }} pp
diff --git a/src/app/loan-origination/loan-pipeline/loan-pipeline.component.ts b/src/app/loan-origination/loan-pipeline/loan-pipeline.component.ts
index 382d07888..a4f0be20d 100644
--- a/src/app/loan-origination/loan-pipeline/loan-pipeline.component.ts
+++ b/src/app/loan-origination/loan-pipeline/loan-pipeline.component.ts
@@ -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)),
diff --git a/src/app/npa/npa-reports/npa-reports.component.html b/src/app/npa/npa-reports/npa-reports.component.html
index 98aaee3e4..e0303b092 100644
--- a/src/app/npa/npa-reports/npa-reports.component.html
+++ b/src/app/npa/npa-reports/npa-reports.component.html
@@ -264,16 +264,22 @@
Record a Recovery Against an NPA Account
-
- NPA Recovery Register — ₹{{ totalRecoveredAmount | number: '1.0-0' }} recovered against ₹{{
- gnpaAmount | number: '1.0-0'
- }}
- GNPA outstanding
+
+
+ NPA Recovery Register — ₹{{ totalRecoveredAmount | number: '1.0-0' }} recovered against ₹{{
+ gnpaAmount | number: '1.0-0'
+ }}
+ GNPA outstanding
+
+ @if (recoveryProductFilter) {
+ Filtered: {{ recoveryProductFilter }}
+ }
+ + Target Recovery is the account's contractual due this month (EMI / interest-servicing / bullet amount) — same + basis as the Recovery Management Dashboard. +
+
+ @if (open) {
+
+ `,
+ styles: [
+ `
+ /* Angular view encapsulation stops the host page's shared styles from reaching inside this
+ * component's own template, so its look is re-declared here rather than inherited. */
+ :host {
+ display: block;
+ width: 100%;
+ }
+ .inv-search-select {
+ position: relative;
+ width: 100%;
+ }
+ .inv-search-select__input {
+ width: 100%;
+ height: 36px;
+ padding: 0 10px;
+ border: 1px solid #e2e8f0;
+ border-radius: 8px;
+ font-size: 13px;
+ color: #0d1b2a;
+ font-family: inherit;
+ background: #fff;
+ box-sizing: border-box;
+ }
+ .inv-search-select__list {
+ position: fixed;
+ z-index: 2000;
+ max-height: 260px;
+ overflow-y: auto;
+ margin: 0;
+ padding: 4px;
+ list-style: none;
+ background: #fff;
+ border: 1px solid #e2e8f0;
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(13, 27, 42, 0.18);
+ }
+ .inv-search-select__option {
+ padding: 8px 10px;
+ border-radius: 6px;
+ font-size: 13px;
+ color: #0d1b2a;
+ cursor: pointer;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ .inv-search-select__option:hover,
+ .inv-search-select__option--active {
+ background: #eef2ff;
+ }
+ .inv-search-select__empty {
+ padding: 8px 10px;
+ font-size: 12px;
+ color: #94a3b8;
+ }
+ `
+ ]
+})
+export class SearchSelectComponent implements ControlValueAccessor {
+ @Input() options: SearchSelectOption[] = [];
+ @Input() placeholder = 'Search…';
+
+ @ViewChild('inputEl') private inputEl?: ElementRef
| |||||||||||||||||||||||||||||||||||||||||||||||||