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 <noreply@anthropic.com>
This commit is contained in:
+38
@@ -45,6 +45,44 @@
|
||||
<fa-icon icon="info-circle" class="m-r-5"></fa-icon>
|
||||
{{ dashboardRenewalTimingText }}
|
||||
</p>
|
||||
|
||||
@if (renewalPreviewLoading) {
|
||||
<p class="pmt__hint">
|
||||
<fa-icon icon="spinner" [class.fa-spin]="true" class="m-r-5"></fa-icon>
|
||||
Calculating gap-period interest...
|
||||
</p>
|
||||
} @else if (renewalPreview && !renewalPreview.withinGrace) {
|
||||
@if (!renewalPreview.linkedAccountId) {
|
||||
<p class="pmt__hint pmt__hint--warn" role="alert">
|
||||
<fa-icon icon="info-circle" class="m-r-5"></fa-icon>
|
||||
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.
|
||||
</p>
|
||||
} @else {
|
||||
<div class="pmt__preview">
|
||||
<div class="pmt__preview-row">
|
||||
<span class="pmt__preview-label">Current Balance</span>
|
||||
<span class="pmt__preview-value">{{ renewalPreview.currentBalance | formatNumber }}</span>
|
||||
</div>
|
||||
<div class="pmt__preview-row">
|
||||
<span class="pmt__preview-label">Gap Days ({{ renewalPreview.maturityDate }} → today)</span>
|
||||
<span class="pmt__preview-value">{{ renewalPreview.gapDays }}</span>
|
||||
</div>
|
||||
<div class="pmt__preview-row">
|
||||
<span class="pmt__preview-label">Savings Account Interest Rate</span>
|
||||
<span class="pmt__preview-value">{{ renewalPreview.linkedAccountRate }}%</span>
|
||||
</div>
|
||||
<div class="pmt__preview-row pmt__preview-row--highlight">
|
||||
<span class="pmt__preview-label">Gap-Period Interest to be Credited</span>
|
||||
<span class="pmt__preview-value">+ {{ renewalPreview.gapInterestAmount | formatNumber }}</span>
|
||||
</div>
|
||||
<div class="pmt__preview-row pmt__preview-row--total">
|
||||
<span class="pmt__preview-label">New Balance for the Renewed Term</span>
|
||||
<span class="pmt__preview-value">{{ renewalPreview.projectedBalance | formatNumber }}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@if (reinvestCap !== null && reinvestCap !== undefined) {
|
||||
|
||||
+62
@@ -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;
|
||||
|
||||
+19
@@ -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));
|
||||
|
||||
@@ -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<any> {
|
||||
return this.http.get(`/fixeddepositaccounts/${accountId}/renewal-preview`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fixedDepositAccount Fixed Deposit Account
|
||||
* @returns {Observable<any>}
|
||||
|
||||
Reference in New Issue
Block a user