feat(investment): FD screens - purchase, maturity/premature/renewal, interest entry and reminders
This commit is contained in:
@@ -403,6 +403,21 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (preview?.investmentFdPending?.length) {
|
||||
<div class="bd-warn">
|
||||
<mat-icon>notifications_active</mat-icon>
|
||||
<div>
|
||||
<strong>Reminder: FD investment entries are pending.</strong>
|
||||
@for (t of preview.investmentFdPending; track t.fdId) {
|
||||
<div>
|
||||
{{ t.lineType === 'MATURITY' ? 'Maturity' : t.lineType === 'PAYOUT' ? 'Interest payout' : 'Provision' }}
|
||||
· {{ t.partyName }} · FD {{ t.dealNo }} · {{ t.intDate }} · ₹{{ t.amount | number: '1.2-2' }}
|
||||
</div>
|
||||
}
|
||||
Post them under Investment → FD Investment. You can still close the day.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (preview?.savingsInterestPending?.length) {
|
||||
<div class="bd-warn">
|
||||
<mat-icon>warning</mat-icon>
|
||||
|
||||
@@ -64,6 +64,28 @@
|
||||
</a>
|
||||
}
|
||||
|
||||
<!-- FDs placed with other banks: interest vouchers and maturities due -->
|
||||
@if (pendingFd.length) {
|
||||
<a
|
||||
class="investment-interest-alert"
|
||||
routerLink="/investment/fd"
|
||||
[queryParams]="{ tab: 'interest', fd: pendingFd[0].fdId }"
|
||||
>
|
||||
<span class="iia-icon">🔔</span>
|
||||
<span class="iia-text">
|
||||
<strong>{{ pendingFd.length }} FD investment entry(ies) pending</strong>
|
||||
@for (t of pendingFd; track t.fdId) {
|
||||
<span class="iia-item">
|
||||
{{ t.lineType === 'MATURITY' ? 'Maturity' : t.lineType === 'PAYOUT' ? 'Interest payout' : 'Provision' }} ·
|
||||
{{ t.partyName }} · FD {{ t.dealNo }} ·
|
||||
{{ t.overdue ? 'overdue since ' + t.intDate : t.postable ? 'due today' : 'due ' + t.intDate }}
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
<span class="iia-action">Open FD Investment →</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
<!-- Quick Action Cards -->
|
||||
<div class="quick-actions">
|
||||
<a class="qa-card" [routerLink]="['/dashboard']" #buttonDashboard>
|
||||
|
||||
@@ -26,7 +26,7 @@ import { PopoverService } from '../configuration-wizard/popover/popover.service'
|
||||
import { ConfigurationWizardService } from '../configuration-wizard/configuration-wizard.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { DscService } from 'app/users/dsc/dsc.service';
|
||||
import { InvestmentInterestDueDto, InvestmentService } from 'app/investment/investment.service';
|
||||
import { InvestmentFdDueDto, InvestmentInterestDueDto, InvestmentService } from 'app/investment/investment.service';
|
||||
|
||||
/** Custom Components */
|
||||
import { NextStepDialogComponent } from '../configuration-wizard/next-step-dialog/next-step-dialog.component';
|
||||
@@ -122,6 +122,8 @@ 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[] = [];
|
||||
/** FDs placed with other banks: interest vouchers and maturities due now or within two days. */
|
||||
pendingFd: InvestmentFdDueDto[] = [];
|
||||
private readonly investmentService = inject(InvestmentService);
|
||||
|
||||
/** Silent when the user cannot see investments or nothing is due — the strip simply does not render. */
|
||||
@@ -130,6 +132,10 @@ export class HomeComponent implements OnInit, AfterViewInit {
|
||||
next: (due) => (this.pendingInvestmentInterest = due ?? []),
|
||||
error: () => (this.pendingInvestmentInterest = [])
|
||||
});
|
||||
this.investmentService.getFdDue().subscribe({
|
||||
next: (due) => (this.pendingFd = due ?? []),
|
||||
error: () => (this.pendingFd = [])
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1906,6 +1906,10 @@ export interface PartyMaster {
|
||||
glAccountId?: number;
|
||||
glCode?: string;
|
||||
glName?: string;
|
||||
/** Interest income GL of the FDs placed with this bank (Bond Type FD). */
|
||||
interestGlAccountId?: number;
|
||||
interestGlCode?: string;
|
||||
interestGlName?: string;
|
||||
rbiCode?: string;
|
||||
/** Exposure limit for this party (₹). */
|
||||
limit?: number | null;
|
||||
|
||||
@@ -162,6 +162,17 @@
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
@if (partyForm.bondType === 'FD') {
|
||||
<label class="inv-form-field">
|
||||
<span>* Interest GL</span>
|
||||
<select [(ngModel)]="partyForm.interestGlAccountId">
|
||||
<option [ngValue]="undefined">-- Select --</option>
|
||||
@for (g of interestGlOptions; track g.id) {
|
||||
<option [ngValue]="g.id">{{ g.glCode }} — {{ g.name }}</option>
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
}
|
||||
<label class="inv-form-field"><span>RBI Code</span><input type="text" [(ngModel)]="partyForm.rbiCode" /></label>
|
||||
<label class="inv-form-field"
|
||||
><span>Limit (₹)</span><input type="number" min="0" [(ngModel)]="partyForm.limit"
|
||||
|
||||
@@ -126,6 +126,8 @@ export class InvestmentMastersComponent implements OnInit {
|
||||
readonly slrTypeOptions = SLR_TYPE_OPTIONS;
|
||||
/** Investment GLs (under the 100700 INVESTMENTS head) a party or script can be mapped to — not loans, banks or test GLs. */
|
||||
investmentGlOptions: InvestmentGlAccount[] = [];
|
||||
/** Income GLs under 400400 INCOME FROM INVESTMENTS, for an FD bank's interest. */
|
||||
interestGlOptions: InvestmentGlAccount[] = [];
|
||||
newSubType: Partial<SubTypeMaster> = {};
|
||||
newBroker: Partial<BrokerMaster> = {};
|
||||
newControlParam: Partial<ControlParameter> = {};
|
||||
@@ -143,6 +145,10 @@ export class InvestmentMastersComponent implements OnInit {
|
||||
)),
|
||||
error: (err) => this.toast(`Could not load GL accounts: ${apiErrorMessage(err)}`)
|
||||
});
|
||||
this.investmentService.getIncomeGlAccounts().subscribe({
|
||||
next: (gls) => (this.interestGlOptions = gls.filter((g) => g.glCode.startsWith('4004'))),
|
||||
error: (err) => this.toast(`Could not load income GLs: ${apiErrorMessage(err)}`)
|
||||
});
|
||||
this.reloadMasters();
|
||||
}
|
||||
|
||||
@@ -182,6 +188,10 @@ export class InvestmentMastersComponent implements OnInit {
|
||||
this.toast('Enter Bond Type, Party Name and GL A/c No.');
|
||||
return;
|
||||
}
|
||||
if (f.bondType === 'FD' && !f.interestGlAccountId) {
|
||||
this.toast('An FD bank needs its Interest GL as well.');
|
||||
return;
|
||||
}
|
||||
const body = {
|
||||
bondType: f.bondType,
|
||||
partyName: f.partyName,
|
||||
@@ -194,6 +204,7 @@ export class InvestmentMastersComponent implements OnInit {
|
||||
fax: f.fax ?? '',
|
||||
bankType: f.bankType ?? '',
|
||||
glAccountId: Number(f.glAccountId),
|
||||
interestGlAccountId: f.bondType === 'FD' && f.interestGlAccountId ? Number(f.interestGlAccountId) : null,
|
||||
rbiCode: f.rbiCode ?? '',
|
||||
limit: f.limit ?? null,
|
||||
leiNo: f.leiNo ?? '',
|
||||
|
||||
@@ -39,6 +39,9 @@ export interface InvestmentPartyDto {
|
||||
glAccountId: number;
|
||||
glCode: string;
|
||||
glName: string;
|
||||
interestGlAccountId?: number;
|
||||
interestGlCode?: string;
|
||||
interestGlName?: string;
|
||||
rbiCode?: string;
|
||||
limit?: number;
|
||||
leiNo?: string;
|
||||
@@ -49,6 +52,126 @@ export interface InvestmentPartyDto {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
/** Where an FD stands against the RBI limits on deposits with other banks (20% overall, 5% with one bank). */
|
||||
export interface InvestmentFdLimitDto {
|
||||
asOnDate: string;
|
||||
totalDeposits?: number;
|
||||
glTotalDeposits: number;
|
||||
grossLimit?: number;
|
||||
fdOutstanding: number;
|
||||
bankBalances: number;
|
||||
grossAfter: number;
|
||||
bankLimit?: number;
|
||||
partyName?: string;
|
||||
bankAfter: number;
|
||||
newAmount: number;
|
||||
withinLimits: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type FdPayout = 'MATURITY' | 'HALF_YEARLY' | 'YEARLY';
|
||||
export type FdCalcType = 'SIMPLE' | 'MONTHLY' | 'QUARTERLY' | 'HALF_YEARLY' | 'YEARLY';
|
||||
|
||||
/** An FD the bank has placed with another bank (dates DD-MM-YYYY); also the purchase preview. */
|
||||
export interface InvestmentFdDto {
|
||||
id?: number;
|
||||
dealNo?: string;
|
||||
partyId?: number;
|
||||
partyCode?: string;
|
||||
partyName?: string;
|
||||
certNo?: string;
|
||||
certDate?: string;
|
||||
dealDate?: string;
|
||||
principal?: number;
|
||||
intRate?: number;
|
||||
maturityDate?: string;
|
||||
days: number;
|
||||
payout: FdPayout;
|
||||
calcType: FdCalcType;
|
||||
expectedInterest?: number;
|
||||
expectedMaturityAmount?: number;
|
||||
periodInterest?: number;
|
||||
fdGlCode?: string;
|
||||
fdGlName?: string;
|
||||
interestGlCode?: string;
|
||||
interestGlName?: string;
|
||||
bankGlAccountId?: number;
|
||||
bankGlCode?: string;
|
||||
bankGlName?: string;
|
||||
status?: 'ACTIVE' | 'MATURED' | 'PREMATURE' | 'RENEWED';
|
||||
remarks?: string;
|
||||
glTransactionId?: string;
|
||||
voucherNo?: string;
|
||||
renewedFromDealNo?: string;
|
||||
closedOnDate?: string;
|
||||
closureType?: string;
|
||||
closureAmount?: number;
|
||||
closureInterest?: number;
|
||||
closureTds?: number;
|
||||
closurePenal?: number;
|
||||
closureVoucherNo?: string;
|
||||
closureGlTransactionId?: string;
|
||||
limit?: InvestmentFdLimitDto;
|
||||
}
|
||||
|
||||
export type FdClosureType = 'MATURITY' | 'PREMATURE' | 'RENEWAL';
|
||||
|
||||
/** One interest line of an FD: a 31 Mar / 30 Sep provision, a payout, or the maturity. */
|
||||
export interface InvestmentFdInterestRowDto {
|
||||
lineType: 'PROVISION' | 'PAYOUT' | 'MATURITY';
|
||||
intDate: string;
|
||||
fromDate: string;
|
||||
toDate: string;
|
||||
days: number;
|
||||
grossInterest: number;
|
||||
alreadyBooked?: number;
|
||||
tds?: number;
|
||||
amount: number;
|
||||
posted: boolean;
|
||||
voucherNo?: string;
|
||||
glTransactionId?: string;
|
||||
postable: boolean;
|
||||
}
|
||||
|
||||
export interface InvestmentFdScheduleDto {
|
||||
fd: InvestmentFdDto;
|
||||
provisioned: number;
|
||||
rows: InvestmentFdInterestRowDto[];
|
||||
}
|
||||
|
||||
/** The next interest voucher (or the maturity) of an FD that is due. */
|
||||
export interface InvestmentFdDueDto {
|
||||
fdId: number;
|
||||
dealNo: string;
|
||||
partyName: string;
|
||||
certNo: string;
|
||||
lineType: 'PROVISION' | 'PAYOUT' | 'MATURITY';
|
||||
intDate: string;
|
||||
amount: number;
|
||||
postable: boolean;
|
||||
overdue: boolean;
|
||||
}
|
||||
|
||||
/** What closing an FD works out to, and the voucher it will post. */
|
||||
export interface InvestmentFdClosureDto {
|
||||
fdId: number;
|
||||
dealNo: string;
|
||||
type: FdClosureType;
|
||||
closeDate: string;
|
||||
principal: number;
|
||||
expectedInterest: number;
|
||||
provisioned: number;
|
||||
receivedAmount: number;
|
||||
tds: number;
|
||||
penalAmount?: number;
|
||||
interest: number;
|
||||
income: number;
|
||||
difference: number;
|
||||
renewalAmount?: number;
|
||||
topUp?: number;
|
||||
lines: Array<{ side: 'Dr' | 'Cr'; glCode: string; glName: string; amount: number }>;
|
||||
}
|
||||
|
||||
/** Investment script as returned by /investment/scripts (dates are DD-MM-YYYY). */
|
||||
export interface InvestmentScriptDto {
|
||||
id: number;
|
||||
@@ -299,6 +422,77 @@ export class InvestmentService {
|
||||
return this.http.delete(`/investment/purchases/${id}`);
|
||||
}
|
||||
|
||||
// -------------------- FD with other banks --------------------
|
||||
getFds(): Observable<InvestmentFdDto[]> {
|
||||
return this.http.get<InvestmentFdDto[]>('/investment/fds');
|
||||
}
|
||||
|
||||
/** Interest, maturity amount and the RBI limit check for an FD before it is saved. */
|
||||
quoteFd(params: Record<string, string | number>): Observable<InvestmentFdDto> {
|
||||
return this.http.get<InvestmentFdDto>('/investment/fds/quote', { params });
|
||||
}
|
||||
|
||||
/** Places the FD: Dr the bank's FD GL / Cr our bank GL at once. */
|
||||
createFd(
|
||||
body: object
|
||||
): Observable<{ resourceIdentifier?: string; changes?: { dealNo?: string; voucherNo?: string } }> {
|
||||
return this.http.post<{ resourceIdentifier?: string; changes?: { dealNo?: string; voucherNo?: string } }>(
|
||||
'/investment/fds',
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
closureQuoteFd(fdId: number, params: Record<string, string | number>): Observable<InvestmentFdClosureDto> {
|
||||
return this.http.get<InvestmentFdClosureDto>(`/investment/fds/${fdId}/closure-quote`, { params });
|
||||
}
|
||||
|
||||
/** Maturity, premature closure or renewal of an FD (voucher posted at once). */
|
||||
closeFd(
|
||||
fdId: number,
|
||||
type: FdClosureType,
|
||||
body: object
|
||||
): Observable<{ resourceIdentifier?: string; changes?: { dealNo?: string; voucherNo?: string; interest?: number } }> {
|
||||
const path = type === 'MATURITY' ? 'mature' : type === 'PREMATURE' ? 'premature' : 'renew';
|
||||
return this.http.post<{
|
||||
resourceIdentifier?: string;
|
||||
changes?: { dealNo?: string; voucherNo?: string; interest?: number };
|
||||
}>(`/investment/fds/${fdId}/${path}`, body);
|
||||
}
|
||||
|
||||
getFdSchedule(fdId: number): Observable<InvestmentFdScheduleDto> {
|
||||
return this.http.get<InvestmentFdScheduleDto>(`/investment/fds/${fdId}/interest-schedule`);
|
||||
}
|
||||
|
||||
/** Posts the next interest line of an FD (provision, or payout with the bank's amount and TDS). */
|
||||
postFdInterest(fdId: number, body: object): Observable<{ resourceIdentifier?: string }> {
|
||||
return this.http.post<{ resourceIdentifier?: string }>(`/investment/fds/${fdId}/interest`, body);
|
||||
}
|
||||
|
||||
/** FD interest vouchers and maturities due now or within two days. */
|
||||
getFdDue(): Observable<InvestmentFdDueDto[]> {
|
||||
return this.http.get<InvestmentFdDueDto[]>('/investment/fds/due');
|
||||
}
|
||||
|
||||
/** Records total deposits as on a 31 March, the base of the RBI limits. */
|
||||
setDepositBase(body: object): Observable<unknown> {
|
||||
return this.http.post('/investment/fds/deposit-base', body);
|
||||
}
|
||||
|
||||
/** Enabled INCOME detail GLs — an FD bank's interest GL. */
|
||||
getIncomeGlAccounts(): Observable<InvestmentGlAccount[]> {
|
||||
return this.http
|
||||
.get<
|
||||
Array<{ id: number; glCode: string; name: string }>
|
||||
>('/glaccounts', { params: { type: '4', usage: '1', disabled: 'false' } })
|
||||
.pipe(
|
||||
map((list) =>
|
||||
list
|
||||
.map((g) => ({ id: g.id, glCode: g.glCode, name: g.name }))
|
||||
.sort((a, b) => a.glCode.localeCompare(b.glCode))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// -------------------- GL accounts --------------------
|
||||
/** Enabled ASSET detail GLs — the only kind an investment party / script can be booked to. */
|
||||
getAssetGlAccounts(): Observable<InvestmentGlAccount[]> {
|
||||
@@ -345,6 +539,9 @@ export function toPartyMaster(p: InvestmentPartyDto): PartyMaster {
|
||||
glAccountId: p.glAccountId,
|
||||
glCode: p.glCode,
|
||||
glName: p.glName,
|
||||
interestGlAccountId: p.interestGlAccountId,
|
||||
interestGlCode: p.interestGlCode,
|
||||
interestGlName: p.interestGlName,
|
||||
rbiCode: p.rbiCode,
|
||||
limit: p.limit ?? null,
|
||||
leiNo: p.leiNo,
|
||||
|
||||
Reference in New Issue
Block a user