feat(investment): non-SLR bonds and mutual funds, rate upload, UAT fixes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -160,7 +160,7 @@
|
||||
<td class="num">₹{{ carryingValue(h) | number: '1.2-2' }}</td>
|
||||
<td class="num">{{ h.interestRate }}%</td>
|
||||
<td>{{ h.purchaseDate }}</td>
|
||||
<td>{{ h.maturityDate }}</td>
|
||||
<td>{{ h.instrumentType === 'MUTUAL_FUND' ? '—' : h.maturityDate }}</td>
|
||||
<td class="num col-in">₹{{ accrued(h) | number: '1.2-2' }}</td>
|
||||
<td class="num col-net">₹{{ maturity(h) | number: '1.2-2' }}</td>
|
||||
<td>
|
||||
@@ -203,7 +203,7 @@
|
||||
<div class="inv-form-grid">
|
||||
<label class="inv-form-field">
|
||||
<span>* Bond Type</span>
|
||||
<select [(ngModel)]="purchaseForm.bondType">
|
||||
<select [ngModel]="purchaseForm.bondType" (ngModelChange)="setBondType($event)">
|
||||
@for (o of bondTypeOptions; track o.value) {
|
||||
<option [value]="o.value">{{ o.label }}</option>
|
||||
}
|
||||
@@ -240,7 +240,7 @@
|
||||
/></label>
|
||||
<label class="inv-form-field">
|
||||
<span>* Settlement Type</span>
|
||||
<select [(ngModel)]="purchaseForm.settlementType">
|
||||
<select [(ngModel)]="purchaseForm.settlementType" (ngModelChange)="onSettlementTypeChange()">
|
||||
<option value="T+0">T+0</option>
|
||||
<option value="T+1">T+1</option>
|
||||
<option value="T+2">T+2</option>
|
||||
@@ -264,14 +264,14 @@
|
||||
}
|
||||
<label class="inv-form-field">
|
||||
<span>* Category</span>
|
||||
<select [(ngModel)]="purchaseForm.category">
|
||||
<select [(ngModel)]="purchaseForm.category" [disabled]="isNonSlrPurchase">
|
||||
<option value="">-- Select --</option>
|
||||
@for (c of categoryOptions; track c) {
|
||||
@for (c of purchaseCategoryOptions; track c) {
|
||||
<option [value]="c">{{ c }}</option>
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
@if (!isMutualFund) {
|
||||
@if (!isNonSlrPurchase) {
|
||||
<label class="inv-form-field"
|
||||
><span>* Last Interest Date</span
|
||||
><mifosx-date-ddmmyyyy [(ngModel)]="purchaseForm.lastIntDt"></mifosx-date-ddmmyyyy
|
||||
@@ -373,7 +373,8 @@
|
||||
<label class="inv-form-field"
|
||||
><span>Book Value</span><input type="text" [value]="'₹' + (purchaseBookValue | number: '1.2-2')" readonly
|
||||
/></label>
|
||||
@if (!isMutualFund) {
|
||||
<!-- Non-SLR has no premium split, broken-period interest or yield: it is booked at the price paid. -->
|
||||
@if (!isNonSlrPurchase) {
|
||||
<label class="inv-form-field"
|
||||
><span>Premium (+) / Discount (−)</span
|
||||
><input type="text" [value]="'₹' + (purchasePremiumDiscount | number: '1.2-2')" readonly
|
||||
@@ -394,7 +395,7 @@
|
||||
/></label>
|
||||
</div>
|
||||
|
||||
@if (!isMutualFund && purchaseCouponDates) {
|
||||
@if (!isNonSlrPurchase && purchaseCouponDates) {
|
||||
<div class="pos-section-label">Interest Cycle</div>
|
||||
<div class="inv-form-grid">
|
||||
<label class="inv-form-field"
|
||||
@@ -753,7 +754,13 @@
|
||||
/></label>
|
||||
<label class="inv-form-field">
|
||||
<span>{{
|
||||
q.profitLoss >= 0 ? 'Discount realised (profit 400405)' : 'Premium written off (expense 500604)'
|
||||
isNonSlrSale(q)
|
||||
? q.profitLoss >= 0
|
||||
? 'Profit on redemption (income 400402)'
|
||||
: 'Loss on redemption (expense 500605)'
|
||||
: q.profitLoss >= 0
|
||||
? 'Discount realised (profit 400405)'
|
||||
: 'Premium written off (expense 500604)'
|
||||
}}</span>
|
||||
<input
|
||||
type="text"
|
||||
@@ -786,9 +793,13 @@
|
||||
· Cr {{ q.premiumGlCode }} Premium ₹{{ q.premiumCleared | number: '1.2-2' }}
|
||||
}
|
||||
@if (q.profitLoss > 0) {
|
||||
· Cr 400405 Profit on Redemption ₹{{ q.profitLoss | number: '1.2-2' }}
|
||||
· Cr {{ isNonSlrSale(q) ? '400402 Interest on Bonds' : '400405 Profit on Redemption' }} ₹{{
|
||||
q.profitLoss | number: '1.2-2'
|
||||
}}
|
||||
} @else if (q.profitLoss < 0) {
|
||||
· Dr 500604 Premium Amortisation ₹{{ -q.profitLoss | number: '1.2-2' }}
|
||||
· Dr {{ isNonSlrSale(q) ? '500605 Loss on Investments' : '500604 Premium Amortisation' }} ₹{{
|
||||
-q.profitLoss | number: '1.2-2'
|
||||
}}
|
||||
}
|
||||
</p>
|
||||
}
|
||||
@@ -1074,7 +1085,7 @@
|
||||
><input type="number" min="0" [(ngModel)]="sale.units" (change)="refreshSaleQuote()"
|
||||
/></label>
|
||||
<label class="inv-form-field"
|
||||
><span>* Sale Rate (per ₹100)</span
|
||||
><span>* {{ saleIsMutualFund ? 'NAV per unit' : 'Sale Rate (per ₹100)' }}</span
|
||||
><input type="number" min="0" step="0.0001" [(ngModel)]="sale.saleRate" (change)="refreshSaleQuote()"
|
||||
/></label>
|
||||
<label class="inv-form-field"
|
||||
@@ -1148,7 +1159,9 @@
|
||||
· Cr {{ q.premiumGlCode }} Premium ₹{{ q.premiumCleared | number: '1.2-2' }}
|
||||
}
|
||||
@if (q.profitLoss > 0) {
|
||||
· Cr 400405 Profit ₹{{ q.profitLoss | number: '1.2-2' }}
|
||||
· Cr {{ isNonSlrSale(q) ? '400402 Interest on Bonds' : '400405 Profit' }} ₹{{
|
||||
q.profitLoss | number: '1.2-2'
|
||||
}}
|
||||
} @else if (q.profitLoss < 0) {
|
||||
· Dr 500605 Loss ₹{{ -q.profitLoss | number: '1.2-2' }}
|
||||
}
|
||||
@@ -1471,7 +1484,7 @@
|
||||
<span>* Deal No</span>
|
||||
<select [(ngModel)]="goiPurchaseId" (ngModelChange)="onGoiDealChange()">
|
||||
<option [ngValue]="null">-- Select --</option>
|
||||
@for (h of saleableHoldings; track h.certNo) {
|
||||
@for (h of goiTransferHoldings; track h.certNo) {
|
||||
<option [ngValue]="h.id">{{ h.dealNo }} — {{ h.scriptCode }} {{ h.scriptName }}</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
@@ -73,6 +73,7 @@ import {
|
||||
InvestmentSecurityTransferDto,
|
||||
InvestmentNonSlrLimitDto,
|
||||
InvestmentService,
|
||||
NON_SLR_TYPES,
|
||||
apiErrorMessage,
|
||||
toHolding,
|
||||
toInterestTxn,
|
||||
@@ -200,6 +201,8 @@ export class BondInvestmentComponent implements OnInit {
|
||||
const tab = this.route.snapshot.queryParamMap.get('tab');
|
||||
if (tab === 'interest' || tab === 'redemption' || tab === 'amortize') {
|
||||
this.activeTab = tab;
|
||||
// The Due Date Diary also names the deal (?purchase=id): it is opened once the holdings have loaded.
|
||||
this.deepLinkPurchaseId = Number(this.route.snapshot.queryParamMap.get('purchase')) || null;
|
||||
}
|
||||
this.investmentService.syncSharedMasters().subscribe({
|
||||
error: (err) => this.toast(`Could not load Script / Party masters: ${apiErrorMessage(err)}`)
|
||||
@@ -230,11 +233,34 @@ export class BondInvestmentComponent implements OnInit {
|
||||
this.purchaseForm = this.blankPurchaseForm();
|
||||
this.loadNonSlrLimit();
|
||||
this.loadInterestLog();
|
||||
this.openDeepLinkedDeal();
|
||||
},
|
||||
error: (err) => this.toast(`Could not load purchases: ${apiErrorMessage(err)}`)
|
||||
});
|
||||
}
|
||||
|
||||
private deepLinkPurchaseId: number | null = null;
|
||||
|
||||
/** Selects the deal a Due Date Diary link named, on its tab, and loads it. */
|
||||
private openDeepLinkedDeal(): void {
|
||||
const h = this.holdings.find((x) => x.id === this.deepLinkPurchaseId);
|
||||
this.deepLinkPurchaseId = null;
|
||||
if (!h) {
|
||||
return;
|
||||
}
|
||||
this.setBondType(h.instrumentType as BondType);
|
||||
if (this.activeTab === 'interest') {
|
||||
this.intPurchaseId = h.id ?? null;
|
||||
this.retrieveInterestSchedule();
|
||||
} else if (this.activeTab === 'redemption') {
|
||||
this.redeemPurchaseId = h.id ?? null;
|
||||
this.onRedeemDealChange();
|
||||
} else if (this.activeTab === 'amortize') {
|
||||
this.amortizePurchaseId = h.id ?? null;
|
||||
this.onAmortizeDealChange();
|
||||
}
|
||||
}
|
||||
|
||||
/** Posted coupons / provisions join the purchases in the transaction log, latest date first. */
|
||||
private loadInterestLog(): void {
|
||||
forkJoin({
|
||||
@@ -350,12 +376,30 @@ export class BondInvestmentComponent implements OnInit {
|
||||
}
|
||||
|
||||
setBondType(type: BondType): void {
|
||||
const typeChanged = this.purchaseForm.bondType !== type;
|
||||
this.bondType = type;
|
||||
this.purchaseForm.bondType = type;
|
||||
this.purchaseForm.subType = this.defaultSubType(type);
|
||||
if (this.isNonSlrPurchase) {
|
||||
this.purchaseForm.category = this.categoryOptions.find((c) => c.startsWith('AFS')) ?? '';
|
||||
} else if (typeChanged && this.purchaseForm.category.startsWith('AFS')) {
|
||||
this.purchaseForm.category = '';
|
||||
}
|
||||
if (typeChanged) {
|
||||
// The script, its ISIN, rating and market rate belong to the old bond type.
|
||||
this.purchaseForm.scriptCode = '';
|
||||
this.purchaseForm.isin = '';
|
||||
this.purchaseForm.bondRating = '';
|
||||
this.purchaseMarketRate = undefined;
|
||||
}
|
||||
this.loadNonSlrLimit();
|
||||
}
|
||||
|
||||
/** Non-SLR (bonds, mutual funds) can be bought and sold at any time, so it is always AFS (Jitendra Sir). */
|
||||
get purchaseCategoryOptions(): string[] {
|
||||
return this.isNonSlrPurchase ? this.categoryOptions.filter((c) => c.startsWith('AFS')) : this.categoryOptions;
|
||||
}
|
||||
|
||||
private defaultSubType(type: BondType): string {
|
||||
return type === 'GOVT_SECURITY'
|
||||
? 'SLR'
|
||||
@@ -372,11 +416,7 @@ export class BondInvestmentComponent implements OnInit {
|
||||
|
||||
/** Bonds, CPs, CDs and mutual funds are non-SLR investments, limited by RBI to 10% of deposits. */
|
||||
get isNonSlrPurchase(): boolean {
|
||||
return [
|
||||
'NON_SLR',
|
||||
'BOND',
|
||||
'MUTUAL_FUND'
|
||||
].includes(this.purchaseForm.bondType);
|
||||
return NON_SLR_TYPES.includes(this.purchaseForm.bondType);
|
||||
}
|
||||
|
||||
loadNonSlrLimit(): void {
|
||||
@@ -530,7 +570,8 @@ export class BondInvestmentComponent implements OnInit {
|
||||
fromDist: '',
|
||||
toDist: '',
|
||||
redumMatDt: '',
|
||||
category: '',
|
||||
// Non-SLR (bond, mutual fund) is always AFS (Jitendra Sir).
|
||||
category: NON_SLR_TYPES.includes(type) ? 'AFS (Available For Sale)' : '',
|
||||
intRate: null,
|
||||
intType: 'HALF_YEARLY',
|
||||
intDate: this.asOnDate,
|
||||
@@ -640,13 +681,19 @@ export class BondInvestmentComponent implements OnInit {
|
||||
if (!f.intRate || !this.purchaseMaturityDate) {
|
||||
return 0;
|
||||
}
|
||||
return approxYtm(f.intRate, f.purchaseRate || 100, yearsBetween(f.issueDate, this.purchaseMaturityDate));
|
||||
// Over the life still to run from settlement, not the security's whole life.
|
||||
return approxYtm(
|
||||
f.intRate,
|
||||
f.purchaseRate || 100,
|
||||
yearsBetween(this.purchaseSettlementDate, this.purchaseMaturityDate)
|
||||
);
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
// Non-SLR has no interest scheme (Jitendra Sir): no broken-period interest.
|
||||
if (this.isNonSlrPurchase || !f.intRate || !f.lastIntDt || !f.dealDate) {
|
||||
return 0;
|
||||
}
|
||||
return Math.round((this.purchaseBaseAmount * f.intRate * this.purchaseAccruedDays) / 360) / 100;
|
||||
@@ -661,7 +708,8 @@ export class BondInvestmentComponent implements OnInit {
|
||||
/** Premium (+) or discount (−) paid over face value. */
|
||||
get purchasePremiumDiscount(): number {
|
||||
const f = this.purchaseForm;
|
||||
if (this.isMutualFund || !f.faceValue || !f.purchaseRate) {
|
||||
// Non-SLR is booked at the price paid; no premium is split out or amortised.
|
||||
if (this.isNonSlrPurchase || !f.faceValue || !f.purchaseRate) {
|
||||
return 0;
|
||||
}
|
||||
return Math.round(f.faceValue * (f.purchaseRate - 100)) / 100;
|
||||
@@ -682,11 +730,23 @@ export class BondInvestmentComponent implements OnInit {
|
||||
private suggestLastIntDate(): void {
|
||||
const f = this.purchaseForm;
|
||||
const s = this.purchaseScript;
|
||||
if (!this.isMutualFund && s?.maturityDate && f.dealDate) {
|
||||
f.lastIntDt = lastHalfYearlyCouponDate(s.maturityDate, f.dealDate) || f.lastIntDt;
|
||||
// The last coupon on or before SETTLEMENT (one falling between deal and settlement goes to the seller), or the
|
||||
// issue date when the security has not paid its first coupon yet — the same date the backend insists on.
|
||||
const settlement = this.purchaseSettlementDate;
|
||||
if (!this.isNonSlrPurchase && s?.maturityDate && settlement) {
|
||||
let last = lastHalfYearlyCouponDate(s.maturityDate, settlement);
|
||||
if (last && s.issueDate && parseDDMMYYYY(last) < parseDDMMYYYY(s.issueDate)) {
|
||||
last = s.issueDate;
|
||||
}
|
||||
f.lastIntDt = last || f.lastIntDt;
|
||||
}
|
||||
}
|
||||
|
||||
/** A different T+n moves the settlement date, and with it the last coupon the deal starts from. */
|
||||
onSettlementTypeChange(): void {
|
||||
this.suggestLastIntDate();
|
||||
}
|
||||
|
||||
/** Settlement Amt of Purchase — book value, accrued interest payable to the seller and the charges. */
|
||||
get purchaseSettlementAmount(): number {
|
||||
return this.purchaseBookValue + this.purchaseAccruedInterest + this.purchaseTotalCharges;
|
||||
@@ -817,7 +877,8 @@ export class BondInvestmentComponent implements OnInit {
|
||||
|
||||
/** Parties offered for a new deal — inactive parties are hidden. */
|
||||
get activePartyOptions(): typeof PARTY_MASTER {
|
||||
return this.partyOptions.filter((p) => p.status !== 'INACTIVE');
|
||||
// An FD bank is where deposits are placed, not a dealer securities are bought from or sold to.
|
||||
return this.partyOptions.filter((p) => p.status !== 'INACTIVE' && p.bondType !== 'FD');
|
||||
}
|
||||
|
||||
get purchaseInvestmentGlCode(): string {
|
||||
@@ -898,7 +959,8 @@ export class BondInvestmentComponent implements OnInit {
|
||||
} else {
|
||||
if (!f.faceValue) missing.push('Face Value');
|
||||
if (!f.purchaseRate) missing.push('Purchase Rate');
|
||||
if (!f.lastIntDt) missing.push('Last Interest Date');
|
||||
// Non-SLR has no interest scheme, so no last interest date (the field is hidden).
|
||||
if (!this.isNonSlrPurchase && !f.lastIntDt) missing.push('Last Interest Date');
|
||||
}
|
||||
if (!f.category) missing.push('Category');
|
||||
if (!f.crToAc) missing.push('CR. To A/c');
|
||||
@@ -926,7 +988,11 @@ export class BondInvestmentComponent implements OnInit {
|
||||
settlementType: f.settlementType,
|
||||
...(this.isMutualFund
|
||||
? { considerAmt: f.considerAmt, nav: f.nav }
|
||||
: { faceValue: f.faceValue, purchaseRate: f.purchaseRate, lastIntDate: f.lastIntDt }),
|
||||
: {
|
||||
faceValue: f.faceValue,
|
||||
purchaseRate: f.purchaseRate,
|
||||
...(this.isNonSlrPurchase ? {} : { lastIntDate: f.lastIntDt })
|
||||
}),
|
||||
stampDuty: f.stampDuty ?? null,
|
||||
brokerage: f.brokerage ?? null,
|
||||
otherCharges: f.otherCharges ?? null,
|
||||
@@ -1095,14 +1161,24 @@ export class BondInvestmentComponent implements OnInit {
|
||||
redeeming = false;
|
||||
|
||||
/** Settled holdings of this type still held — the ones that can be sold or redeemed. */
|
||||
/** A mutual fund is sold / redeemed at its NAV per unit, not at a price per ₹100. */
|
||||
get saleIsMutualFund(): boolean {
|
||||
return this.holdings.find((h) => h.id === this.sale.purchaseId)?.instrumentType === 'MUTUAL_FUND';
|
||||
}
|
||||
|
||||
get saleableHoldings(): InvestmentHolding[] {
|
||||
return this.activeHoldingsForType.filter((h) => !!h.id);
|
||||
}
|
||||
|
||||
/** Redemption is at maturity, so only holdings that have reached it are offered. */
|
||||
/**
|
||||
* Redemption is at maturity, so only holdings that have reached it are offered. A mutual fund has no maturity: it
|
||||
* is redeemed at its NAV through the Sell entry.
|
||||
*/
|
||||
get redeemableHoldings(): InvestmentHolding[] {
|
||||
const today = parseDDMMYYYY(this.asOnDate);
|
||||
return this.saleableHoldings.filter((h) => !!h.maturityDate && parseDDMMYYYY(h.maturityDate) <= today);
|
||||
return this.saleableHoldings.filter(
|
||||
(h) => h.instrumentType !== 'MUTUAL_FUND' && !!h.maturityDate && parseDDMMYYYY(h.maturityDate) <= today
|
||||
);
|
||||
}
|
||||
|
||||
/** Shows what the redemption will post, or why it cannot be done yet (before maturity, coupon not posted). */
|
||||
@@ -1158,6 +1234,7 @@ export class BondInvestmentComponent implements OnInit {
|
||||
(h) =>
|
||||
h.id != null &&
|
||||
h.status === 'ACTIVE' &&
|
||||
h.instrumentType === 'GOVT_SECURITY' &&
|
||||
(!this.intType || h.instrumentType === this.intType) &&
|
||||
(!this.intScript || h.scriptCode === this.intScript)
|
||||
);
|
||||
@@ -1257,7 +1334,7 @@ export class BondInvestmentComponent implements OnInit {
|
||||
|
||||
/** 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);
|
||||
return (this.intSchedule?.rows ?? []).filter((r) => r.due && !r.posted).reduce((sum, r) => sum + r.amount, 0);
|
||||
}
|
||||
|
||||
// -------------------- Sell Entry --------------------
|
||||
@@ -1310,8 +1387,12 @@ export class BondInvestmentComponent implements OnInit {
|
||||
}
|
||||
|
||||
/** Price, interest from the buyer, cost and profit / loss worked out by the backend, as Settle will post them. */
|
||||
/** Each quote request is numbered so a slower, older response cannot overwrite the latest one. */
|
||||
private saleQuoteSeq = 0;
|
||||
|
||||
refreshSaleQuote(): void {
|
||||
const f = this.sale;
|
||||
const seq = ++this.saleQuoteSeq;
|
||||
this.saleQuote = null;
|
||||
this.saleQuoteError = '';
|
||||
if (!f.purchaseId || !f.units || !f.saleRate || !f.dealDate) {
|
||||
@@ -1329,8 +1410,16 @@ export class BondInvestmentComponent implements OnInit {
|
||||
...(bankGlAccountId ? { bankGlAccountId } : {})
|
||||
})
|
||||
.subscribe({
|
||||
next: (q) => (this.saleQuote = q),
|
||||
error: (err) => (this.saleQuoteError = apiErrorMessage(err))
|
||||
next: (q) => {
|
||||
if (seq === this.saleQuoteSeq) {
|
||||
this.saleQuote = q;
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
if (seq === this.saleQuoteSeq) {
|
||||
this.saleQuoteError = apiErrorMessage(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1434,6 +1523,11 @@ export class BondInvestmentComponent implements OnInit {
|
||||
return !!q.premiumCleared && q.premiumCleared > 0 && !!q.premiumGlCode && q.premiumGlCode !== q.investmentGlCode;
|
||||
}
|
||||
|
||||
/** Non-SLR gain on sale / redemption is its income, 400402; a shortfall is a loss, 500605 (Jitendra Sir). */
|
||||
isNonSlrSale(q: InvestmentSaleDto): boolean {
|
||||
return NON_SLR_TYPES.includes(q.bondType);
|
||||
}
|
||||
|
||||
/** Book value of a holding as it stands: cost less what sales and write-offs have taken off. */
|
||||
carryingValue(h: InvestmentHolding): number {
|
||||
const taken = this.sales
|
||||
@@ -1573,6 +1667,11 @@ export class BondInvestmentComponent implements OnInit {
|
||||
goiRemarks = '';
|
||||
goiSaving = false;
|
||||
|
||||
/** Only Govt Security holdings can be transferred (the backend refuses bonds, which would skip the non-SLR checks). */
|
||||
get goiTransferHoldings(): InvestmentHolding[] {
|
||||
return this.saleableHoldings.filter((h) => h.instrumentType === 'GOVT_SECURITY');
|
||||
}
|
||||
|
||||
get goiHolding(): InvestmentHolding | undefined {
|
||||
return this.holdings.find((h) => h.id === this.goiPurchaseId);
|
||||
}
|
||||
@@ -1763,6 +1862,10 @@ export class BondInvestmentComponent implements OnInit {
|
||||
* Coupons fall every six months from the purchase's last interest date; a coupon received resets it to zero.
|
||||
*/
|
||||
accrued(h: InvestmentHolding): number {
|
||||
// Non-SLR has no interest scheme: nothing accrues.
|
||||
if (NON_SLR_TYPES.includes(h.instrumentType)) {
|
||||
return 0;
|
||||
}
|
||||
if (!h.lastIntDate || h.status !== 'ACTIVE') {
|
||||
return h.lastIntDate ? 0 : accruedInterest(h, this.asOnDate);
|
||||
}
|
||||
@@ -1774,8 +1877,11 @@ export class BondInvestmentComponent implements OnInit {
|
||||
return Math.round((h.faceValue * h.interestRate * days360(lastCoupon, asOn)) / 360) / 100;
|
||||
}
|
||||
|
||||
/** A fund or share has no maturity: it comes back at the book value still held, as Reports and the Dashboard show. */
|
||||
maturity(h: InvestmentHolding): number {
|
||||
return maturityAmount(h);
|
||||
return h.instrumentType === 'MUTUAL_FUND' || h.instrumentType === 'SHARE'
|
||||
? this.carryingValue(h)
|
||||
: maturityAmount(h);
|
||||
}
|
||||
|
||||
daysLeft(h: InvestmentHolding): number {
|
||||
@@ -1833,7 +1939,7 @@ export class BondInvestmentComponent implements OnInit {
|
||||
h.faceValue,
|
||||
h.interestRate,
|
||||
h.purchaseDate,
|
||||
h.maturityDate,
|
||||
h.instrumentType === 'MUTUAL_FUND' ? '' : h.maturityDate,
|
||||
this.accrued(h),
|
||||
this.maturity(h),
|
||||
h.status
|
||||
|
||||
Reference in New Issue
Block a user