From 5ee0a3fc53a285d8264e91437577ffc005db72a7 Mon Sep 17 00:00:00 2001
From: Nitesh Yadav
Date: Thu, 24 Sep 2026 18:20:11 +0530
Subject: [PATCH] feat(fd): show gap-interest renewal preview in Payment dialog
before confirming a late renewal
Fetches current balance, gap days since maturity, the linked savings
account's rate, and the resulting gap-interest amount and projected new
balance, and displays them above the dashboard-renewal timing text --
so staff see exactly what a late renewal is about to credit before
confirming anything. Read-only; failure to load the preview doesn't
block the renewal itself, since the existing timing text already
explains the action.
Co-Authored-By: Claude Sonnet 5
---
.../payment-dialog.component.html | 38 ++++++++++++
.../payment-dialog.component.scss | 62 +++++++++++++++++++
.../payment-dialog.component.ts | 19 ++++++
.../fixed-deposits/fixed-deposits.service.ts | 7 +++
4 files changed, 126 insertions(+)
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.html b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.html
index 34a510e7f..870c04a21 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.html
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.html
@@ -45,6 +45,44 @@
{{ dashboardRenewalTimingText }}
+
+ @if (renewalPreviewLoading) {
+
+
+ Calculating gap-period interest...
+
+ } @else if (renewalPreview && !renewalPreview.withinGrace) {
+ @if (!renewalPreview.linkedAccountId) {
+
+
+ This FD has no Deposit Account Link, so there is no account to read a gap-interest rate from. Link a
+ Savings account to it before renewing late.
+
+ } @else {
+
+
+ Current Balance
+ {{ renewalPreview.currentBalance | formatNumber }}
+
+
+ Gap Days ({{ renewalPreview.maturityDate }} → today)
+ {{ renewalPreview.gapDays }}
+
+
+ Savings Account Interest Rate
+ {{ renewalPreview.linkedAccountRate }}%
+
+
+ Gap-Period Interest to be Credited
+ + {{ renewalPreview.gapInterestAmount | formatNumber }}
+
+
+ New Balance for the Renewed Term
+ {{ renewalPreview.projectedBalance | formatNumber }}
+
+
+ }
+ }
}
@if (reinvestCap !== null && reinvestCap !== undefined) {
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.scss b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.scss
index a95aa35aa..c6c8920ad 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.scss
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.scss
@@ -164,6 +164,68 @@
}
}
+.pmt__preview {
+ display: flex;
+ flex-direction: column;
+ background: #fff;
+ border: 1px solid #e2e8f0;
+ border-radius: 14px;
+ padding: 4px 18px;
+ margin: 0 0 18px;
+
+ &-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 11px 0;
+ border-bottom: 1px solid #f1f5f9;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ &--highlight {
+ .pmt__preview-value {
+ color: #0d9488;
+ font-weight: 800;
+ }
+ }
+
+ &--total {
+ margin: 0 -18px;
+ padding: 14px 18px;
+ background: linear-gradient(135deg, #eff6ff 0%, #e0e9ff 100%);
+ border-bottom: none;
+ border-radius: 0 0 12px 12px;
+
+ .pmt__preview-label {
+ color: #1d4ed8;
+ font-weight: 700;
+ }
+
+ .pmt__preview-value {
+ color: #0f172a;
+ font-size: 18px;
+ font-weight: 800;
+ }
+ }
+ }
+
+ &-label {
+ font-size: 12.5px;
+ color: #64748b;
+ font-weight: 600;
+ }
+
+ &-value {
+ font-family: 'JetBrains Mono', 'DM Sans', monospace;
+ font-size: 14px;
+ font-weight: 700;
+ color: #0f172a;
+ }
+}
+
.pmt__section-label {
font-size: 12px;
font-weight: 700;
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.ts
index 4e1cc8c39..789d1e902 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/payment-dialog/payment-dialog.component.ts
@@ -113,6 +113,12 @@ export class PaymentDialogComponent implements OnInit {
* after open; cleared as soon as `submit()` has credited it, so a retried submit doesn't double-post. */
private gapInterestPending = false;
+ /** Read-only numbers (current balance, linked account's rate, gap days, gap-interest amount,
+ * projected new balance) fetched once on open in dashboard-renewal mode, shown above the Action
+ * cards so staff see exactly what a late renewal is about to credit before confirming anything. */
+ renewalPreview: any = null;
+ renewalPreviewLoading = false;
+
ngOnInit(): void {
this.maxDate = this.settingsService.businessDate;
this.paymentForm = this.formBuilder.group({
@@ -136,6 +142,19 @@ export class PaymentDialogComponent implements OnInit {
.get('closedOnDate')
.setValue(dashboardRow.withinGraceWindow ? new Date(dashboardRow.maturityDate) : this.maxDate);
this.gapInterestPending = !dashboardRow.withinGraceWindow;
+
+ this.renewalPreviewLoading = true;
+ this.fixedDepositsService.getFdRenewalPreview(this.data.accountId).subscribe({
+ next: (preview: any) => {
+ this.renewalPreview = preview;
+ this.renewalPreviewLoading = false;
+ },
+ // Non-fatal -- the plain timing text above still explains what will happen even if the
+ // numeric breakdown couldn't be fetched; only the numbers are missing, not the action itself.
+ error: () => {
+ this.renewalPreviewLoading = false;
+ }
+ });
}
this.paymentForm.get('onAccountClosureId').valueChanges.subscribe((id: number) => this.onClosureTypeChange(id));
diff --git a/src/app/deposits/fixed-deposits/fixed-deposits.service.ts b/src/app/deposits/fixed-deposits/fixed-deposits.service.ts
index 79976fe80..e5af75631 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposits.service.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposits.service.ts
@@ -174,6 +174,13 @@ export class FixedDepositsService {
return this.http.post(`/fixeddepositaccounts/${accountId}/credit-gap-interest`, {});
}
+ /** Read-only -- what a Renew is about to do (current balance, linked account's rate, gap days,
+ * gap-interest amount, projected new balance), shown in the Payment dialog before anything is
+ * confirmed. Posts nothing; safe to call as often as the dialog needs. */
+ getFdRenewalPreview(accountId: string | number): Observable {
+ return this.http.get(`/fixeddepositaccounts/${accountId}/renewal-preview`);
+ }
+
/**
* @param fixedDepositAccount Fixed Deposit Account
* @returns {Observable}