diff --git a/src/app/investment/bond-investment/bond-investment.component.html b/src/app/investment/bond-investment/bond-investment.component.html
index bfcc033c6..9d516fe8d 100644
--- a/src/app/investment/bond-investment/bond-investment.component.html
+++ b/src/app/investment/bond-investment/bond-investment.component.html
@@ -626,20 +626,14 @@
}
-
+
@if (activeTab === 'interest') {
}
diff --git a/src/app/investment/bond-investment/bond-investment.component.ts b/src/app/investment/bond-investment/bond-investment.component.ts
index 414a45801..d08242915 100644
--- a/src/app/investment/bond-investment/bond-investment.component.ts
+++ b/src/app/investment/bond-investment/bond-investment.component.ts
@@ -26,8 +26,6 @@ import {
BOND_RATING_OPTIONS,
INT_TYPE_OPTIONS,
AUTHORITY_OPTIONS,
- INTEREST_INCOME_GL_MASTER,
- INTEREST_INCOME_GL_BY_TYPE,
INVESTMENT_GL_BY_TYPE,
INVESTMENT_GL_LOOKUP,
INTEREST_RECEIVABLE_GL,
@@ -42,8 +40,6 @@ import {
InterestFreq,
INSTRUMENT_LABELS,
accruedInterest,
- accruedInterestBetween,
- canAccrueInterest,
daysToMaturity,
maturityAmount,
addPeriod,
@@ -52,7 +48,13 @@ import {
todayDDMMYYYY
} from '../investment-data';
import { exportWorkbook } from '../investment-export';
-import { InvestmentPurchaseDto, InvestmentService, apiErrorMessage, toHolding } from '../investment.service';
+import {
+ InvestmentInterestScheduleDto,
+ InvestmentPurchaseDto,
+ InvestmentService,
+ apiErrorMessage,
+ toHolding
+} from '../investment.service';
type BondType = 'GOVT_SECURITY' | 'NON_SLR' | 'BOND' | 'MUTUAL_FUND';
type BondTab =
@@ -286,17 +288,16 @@ export class BondInvestmentComponent implements OnInit {
redeemDate = this.asOnDate;
redeemRemarks = '';
- // Interest Calculation (legacy: From/To Date / Type / Script / Deal No / ALL + Process) —
- // posts interest accrued strictly within [intCalcFromDate, intCalcToDate], same period-based
- // basis as the Investment Interest module's Govt./Bond Interest Calculation.
- intCalcFromDate = addPeriod(this.asOnDate, 0, -3);
- intCalcToDate = this.asOnDate;
- intCalcType: BondType | '' = '';
- intCalcScript = '';
- intCalcDealNo = '';
- intCalcAll = false;
- intCalcGlCode = INTEREST_INCOME_GL_BY_TYPE.GOVT_SECURITY;
- readonly interestIncomeGlOptions = INTEREST_INCOME_GL_MASTER;
+ // 03 Interest Entry (legacy: Type / Script / Deal No + Retrieve) — the backend works out every
+ // half-yearly coupon and 31 Mar / 30 Sep closing provision of the holding. Display only:
+ // nothing is posted to GL until the posting rules are approved.
+ intType: BondType | '' = '';
+ intScript = '';
+ intPurchaseId: number | null = null;
+ intAsOnDate = this.asOnDate;
+ intSchedule: InvestmentInterestScheduleDto | null = null;
+ intLoading = false;
+ readonly interestReceivableGl = INTEREST_RECEIVABLE_GL;
// Write-off
writeoffCertNo = '';
@@ -892,78 +893,53 @@ export class BondInvestmentComponent implements OnInit {
}
// -------------------- Interest calculation --------------------
- /** Holdings the Interest Calculation "Process" run would cover, given the filters. A
- * Non-Performing holding is always excluded — same accrual-basis rule as the Investment
- * Interest module (it's still collectible on cash basis via Interest Receive there). */
- get intCalcTargets(): InvestmentHolding[] {
- const base = this.holdings.filter((h) => h.status === 'ACTIVE' && canAccrueInterest(h));
- if (this.intCalcAll) {
- return base;
+ /** Saved holdings for the Deal No dropdown, narrowed by Type and Script. */
+ get intDealOptions(): InvestmentHolding[] {
+ return this.holdings.filter(
+ (h) =>
+ h.id != null &&
+ h.status === 'ACTIVE' &&
+ (!this.intType || h.instrumentType === this.intType) &&
+ (!this.intScript || h.scriptCode === this.intScript)
+ );
+ }
+
+ get intScriptOptions(): typeof SCRIPT_MASTER {
+ return this.scriptOptions.filter((s) => !this.intType || s.instrumentType === this.intType);
+ }
+
+ onIntFilterChange(): void {
+ if (this.intScript && !this.intScriptOptions.some((s) => s.scriptCode === this.intScript)) {
+ this.intScript = '';
}
- return base.filter((h) => {
- if (this.intCalcType && h.instrumentType !== this.intCalcType) return false;
- if (this.intCalcScript && h.scriptCode !== this.intCalcScript) return false;
- if (this.intCalcDealNo && h.certNo !== this.intCalcDealNo.trim()) return false;
- return true;
- });
- }
-
- get intCalcExcludedNpi(): InvestmentHolding[] {
- return this.holdings.filter((h) => h.status === 'ACTIVE' && !canAccrueInterest(h));
- }
-
- /** Re-suggests the Interest Income GL when the Type filter changes — still overridable. */
- onIntCalcTypeChange(): void {
- if (this.intCalcType) {
- this.intCalcGlCode = INTEREST_INCOME_GL_BY_TYPE[this.intCalcType];
+ if (this.intPurchaseId != null && !this.intDealOptions.some((h) => h.id === this.intPurchaseId)) {
+ this.intPurchaseId = null;
}
+ this.intSchedule = null;
}
- glName(glCode: string): string {
- return this.interestIncomeGlOptions.find((g) => g.glCode === glCode)?.glName ?? glCode;
- }
-
- /** Interest accrued strictly within [intCalcFromDate, intCalcToDate] — a period-based
- * Interest Calculation & Post run, same basis as the Investment Interest module. */
- interestAccrued(h: InvestmentHolding): number {
- return accruedInterestBetween(h, this.intCalcFromDate || h.purchaseDate, this.intCalcToDate || this.asOnDate);
- }
-
- processInterest(): void {
- const targets = this.intCalcTargets.filter((h) => this.interestAccrued(h) > 0);
- if (targets.length === 0) {
- this.toast('No holdings have interest due to be posted for this period.');
+ retrieveInterestSchedule(): void {
+ if (this.intPurchaseId == null) {
+ this.toast('Select a Deal No first.');
return;
}
- const toDate = this.intCalcToDate || this.asOnDate;
- const fromDate = this.intCalcFromDate || toDate;
- const glCode = this.intCalcGlCode;
- const glName = this.glName(glCode);
- const newTxns: InvestmentTxn[] = targets.map((h, i) => {
- const amt = this.interestAccrued(h);
- h.interestPosted = (h.interestPosted ?? 0) + amt;
- h.interestPostedTillDate = toDate;
- return {
- certNo: h.certNo,
- instrumentType: h.instrumentType,
- txnType: 'INTEREST',
- voucherNo: `INV/INT/${h.certNo}-${this.txns.length + i + 1}`,
- txnDate: toDate,
- amount: amt,
- particulars: `Interest calculated (accrual) for period ${fromDate} to ${toDate} - ${h.scriptName}`,
- glCode,
- glName
- };
+ this.intLoading = true;
+ this.investmentService.getInterestSchedule(this.intPurchaseId, this.intAsOnDate || undefined).subscribe({
+ next: (schedule) => {
+ this.intSchedule = schedule;
+ this.intLoading = false;
+ },
+ error: (err) => {
+ this.intSchedule = null;
+ this.intLoading = false;
+ this.toast(apiErrorMessage(err));
+ }
});
- this.txns = [
- ...newTxns,
- ...this.txns
- ];
- this.intCalcFromDate = toDate;
- const total = newTxns.reduce((s, t) => s + t.amount, 0);
- this.toast(
- `Interest processed for ${targets.length} holding(s) to ${glCode} - ${glName}. Total ₹${total.toLocaleString('en-IN')}.`
- );
+ }
+
+ /** Total of the lines falling due up to the as-on date. */
+ get intDueTotal(): number {
+ return (this.intSchedule?.rows ?? []).filter((r) => r.due).reduce((sum, r) => sum + r.amount, 0);
}
// -------------------- Sell / Redeem Entry --------------------
diff --git a/src/app/investment/investment.service.ts b/src/app/investment/investment.service.ts
index 52a92ac78..9d04db151 100644
--- a/src/app/investment/investment.service.ts
+++ b/src/app/investment/investment.service.ts
@@ -169,6 +169,14 @@ export class InvestmentService {
return this.http.get