feat(investment): interest voucher posting UI, home pending alert and day-end reminder
This commit is contained in:
@@ -386,6 +386,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
<!-- Investment interest vouchers that are due but not posted. A reminder only: unlike the interest
|
||||||
|
above it never stops the close (Jitendra Sir); the voucher can still be posted afterwards. -->
|
||||||
|
@if (preview?.investmentInterestPending?.length) {
|
||||||
|
<div class="bd-warn">
|
||||||
|
<mat-icon>notifications_active</mat-icon>
|
||||||
|
<div>
|
||||||
|
<strong>Reminder: investment interest vouchers are pending.</strong>
|
||||||
|
@for (t of preview.investmentInterestPending; track t.purchaseId) {
|
||||||
|
<div>
|
||||||
|
{{ t.lineType === 'COUPON' ? 'Coupon' : 'Provision' }} · {{ t.scriptDesc }} · deal {{ t.dealNo }} ·
|
||||||
|
{{ t.intDate }} · ₹{{ (t.lineType === 'COUPON' ? t.grossInterest : t.amount) | number: '1.2-2' }}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
Post them under Investment → Govt Security / Bond → 03 Interest Entry. You can still close the day.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@if (preview?.savingsInterestPending?.length) {
|
@if (preview?.savingsInterestPending?.length) {
|
||||||
<div class="bd-warn">
|
<div class="bd-warn">
|
||||||
<mat-icon>warning</mat-icon>
|
<mat-icon>warning</mat-icon>
|
||||||
|
|||||||
@@ -47,6 +47,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pending investment interest vouchers (coupon / provision due now or within two days) -->
|
||||||
|
@if (pendingInvestmentInterest.length) {
|
||||||
|
<a class="investment-interest-alert" routerLink="/investment/bonds" [queryParams]="{ tab: 'interest' }">
|
||||||
|
<span class="iia-icon">🔔</span>
|
||||||
|
<span class="iia-text">
|
||||||
|
<strong>{{ pendingInvestmentInterest.length }} investment interest voucher(s) pending</strong>
|
||||||
|
@for (t of pendingInvestmentInterest; track t.purchaseId) {
|
||||||
|
<span class="iia-item">
|
||||||
|
{{ t.lineType === 'COUPON' ? 'Coupon' : 'Provision' }} · {{ t.scriptDesc }} · deal {{ t.dealNo }} ·
|
||||||
|
{{ t.overdue ? 'overdue since ' + t.intDate : t.postable ? 'due today' : 'due ' + t.intDate }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span class="iia-action">Open Interest Entry →</span>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
|
||||||
<!-- Quick Action Cards -->
|
<!-- Quick Action Cards -->
|
||||||
<div class="quick-actions">
|
<div class="quick-actions">
|
||||||
<a class="qa-card" [routerLink]="['/dashboard']" #buttonDashboard>
|
<a class="qa-card" [routerLink]="['/dashboard']" #buttonDashboard>
|
||||||
|
|||||||
@@ -424,3 +424,39 @@ $accent: #0ea5e9;
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.investment-interest-alert {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 12px 0;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid #f5c26b;
|
||||||
|
border-left: 4px solid #e08e0b;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff8e6;
|
||||||
|
color: #5c3d00;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
.iia-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iia-text {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iia-item {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iia-action {
|
||||||
|
align-self: center;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { PopoverService } from '../configuration-wizard/popover/popover.service'
|
|||||||
import { ConfigurationWizardService } from '../configuration-wizard/configuration-wizard.service';
|
import { ConfigurationWizardService } from '../configuration-wizard/configuration-wizard.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
import { DscService } from 'app/users/dsc/dsc.service';
|
import { DscService } from 'app/users/dsc/dsc.service';
|
||||||
|
import { InvestmentInterestDueDto, InvestmentService } from 'app/investment/investment.service';
|
||||||
|
|
||||||
/** Custom Components */
|
/** Custom Components */
|
||||||
import { NextStepDialogComponent } from '../configuration-wizard/next-step-dialog/next-step-dialog.component';
|
import { NextStepDialogComponent } from '../configuration-wizard/next-step-dialog/next-step-dialog.component';
|
||||||
@@ -109,6 +110,7 @@ export class HomeComponent implements OnInit, AfterViewInit {
|
|||||||
this.tenant = this.tenantIdentifier();
|
this.tenant = this.tenantIdentifier();
|
||||||
this.setFilteredActivities();
|
this.setFilteredActivities();
|
||||||
this.loadDscStatus();
|
this.loadDscStatus();
|
||||||
|
this.loadPendingInvestmentInterest();
|
||||||
if (!this.authenticationService.hasDialogBeenShown()) {
|
if (!this.authenticationService.hasDialogBeenShown()) {
|
||||||
this.dialog.open(WarningDialogComponent, {
|
this.dialog.open(WarningDialogComponent, {
|
||||||
disableClose: false,
|
disableClose: false,
|
||||||
@@ -118,6 +120,18 @@ export class HomeComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Investment interest lines due now or within two days, shown on the home screen so nobody has to look for them. */
|
||||||
|
pendingInvestmentInterest: InvestmentInterestDueDto[] = [];
|
||||||
|
private readonly investmentService = inject(InvestmentService);
|
||||||
|
|
||||||
|
/** Silent when the user cannot see investments or nothing is due — the strip simply does not render. */
|
||||||
|
private loadPendingInvestmentInterest(): void {
|
||||||
|
this.investmentService.getDueInterest().subscribe({
|
||||||
|
next: (due) => (this.pendingInvestmentInterest = due ?? []),
|
||||||
|
error: () => (this.pendingInvestmentInterest = [])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Surface a small "DSC verified" badge in the welcome banner when the
|
* Surface a small "DSC verified" badge in the welcome banner when the
|
||||||
* signed-in user has at least one ACTIVE DSC certificate. Silent on
|
* signed-in user has at least one ACTIVE DSC certificate. Silent on
|
||||||
|
|||||||
@@ -693,6 +693,39 @@
|
|||||||
@if (activeTab === 'interest') {
|
@if (activeTab === 'interest') {
|
||||||
<div class="pg-card">
|
<div class="pg-card">
|
||||||
<div class="pos-section-label">Interest Entry — coupon and 31 Mar / 30 Sep provision schedule</div>
|
<div class="pos-section-label">Interest Entry — coupon and 31 Mar / 30 Sep provision schedule</div>
|
||||||
|
@if (intDueTasks.length) {
|
||||||
|
<div class="pos-section-label">🔔 Interest entries due ({{ intDueTasks.length }})</div>
|
||||||
|
<table class="pg-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Deal No</th>
|
||||||
|
<th>Script</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Int Date</th>
|
||||||
|
<th class="num">Amount</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (t of intDueTasks; track t.purchaseId) {
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="pg-chip">{{ t.dealNo }}</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ t.scriptCode }} — {{ t.scriptDesc }}</td>
|
||||||
|
<td>{{ t.lineType === 'COUPON' ? 'Coupon' : 'Provision' }}</td>
|
||||||
|
<td>{{ t.intDate }}</td>
|
||||||
|
<td class="num">₹{{ (t.lineType === 'COUPON' ? t.grossInterest : t.amount) | number: '1.2-2' }}</td>
|
||||||
|
<td>{{ t.overdue ? 'Overdue — post now' : t.postable ? 'Due today' : 'Due on ' + t.intDate }}</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="pg-btn pg-btn--ghost" (click)="openDueTask(t)">Open</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
<div class="inv-form-grid">
|
<div class="inv-form-grid">
|
||||||
<label class="inv-form-field">
|
<label class="inv-form-field">
|
||||||
<span>Type</span>
|
<span>Type</span>
|
||||||
@@ -775,14 +808,30 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pos-section-label">
|
<div class="pos-section-label">
|
||||||
Interest Schedule ({{ sc.rows.length }} lines) — due up to {{ sc.asOnDate }}: ₹{{
|
Interest Schedule ({{ intVisibleRows.length }} of {{ sc.rows.length }} lines) — due up to {{ sc.asOnDate }}:
|
||||||
intDueTotal | number: '1.2-2'
|
₹{{ intDueTotal | number: '1.2-2' }}
|
||||||
}}
|
|
||||||
</div>
|
</div>
|
||||||
<p class="pg-sub" style="padding: 0 16px">
|
<p class="pg-sub" style="padding: 0 16px">
|
||||||
Days are 30/360. A provision books the interest earned since the last coupon; the coupon then clears what is
|
Days are 30/360. A provision books the interest earned since the last coupon; the coupon then clears what is
|
||||||
already booked. Display only — nothing is posted to GL yet.
|
already booked. Post each line with Generate Voucher on or after its date, in date order — the JV voucher is
|
||||||
|
passed as it is raised.
|
||||||
</p>
|
</p>
|
||||||
|
<div class="inv-form-grid">
|
||||||
|
<label class="inv-form-field">
|
||||||
|
<span>Coupon received into (Bank)</span>
|
||||||
|
<select [(ngModel)]="intBankGlCode">
|
||||||
|
@for (g of settlementBankOptions; track g.glCode) {
|
||||||
|
<option [value]="g.glCode">{{ g.glCode }} — {{ g.glName }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="inv-form-field">
|
||||||
|
<span>Schedule</span>
|
||||||
|
<button type="button" class="pg-btn pg-btn--ghost" (click)="intShowFullSchedule = !intShowFullSchedule">
|
||||||
|
{{ intShowFullSchedule ? 'Show up to next year' : 'Show full schedule to maturity' }}
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<table class="pg-table">
|
<table class="pg-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -796,10 +845,11 @@
|
|||||||
<th class="num">Already Booked</th>
|
<th class="num">Already Booked</th>
|
||||||
<th class="num col-in">Amount</th>
|
<th class="num col-in">Amount</th>
|
||||||
<th>Accounting</th>
|
<th>Accounting</th>
|
||||||
|
<th>Voucher</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (r of sc.rows; track r.intDate) {
|
@for (r of intVisibleRows; track r.type + r.intDate) {
|
||||||
<tr [style.opacity]="r.due ? 1 : 0.6">
|
<tr [style.opacity]="r.due ? 1 : 0.6">
|
||||||
<td>
|
<td>
|
||||||
<span class="pg-chip">{{ r.type === 'COUPON' ? 'Coupon' : 'Provision' }}</span>
|
<span class="pg-chip">{{ r.type === 'COUPON' ? 'Coupon' : 'Provision' }}</span>
|
||||||
@@ -829,10 +879,26 @@
|
|||||||
<div>Cr {{ sc.interestGlCode }} Int. Income ₹{{ r.amount | number: '1.0-0' }}</div>
|
<div>Cr {{ sc.interestGlCode }} Int. Income ₹{{ r.amount | number: '1.0-0' }}</div>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
<td style="white-space: nowrap">
|
||||||
|
@if (r.posted) {
|
||||||
|
<span class="pg-chip" [title]="'GL ' + r.glTransactionId">Posted · {{ r.voucherNo }}</span>
|
||||||
|
} @else if (r.postable) {
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="pg-btn pg-btn--primary"
|
||||||
|
[disabled]="intPosting"
|
||||||
|
(click)="generateVoucher(r)"
|
||||||
|
>
|
||||||
|
Generate Voucher
|
||||||
|
</button>
|
||||||
|
} @else if (r.dueSoon) {
|
||||||
|
<span class="pg-chip">🔔 Due {{ r.intDate }}</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
} @empty {
|
} @empty {
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="10" class="pg-empty">
|
<td colspan="11" class="pg-empty">
|
||||||
No interest lines — check the rate, last interest date and maturity date.
|
No interest lines — check the rate, last interest date and maturity date.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -840,8 +906,8 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p class="pg-sub" style="padding: 8px 16px 12px">
|
<p class="pg-sub" style="padding: 8px 16px 12px">
|
||||||
Interest income GL: {{ sc.interestGlCode }} — {{ sc.interestGlName }}. Posting to GL will be enabled after
|
Interest income GL: {{ sc.interestGlCode }} — {{ sc.interestGlName }} · Interest receivable GL:
|
||||||
approval.
|
{{ interestReceivableGl.glCode }}.
|
||||||
</p>
|
</p>
|
||||||
} @else {
|
} @else {
|
||||||
<p class="pg-sub" style="padding: 0 16px 12px">
|
<p class="pg-sub" style="padding: 0 16px 12px">
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import { Component, OnInit, inject } from '@angular/core';
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component';
|
import { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component';
|
||||||
import {
|
import {
|
||||||
SCRIPT_MASTER,
|
SCRIPT_MASTER,
|
||||||
@@ -56,6 +57,8 @@ import {
|
|||||||
import { exportWorkbook } from '../investment-export';
|
import { exportWorkbook } from '../investment-export';
|
||||||
import {
|
import {
|
||||||
InvestmentDailyRateDto,
|
InvestmentDailyRateDto,
|
||||||
|
InvestmentInterestDueDto,
|
||||||
|
InvestmentInterestRowDto,
|
||||||
InvestmentInterestScheduleDto,
|
InvestmentInterestScheduleDto,
|
||||||
InvestmentPurchaseDto,
|
InvestmentPurchaseDto,
|
||||||
InvestmentService,
|
InvestmentService,
|
||||||
@@ -205,9 +208,14 @@ interface BondSellForm {
|
|||||||
})
|
})
|
||||||
export class BondInvestmentComponent implements OnInit {
|
export class BondInvestmentComponent implements OnInit {
|
||||||
private readonly investmentService = inject(InvestmentService);
|
private readonly investmentService = inject(InvestmentService);
|
||||||
|
private readonly route = inject(ActivatedRoute);
|
||||||
|
|
||||||
/** Script and party dropdowns offer the masters saved in the backend. */
|
/** Script and party dropdowns offer the masters saved in the backend. */
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
// The home-screen interest alert links here with ?tab=interest.
|
||||||
|
if (this.route.snapshot.queryParamMap.get('tab') === 'interest') {
|
||||||
|
this.activeTab = 'interest';
|
||||||
|
}
|
||||||
this.investmentService.syncSharedMasters().subscribe({
|
this.investmentService.syncSharedMasters().subscribe({
|
||||||
error: (err) => this.toast(`Could not load Script / Party masters: ${apiErrorMessage(err)}`)
|
error: (err) => this.toast(`Could not load Script / Party masters: ${apiErrorMessage(err)}`)
|
||||||
});
|
});
|
||||||
@@ -222,6 +230,7 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
error: (err) => this.toast(`Could not load GL accounts: ${apiErrorMessage(err)}`)
|
error: (err) => this.toast(`Could not load GL accounts: ${apiErrorMessage(err)}`)
|
||||||
});
|
});
|
||||||
this.loadPurchases();
|
this.loadPurchases();
|
||||||
|
this.loadDueInterest();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GL id for a GL code — the purchase API takes ids, the form works with codes. */
|
/** GL id for a GL code — the purchase API takes ids, the form works with codes. */
|
||||||
@@ -307,6 +316,13 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
intAsOnDate = this.asOnDate;
|
intAsOnDate = this.asOnDate;
|
||||||
intSchedule: InvestmentInterestScheduleDto | null = null;
|
intSchedule: InvestmentInterestScheduleDto | null = null;
|
||||||
intLoading = false;
|
intLoading = false;
|
||||||
|
/** Interest lines of all holdings due now or within two days — what Jitendra Sir asked to be reminded of. */
|
||||||
|
intDueTasks: InvestmentInterestDueDto[] = [];
|
||||||
|
/** By default the schedule shows only lines up to the end of next year (and anything already posted). */
|
||||||
|
intShowFullSchedule = false;
|
||||||
|
/** Bank a coupon is received into; defaults to the purchase's paying bank. */
|
||||||
|
intBankGlCode = '';
|
||||||
|
intPosting = false;
|
||||||
readonly interestReceivableGl = INTEREST_RECEIVABLE_GL;
|
readonly interestReceivableGl = INTEREST_RECEIVABLE_GL;
|
||||||
|
|
||||||
// Write-off
|
// Write-off
|
||||||
@@ -1052,6 +1068,7 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
this.investmentService.getInterestSchedule(this.intPurchaseId, this.intAsOnDate || undefined).subscribe({
|
this.investmentService.getInterestSchedule(this.intPurchaseId, this.intAsOnDate || undefined).subscribe({
|
||||||
next: (schedule) => {
|
next: (schedule) => {
|
||||||
this.intSchedule = schedule;
|
this.intSchedule = schedule;
|
||||||
|
this.intBankGlCode = schedule.bankGlCode;
|
||||||
this.intLoading = false;
|
this.intLoading = false;
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
@@ -1062,6 +1079,63 @@ export class BondInvestmentComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadDueInterest(): void {
|
||||||
|
this.investmentService.getDueInterest().subscribe({
|
||||||
|
next: (tasks) => (this.intDueTasks = tasks),
|
||||||
|
error: () => (this.intDueTasks = [])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Opens a due task's holding in the schedule below. */
|
||||||
|
openDueTask(t: InvestmentInterestDueDto): void {
|
||||||
|
this.intType = '';
|
||||||
|
this.intScript = '';
|
||||||
|
this.intPurchaseId = t.purchaseId;
|
||||||
|
this.retrieveInterestSchedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lines shown: up to 31 December of next year, plus anything already posted — unless the full schedule is asked for. */
|
||||||
|
get intVisibleRows(): InvestmentInterestRowDto[] {
|
||||||
|
const rows = this.intSchedule?.rows ?? [];
|
||||||
|
if (this.intShowFullSchedule) {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
const upTo = new Date(new Date().getFullYear() + 1, 11, 31);
|
||||||
|
return rows.filter((r) => r.posted || parseDDMMYYYY(r.intDate) <= upTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Posts one interest line with its JV voucher (passed as it is raised), then refreshes the schedule and tasks. */
|
||||||
|
generateVoucher(r: InvestmentInterestRowDto): void {
|
||||||
|
const sc = this.intSchedule;
|
||||||
|
if (!sc || this.intPosting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bankGlAccountId =
|
||||||
|
r.type === 'COUPON' ? (this.glIdByCode.get(this.intBankGlCode) ?? sc.bankGlAccountId) : undefined;
|
||||||
|
const body = {
|
||||||
|
lineType: r.type,
|
||||||
|
intDate: r.intDate,
|
||||||
|
...(bankGlAccountId ? { bankGlAccountId } : {}),
|
||||||
|
dateFormat: 'dd-MM-yyyy',
|
||||||
|
locale: 'en'
|
||||||
|
};
|
||||||
|
this.intPosting = true;
|
||||||
|
this.investmentService.postInterest(sc.purchaseId, body).subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.intPosting = false;
|
||||||
|
this.toast(
|
||||||
|
`${r.type === 'COUPON' ? 'Coupon' : 'Provision'} of ${r.intDate} posted — voucher ${res.resourceIdentifier ?? ''}.`
|
||||||
|
);
|
||||||
|
this.retrieveInterestSchedule();
|
||||||
|
this.loadDueInterest();
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.intPosting = false;
|
||||||
|
this.toast(apiErrorMessage(err));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** Total of the lines falling due up to the as-on date. */
|
/** Total of the lines falling due up to the as-on date. */
|
||||||
get intDueTotal(): number {
|
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).reduce((sum, r) => sum + r.amount, 0);
|
||||||
|
|||||||
@@ -175,6 +175,19 @@ export class InvestmentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Coupon and 31 Mar / 30 Sep provision lines of a holding up to maturity (calculated, not posted). */
|
/** Coupon and 31 Mar / 30 Sep provision lines of a holding up to maturity (calculated, not posted). */
|
||||||
|
/** Interest lines of all holdings that are due now or within the next few days. */
|
||||||
|
getDueInterest(): Observable<InvestmentInterestDueDto[]> {
|
||||||
|
return this.http.get<InvestmentInterestDueDto[]>('/investment/interest-postings/due');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Posts one interest line (coupon / provision) with its JV voucher; the voucher number comes back as resourceIdentifier. */
|
||||||
|
postInterest(purchaseId: number, body: object): Observable<{ resourceIdentifier?: string; transactionId?: string }> {
|
||||||
|
return this.http.post<{ resourceIdentifier?: string; transactionId?: string }>(
|
||||||
|
`/investment/purchases/${purchaseId}/interest-postings`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getInterestSchedule(purchaseId: number, asOnDate?: string): Observable<InvestmentInterestScheduleDto> {
|
getInterestSchedule(purchaseId: number, asOnDate?: string): Observable<InvestmentInterestScheduleDto> {
|
||||||
const params = asOnDate ? `?asOnDate=${encodeURIComponent(asOnDate)}` : '';
|
const params = asOnDate ? `?asOnDate=${encodeURIComponent(asOnDate)}` : '';
|
||||||
return this.http.get<InvestmentInterestScheduleDto>(
|
return this.http.get<InvestmentInterestScheduleDto>(
|
||||||
@@ -347,6 +360,30 @@ export interface InvestmentInterestRowDto {
|
|||||||
alreadyBooked: number;
|
alreadyBooked: number;
|
||||||
amount: number;
|
amount: number;
|
||||||
due: boolean;
|
due: boolean;
|
||||||
|
/** Posted to GL: its JV voucher and GL transaction. */
|
||||||
|
posted: boolean;
|
||||||
|
voucherNo?: string;
|
||||||
|
glTransactionId?: string;
|
||||||
|
postedOnDate?: string;
|
||||||
|
/** The next line to post and its date has come. */
|
||||||
|
postable: boolean;
|
||||||
|
/** Unposted and due within the reminder window (or past). */
|
||||||
|
dueSoon: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A holding's next interest line that is due (or due within the reminder window) — a task to post. */
|
||||||
|
export interface InvestmentInterestDueDto {
|
||||||
|
purchaseId: number;
|
||||||
|
dealNo: string;
|
||||||
|
certNo: string;
|
||||||
|
scriptCode: string;
|
||||||
|
scriptDesc: string;
|
||||||
|
lineType: 'COUPON' | 'PROVISION';
|
||||||
|
intDate: string;
|
||||||
|
grossInterest: number;
|
||||||
|
amount: number;
|
||||||
|
postable: boolean;
|
||||||
|
overdue: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InvestmentInterestScheduleDto {
|
export interface InvestmentInterestScheduleDto {
|
||||||
@@ -369,6 +406,10 @@ export interface InvestmentInterestScheduleDto {
|
|||||||
asOnDate: string;
|
asOnDate: string;
|
||||||
interestGlCode: string;
|
interestGlCode: string;
|
||||||
interestGlName: string;
|
interestGlName: string;
|
||||||
|
bankGlAccountId: number;
|
||||||
|
bankGlCode: string;
|
||||||
|
bankGlName: string;
|
||||||
|
status: string;
|
||||||
rows: InvestmentInterestRowDto[];
|
rows: InvestmentInterestRowDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user