feat(investment): FD TDS fields, GOI interest to the paisa, compact home reminders, deal no in holdings and settle guard

This commit is contained in:
2026-10-20 16:16:12 +05:30
parent d584af8e54
commit 6890584b16
11 changed files with 316 additions and 82 deletions
@@ -131,7 +131,7 @@
<table class="pg-table">
<thead>
<tr>
<th>Cert No</th>
<th>Deal No / Cert No</th>
<th>Script</th>
<th>Party</th>
<th class="num">Units</th>
@@ -150,7 +150,8 @@
@for (h of holdingsForType; track h.certNo) {
<tr>
<td>
<span class="pg-chip">{{ h.certNo }}</span>
<span class="pg-chip">{{ h.dealNo }}</span>
<br /><small>Cert {{ h.certNo }}</small>
</td>
<td>{{ h.scriptName }}</td>
<td>{{ h.party }}</td>
@@ -169,8 +170,13 @@
</td>
<td>
@if (h.status === 'PENDING_SETTLEMENT') {
<button type="button" class="pg-btn pg-btn--primary" (click)="settleDeal(h)">
Settle ({{ h.settlementDate }})
<button
type="button"
class="pg-btn pg-btn--primary"
[disabled]="settlingDealId !== null"
(click)="settleDeal(h)"
>
{{ settlingDealId === h.id ? 'Settling…' : 'Settle (' + h.settlementDate + ')' }}
</button>
} @else if (h.glTransactionId) {
<span class="pg-chip" title="Journal entry transaction">GL {{ h.glTransactionId }}</span>
@@ -357,7 +363,7 @@
@if (!isMutualFund) {
<label class="inv-form-field"
><span>Premium (+) / Discount (−)</span
><input type="text" [value]="'₹' + (purchasePremiumDiscount | number: '1.0-0')" readonly
><input type="text" [value]="'₹' + (purchasePremiumDiscount | number: '1.2-2')" readonly
/></label>
<label class="inv-form-field"
><span>Accrued Interest ({{ purchaseAccruedDays }} days)</span
@@ -908,16 +914,16 @@
<td class="num col-in">₹{{ r.amount | number: '1.2-2' }}</td>
<td style="white-space: nowrap; font-size: 12px">
@if (r.type === 'COUPON') {
<div>Dr Bank ₹{{ r.grossInterest | number: '1.0-0' }}</div>
<div>Dr Bank ₹{{ r.grossInterest | number: '1.2-2' }}</div>
@if (r.alreadyBooked > 0) {
<div>
Cr {{ interestReceivableGl.glCode }} Int. Receivable ₹{{ r.alreadyBooked | number: '1.0-0' }}
Cr {{ interestReceivableGl.glCode }} Int. Receivable ₹{{ r.alreadyBooked | number: '1.2-2' }}
</div>
}
<div>Cr {{ sc.interestGlCode }} Int. Income ₹{{ r.amount | number: '1.0-0' }}</div>
<div>Cr {{ sc.interestGlCode }} Int. Income ₹{{ r.amount | number: '1.2-2' }}</div>
} @else {
<div>Dr {{ interestReceivableGl.glCode }} Int. Receivable ₹{{ r.amount | number: '1.0-0' }}</div>
<div>Cr {{ sc.interestGlCode }} Int. Income ₹{{ r.amount | number: '1.0-0' }}</div>
<div>Dr {{ interestReceivableGl.glCode }} Int. Receivable ₹{{ r.amount | number: '1.2-2' }}</div>
<div>Cr {{ sc.interestGlCode }} Int. Income ₹{{ r.amount | number: '1.2-2' }}</div>
}
</td>
<td style="white-space: nowrap">
@@ -1129,8 +1135,22 @@
<td class="num">₹{{ s.netAmount | number: '1.2-2' }}</td>
<td class="num">₹{{ s.profitLoss | number: '1.2-2' }}</td>
<td style="white-space: nowrap">
<button type="button" class="pg-btn pg-btn--primary" (click)="settleSale(s)">Settle</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="dropSale(s)">Drop</button>
<button
type="button"
class="pg-btn pg-btn--primary"
[disabled]="busySaleId !== null"
(click)="settleSale(s)"
>
{{ busySaleId === s.id ? 'Settling…' : 'Settle' }}
</button>
<button
type="button"
class="pg-btn pg-btn--ghost"
[disabled]="busySaleId !== null"
(click)="dropSale(s)"
>
Drop
</button>
</td>
</tr>
} @empty {
@@ -604,7 +604,7 @@ export class BondInvestmentComponent implements OnInit {
return this.purchaseBaseAmount;
}
const rate = f.purchaseRate || 100;
return Math.round((this.purchaseBaseAmount * rate) / 100);
return Math.round(this.purchaseBaseAmount * rate) / 100;
}
/** Stamp duty + brokerage + other charges — paid with the settlement, booked to expense GLs. */
@@ -627,7 +627,7 @@ export class BondInvestmentComponent implements OnInit {
get purchaseNextCouponAmount(): number {
const f = this.purchaseForm;
return f.intRate ? Math.round((this.purchaseBaseAmount * f.intRate) / 200) : 0;
return f.intRate ? Math.round((this.purchaseBaseAmount * f.intRate) / 2) / 100 : 0;
}
get purchaseDaysToNextCoupon(): number {
@@ -648,7 +648,7 @@ export class BondInvestmentComponent implements OnInit {
if (!f.intRate || !f.lastIntDt || !f.dealDate) {
return 0;
}
return Math.round((this.purchaseBaseAmount * (f.intRate / 100) * this.purchaseAccruedDays) / 360);
return Math.round((this.purchaseBaseAmount * f.intRate * this.purchaseAccruedDays) / 360) / 100;
}
get purchaseAccruedDays(): number {
@@ -663,7 +663,7 @@ export class BondInvestmentComponent implements OnInit {
if (this.isMutualFund || !f.faceValue || !f.purchaseRate) {
return 0;
}
return Math.round((f.faceValue * (f.purchaseRate - 100)) / 100);
return Math.round(f.faceValue * (f.purchaseRate - 100)) / 100;
}
/** Re-suggests the last coupon date when the deal date changes. */
@@ -1022,12 +1022,17 @@ export class BondInvestmentComponent implements OnInit {
}
/** Posts a pending deal to GL dated its settlement date; under maker-checker it waits in the Checker Inbox. */
/** The purchase being settled; its button stays disabled so it cannot be sent twice. */
settlingDealId: number | null = null;
settleDeal(h: InvestmentHolding): void {
if (!h.id) {
if (!h.id || this.settlingDealId !== null) {
return;
}
this.settlingDealId = h.id;
this.investmentService.settlePurchase(h.id).subscribe({
next: (res) => {
this.settlingDealId = null;
this.toast(
res.rollbackTransaction
? `Settlement of deal ${h.dealNo} sent for approval (Checker Inbox).`
@@ -1035,7 +1040,10 @@ export class BondInvestmentComponent implements OnInit {
);
this.loadPurchases();
},
error: (err) => this.toast(apiErrorMessage(err))
error: (err) => {
this.settlingDealId = null;
this.toast(apiErrorMessage(err));
}
});
}
@@ -1345,29 +1353,44 @@ export class BondInvestmentComponent implements OnInit {
});
}
/** The pending sale being settled or dropped; its buttons stay disabled so it cannot be sent twice. */
busySaleId: number | null = null;
settleSale(s: InvestmentSaleDto): void {
if (!s.id) {
if (!s.id || this.busySaleId !== null) {
return;
}
this.busySaleId = s.id;
this.investmentService.settleSale(s.id).subscribe({
next: (res) => {
this.busySaleId = null;
this.sales = this.sales.filter((x) => x.id !== s.id);
this.toast(`Sale of deal ${s.dealNo} settled — voucher ${res.resourceIdentifier ?? ''}.`);
this.loadPurchases();
},
error: (err) => this.toast(apiErrorMessage(err))
error: (err) => {
this.busySaleId = null;
this.toast(apiErrorMessage(err));
}
});
}
dropSale(s: InvestmentSaleDto): void {
if (!s.id) {
if (!s.id || this.busySaleId !== null) {
return;
}
this.busySaleId = s.id;
this.investmentService.deleteSale(s.id).subscribe({
next: () => {
this.busySaleId = null;
this.sales = this.sales.filter((x) => x.id !== s.id);
this.toast(`Pending sale of deal ${s.dealNo} dropped.`);
this.loadInterestLog();
},
error: (err) => this.toast(apiErrorMessage(err))
error: (err) => {
this.busySaleId = null;
this.toast(apiErrorMessage(err));
}
});
}
@@ -1723,7 +1746,7 @@ export class BondInvestmentComponent implements OnInit {
for (let k = 1; parseDDMMYYYY(addPeriod(h.lastIntDate, 0, 6 * k)) <= parseDDMMYYYY(asOn); k++) {
lastCoupon = addPeriod(h.lastIntDate, 0, 6 * k);
}
return Math.round((h.faceValue * (h.interestRate / 100) * days360(lastCoupon, asOn)) / 360);
return Math.round((h.faceValue * h.interestRate * days360(lastCoupon, asOn)) / 360) / 100;
}
maturity(h: InvestmentHolding): number {
@@ -1749,6 +1772,7 @@ export class BondInvestmentComponent implements OnInit {
title: `${this.instrumentLabel(this.bondType)} Holdings Register`,
subtitle: `As on ${this.asOnDate}`,
headers: [
'Deal No',
'Cert No',
'Script',
'Party',
@@ -1762,7 +1786,8 @@ export class BondInvestmentComponent implements OnInit {
'Status'
],
widths: [
14,
16,
10,
26,
22,
12,
@@ -1775,6 +1800,7 @@ export class BondInvestmentComponent implements OnInit {
12
],
rows: this.holdingsForType.map((h) => [
h.dealNo ?? '',
h.certNo,
h.scriptName,
h.party,