diff --git a/src/app/cashier/branch-day/branch-day.component.html b/src/app/cashier/branch-day/branch-day.component.html
index 9c13b940d..fefbc2c33 100644
--- a/src/app/cashier/branch-day/branch-day.component.html
+++ b/src/app/cashier/branch-day/branch-day.component.html
@@ -386,6 +386,40 @@
}
+
+
+ @if (preview?.fdMaturityDue?.length) {
+
+
+ event_available
+ Fixed deposits maturing today
+
+ @for (d of preview.fdMaturityDue; track $index) {
+
+
+ {{ d.accountCount }} deposit(s)
+ @if (d.reportStatus === 'APPROVED' || d.reportStatus === 'POSTED') {
+ {{ d.reportStatus }}
+ } @else {
+ {{ d.reportStatus === 'NONE' ? 'NO REPORT' : d.reportStatus }}
+ }
+
+ ₹{{ d.totalInterest | number: '1.2-2' }}
+
+ }
+
+ }
+ @if (preview?.fdMaturityPending?.length) {
+
+
+
+
Interest by product
+
+ Posted through this report, still awaiting approval or posting, and posted directly at maturity instead —
+ added up to what the product has paid out in total over the period.
+
+
+
+ Total, all products
+ ₹{{ productSummaryGrandTotal.total | number: '1.2-2' }}
+
+
+
+
+ From
+
+
+
+ To
+
+
+
+ Refresh
+
+
+ Close
+
+
+ @if (productSummaryLoading) {
+
Adding up interest by product…
+ } @else {
+
+
+
+
+ Product
+ Scheme
+ Posting period
+ Posted (report)
+ Pending approval
+ Posted at maturity
+ Total
+
+
+
+ @for (row of productSummaryRows; track row.productId) {
+
+
+ {{ row.productName }}
+
+
+ {{ fdTypeShort(row.fdType) }}
+
+ {{ row.postingPeriodLabel || '—' }}
+ ₹{{ row.posted | number: '1.2-2' }}
+ ₹{{ row.pending | number: '1.2-2' }}
+ ₹{{ row.maturityPosted | number: '1.2-2' }}
+
+ ₹{{ row.total | number: '1.2-2' }}
+
+
+ } @empty {
+
+ No fixed deposit interest in this period.
+
+ }
+
+
+
+ Total
+
+ ₹{{ productSummaryGrandTotal.posted | number: '1.2-2' }}
+
+
+ ₹{{ productSummaryGrandTotal.pending | number: '1.2-2' }}
+
+
+ ₹{{ productSummaryGrandTotal.maturityPosted | number: '1.2-2' }}
+
+
+ ₹{{ productSummaryGrandTotal.total | number: '1.2-2' }}
+
+
+
+
+
+ }
+
+ }
+
Reports{{ postingDate ? ' for ' + postingDate : '' }}
diff --git a/src/app/fd-interest-posting-report/fd-interest-posting-report.component.ts b/src/app/fd-interest-posting-report/fd-interest-posting-report.component.ts
index d77e345fb..38ff43ae1 100644
--- a/src/app/fd-interest-posting-report/fd-interest-posting-report.component.ts
+++ b/src/app/fd-interest-posting-report/fd-interest-posting-report.component.ts
@@ -18,6 +18,7 @@ import {
FdInterestReport,
FdInterestReportDetail,
FdInterestPostingReportService,
+ FdInterestSummaryRow,
FdOutsideApprovalRow,
FdReportScope,
FdType
@@ -534,6 +535,138 @@ export class FdInterestPostingReportComponent implements OnInit {
return [...map.values()];
}
+ // ── Product summary: due for the product vs posted through this report vs posted at maturity ──
+ // Head office only, same as the branch-wise summary it reuses — a branch reads its own due and
+ // posted totals off the strip and tables above, which already cover it.
+
+ showProductSummary = false;
+ productSummaryLoading = false;
+ productSummaryFrom = '';
+ productSummaryTo = '';
+ private summaryRows: FdInterestSummaryRow[] = [];
+ private productOutsideRows: FdOutsideApprovalRow[] = [];
+
+ openProductSummary(): void {
+ this.showProductSummary = true;
+ if (!this.productSummaryTo) {
+ this.productSummaryTo = this.businessDateIso();
+ this.productSummaryFrom = `${this.productSummaryTo.slice(0, 7)}-01`;
+ }
+ this.loadProductSummary();
+ }
+
+ loadProductSummary(): void {
+ this.productSummaryLoading = true;
+ this.errorMessage = '';
+ let pending = 2;
+ const done = () => {
+ pending -= 1;
+ if (pending === 0) this.productSummaryLoading = false;
+ };
+ this.reportService.summary(this.productSummaryFrom || undefined, this.productSummaryTo || undefined).subscribe({
+ next: (rows) => {
+ this.summaryRows = rows ?? [];
+ done();
+ },
+ error: (err) => {
+ this.summaryRows = [];
+ this.errorMessage = this.messageOf(err, 'Could not load the product summary.');
+ done();
+ }
+ });
+ this.reportService
+ .outsideApproval(this.productSummaryFrom || undefined, this.productSummaryTo || undefined)
+ .subscribe({
+ next: (rows) => {
+ this.productOutsideRows = rows ?? [];
+ done();
+ },
+ error: () => {
+ this.productOutsideRows = [];
+ done();
+ }
+ });
+ }
+
+ closeProductSummary(): void {
+ this.showProductSummary = false;
+ }
+
+ /**
+ * One row per product and scheme: what posted through this report (and what is still pending
+ * approval or posting), plus what posted outside it — chiefly a deposit's last interest, paid the
+ * moment it matured rather than through a periodic report.
+ */
+ get productSummaryRows(): {
+ productId: number;
+ productName: string;
+ fdType: string;
+ postingPeriodLabel: string;
+ posted: number;
+ pending: number;
+ maturityPosted: number;
+ total: number;
+ }[] {
+ const map = new Map<
+ number,
+ {
+ productId: number;
+ productName: string;
+ fdType: string;
+ postingPeriodLabel: string;
+ posted: number;
+ pending: number;
+ maturityPosted: number;
+ total: number;
+ }
+ >();
+ const get = (productId: number, productName: string, fdType: string, label: string) => {
+ const existing = map.get(productId);
+ if (existing) return existing;
+ const created = {
+ productId,
+ productName,
+ fdType,
+ postingPeriodLabel: label,
+ posted: 0,
+ pending: 0,
+ maturityPosted: 0,
+ total: 0
+ };
+ map.set(productId, created);
+ return created;
+ };
+ this.summaryRows.forEach((r) => {
+ const row = get(r.productId, r.productName, r.fdType, r.postingPeriodLabel);
+ const amount = Number(r.totalInterest || 0);
+ if (r.status === 'POSTED') {
+ row.posted += amount;
+ } else if (r.status === 'GENERATED' || r.status === 'APPROVED') {
+ row.pending += amount;
+ }
+ });
+ this.productOutsideRows.forEach((r) => {
+ const row = get(r.productId, r.productName, r.fdType, '');
+ row.maturityPosted += Number(r.interestAmount || 0);
+ });
+ map.forEach((row) => {
+ row.total = row.posted + row.pending + row.maturityPosted;
+ });
+ return [...map.values()].sort((a, b) => a.productName.localeCompare(b.productName));
+ }
+
+ get productSummaryGrandTotal(): { posted: number; pending: number; maturityPosted: number; total: number } {
+ return this.productSummaryRows.reduce(
+ (sum, r) => ({
+ posted: sum.posted + r.posted,
+ pending: sum.pending + r.pending,
+ maturityPosted: sum.maturityPosted + r.maturityPosted,
+ total: sum.total + r.total
+ }),
+ { posted: 0, pending: 0, maturityPosted: 0, total: 0 }
+ );
+ }
+
private businessDateIso(): string {
const businessDate = this.settingsService.businessDate as any;
const parsed = businessDate ? new Date(businessDate) : new Date();
diff --git a/src/app/reports/reports-routing.module.ts b/src/app/reports/reports-routing.module.ts
index 0fb1f75c1..84c9eecf4 100644
--- a/src/app/reports/reports-routing.module.ts
+++ b/src/app/reports/reports-routing.module.ts
@@ -100,6 +100,12 @@ const routes: Routes = [
(m) => m.FdInterestPostingReportComponent
)
},
+ {
+ path: 'fd-maturity',
+ data: { title: 'FD Maturity Reports', breadcrumb: 'FD Maturity Reports' },
+ loadComponent: () =>
+ import('../fd-maturity-report/fd-maturity-report.component').then((m) => m.FdMaturityReportComponent)
+ },
{
path: 'dormancy',
data: { title: 'Inactive, Dormant & DEAF', breadcrumb: 'Inactive, Dormant & DEAF' },