diff --git a/src/app/core/shell/shell.component.html b/src/app/core/shell/shell.component.html index ca8e9e607..109385ccd 100644 --- a/src/app/core/shell/shell.component.html +++ b/src/app/core/shell/shell.component.html @@ -1525,7 +1525,7 @@ - + + +
+
+ + + +
+
+ +
+ @if (error) { +

{{ error }}

+ } +
+ + @if (report; as r) { +
+
+ Received₹{{ totals.received | number: '1.2-2' }} +
+
+ Receivable₹{{ totals.receivable | number: '1.2-2' }} +
+
+ Of which provisioned (100603)₹{{ totals.provisioned | number: '1.2-2' }} +
+
+ Total₹{{ totals.total | number: '1.2-2' }} +
+
+ +
+ + + + + + + + + + + + + + + + + + @for (x of rows; track x.instrument + x.refId) { + + + + + + + + + + + + + } @empty { + + + + } + + @if (rows.length) { + + + + + + + + + + } +
InvestmentDeal NoParticularsStatusPrincipal / Face ValueRateReceivedReceivableOf which provisionedTotal
{{ x.instrument === 'FD' ? 'FD' : 'Govt Security / Bond' }} + {{ x.dealNo }} + {{ x.particulars }}{{ x.status }}₹{{ x.principal | number: '1.2-2' }}{{ x.rate !== null && x.rate !== undefined ? x.rate + '%' : '—' }}₹{{ x.received | number: '1.2-2' }}₹{{ x.receivable | number: '1.2-2' }}₹{{ x.provisioned | number: '1.2-2' }}₹{{ x.total | number: '1.2-2' }}
No interest received or receivable in this period.
Total₹{{ totals.received | number: '1.2-2' }}₹{{ totals.receivable | number: '1.2-2' }}₹{{ totals.provisioned | number: '1.2-2' }}₹{{ totals.total | number: '1.2-2' }}
+
+ } + diff --git a/src/app/investment/investment-interest-report/investment-interest-report.component.ts b/src/app/investment/investment-interest-report/investment-interest-report.component.ts new file mode 100644 index 000000000..913aca0b0 --- /dev/null +++ b/src/app/investment/investment-interest-report/investment-interest-report.component.ts @@ -0,0 +1,173 @@ +/** + * 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 { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { DateDdmmyyyyPickerComponent } from '../date-ddmmyyyy-picker/date-ddmmyyyy-picker.component'; +import { parseDDMMYYYY, todayDDMMYYYY } from '../investment-data'; +import { exportWorkbook } from '../investment-export'; +import { + InvestmentInterestReportDto, + InvestmentInterestReportRowDto, + InvestmentService, + apiErrorMessage +} from '../investment.service'; + +type InstrumentFilter = '' | 'SECURITY' | 'FD'; + +/** + * Total Interest Received & Receivable: per Govt Security / bond deal and FD, the interest received in a period and the + * interest earned up to its last day that has not come in yet, with the part already provisioned in the books. + */ +@Component({ + selector: 'mifosx-investment-interest-report', + templateUrl: './investment-interest-report.component.html', + styleUrls: [ + '../../cashier/cashier-shared.scss', + '../investment-shared.scss', + '../investment-diary/investment-diary.component.scss' + ], + standalone: true, + imports: [ + CommonModule, + FormsModule, + DateDdmmyyyyPickerComponent + ] +}) +export class InvestmentInterestReportComponent implements OnInit { + private readonly investmentService = inject(InvestmentService); + + /** Defaults to the current financial year, 1 April to today. */ + fromDate = InvestmentInterestReportComponent.financialYearStart(); + toDate = todayDDMMYYYY(); + instrument: InstrumentFilter = ''; + report: InvestmentInterestReportDto | null = null; + loading = false; + generating = false; + error = ''; + + ngOnInit(): void { + this.load(); + } + + private static financialYearStart(): string { + const d = parseDDMMYYYY(todayDDMMYYYY()); + const year = d.getMonth() >= 3 ? d.getFullYear() : d.getFullYear() - 1; + return `01-04-${year}`; + } + + load(): void { + if (!this.fromDate || !this.toDate) { + this.error = 'Enter the From and To dates.'; + return; + } + this.loading = true; + this.error = ''; + this.investmentService.getInterestReport(this.fromDate, this.toDate).subscribe({ + next: (r) => { + this.report = r; + this.loading = false; + }, + error: (err) => { + this.report = null; + this.loading = false; + this.error = apiErrorMessage(err); + } + }); + } + + get rows(): InvestmentInterestReportRowDto[] { + return (this.report?.rows ?? []).filter((r) => !this.instrument || r.instrument === this.instrument); + } + + /** Totals of the rows shown, so they follow the Investment filter. */ + get totals(): { received: number; receivable: number; provisioned: number; total: number } { + return this.rows.reduce( + (t, r) => ({ + received: t.received + r.received, + receivable: t.receivable + r.receivable, + provisioned: t.provisioned + r.provisioned, + total: t.total + r.total + }), + { received: 0, receivable: 0, provisioned: 0, total: 0 } + ); + } + + async exportExcel(): Promise { + if (!this.report) { + return; + } + this.generating = true; + try { + const t = this.totals; + await exportWorkbook( + [ + { + sheetName: 'Interest Received', + title: 'Total Interest Received & Receivable', + subtitle: `${this.report.fromDate} to ${this.report.toDate}`, + headers: [ + 'Investment', + 'Deal No', + 'Particulars', + 'Status', + 'Principal / Face Value', + 'Rate %', + 'Received', + 'Receivable', + 'Of which provisioned', + 'Total' + ], + widths: [ + 14, + 16, + 40, + 12, + 18, + 8, + 16, + 16, + 18, + 16 + ], + rows: [ + ...this.rows.map((r) => [ + r.instrument === 'FD' ? 'FD' : 'Govt Security / Bond', + r.dealNo, + r.particulars, + r.status, + r.principal, + r.rate ?? null, + r.received, + r.receivable, + r.provisioned, + r.total + ]), + [ + 'Total', + '', + '', + '', + null, + null, + t.received, + t.receivable, + t.provisioned, + t.total + ] + ] + } + ], + `Interest-Received-Receivable-${this.report.toDate}` + ); + } finally { + this.generating = false; + } + } +} diff --git a/src/app/investment/investment-routing.module.ts b/src/app/investment/investment-routing.module.ts index 116702873..f79755069 100644 --- a/src/app/investment/investment-routing.module.ts +++ b/src/app/investment/investment-routing.module.ts @@ -18,6 +18,7 @@ import { InvestmentMastersComponent } from './investment-masters/investment-mast import { InvestmentReportsComponent } from './investment-reports/investment-reports.component'; import { InvestmentInterestComponent } from './investment-interest/investment-interest.component'; import { InvestmentDiaryComponent } from './investment-diary/investment-diary.component'; +import { InvestmentInterestReportComponent } from './investment-interest-report/investment-interest-report.component'; const routes: Routes = [ Route.withShell([ @@ -60,6 +61,11 @@ const routes: Routes = [ component: InvestmentDiaryComponent, data: { title: 'Due Date Diary', breadcrumb: 'Due Date Diary' } }, + { + path: 'interest-report', + component: InvestmentInterestReportComponent, + data: { title: 'Interest Received & Receivable', breadcrumb: 'Interest Received & Receivable' } + }, { path: 'reports', component: InvestmentReportsComponent, diff --git a/src/app/investment/investment.service.ts b/src/app/investment/investment.service.ts index ad17e1e76..e702425c8 100644 --- a/src/app/investment/investment.service.ts +++ b/src/app/investment/investment.service.ts @@ -143,6 +143,32 @@ export interface InvestmentFdScheduleDto { rows: InvestmentFdInterestRowDto[]; } +/** One Govt Security / bond deal or FD in the Interest Received & Receivable report. */ +export interface InvestmentInterestReportRowDto { + instrument: 'SECURITY' | 'FD'; + refId: number; + dealNo: string; + particulars: string; + status: string; + principal: number; + rate?: number; + received: number; + receivable: number; + provisioned: number; + total: number; +} + +/** Interest received in a period and receivable at its end, with totals. */ +export interface InvestmentInterestReportDto { + fromDate: string; + toDate: string; + totalReceived: number; + totalReceivable: number; + totalProvisioned: number; + total: number; + rows: InvestmentInterestReportRowDto[]; +} + /** One dated event in the Due Date Diary. */ export interface InvestmentDiaryRowDto { date: string; @@ -501,6 +527,11 @@ export class InvestmentService { return this.http.get('/investment/fds/due'); } + /** Total Interest Received & Receivable for a period (DD-MM-YYYY). */ + getInterestReport(fromDate: string, toDate: string): Observable { + return this.http.get('/investment/interest-report', { params: { fromDate, toDate } }); + } + /** Due Date Diary: dated events of all active investments between two dates (DD-MM-YYYY). */ getDiary(fromDate: string, toDate: string): Observable { return this.http.get('/investment/diary', { params: { fromDate, toDate } });