feat(investment): interest voucher posting UI, home pending alert and day-end reminder
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component';
|
||||
import {
|
||||
SCRIPT_MASTER,
|
||||
@@ -56,6 +57,8 @@ import {
|
||||
import { exportWorkbook } from '../investment-export';
|
||||
import {
|
||||
InvestmentDailyRateDto,
|
||||
InvestmentInterestDueDto,
|
||||
InvestmentInterestRowDto,
|
||||
InvestmentInterestScheduleDto,
|
||||
InvestmentPurchaseDto,
|
||||
InvestmentService,
|
||||
@@ -205,9 +208,14 @@ interface BondSellForm {
|
||||
})
|
||||
export class BondInvestmentComponent implements OnInit {
|
||||
private readonly investmentService = inject(InvestmentService);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
|
||||
/** Script and party dropdowns offer the masters saved in the backend. */
|
||||
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({
|
||||
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)}`)
|
||||
});
|
||||
this.loadPurchases();
|
||||
this.loadDueInterest();
|
||||
}
|
||||
|
||||
/** 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;
|
||||
intSchedule: InvestmentInterestScheduleDto | null = null;
|
||||
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;
|
||||
|
||||
// Write-off
|
||||
@@ -1052,6 +1068,7 @@ export class BondInvestmentComponent implements OnInit {
|
||||
this.investmentService.getInterestSchedule(this.intPurchaseId, this.intAsOnDate || undefined).subscribe({
|
||||
next: (schedule) => {
|
||||
this.intSchedule = schedule;
|
||||
this.intBankGlCode = schedule.bankGlCode;
|
||||
this.intLoading = false;
|
||||
},
|
||||
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. */
|
||||
get intDueTotal(): number {
|
||||
return (this.intSchedule?.rows ?? []).filter((r) => r.due).reduce((sum, r) => sum + r.amount, 0);
|
||||
|
||||
Reference in New Issue
Block a user