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:
@@ -47,43 +47,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pending investment interest vouchers (coupon / provision due now or within two days) -->
|
<!-- Pending investment entries (GOI / bond interest, FDs with other banks): one line each, details on demand -->
|
||||||
@if (pendingInvestmentInterest.length) {
|
@if (pendingInvestmentInterest.length || pendingFd.length) {
|
||||||
<a class="investment-interest-alert" routerLink="/investment/bonds" [queryParams]="{ tab: 'interest' }">
|
<div class="investment-interest-alert">
|
||||||
<span class="iia-icon">🔔</span>
|
<span class="iia-icon">🔔</span>
|
||||||
<span class="iia-text">
|
<div class="iia-text">
|
||||||
<strong>{{ pendingInvestmentInterest.length }} investment interest voucher(s) pending</strong>
|
<strong>Investment entries pending</strong>
|
||||||
@for (t of pendingInvestmentInterest; track t.purchaseId) {
|
@if (pendingInvestmentInterest.length) {
|
||||||
<span class="iia-item">
|
<div class="iia-row">
|
||||||
{{ t.lineType === 'COUPON' ? 'Coupon' : 'Provision' }} · {{ t.scriptDesc }} · deal {{ t.dealNo }} ·
|
<span class="iia-summary"
|
||||||
{{ t.overdue ? 'overdue since ' + t.intDate : t.postable ? 'due today' : 'due ' + t.intDate }}
|
>GOI / Bonds: {{ pendingInvestmentInterest.length }} interest voucher(s){{
|
||||||
</span>
|
overdueNote(pendingInvestmentInterest)
|
||||||
|
}}</span
|
||||||
|
>
|
||||||
|
<button type="button" class="iia-toggle" (click)="showInterestDetails = !showInterestDetails">
|
||||||
|
{{ showInterestDetails ? 'Hide' : 'Details' }}
|
||||||
|
</button>
|
||||||
|
<a class="iia-action" routerLink="/investment/bonds" [queryParams]="{ tab: 'interest' }"
|
||||||
|
>Open Interest Entry →</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
@if (showInterestDetails) {
|
||||||
|
@for (t of pendingInvestmentInterest.slice(0, detailLimit); track t.purchaseId) {
|
||||||
|
<a class="iia-item" routerLink="/investment/bonds" [queryParams]="{ tab: 'interest' }">
|
||||||
|
{{ t.lineType === 'COUPON' ? 'Coupon' : 'Provision' }} · {{ t.scriptDesc }} · deal {{ t.dealNo }} ·
|
||||||
|
{{ t.overdue ? 'overdue since ' + t.intDate : t.postable ? 'due today' : 'due ' + t.intDate }}
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
@if (pendingInvestmentInterest.length > detailLimit) {
|
||||||
|
<span class="iia-more">+{{ pendingInvestmentInterest.length - detailLimit }} more in Interest Entry</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</span>
|
@if (pendingFd.length) {
|
||||||
<span class="iia-action">Open Interest Entry →</span>
|
<div class="iia-row">
|
||||||
</a>
|
<span class="iia-summary"
|
||||||
}
|
>FD with banks: {{ pendingFd.length }} entry(ies){{ overdueNote(pendingFd) }}</span
|
||||||
|
>
|
||||||
<!-- FDs placed with other banks: interest vouchers and maturities due -->
|
<button type="button" class="iia-toggle" (click)="showFdDetails = !showFdDetails">
|
||||||
@if (pendingFd.length) {
|
{{ showFdDetails ? 'Hide' : 'Details' }}
|
||||||
<a
|
</button>
|
||||||
class="investment-interest-alert"
|
<a class="iia-action" routerLink="/investment/fd">Open FD Investment →</a>
|
||||||
routerLink="/investment/fd"
|
</div>
|
||||||
[queryParams]="{ tab: 'interest', fd: pendingFd[0].fdId }"
|
@if (showFdDetails) {
|
||||||
>
|
@for (t of pendingFd.slice(0, detailLimit); track t.fdId + t.lineType + t.intDate) {
|
||||||
<span class="iia-icon">🔔</span>
|
<a class="iia-item" routerLink="/investment/fd" [queryParams]="{ tab: 'interest', fd: t.fdId }">
|
||||||
<span class="iia-text">
|
{{ t.lineType === 'MATURITY' ? 'Maturity' : t.lineType === 'PAYOUT' ? 'Interest payout' : 'Provision' }}
|
||||||
<strong>{{ pendingFd.length }} FD investment entry(ies) pending</strong>
|
· {{ t.partyName }} · FD {{ t.dealNo }} ·
|
||||||
@for (t of pendingFd; track t.fdId) {
|
{{ t.overdue ? 'overdue since ' + t.intDate : t.postable ? 'due today' : 'due ' + t.intDate }}
|
||||||
<span class="iia-item">
|
</a>
|
||||||
{{ t.lineType === 'MATURITY' ? 'Maturity' : t.lineType === 'PAYOUT' ? 'Interest payout' : 'Provision' }} ·
|
}
|
||||||
{{ t.partyName }} · FD {{ t.dealNo }} ·
|
@if (pendingFd.length > detailLimit) {
|
||||||
{{ t.overdue ? 'overdue since ' + t.intDate : t.postable ? 'due today' : 'due ' + t.intDate }}
|
<span class="iia-more">+{{ pendingFd.length - detailLimit }} more in FD Investment</span>
|
||||||
</span>
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</span>
|
</div>
|
||||||
<span class="iia-action">Open FD Investment →</span>
|
</div>
|
||||||
</a>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Quick Action Cards -->
|
<!-- Quick Action Cards -->
|
||||||
|
|||||||
@@ -447,16 +447,55 @@ $accent: #0ea5e9;
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2px;
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iia-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iia-summary {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iia-toggle {
|
||||||
|
padding: 2px 10px;
|
||||||
|
border: 1px solid #e0a43a;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iia-item {
|
.iia-item {
|
||||||
|
padding-left: 12px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iia-more {
|
||||||
|
padding-left: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.iia-item:hover {
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iia-action {
|
.iia-action {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,8 +124,23 @@ export class HomeComponent implements OnInit, AfterViewInit {
|
|||||||
pendingInvestmentInterest: InvestmentInterestDueDto[] = [];
|
pendingInvestmentInterest: InvestmentInterestDueDto[] = [];
|
||||||
/** FDs placed with other banks: interest vouchers and maturities due now or within two days. */
|
/** FDs placed with other banks: interest vouchers and maturities due now or within two days. */
|
||||||
pendingFd: InvestmentFdDueDto[] = [];
|
pendingFd: InvestmentFdDueDto[] = [];
|
||||||
|
/** The lists stay folded to one line each; the user opens them when needed. */
|
||||||
|
showInterestDetails = false;
|
||||||
|
showFdDetails = false;
|
||||||
|
readonly detailLimit = 5;
|
||||||
private readonly investmentService = inject(InvestmentService);
|
private readonly investmentService = inject(InvestmentService);
|
||||||
|
|
||||||
|
/** " · 3 overdue, oldest 14-08-2025" for a list of due entries (dates DD-MM-YYYY); empty when none is overdue. */
|
||||||
|
overdueNote(items: Array<{ overdue?: boolean; intDate: string }>): string {
|
||||||
|
const late = items.filter((t) => t.overdue);
|
||||||
|
if (!late.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const key = (d: string) => d.split('-').reverse().join('');
|
||||||
|
const oldest = late.map((t) => t.intDate).sort((a, b) => key(a).localeCompare(key(b)))[0];
|
||||||
|
return ` · ${late.length} overdue, oldest ${oldest}`;
|
||||||
|
}
|
||||||
|
|
||||||
/** Silent when the user cannot see investments or nothing is due — the strip simply does not render. */
|
/** Silent when the user cannot see investments or nothing is due — the strip simply does not render. */
|
||||||
private loadPendingInvestmentInterest(): void {
|
private loadPendingInvestmentInterest(): void {
|
||||||
this.investmentService.getDueInterest().subscribe({
|
this.investmentService.getDueInterest().subscribe({
|
||||||
|
|||||||
@@ -131,7 +131,7 @@
|
|||||||
<table class="pg-table">
|
<table class="pg-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Cert No</th>
|
<th>Deal No / Cert No</th>
|
||||||
<th>Script</th>
|
<th>Script</th>
|
||||||
<th>Party</th>
|
<th>Party</th>
|
||||||
<th class="num">Units</th>
|
<th class="num">Units</th>
|
||||||
@@ -150,7 +150,8 @@
|
|||||||
@for (h of holdingsForType; track h.certNo) {
|
@for (h of holdingsForType; track h.certNo) {
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<span class="pg-chip">{{ h.certNo }}</span>
|
<span class="pg-chip">{{ h.dealNo }}</span>
|
||||||
|
<br /><small>Cert {{ h.certNo }}</small>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ h.scriptName }}</td>
|
<td>{{ h.scriptName }}</td>
|
||||||
<td>{{ h.party }}</td>
|
<td>{{ h.party }}</td>
|
||||||
@@ -169,8 +170,13 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if (h.status === 'PENDING_SETTLEMENT') {
|
@if (h.status === 'PENDING_SETTLEMENT') {
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="settleDeal(h)">
|
<button
|
||||||
Settle ({{ h.settlementDate }})
|
type="button"
|
||||||
|
class="pg-btn pg-btn--primary"
|
||||||
|
[disabled]="settlingDealId !== null"
|
||||||
|
(click)="settleDeal(h)"
|
||||||
|
>
|
||||||
|
{{ settlingDealId === h.id ? 'Settling…' : 'Settle (' + h.settlementDate + ')' }}
|
||||||
</button>
|
</button>
|
||||||
} @else if (h.glTransactionId) {
|
} @else if (h.glTransactionId) {
|
||||||
<span class="pg-chip" title="Journal entry transaction">GL {{ h.glTransactionId }}</span>
|
<span class="pg-chip" title="Journal entry transaction">GL {{ h.glTransactionId }}</span>
|
||||||
@@ -357,7 +363,7 @@
|
|||||||
@if (!isMutualFund) {
|
@if (!isMutualFund) {
|
||||||
<label class="inv-form-field"
|
<label class="inv-form-field"
|
||||||
><span>Premium (+) / Discount (−)</span
|
><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>
|
||||||
<label class="inv-form-field"
|
<label class="inv-form-field"
|
||||||
><span>Accrued Interest ({{ purchaseAccruedDays }} days)</span
|
><span>Accrued Interest ({{ purchaseAccruedDays }} days)</span
|
||||||
@@ -908,16 +914,16 @@
|
|||||||
<td class="num col-in">₹{{ r.amount | number: '1.2-2' }}</td>
|
<td class="num col-in">₹{{ r.amount | number: '1.2-2' }}</td>
|
||||||
<td style="white-space: nowrap; font-size: 12px">
|
<td style="white-space: nowrap; font-size: 12px">
|
||||||
@if (r.type === 'COUPON') {
|
@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) {
|
@if (r.alreadyBooked > 0) {
|
||||||
<div>
|
<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>
|
||||||
}
|
}
|
||||||
<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 {
|
} @else {
|
||||||
<div>Dr {{ interestReceivableGl.glCode }} Int. Receivable ₹{{ 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.0-0' }}</div>
|
<div>Cr {{ sc.interestGlCode }} Int. Income ₹{{ r.amount | number: '1.2-2' }}</div>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td style="white-space: nowrap">
|
<td style="white-space: nowrap">
|
||||||
@@ -1129,8 +1135,22 @@
|
|||||||
<td class="num">₹{{ s.netAmount | number: '1.2-2' }}</td>
|
<td class="num">₹{{ s.netAmount | number: '1.2-2' }}</td>
|
||||||
<td class="num">₹{{ s.profitLoss | number: '1.2-2' }}</td>
|
<td class="num">₹{{ s.profitLoss | number: '1.2-2' }}</td>
|
||||||
<td style="white-space: nowrap">
|
<td style="white-space: nowrap">
|
||||||
<button type="button" class="pg-btn pg-btn--primary" (click)="settleSale(s)">Settle</button>
|
<button
|
||||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="dropSale(s)">Drop</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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
} @empty {
|
} @empty {
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
return this.purchaseBaseAmount;
|
return this.purchaseBaseAmount;
|
||||||
}
|
}
|
||||||
const rate = f.purchaseRate || 100;
|
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. */
|
/** 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 {
|
get purchaseNextCouponAmount(): number {
|
||||||
const f = this.purchaseForm;
|
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 {
|
get purchaseDaysToNextCoupon(): number {
|
||||||
@@ -648,7 +648,7 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
if (!f.intRate || !f.lastIntDt || !f.dealDate) {
|
if (!f.intRate || !f.lastIntDt || !f.dealDate) {
|
||||||
return 0;
|
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 {
|
get purchaseAccruedDays(): number {
|
||||||
@@ -663,7 +663,7 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
if (this.isMutualFund || !f.faceValue || !f.purchaseRate) {
|
if (this.isMutualFund || !f.faceValue || !f.purchaseRate) {
|
||||||
return 0;
|
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. */
|
/** 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. */
|
/** 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 {
|
settleDeal(h: InvestmentHolding): void {
|
||||||
if (!h.id) {
|
if (!h.id || this.settlingDealId !== null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.settlingDealId = h.id;
|
||||||
this.investmentService.settlePurchase(h.id).subscribe({
|
this.investmentService.settlePurchase(h.id).subscribe({
|
||||||
next: (res) => {
|
next: (res) => {
|
||||||
|
this.settlingDealId = null;
|
||||||
this.toast(
|
this.toast(
|
||||||
res.rollbackTransaction
|
res.rollbackTransaction
|
||||||
? `Settlement of deal ${h.dealNo} sent for approval (Checker Inbox).`
|
? `Settlement of deal ${h.dealNo} sent for approval (Checker Inbox).`
|
||||||
@@ -1035,7 +1040,10 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
this.loadPurchases();
|
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 {
|
settleSale(s: InvestmentSaleDto): void {
|
||||||
if (!s.id) {
|
if (!s.id || this.busySaleId !== null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.busySaleId = s.id;
|
||||||
this.investmentService.settleSale(s.id).subscribe({
|
this.investmentService.settleSale(s.id).subscribe({
|
||||||
next: (res) => {
|
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.toast(`Sale of deal ${s.dealNo} settled — voucher ${res.resourceIdentifier ?? ''}.`);
|
||||||
this.loadPurchases();
|
this.loadPurchases();
|
||||||
},
|
},
|
||||||
error: (err) => this.toast(apiErrorMessage(err))
|
error: (err) => {
|
||||||
|
this.busySaleId = null;
|
||||||
|
this.toast(apiErrorMessage(err));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dropSale(s: InvestmentSaleDto): void {
|
dropSale(s: InvestmentSaleDto): void {
|
||||||
if (!s.id) {
|
if (!s.id || this.busySaleId !== null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.busySaleId = s.id;
|
||||||
this.investmentService.deleteSale(s.id).subscribe({
|
this.investmentService.deleteSale(s.id).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
|
this.busySaleId = null;
|
||||||
|
this.sales = this.sales.filter((x) => x.id !== s.id);
|
||||||
this.toast(`Pending sale of deal ${s.dealNo} dropped.`);
|
this.toast(`Pending sale of deal ${s.dealNo} dropped.`);
|
||||||
this.loadInterestLog();
|
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++) {
|
for (let k = 1; parseDDMMYYYY(addPeriod(h.lastIntDate, 0, 6 * k)) <= parseDDMMYYYY(asOn); k++) {
|
||||||
lastCoupon = addPeriod(h.lastIntDate, 0, 6 * 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 {
|
maturity(h: InvestmentHolding): number {
|
||||||
@@ -1749,6 +1772,7 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
title: `${this.instrumentLabel(this.bondType)} Holdings Register`,
|
title: `${this.instrumentLabel(this.bondType)} Holdings Register`,
|
||||||
subtitle: `As on ${this.asOnDate}`,
|
subtitle: `As on ${this.asOnDate}`,
|
||||||
headers: [
|
headers: [
|
||||||
|
'Deal No',
|
||||||
'Cert No',
|
'Cert No',
|
||||||
'Script',
|
'Script',
|
||||||
'Party',
|
'Party',
|
||||||
@@ -1762,7 +1786,8 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
'Status'
|
'Status'
|
||||||
],
|
],
|
||||||
widths: [
|
widths: [
|
||||||
14,
|
16,
|
||||||
|
10,
|
||||||
26,
|
26,
|
||||||
22,
|
22,
|
||||||
12,
|
12,
|
||||||
@@ -1775,6 +1800,7 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
12
|
12
|
||||||
],
|
],
|
||||||
rows: this.holdingsForType.map((h) => [
|
rows: this.holdingsForType.map((h) => [
|
||||||
|
h.dealNo ?? '',
|
||||||
h.certNo,
|
h.certNo,
|
||||||
h.scriptName,
|
h.scriptName,
|
||||||
h.party,
|
h.party,
|
||||||
|
|||||||
@@ -106,6 +106,9 @@
|
|||||||
@if (f.closureTds) {
|
@if (f.closureTds) {
|
||||||
· TDS ₹{{ f.closureTds | number: '1.2-2' }}
|
· TDS ₹{{ f.closureTds | number: '1.2-2' }}
|
||||||
}
|
}
|
||||||
|
@if (f.closurePenal) {
|
||||||
|
· penal ₹{{ f.closurePenal | number: '1.2-2' }}
|
||||||
|
}
|
||||||
</small>
|
</small>
|
||||||
}
|
}
|
||||||
@if (f.renewedFromDealNo) {
|
@if (f.renewedFromDealNo) {
|
||||||
@@ -240,7 +243,14 @@
|
|||||||
/></label>
|
/></label>
|
||||||
<label class="inv-form-field"
|
<label class="inv-form-field"
|
||||||
><span>Maturity Amount</span
|
><span>Maturity Amount</span
|
||||||
><input type="text" [value]="'₹' + (q.expectedMaturityAmount | number: '1.2-2')" readonly
|
><input
|
||||||
|
type="text"
|
||||||
|
[value]="
|
||||||
|
q.expectedMaturityAmount === null || q.expectedMaturityAmount === undefined
|
||||||
|
? ''
|
||||||
|
: '₹' + (q.expectedMaturityAmount | number: '1.2-2')
|
||||||
|
"
|
||||||
|
readonly
|
||||||
/></label>
|
/></label>
|
||||||
@if (q.payout !== 'MATURITY') {
|
@if (q.payout !== 'MATURITY') {
|
||||||
<label class="inv-form-field"
|
<label class="inv-form-field"
|
||||||
@@ -416,12 +426,18 @@
|
|||||||
<div class="inv-form-grid">
|
<div class="inv-form-grid">
|
||||||
<label class="inv-form-field"
|
<label class="inv-form-field"
|
||||||
><span>Amount received from Bank (₹)</span
|
><span>Amount received from Bank (₹)</span
|
||||||
><input type="number" min="0" [(ngModel)]="payoutReceived" [placeholder]="r.grossInterest"
|
><input
|
||||||
/></label>
|
type="number"
|
||||||
<label class="inv-form-field"
|
min="0"
|
||||||
><span>TDS deducted (₹)</span
|
[(ngModel)]="payoutReceived"
|
||||||
><input type="number" min="0" [(ngModel)]="payoutTds" placeholder="0"
|
[placeholder]="r.grossInterest - (r.tds || 0)"
|
||||||
/></label>
|
/></label>
|
||||||
|
@if (s.fd.tdsRate) {
|
||||||
|
<label class="inv-form-field"
|
||||||
|
><span>TDS deducted (₹) · auto at {{ s.fd.tdsRate }}%</span
|
||||||
|
><input type="number" min="0" [(ngModel)]="payoutTds" [placeholder]="r.tds || 0"
|
||||||
|
/></label>
|
||||||
|
}
|
||||||
<label class="inv-form-field">
|
<label class="inv-form-field">
|
||||||
<span>Received into</span>
|
<span>Received into</span>
|
||||||
<select [(ngModel)]="payoutBankGlAccountId">
|
<select [(ngModel)]="payoutBankGlAccountId">
|
||||||
@@ -432,8 +448,13 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<small
|
<small
|
||||||
>Leave the amount blank to take the system's interest. Voucher: Dr bank + Dr 100602 TDS / Cr
|
>Leave the amount blank to take the system's interest.
|
||||||
100603 (provisioned) + Cr interest income (the rest).</small
|
@if (s.fd.tdsRate) {
|
||||||
|
Leave TDS blank to work it out at {{ s.fd.tdsRate }}%; enter the bank's figure if it differs.
|
||||||
|
} @else {
|
||||||
|
{{ s.fd.partyName }} does not deduct TDS (Bank / Party Master).
|
||||||
|
}
|
||||||
|
Voucher: Dr bank + Dr 100602 TDS / Cr 100603 (provisioned) + Cr interest income (the rest).</small
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -506,10 +527,17 @@
|
|||||||
[placeholder]="closure.type === 'PREMATURE' ? '' : (closureQuote?.receivedAmount ?? '')"
|
[placeholder]="closure.type === 'PREMATURE' ? '' : (closureQuote?.receivedAmount ?? '')"
|
||||||
(change)="refreshClosureQuote()"
|
(change)="refreshClosureQuote()"
|
||||||
/></label>
|
/></label>
|
||||||
<label class="inv-form-field"
|
@if (closureFd?.tdsRate) {
|
||||||
><span>TDS deducted by Bank (₹)</span
|
<label class="inv-form-field"
|
||||||
><input type="number" min="0" [(ngModel)]="closure.tds" placeholder="0" (change)="refreshClosureQuote()"
|
><span>TDS deducted by Bank (₹) · auto at {{ closureFd?.tdsRate }}%</span
|
||||||
/></label>
|
><input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
[(ngModel)]="closure.tds"
|
||||||
|
[placeholder]="closureQuote?.tds ?? ''"
|
||||||
|
(change)="refreshClosureQuote()"
|
||||||
|
/></label>
|
||||||
|
}
|
||||||
@if (closure.type === 'PREMATURE') {
|
@if (closure.type === 'PREMATURE') {
|
||||||
<label class="inv-form-field"
|
<label class="inv-form-field"
|
||||||
><span>Penal Interest (₹)</span
|
><span>Penal Interest (₹)</span
|
||||||
@@ -519,7 +547,7 @@
|
|||||||
@if (closure.type !== 'RENEWAL') {
|
@if (closure.type !== 'RENEWAL') {
|
||||||
<label class="inv-form-field">
|
<label class="inv-form-field">
|
||||||
<span>Principal received into</span>
|
<span>Principal received into</span>
|
||||||
<select [(ngModel)]="closure.principalBankGlAccountId">
|
<select [(ngModel)]="closure.principalBankGlAccountId" (ngModelChange)="refreshClosureQuote()">
|
||||||
@for (g of bankGlOptions; track g.id) {
|
@for (g of bankGlOptions; track g.id) {
|
||||||
<option [ngValue]="g.id">{{ g.glCode }} — {{ g.name }}</option>
|
<option [ngValue]="g.id">{{ g.glCode }} — {{ g.name }}</option>
|
||||||
}
|
}
|
||||||
@@ -527,7 +555,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<label class="inv-form-field">
|
<label class="inv-form-field">
|
||||||
<span>Interest received into</span>
|
<span>Interest received into</span>
|
||||||
<select [(ngModel)]="closure.interestBankGlAccountId">
|
<select [(ngModel)]="closure.interestBankGlAccountId" (ngModelChange)="refreshClosureQuote()">
|
||||||
@for (g of bankGlOptions; track g.id) {
|
@for (g of bankGlOptions; track g.id) {
|
||||||
<option [ngValue]="g.id">{{ g.glCode }} — {{ g.name }}</option>
|
<option [ngValue]="g.id">{{ g.glCode }} — {{ g.name }}</option>
|
||||||
}
|
}
|
||||||
@@ -601,7 +629,7 @@
|
|||||||
}}
|
}}
|
||||||
— {{ closureTopUp > 0 ? 'paid from' : 'credited to' }}</span
|
— {{ closureTopUp > 0 ? 'paid from' : 'credited to' }}</span
|
||||||
>
|
>
|
||||||
<select [(ngModel)]="closure.diffBankGlAccountId">
|
<select [(ngModel)]="closure.diffBankGlAccountId" (ngModelChange)="refreshClosureQuote()">
|
||||||
<option [ngValue]="null">-- Select --</option>
|
<option [ngValue]="null">-- Select --</option>
|
||||||
@for (g of bankGlOptions; track g.id) {
|
@for (g of bankGlOptions; track g.id) {
|
||||||
<option [ngValue]="g.id">{{ g.glCode }} — {{ g.name }}</option>
|
<option [ngValue]="g.id">{{ g.glCode }} — {{ g.name }}</option>
|
||||||
@@ -659,6 +687,12 @@
|
|||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@if (closure.type === 'RENEWAL' && closureTopUp !== 0 && !closure.diffBankGlAccountId) {
|
||||||
|
<p class="pg-sub" style="padding: 0 16px; color: #b42318">
|
||||||
|
Choose the bank for the {{ closureTopUp > 0 ? 'extra amount' : 'amount taken out' }} to complete the
|
||||||
|
voucher.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
<p class="pg-sub" style="padding: 0 16px">
|
<p class="pg-sub" style="padding: 0 16px">
|
||||||
Voucher dated {{ q.closeDate }}. The lines follow the amounts entered above.
|
Voucher dated {{ q.closeDate }}. The lines follow the amounts entered above.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export class FdInvestmentComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.investmentService.getParties().subscribe({
|
this.investmentService.getParties().subscribe({
|
||||||
next: (ps) => (this.fdParties = ps.filter((p) => p.bondType === 'FD' && p.active)),
|
next: (ps) => (this.fdParties = ps.filter((p) => p.bondType === 'FD' && p.active && !!p.interestGlAccountId)),
|
||||||
error: (err) => this.toast(`Could not load FD banks: ${apiErrorMessage(err)}`)
|
error: (err) => this.toast(`Could not load FD banks: ${apiErrorMessage(err)}`)
|
||||||
});
|
});
|
||||||
this.investmentService.getAssetGlAccounts().subscribe({
|
this.investmentService.getAssetGlAccounts().subscribe({
|
||||||
@@ -162,9 +162,19 @@ export class FdInvestmentComponent implements OnInit {
|
|||||||
this.activeTab = tab;
|
this.activeTab = tab;
|
||||||
if (tab === 'purchase') {
|
if (tab === 'purchase') {
|
||||||
this.refreshQuote();
|
this.refreshQuote();
|
||||||
|
} else if (tab === 'closure' && this.closure.fdId) {
|
||||||
|
// Interest may have been posted on the other tab meanwhile: work the closure out again.
|
||||||
|
this.refreshClosureQuote();
|
||||||
|
} else if (tab === 'interest' && this.interestFdId) {
|
||||||
|
this.loadSchedule();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A complete DD-MM-YYYY date: half-typed dates are not sent to the server. */
|
||||||
|
private static fullDate(d: string | null | undefined): boolean {
|
||||||
|
return !!d && /^\d{2}-\d{2}-\d{4}$/.test(d);
|
||||||
|
}
|
||||||
|
|
||||||
private loadFds(): void {
|
private loadFds(): void {
|
||||||
this.investmentService.getFds().subscribe({
|
this.investmentService.getFds().subscribe({
|
||||||
next: (list) => (this.fds = list),
|
next: (list) => (this.fds = list),
|
||||||
@@ -196,7 +206,7 @@ export class FdInvestmentComponent implements OnInit {
|
|||||||
/** Period (years / months / days) sets the maturity date; the maturity date can also be typed directly. */
|
/** Period (years / months / days) sets the maturity date; the maturity date can also be typed directly. */
|
||||||
onPeriodChange(): void {
|
onPeriodChange(): void {
|
||||||
const f = this.form;
|
const f = this.form;
|
||||||
if (f.dealDate && (f.periodYears || f.periodMonths || f.periodDays)) {
|
if (FdInvestmentComponent.fullDate(f.dealDate) && (f.periodYears || f.periodMonths || f.periodDays)) {
|
||||||
f.maturityDate = addPeriod(f.dealDate, f.periodYears ?? 0, f.periodMonths ?? 0, f.periodDays ?? 0);
|
f.maturityDate = addPeriod(f.dealDate, f.periodYears ?? 0, f.periodMonths ?? 0, f.periodDays ?? 0);
|
||||||
}
|
}
|
||||||
this.refreshQuote();
|
this.refreshQuote();
|
||||||
@@ -206,6 +216,9 @@ export class FdInvestmentComponent implements OnInit {
|
|||||||
refreshQuote(): void {
|
refreshQuote(): void {
|
||||||
const f = this.form;
|
const f = this.form;
|
||||||
this.quoteError = '';
|
this.quoteError = '';
|
||||||
|
if (f.dealDate && !FdInvestmentComponent.fullDate(f.dealDate)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const params: Record<string, string | number> = {
|
const params: Record<string, string | number> = {
|
||||||
dealDate: f.dealDate || todayDDMMYYYY(),
|
dealDate: f.dealDate || todayDDMMYYYY(),
|
||||||
payout: f.payout,
|
payout: f.payout,
|
||||||
@@ -220,7 +233,7 @@ export class FdInvestmentComponent implements OnInit {
|
|||||||
if (f.intRate) {
|
if (f.intRate) {
|
||||||
params['intRate'] = f.intRate;
|
params['intRate'] = f.intRate;
|
||||||
}
|
}
|
||||||
if (f.maturityDate) {
|
if (FdInvestmentComponent.fullDate(f.maturityDate)) {
|
||||||
params['maturityDate'] = f.maturityDate;
|
params['maturityDate'] = f.maturityDate;
|
||||||
}
|
}
|
||||||
this.investmentService.quoteFd(params).subscribe({
|
this.investmentService.quoteFd(params).subscribe({
|
||||||
@@ -315,6 +328,7 @@ export class FdInvestmentComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resetPurchase(): void {
|
resetPurchase(): void {
|
||||||
|
this.quoteError = '';
|
||||||
this.form = this.blankForm();
|
this.form = this.blankForm();
|
||||||
this.refreshQuote();
|
this.refreshQuote();
|
||||||
}
|
}
|
||||||
@@ -362,7 +376,8 @@ export class FdInvestmentComponent implements OnInit {
|
|||||||
if (this.payoutReceived) {
|
if (this.payoutReceived) {
|
||||||
body['receivedAmount'] = this.payoutReceived;
|
body['receivedAmount'] = this.payoutReceived;
|
||||||
}
|
}
|
||||||
if (this.payoutTds) {
|
// Blank TDS: the system works it out at the bank's rate; an entered figure (even 0) is used as it is.
|
||||||
|
if (entered(this.payoutTds)) {
|
||||||
body['tds'] = this.payoutTds;
|
body['tds'] = this.payoutTds;
|
||||||
}
|
}
|
||||||
if (this.payoutBankGlAccountId) {
|
if (this.payoutBankGlAccountId) {
|
||||||
@@ -434,14 +449,29 @@ export class FdInvestmentComponent implements OnInit {
|
|||||||
this.closureQuote = null;
|
this.closureQuote = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (c.type === 'PREMATURE' && c.closeDate && !FdInvestmentComponent.fullDate(c.closeDate)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const params: Record<string, string | number> = { type: c.type };
|
const params: Record<string, string | number> = { type: c.type };
|
||||||
if (c.type === 'PREMATURE' && c.closeDate) {
|
if (c.type === 'PREMATURE' && c.closeDate) {
|
||||||
params['closeDate'] = c.closeDate;
|
params['closeDate'] = c.closeDate;
|
||||||
}
|
}
|
||||||
|
if (c.type === 'RENEWAL') {
|
||||||
|
if (c.diffBankGlAccountId) {
|
||||||
|
params['diffBankGlAccountId'] = c.diffBankGlAccountId;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (c.principalBankGlAccountId) {
|
||||||
|
params['principalBankGlAccountId'] = c.principalBankGlAccountId;
|
||||||
|
}
|
||||||
|
if (c.interestBankGlAccountId) {
|
||||||
|
params['interestBankGlAccountId'] = c.interestBankGlAccountId;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (c.receivedAmount) {
|
if (c.receivedAmount) {
|
||||||
params['receivedAmount'] = c.receivedAmount;
|
params['receivedAmount'] = c.receivedAmount;
|
||||||
}
|
}
|
||||||
if (c.tds) {
|
if (entered(c.tds)) {
|
||||||
params['tds'] = c.tds;
|
params['tds'] = c.tds;
|
||||||
}
|
}
|
||||||
if (c.type === 'RENEWAL' && c.renewalAmount) {
|
if (c.type === 'RENEWAL' && c.renewalAmount) {
|
||||||
@@ -464,7 +494,7 @@ export class FdInvestmentComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const base = { locale: 'en', dateFormat: 'dd-MM-yyyy', ...(c.remarks.trim() ? { remarks: c.remarks.trim() } : {}) };
|
const base = { locale: 'en', dateFormat: 'dd-MM-yyyy', ...(c.remarks.trim() ? { remarks: c.remarks.trim() } : {}) };
|
||||||
const tds = c.tds ? { tds: c.tds } : {};
|
const tds = entered(c.tds) ? { tds: c.tds } : {};
|
||||||
let body: object;
|
let body: object;
|
||||||
if (c.type === 'RENEWAL') {
|
if (c.type === 'RENEWAL') {
|
||||||
if (!c.newCertNo.trim() || !c.newIntRate || !c.newMaturityDate) {
|
if (!c.newCertNo.trim() || !c.newIntRate || !c.newMaturityDate) {
|
||||||
@@ -587,3 +617,8 @@ export class FdInvestmentComponent implements OnInit {
|
|||||||
this.toastTimer = setTimeout(() => (this.toastVisible = false), 4000);
|
this.toastTimer = setTimeout(() => (this.toastVisible = false), 4000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A number box the user filled in (0 counts; blank does not). */
|
||||||
|
function entered(v: number | null | undefined | string): boolean {
|
||||||
|
return v !== null && v !== undefined && v !== '';
|
||||||
|
}
|
||||||
|
|||||||
@@ -1910,6 +1910,9 @@ export interface PartyMaster {
|
|||||||
interestGlAccountId?: number;
|
interestGlAccountId?: number;
|
||||||
interestGlCode?: string;
|
interestGlCode?: string;
|
||||||
interestGlName?: string;
|
interestGlName?: string;
|
||||||
|
/** Whether this bank deducts TDS from FD interest (some do, some do not), and at what rate (%). */
|
||||||
|
tdsApplicable?: boolean;
|
||||||
|
tdsRate?: number | null;
|
||||||
rbiCode?: string;
|
rbiCode?: string;
|
||||||
/** Exposure limit for this party (₹). */
|
/** Exposure limit for this party (₹). */
|
||||||
limit?: number | null;
|
limit?: number | null;
|
||||||
|
|||||||
@@ -157,7 +157,7 @@
|
|||||||
<span>* GL A/c No.</span>
|
<span>* GL A/c No.</span>
|
||||||
<select [(ngModel)]="partyForm.glAccountId">
|
<select [(ngModel)]="partyForm.glAccountId">
|
||||||
<option [ngValue]="undefined">-- Select --</option>
|
<option [ngValue]="undefined">-- Select --</option>
|
||||||
@for (g of investmentGlOptions; track g.id) {
|
@for (g of partyGlOptions; track g.id) {
|
||||||
<option [ngValue]="g.id">{{ g.glCode }} — {{ g.name }}</option>
|
<option [ngValue]="g.id">{{ g.glCode }} — {{ g.name }}</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
@@ -172,6 +172,19 @@
|
|||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="inv-form-field">
|
||||||
|
<span>Bank deducts TDS on FD interest?</span>
|
||||||
|
<select [ngModel]="partyForm.tdsApplicable ?? false" (ngModelChange)="partyForm.tdsApplicable = $event">
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
@if (partyForm.tdsApplicable) {
|
||||||
|
<label class="inv-form-field"
|
||||||
|
><span>* TDS rate (%)</span
|
||||||
|
><input type="number" min="0" max="99" step="0.01" [(ngModel)]="partyForm.tdsRate" placeholder="e.g. 10"
|
||||||
|
/></label>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
<label class="inv-form-field"><span>RBI Code</span><input type="text" [(ngModel)]="partyForm.rbiCode" /></label>
|
<label class="inv-form-field"><span>RBI Code</span><input type="text" [(ngModel)]="partyForm.rbiCode" /></label>
|
||||||
<label class="inv-form-field"
|
<label class="inv-form-field"
|
||||||
@@ -280,7 +293,7 @@
|
|||||||
<span>* GL A/c No.</span>
|
<span>* GL A/c No.</span>
|
||||||
<select [(ngModel)]="scriptForm.glAccountId">
|
<select [(ngModel)]="scriptForm.glAccountId">
|
||||||
<option [ngValue]="undefined">-- Select --</option>
|
<option [ngValue]="undefined">-- Select --</option>
|
||||||
@for (g of investmentGlOptions; track g.id) {
|
@for (g of scriptGlOptions; track g.id) {
|
||||||
<option [ngValue]="g.id">{{ g.glCode }} — {{ g.name }}</option>
|
<option [ngValue]="g.id">{{ g.glCode }} — {{ g.name }}</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -175,6 +175,22 @@ export class InvestmentMastersComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** FD GLs are the bank-wise children of 100704 (codes 1007040x). */
|
||||||
|
private static isFdGl(glCode: string): boolean {
|
||||||
|
return glCode.startsWith('1007040');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An FD bank is mapped to its own FD GL; any other party to a non-FD investment GL. */
|
||||||
|
get partyGlOptions(): InvestmentGlAccount[] {
|
||||||
|
const fd = this.partyForm.bondType === 'FD';
|
||||||
|
return this.investmentGlOptions.filter((g) => InvestmentMastersComponent.isFdGl(g.glCode) === fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Scripts are securities, never an FD with a bank. */
|
||||||
|
get scriptGlOptions(): InvestmentGlAccount[] {
|
||||||
|
return this.investmentGlOptions.filter((g) => !InvestmentMastersComponent.isFdGl(g.glCode));
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------- Bank / Party Master --------------------
|
// -------------------- Bank / Party Master --------------------
|
||||||
/** Preview of the code the backend will assign; the saved code always comes from the backend. */
|
/** Preview of the code the backend will assign; the saved code always comes from the backend. */
|
||||||
get nextPartyCode(): string {
|
get nextPartyCode(): string {
|
||||||
@@ -192,6 +208,11 @@ export class InvestmentMastersComponent implements OnInit {
|
|||||||
this.toast('An FD bank needs its Interest GL as well.');
|
this.toast('An FD bank needs its Interest GL as well.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const tds = f.bondType === 'FD' && !!f.tdsApplicable;
|
||||||
|
if (tds && !(Number(f.tdsRate) > 0 && Number(f.tdsRate) < 100)) {
|
||||||
|
this.toast('Enter the TDS rate (%) this bank deducts on FD interest.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const body = {
|
const body = {
|
||||||
bondType: f.bondType,
|
bondType: f.bondType,
|
||||||
partyName: f.partyName,
|
partyName: f.partyName,
|
||||||
@@ -205,6 +226,8 @@ export class InvestmentMastersComponent implements OnInit {
|
|||||||
bankType: f.bankType ?? '',
|
bankType: f.bankType ?? '',
|
||||||
glAccountId: Number(f.glAccountId),
|
glAccountId: Number(f.glAccountId),
|
||||||
interestGlAccountId: f.bondType === 'FD' && f.interestGlAccountId ? Number(f.interestGlAccountId) : null,
|
interestGlAccountId: f.bondType === 'FD' && f.interestGlAccountId ? Number(f.interestGlAccountId) : null,
|
||||||
|
tdsApplicable: tds,
|
||||||
|
tdsRate: tds ? Number(f.tdsRate) : null,
|
||||||
rbiCode: f.rbiCode ?? '',
|
rbiCode: f.rbiCode ?? '',
|
||||||
limit: f.limit ?? null,
|
limit: f.limit ?? null,
|
||||||
leiNo: f.leiNo ?? '',
|
leiNo: f.leiNo ?? '',
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ export interface InvestmentPartyDto {
|
|||||||
interestGlAccountId?: number;
|
interestGlAccountId?: number;
|
||||||
interestGlCode?: string;
|
interestGlCode?: string;
|
||||||
interestGlName?: string;
|
interestGlName?: string;
|
||||||
|
tdsApplicable?: boolean;
|
||||||
|
tdsRate?: number;
|
||||||
rbiCode?: string;
|
rbiCode?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
leiNo?: string;
|
leiNo?: string;
|
||||||
@@ -76,6 +78,8 @@ export type FdCalcType = 'SIMPLE' | 'MONTHLY' | 'QUARTERLY' | 'HALF_YEARLY' | 'Y
|
|||||||
export interface InvestmentFdDto {
|
export interface InvestmentFdDto {
|
||||||
id?: number;
|
id?: number;
|
||||||
dealNo?: string;
|
dealNo?: string;
|
||||||
|
/** TDS rate the bank deducts on interest (from its FD party); absent when it does not deduct TDS. */
|
||||||
|
tdsRate?: number | null;
|
||||||
partyId?: number;
|
partyId?: number;
|
||||||
partyCode?: string;
|
partyCode?: string;
|
||||||
partyName?: string;
|
partyName?: string;
|
||||||
@@ -542,6 +546,8 @@ export function toPartyMaster(p: InvestmentPartyDto): PartyMaster {
|
|||||||
interestGlAccountId: p.interestGlAccountId,
|
interestGlAccountId: p.interestGlAccountId,
|
||||||
interestGlCode: p.interestGlCode,
|
interestGlCode: p.interestGlCode,
|
||||||
interestGlName: p.interestGlName,
|
interestGlName: p.interestGlName,
|
||||||
|
tdsApplicable: p.tdsApplicable ?? false,
|
||||||
|
tdsRate: p.tdsRate ?? null,
|
||||||
rbiCode: p.rbiCode,
|
rbiCode: p.rbiCode,
|
||||||
limit: p.limit ?? null,
|
limit: p.limit ?? null,
|
||||||
leiNo: p.leiNo,
|
leiNo: p.leiNo,
|
||||||
|
|||||||
Reference in New Issue
Block a user