625 lines
20 KiB
TypeScript
625 lines
20 KiB
TypeScript
/**
|
||
* Copyright since 2025 Mifos Initiative
|
||
*
|
||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||
*/
|
||
|
||
import { Component, OnInit, inject } from '@angular/core';
|
||
import { ActivatedRoute } from '@angular/router';
|
||
import { CommonModule } from '@angular/common';
|
||
import { FormsModule } from '@angular/forms';
|
||
import { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component';
|
||
import { addPeriod, parseDDMMYYYY, todayDDMMYYYY } from '../investment-data';
|
||
import {
|
||
FdCalcType,
|
||
FdClosureType,
|
||
FdPayout,
|
||
InvestmentFdClosureDto,
|
||
InvestmentFdInterestRowDto,
|
||
InvestmentFdScheduleDto,
|
||
InvestmentFdDto,
|
||
InvestmentGlAccount,
|
||
InvestmentPartyDto,
|
||
InvestmentService,
|
||
apiErrorMessage
|
||
} from '../investment.service';
|
||
|
||
type FdTab = 'holdings' | 'purchase' | 'closure' | 'interest';
|
||
|
||
interface FdClosureForm {
|
||
fdId: number | null;
|
||
type: FdClosureType;
|
||
closeDate: string;
|
||
/** Maturity / premature: money credited by the bank. Renewal: the maturity amount as per the bank. */
|
||
receivedAmount: number | null;
|
||
tds: number | null;
|
||
penalAmount: number | null;
|
||
principalBankGlAccountId: number | null;
|
||
interestBankGlAccountId: number | null;
|
||
newCertNo: string;
|
||
newCertDate: string;
|
||
newIntRate: number | null;
|
||
newPeriodYears: number | null;
|
||
newPeriodMonths: number | null;
|
||
newPeriodDays: number | null;
|
||
newMaturityDate: string;
|
||
newPayout: FdPayout;
|
||
newCalcType: FdCalcType;
|
||
renewalAmount: number | null;
|
||
diffBankGlAccountId: number | null;
|
||
remarks: string;
|
||
}
|
||
|
||
interface FdPurchaseForm {
|
||
partyId: number | null;
|
||
certNo: string;
|
||
certDate: string;
|
||
dealDate: string;
|
||
amount: number | null;
|
||
intRate: number | null;
|
||
periodYears: number | null;
|
||
periodMonths: number | null;
|
||
periodDays: number | null;
|
||
maturityDate: string;
|
||
payout: FdPayout;
|
||
calcType: FdCalcType;
|
||
bankGlAccountId: number | null;
|
||
remarks: string;
|
||
}
|
||
|
||
/**
|
||
* FDs the bank places with other banks (Jitendra Sir): the bank is an FD party with its own FD GL and interest GL;
|
||
* placing an FD posts Dr the bank's FD GL / Cr our bank GL at once. The RBI limits (20% of total deposits as on the
|
||
* previous 31 March with all banks, 5% with one bank) are checked before saving.
|
||
*/
|
||
@Component({
|
||
selector: 'mifosx-fd-investment',
|
||
templateUrl: './fd-investment.component.html',
|
||
styleUrls: [
|
||
'../../cashier/cashier-shared.scss',
|
||
'../investment-shared.scss'
|
||
],
|
||
standalone: true,
|
||
imports: [
|
||
CommonModule,
|
||
FormsModule,
|
||
DateDdmmyyyyPickerComponent
|
||
]
|
||
})
|
||
export class FdInvestmentComponent implements OnInit {
|
||
private readonly investmentService = inject(InvestmentService);
|
||
private readonly route = inject(ActivatedRoute);
|
||
|
||
activeTab: FdTab = 'holdings';
|
||
fds: InvestmentFdDto[] = [];
|
||
fdParties: InvestmentPartyDto[] = [];
|
||
/** Our bank accounts (current accounts under 100200) the money can be paid from. */
|
||
bankGlOptions: InvestmentGlAccount[] = [];
|
||
|
||
readonly payoutOptions: Array<{ value: FdPayout; label: string }> = [
|
||
{ value: 'MATURITY', label: 'At Maturity' },
|
||
{ value: 'HALF_YEARLY', label: 'Half-Yearly' },
|
||
{ value: 'YEARLY', label: 'Yearly' }
|
||
];
|
||
readonly calcTypeOptions: Array<{ value: FdCalcType; label: string }> = [
|
||
{ value: 'QUARTERLY', label: 'Compound – Quarterly' },
|
||
{ value: 'MONTHLY', label: 'Compound – Monthly' },
|
||
{ value: 'HALF_YEARLY', label: 'Compound – Half-Yearly' },
|
||
{ value: 'YEARLY', label: 'Compound – Yearly' },
|
||
{ value: 'SIMPLE', label: 'Simple Interest' }
|
||
];
|
||
|
||
form: FdPurchaseForm = this.blankForm();
|
||
quote: InvestmentFdDto | null = null;
|
||
quoteError = '';
|
||
saving = false;
|
||
|
||
// 03 Interest Entry
|
||
interestFdId: number | null = null;
|
||
schedule: InvestmentFdScheduleDto | null = null;
|
||
payoutReceived: number | null = null;
|
||
payoutTds: number | null = null;
|
||
payoutBankGlAccountId: number | null = null;
|
||
postingInterest = false;
|
||
|
||
closure: FdClosureForm = this.blankClosure();
|
||
closureQuote: InvestmentFdClosureDto | null = null;
|
||
closureError = '';
|
||
closing = false;
|
||
|
||
/** Total deposits as on the 31 March the limits use, when it still has to be entered. */
|
||
baseAmount: number | null = null;
|
||
savingBase = false;
|
||
|
||
toastMsg = '';
|
||
toastVisible = false;
|
||
private toastTimer: ReturnType<typeof setTimeout> | undefined;
|
||
|
||
ngOnInit(): void {
|
||
this.investmentService.getParties().subscribe({
|
||
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)}`)
|
||
});
|
||
this.investmentService.getAssetGlAccounts().subscribe({
|
||
next: (gls) => (this.bankGlOptions = gls.filter((g) => g.glCode.startsWith('1002'))),
|
||
error: (err) => this.toast(`Could not load bank GLs: ${apiErrorMessage(err)}`)
|
||
});
|
||
this.loadFds();
|
||
// Opened from a reminder (home screen / day close): straight to the FD's interest lines.
|
||
const fdId = Number(this.route.snapshot.queryParamMap.get('fd'));
|
||
if (this.route.snapshot.queryParamMap.get('tab') === 'interest') {
|
||
this.activeTab = 'interest';
|
||
if (fdId) {
|
||
this.interestFdId = fdId;
|
||
this.loadSchedule();
|
||
}
|
||
}
|
||
}
|
||
|
||
setTab(tab: FdTab): void {
|
||
this.activeTab = tab;
|
||
if (tab === 'purchase') {
|
||
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 {
|
||
this.investmentService.getFds().subscribe({
|
||
next: (list) => (this.fds = list),
|
||
error: (err) => this.toast(`Could not load FDs: ${apiErrorMessage(err)}`)
|
||
});
|
||
}
|
||
|
||
get selectedParty(): InvestmentPartyDto | undefined {
|
||
return this.fdParties.find((p) => p.id === this.form.partyId);
|
||
}
|
||
|
||
get activeFds(): InvestmentFdDto[] {
|
||
return this.fds.filter((f) => f.status === 'ACTIVE');
|
||
}
|
||
|
||
get totalPlaced(): number {
|
||
return this.activeFds.reduce((sum, f) => sum + (f.principal ?? 0), 0);
|
||
}
|
||
|
||
payoutLabel(p?: string): string {
|
||
return this.payoutOptions.find((o) => o.value === p)?.label ?? p ?? '';
|
||
}
|
||
|
||
calcLabel(c?: string): string {
|
||
return this.calcTypeOptions.find((o) => o.value === c)?.label ?? c ?? '';
|
||
}
|
||
|
||
// -------------------- Purchase --------------------
|
||
/** Period (years / months / days) sets the maturity date; the maturity date can also be typed directly. */
|
||
onPeriodChange(): void {
|
||
const f = this.form;
|
||
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);
|
||
}
|
||
this.refreshQuote();
|
||
}
|
||
|
||
/** Asks the backend for the interest, maturity amount and the RBI limit position of the FD being entered. */
|
||
refreshQuote(): void {
|
||
const f = this.form;
|
||
this.quoteError = '';
|
||
if (f.dealDate && !FdInvestmentComponent.fullDate(f.dealDate)) {
|
||
return;
|
||
}
|
||
const params: Record<string, string | number> = {
|
||
dealDate: f.dealDate || todayDDMMYYYY(),
|
||
payout: f.payout,
|
||
calcType: f.calcType
|
||
};
|
||
if (f.partyId) {
|
||
params['partyId'] = f.partyId;
|
||
}
|
||
if (f.amount) {
|
||
params['amount'] = f.amount;
|
||
}
|
||
if (f.intRate) {
|
||
params['intRate'] = f.intRate;
|
||
}
|
||
if (FdInvestmentComponent.fullDate(f.maturityDate)) {
|
||
params['maturityDate'] = f.maturityDate;
|
||
}
|
||
this.investmentService.quoteFd(params).subscribe({
|
||
next: (q) => {
|
||
this.quote = q;
|
||
if (q.limit && q.limit.totalDeposits == null && this.baseAmount == null) {
|
||
this.baseAmount = q.limit.glTotalDeposits || null;
|
||
}
|
||
},
|
||
error: (err) => {
|
||
this.quote = null;
|
||
this.quoteError = apiErrorMessage(err);
|
||
}
|
||
});
|
||
}
|
||
|
||
saveBase(): void {
|
||
const asOn = this.quote?.limit?.asOnDate;
|
||
if (!asOn || !this.baseAmount || this.baseAmount <= 0) {
|
||
this.toast('Enter the total deposits as on 31 March.');
|
||
return;
|
||
}
|
||
this.savingBase = true;
|
||
this.investmentService
|
||
.setDepositBase({ asOnDate: asOn, totalDeposits: this.baseAmount, locale: 'en', dateFormat: 'dd-MM-yyyy' })
|
||
.subscribe({
|
||
next: () => {
|
||
this.savingBase = false;
|
||
this.toast(`Total deposits as on ${asOn} saved.`);
|
||
this.refreshQuote();
|
||
},
|
||
error: (err) => {
|
||
this.savingBase = false;
|
||
this.toast(apiErrorMessage(err));
|
||
}
|
||
});
|
||
}
|
||
|
||
savePurchase(): void {
|
||
const f = this.form;
|
||
if (
|
||
!f.partyId ||
|
||
!f.certNo.trim() ||
|
||
!f.dealDate ||
|
||
!f.amount ||
|
||
!f.intRate ||
|
||
!f.maturityDate ||
|
||
!f.bankGlAccountId
|
||
) {
|
||
this.toast(
|
||
'Enter the bank, certificate no, deal date, amount, rate, maturity date and the bank account paid from.'
|
||
);
|
||
return;
|
||
}
|
||
if (this.quote?.limit && !this.quote.limit.withinLimits) {
|
||
this.toast(this.quote.limit.message);
|
||
return;
|
||
}
|
||
this.saving = true;
|
||
this.investmentService
|
||
.createFd({
|
||
partyId: f.partyId,
|
||
certNo: f.certNo.trim(),
|
||
...(f.certDate ? { certDate: f.certDate } : {}),
|
||
dealDate: f.dealDate,
|
||
amount: f.amount,
|
||
intRate: f.intRate,
|
||
maturityDate: f.maturityDate,
|
||
payout: f.payout,
|
||
calcType: f.calcType,
|
||
bankGlAccountId: f.bankGlAccountId,
|
||
...(f.remarks.trim() ? { remarks: f.remarks.trim() } : {}),
|
||
locale: 'en',
|
||
dateFormat: 'dd-MM-yyyy'
|
||
})
|
||
.subscribe({
|
||
next: (res) => {
|
||
this.saving = false;
|
||
this.toast(
|
||
`FD ${res.changes?.dealNo ?? ''} placed — voucher ${res.changes?.voucherNo ?? res.resourceIdentifier ?? ''}.`
|
||
);
|
||
this.form = this.blankForm();
|
||
this.quote = null;
|
||
this.loadFds();
|
||
this.setTab('holdings');
|
||
},
|
||
error: (err) => {
|
||
this.saving = false;
|
||
this.toast(apiErrorMessage(err));
|
||
}
|
||
});
|
||
}
|
||
|
||
resetPurchase(): void {
|
||
this.quoteError = '';
|
||
this.form = this.blankForm();
|
||
this.refreshQuote();
|
||
}
|
||
|
||
// -------------------- Interest Entry --------------------
|
||
openInterest(f: InvestmentFdDto): void {
|
||
this.interestFdId = f.id ?? null;
|
||
this.setTab('interest');
|
||
this.loadSchedule();
|
||
}
|
||
|
||
loadSchedule(): void {
|
||
this.schedule = null;
|
||
this.payoutReceived = null;
|
||
this.payoutTds = null;
|
||
if (!this.interestFdId) {
|
||
return;
|
||
}
|
||
this.investmentService.getFdSchedule(this.interestFdId).subscribe({
|
||
next: (s) => {
|
||
this.schedule = s;
|
||
this.payoutBankGlAccountId = s.fd.bankGlAccountId ?? null;
|
||
},
|
||
error: (err) => this.toast(apiErrorMessage(err))
|
||
});
|
||
}
|
||
|
||
lineLabel(t: string): string {
|
||
return t === 'PROVISION' ? 'Provision' : t === 'PAYOUT' ? 'Interest Payout' : 'Maturity';
|
||
}
|
||
|
||
/** Posts the next line: a provision as worked out, a payout with the bank's amount and TDS if entered. */
|
||
postInterest(r: InvestmentFdInterestRowDto): void {
|
||
const fdId = this.interestFdId;
|
||
if (!fdId) {
|
||
return;
|
||
}
|
||
const body: Record<string, string | number> = {
|
||
lineType: r.lineType,
|
||
intDate: r.intDate,
|
||
locale: 'en',
|
||
dateFormat: 'dd-MM-yyyy'
|
||
};
|
||
if (r.lineType === 'PAYOUT') {
|
||
if (this.payoutReceived) {
|
||
body['receivedAmount'] = this.payoutReceived;
|
||
}
|
||
// 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;
|
||
}
|
||
if (this.payoutBankGlAccountId) {
|
||
body['bankGlAccountId'] = this.payoutBankGlAccountId;
|
||
}
|
||
}
|
||
this.postingInterest = true;
|
||
this.investmentService.postFdInterest(fdId, body).subscribe({
|
||
next: (res) => {
|
||
this.postingInterest = false;
|
||
this.toast(`${this.lineLabel(r.lineType)} of ${r.intDate} posted — voucher ${res.resourceIdentifier ?? ''}.`);
|
||
this.loadSchedule();
|
||
},
|
||
error: (err) => {
|
||
this.postingInterest = false;
|
||
this.toast(apiErrorMessage(err));
|
||
}
|
||
});
|
||
}
|
||
|
||
// -------------------- Maturity / Premature / Renewal --------------------
|
||
get closureFd(): InvestmentFdDto | undefined {
|
||
return this.fds.find((f) => f.id === this.closure.fdId);
|
||
}
|
||
|
||
/** Maturity and renewal happen on or after the maturity date; before it only a premature closure is possible. */
|
||
get closureFdMatured(): boolean {
|
||
const f = this.closureFd;
|
||
return !!f?.maturityDate && parseDDMMYYYY(f.maturityDate) <= parseDDMMYYYY(todayDDMMYYYY());
|
||
}
|
||
|
||
/** The renewal top-up (positive) or amount taken out (negative), as worked out by the backend. */
|
||
get closureTopUp(): number {
|
||
return this.closureQuote?.topUp ?? 0;
|
||
}
|
||
|
||
openClosure(f: InvestmentFdDto): void {
|
||
this.closure = this.blankClosure();
|
||
this.closure.fdId = f.id ?? null;
|
||
this.onClosureFdChange();
|
||
this.setTab('closure');
|
||
}
|
||
|
||
onClosureFdChange(): void {
|
||
const f = this.closureFd;
|
||
const c = this.closure;
|
||
c.receivedAmount = null;
|
||
c.renewalAmount = null;
|
||
c.type = this.closureFdMatured ? 'MATURITY' : 'PREMATURE';
|
||
c.principalBankGlAccountId = f?.bankGlAccountId ?? null;
|
||
c.interestBankGlAccountId = f?.bankGlAccountId ?? null;
|
||
c.newPayout = f?.payout ?? 'MATURITY';
|
||
c.newCalcType = f?.calcType ?? 'QUARTERLY';
|
||
this.refreshClosureQuote();
|
||
}
|
||
|
||
onNewPeriodChange(): void {
|
||
const c = this.closure;
|
||
const start = this.closureFd?.maturityDate;
|
||
if (start && (c.newPeriodYears || c.newPeriodMonths || c.newPeriodDays)) {
|
||
c.newMaturityDate = addPeriod(start, c.newPeriodYears ?? 0, c.newPeriodMonths ?? 0, c.newPeriodDays ?? 0);
|
||
}
|
||
}
|
||
|
||
refreshClosureQuote(): void {
|
||
const c = this.closure;
|
||
this.closureError = '';
|
||
if (!c.fdId) {
|
||
this.closureQuote = null;
|
||
return;
|
||
}
|
||
if (c.type === 'PREMATURE' && c.closeDate && !FdInvestmentComponent.fullDate(c.closeDate)) {
|
||
return;
|
||
}
|
||
const params: Record<string, string | number> = { type: c.type };
|
||
if (c.type === 'PREMATURE' && 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) {
|
||
params['receivedAmount'] = c.receivedAmount;
|
||
}
|
||
if (entered(c.tds)) {
|
||
params['tds'] = c.tds;
|
||
}
|
||
if (c.type === 'RENEWAL' && c.renewalAmount) {
|
||
params['renewalAmount'] = c.renewalAmount;
|
||
}
|
||
this.investmentService.closureQuoteFd(c.fdId, params).subscribe({
|
||
next: (q) => (this.closureQuote = q),
|
||
error: (err) => {
|
||
this.closureQuote = null;
|
||
this.closureError = apiErrorMessage(err);
|
||
}
|
||
});
|
||
}
|
||
|
||
saveClosure(): void {
|
||
const c = this.closure;
|
||
const f = this.closureFd;
|
||
if (!f?.id) {
|
||
this.toast('Select the FD.');
|
||
return;
|
||
}
|
||
const base = { locale: 'en', dateFormat: 'dd-MM-yyyy', ...(c.remarks.trim() ? { remarks: c.remarks.trim() } : {}) };
|
||
const tds = entered(c.tds) ? { tds: c.tds } : {};
|
||
let body: object;
|
||
if (c.type === 'RENEWAL') {
|
||
if (!c.newCertNo.trim() || !c.newIntRate || !c.newMaturityDate) {
|
||
this.toast('Enter the new certificate no, interest rate and maturity date.');
|
||
return;
|
||
}
|
||
const topUp = this.closureTopUp;
|
||
if (topUp !== 0 && !c.diffBankGlAccountId) {
|
||
this.toast(
|
||
topUp > 0 ? 'Choose the bank the extra amount is paid from.' : 'Choose the bank the amount taken out goes to.'
|
||
);
|
||
return;
|
||
}
|
||
body = {
|
||
...base,
|
||
...tds,
|
||
newCertNo: c.newCertNo.trim(),
|
||
...(c.newCertDate ? { newCertDate: c.newCertDate } : {}),
|
||
newIntRate: c.newIntRate,
|
||
newMaturityDate: c.newMaturityDate,
|
||
newPayout: c.newPayout,
|
||
newCalcType: c.newCalcType,
|
||
...(c.receivedAmount ? { maturityAmount: c.receivedAmount } : {}),
|
||
...(c.renewalAmount ? { renewalAmount: c.renewalAmount } : {}),
|
||
...(topUp !== 0 && c.diffBankGlAccountId ? { diffBankGlAccountId: c.diffBankGlAccountId } : {})
|
||
};
|
||
} else {
|
||
if (c.type === 'PREMATURE' && (!c.closeDate || !c.receivedAmount)) {
|
||
this.toast('Enter the closure date and the amount received from the bank.');
|
||
return;
|
||
}
|
||
body = {
|
||
...base,
|
||
...tds,
|
||
...(c.type === 'PREMATURE' ? { closeDate: c.closeDate } : {}),
|
||
...(c.receivedAmount ? { receivedAmount: c.receivedAmount } : {}),
|
||
...(c.type === 'PREMATURE' && c.penalAmount ? { penalAmount: c.penalAmount } : {}),
|
||
...(c.principalBankGlAccountId ? { principalBankGlAccountId: c.principalBankGlAccountId } : {}),
|
||
...(c.interestBankGlAccountId ? { interestBankGlAccountId: c.interestBankGlAccountId } : {})
|
||
};
|
||
}
|
||
this.closing = true;
|
||
this.investmentService.closeFd(f.id, c.type, body).subscribe({
|
||
next: (res) => {
|
||
this.closing = false;
|
||
const what =
|
||
c.type === 'RENEWAL'
|
||
? `renewed as ${res.changes?.dealNo ?? ''}`
|
||
: c.type === 'PREMATURE'
|
||
? 'closed'
|
||
: 'matured';
|
||
this.toast(`FD ${f.dealNo} ${what} — voucher ${res.changes?.voucherNo ?? res.resourceIdentifier ?? ''}.`);
|
||
this.closure = this.blankClosure();
|
||
this.closureQuote = null;
|
||
this.loadFds();
|
||
this.setTab('holdings');
|
||
},
|
||
error: (err) => {
|
||
this.closing = false;
|
||
this.toast(apiErrorMessage(err));
|
||
}
|
||
});
|
||
}
|
||
|
||
private blankClosure(): FdClosureForm {
|
||
return {
|
||
fdId: null,
|
||
type: 'MATURITY',
|
||
closeDate: todayDDMMYYYY(),
|
||
receivedAmount: null,
|
||
tds: null,
|
||
penalAmount: null,
|
||
principalBankGlAccountId: null,
|
||
interestBankGlAccountId: null,
|
||
newCertNo: '',
|
||
newCertDate: '',
|
||
newIntRate: null,
|
||
newPeriodYears: null,
|
||
newPeriodMonths: null,
|
||
newPeriodDays: null,
|
||
newMaturityDate: '',
|
||
newPayout: 'MATURITY',
|
||
newCalcType: 'QUARTERLY',
|
||
renewalAmount: null,
|
||
diffBankGlAccountId: null,
|
||
remarks: ''
|
||
};
|
||
}
|
||
|
||
bankGlName(id: number | null): string {
|
||
const g = this.bankGlOptions.find((x) => x.id === id);
|
||
return g ? `${g.glCode} ${g.name}` : '';
|
||
}
|
||
|
||
private blankForm(): FdPurchaseForm {
|
||
return {
|
||
partyId: null,
|
||
certNo: '',
|
||
certDate: '',
|
||
dealDate: todayDDMMYYYY(),
|
||
amount: null,
|
||
intRate: null,
|
||
periodYears: null,
|
||
periodMonths: null,
|
||
periodDays: null,
|
||
maturityDate: '',
|
||
payout: 'MATURITY',
|
||
calcType: 'QUARTERLY',
|
||
bankGlAccountId: null,
|
||
remarks: ''
|
||
};
|
||
}
|
||
|
||
private toast(msg: string): void {
|
||
this.toastMsg = msg;
|
||
this.toastVisible = true;
|
||
if (this.toastTimer) {
|
||
clearTimeout(this.toastTimer);
|
||
}
|
||
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 !== '';
|
||
}
|