diff --git a/src/app/investment/bond-investment/bond-investment.component.html b/src/app/investment/bond-investment/bond-investment.component.html index 958cca5e9..ea4c4f67a 100644 --- a/src/app/investment/bond-investment/bond-investment.component.html +++ b/src/app/investment/bond-investment/bond-investment.component.html @@ -137,11 +137,12 @@ Units Face Value Rate - Purchase Date + Settlement Date Maturity Date Accrued Interest Maturity Value Status + Settlement @@ -160,12 +161,23 @@ ₹{{ accrued(h) | number: '1.2-2' }} ₹{{ maturity(h) | number: '1.2-2' }} - {{ h.status }} + {{ + h.status === 'PENDING_SETTLEMENT' ? 'PENDING SETTLEMENT' : h.status + }} + + + @if (h.status === 'PENDING_SETTLEMENT') { + + } @else if (h.glTransactionId) { + GL {{ h.glTransactionId }} + } } @empty { - No {{ instrumentLabel(bondType) }} holdings yet. + No {{ instrumentLabel(bondType) }} holdings yet. } @@ -384,7 +396,7 @@ } -
Accounting Preview (posted after checker approval)
+
Accounting Preview (posted on Settle, dated the settlement date)
@if (purchaseAccountingPreview.length) { @@ -1284,7 +1296,7 @@ - + } @empty { diff --git a/src/app/investment/bond-investment/bond-investment.component.ts b/src/app/investment/bond-investment/bond-investment.component.ts index ddd063f60..dbdee702a 100644 --- a/src/app/investment/bond-investment/bond-investment.component.ts +++ b/src/app/investment/bond-investment/bond-investment.component.ts @@ -239,17 +239,19 @@ export class BondInvestmentComponent implements OnInit { }); } + /** A purchase reaches the GL only when it is settled, dated the settlement date. */ private purchaseTxn(d: InvestmentPurchaseDto): InvestmentTxn { + const posted = !!d.glTransactionId; return { certNo: d.certNo, instrumentType: d.bondType, txnType: 'PURCHASE', voucherNo: `INV/PUR/${d.dealNo}`, - txnDate: d.dealDate, + txnDate: d.settlementDate, amount: d.settlementAmount, particulars: `Purchase Deal ${d.dealNo} - ${d.scriptDesc} - ${d.partyName} [${d.category}]`, - glCode: d.investmentGlCode, - glName: d.investmentGlName + glCode: posted ? d.investmentGlCode : '', + glName: posted ? `${d.investmentGlName} (txn ${d.glTransactionId})` : 'Not posted (pending settlement)' }; } @@ -573,20 +575,19 @@ export class BondInvestmentComponent implements OnInit { return approxYtm(f.intRate, f.purchaseRate || 100, yearsBetween(f.issueDate, this.purchaseMaturityDate)); } - /** Accrued Interest at purchase — interest earned by the seller since the last coupon date. */ - /** Interest owed to the seller from the last coupon date up to the deal date, on a 30/360 basis — - * interest is counted to the event date, not the date the money moves (Jitendra Sir). */ + /** Broken-period interest owed to the seller from the last coupon date up to the settlement date, 30/360. */ get purchaseAccruedInterest(): number { const f = this.purchaseForm; if (!f.intRate || !f.lastIntDt || !f.dealDate) { return 0; } - return Math.round((this.purchaseBaseAmount * (f.intRate / 100) * days360(f.lastIntDt, f.dealDate)) / 360); + return Math.round((this.purchaseBaseAmount * (f.intRate / 100) * this.purchaseAccruedDays) / 360); } get purchaseAccruedDays(): number { const f = this.purchaseForm; - return f.lastIntDt && f.dealDate ? days360(f.lastIntDt, f.dealDate) : 0; + // Up to the settlement date, when the bond leaves the seller (RBI para 83; Jitendra Sir). + return f.lastIntDt && this.purchaseSettlementDate ? days360(f.lastIntDt, this.purchaseSettlementDate) : 0; } /** Premium (+) or discount (−) paid over face value. */ @@ -842,7 +843,9 @@ export class BondInvestmentComponent implements OnInit { }; this.investmentService.createPurchase(body).subscribe({ next: (res) => { - this.toast(`Deal ${res.resourceIdentifier} saved. GL posting will follow once accounting is approved.`); + this.toast( + `Deal ${res.resourceIdentifier} saved — pending settlement. Settle it from Holdings on or after the settlement date.` + ); this.activeTab = 'holdings'; this.loadPurchases(); }, @@ -941,6 +944,24 @@ export class BondInvestmentComponent implements OnInit { this.toast(`Deal for Cert No ${h.certNo} retrieved.`); } + /** Posts a pending deal to GL dated its settlement date; under maker-checker it waits in the Checker Inbox. */ + settleDeal(h: InvestmentHolding): void { + if (!h.id) { + return; + } + this.investmentService.settlePurchase(h.id).subscribe({ + next: (res) => { + this.toast( + res.rollbackTransaction + ? `Settlement of deal ${h.dealNo} sent for approval (Checker Inbox).` + : `Deal ${h.dealNo} settled and posted to GL (transaction ${res.transactionId}).` + ); + this.loadPurchases(); + }, + error: (err) => this.toast(apiErrorMessage(err)) + }); + } + dropPurchase(): void { const h = this.holdings.find((x) => x.certNo === this.purchaseForm.certNo); if (!h?.id) { diff --git a/src/app/investment/investment-data.ts b/src/app/investment/investment-data.ts index d81448f20..cf69b9abe 100644 --- a/src/app/investment/investment-data.ts +++ b/src/app/investment/investment-data.ts @@ -17,7 +17,7 @@ */ export type InstrumentType = 'FD' | 'GOVT_SECURITY' | 'NON_SLR' | 'BOND' | 'MUTUAL_FUND' | 'SHARE'; -export type HoldingStatus = 'ACTIVE' | 'MATURED' | 'SOLD' | 'WRITTEN_OFF'; +export type HoldingStatus = 'PENDING_SETTLEMENT' | 'ACTIVE' | 'MATURED' | 'SOLD' | 'WRITTEN_OFF'; export type InterestFreq = 'MONTHLY' | 'QUARTERLY' | 'HALF_YEARLY' | 'YEARLY' | 'CUMULATIVE'; export type TxnType = | 'PURCHASE' @@ -133,6 +133,10 @@ export interface InvestmentHolding { partyCode?: string; bookValue?: number; settlementAmount?: number; + /** Settlement date of the deal (deal date + T+n) — it can be settled on or after this day. */ + settlementDate?: string; + /** Journal entry transaction posted when the deal was settled. */ + glTransactionId?: string; } export interface InvestmentTxn { diff --git a/src/app/investment/investment.service.ts b/src/app/investment/investment.service.ts index 947f050cb..b9723b0e4 100644 --- a/src/app/investment/investment.service.ts +++ b/src/app/investment/investment.service.ts @@ -12,6 +12,7 @@ import { Observable, forkJoin } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { DailyScriptRate, + HoldingStatus, InstrumentType, InvestmentHolding, PARTY_MASTER, @@ -119,6 +120,8 @@ export interface InvestmentPurchaseDto { crGlName: string; otherDetails?: string; status: string; + glTransactionId?: string; + settledOnDate?: string; } export interface InvestmentGlAccount { @@ -200,6 +203,14 @@ export class InvestmentService { return this.http.delete(`/investment/daily-rates/${rateId}`); } + /** Settles a deal: posts it to GL dated its settlement date. Under maker-checker the response only says it awaits approval. */ + settlePurchase(purchaseId: number): Observable<{ transactionId?: string; rollbackTransaction?: boolean }> { + return this.http.post<{ transactionId?: string; rollbackTransaction?: boolean }>( + `/investment/purchases/${purchaseId}/settle`, + {} + ); + } + createPurchase(body: object): Observable<{ resourceId: number; resourceIdentifier: string }> { return this.http.post<{ resourceId: number; resourceIdentifier: string }>('/investment/purchases', body); } @@ -316,7 +327,9 @@ export function toHolding(p: InvestmentPurchaseDto): InvestmentHolding { glName: p.investmentGlName, crToAc: p.crGlCode, brokerCode: '', - status: 'ACTIVE', + status: p.status as HoldingStatus, + settlementDate: p.settlementDate, + glTransactionId: p.glTransactionId, category: p.category, bookValue: p.bookValue, settlementAmount: p.settlementAmount
{{ t.certNo }} {{ t.txnType }} {{ t.particulars }}{{ t.glCode ? t.glCode + ' — ' + t.glName : '—' }}{{ t.glCode ? t.glCode + ' — ' + t.glName : t.glName || '—' }} ₹{{ t.amount | number: '1.2-2' }}