feat: senior citizen rate fix, joint ownership cap, FD/RD UI improvements

- Senior citizen interest rate correctly sent in FD/RD create payload
  (isSeniorCitizen, seniorCitizenInterestRate, nominalAnnualInterestRate override)
- Joint account ownership capped at 100%: Add Holder/Partner button disabled
  when 100% reached; dialog enforces remaining share as max across all account types
- FD/RD interest rate chart step: rate calculator with tenure, senior citizen toggle,
  maturity preview
- FD/RD preview step improvements
- Base FD interest rate chart component added under fixed-deposit-products
- Base recurring deposit interest rate chart component removed (replaced)
- Savings account details step: joint holder improvements
- Products routing and module updated for base FD rate chart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-05-21 21:15:45 +05:30
co-authored by Claude Sonnet 4.6
parent 846b2484d7
commit 61b0244fa0
37 changed files with 1998 additions and 435 deletions
@@ -74,6 +74,7 @@
<mifosx-fixed-deposit-account-interest-rate-chart-step
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
[cbsBaseRates]="cbsBaseRates"
(tenureSelected)="onTenureSelected($event)"
>
</mifosx-fixed-deposit-account-interest-rate-chart-step>
@@ -14,6 +14,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { FixedDepositsService } from '../fixed-deposits.service';
import { SettingsService } from 'app/settings/settings.service';
import { AlertService } from 'app/core/alert/alert.service';
import { ProductsService } from 'app/products/products.service';
/** Custom Components */
import { FixedDepositAccountDetailsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component';
@@ -61,6 +62,7 @@ export class CreateFixedDepositAccountComponent {
private fixedDepositsService = inject(FixedDepositsService);
private settingsService = inject(SettingsService);
private alertService = inject(AlertService);
private productsService = inject(ProductsService);
@ViewChild(FixedDepositAccountDetailsStepComponent, { static: true })
fixedDepositsAccountDetailsStep: FixedDepositAccountDetailsStepComponent;
@@ -71,6 +73,8 @@ export class CreateFixedDepositAccountComponent {
fixedDepositsAccountTemplate: any;
/** Fixed Deposit Account Product Template */
fixedDepositsAccountProductTemplate: any;
/** Active CBS base interest rate versions for the selected product's FD type */
cbsBaseRates: any[] = [];
/** Tenure selected from the interest rate chart step */
selectedTenure: {
depositPeriod: number;
@@ -79,6 +83,7 @@ export class CreateFixedDepositAccountComponent {
maturityAmount?: number;
interestEarned?: number;
appliedRate?: number;
isSeniorCitizen?: boolean;
} | null = null;
/**
@@ -101,6 +106,11 @@ export class CreateFixedDepositAccountComponent {
*/
setTemplate($event: any) {
this.fixedDepositsAccountProductTemplate = $event;
const fdType = $event?.fdType;
this.productsService.getFdBaseInterestRates(fdType).subscribe({
next: (rates: any[]) => (this.cbsBaseRates = rates || []),
error: () => (this.cbsBaseRates = [])
});
}
onTenureSelected($event: {
@@ -110,6 +120,7 @@ export class CreateFixedDepositAccountComponent {
maturityAmount?: number;
interestEarned?: number;
appliedRate?: number;
isSeniorCitizen?: boolean;
}) {
this.selectedTenure = $event;
}
@@ -257,6 +268,17 @@ export class CreateFixedDepositAccountComponent {
}));
}
// Senior citizen rate override
if (
this.selectedTenure?.isSeniorCitizen &&
this.selectedTenure.appliedRate != null &&
this.selectedTenure.appliedRate > 0
) {
fixedDepositAccount.isSeniorCitizen = true;
fixedDepositAccount.seniorCitizenInterestRate = this.selectedTenure.appliedRate;
fixedDepositAccount.nominalAnnualInterestRate = this.selectedTenure.appliedRate;
}
this.fixedDepositsService.createFixedDepositAccount(fixedDepositAccount).subscribe({
next: (response: any) => {
this.router.navigate(
@@ -370,6 +370,7 @@
color="primary"
type="button"
class="soa-add-btn"
[disabled]="totalJointSharePercentage >= 100"
(click)="openAddJointHolderDialog()"
>
<mat-icon>person_add</mat-icon> Add Holder
@@ -473,11 +473,12 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
...(this.primaryClientId ? [this.primaryClientId] : []),
...this.jointHolders.map((h: any) => h.clientId).filter(Boolean)
];
const maxSharePercentage = Math.max(0, 100 - this.totalJointSharePercentage);
const ref = this.dialog.open(AddRelatedClientDialogComponent, {
width: '500px',
panelClass: 'arc-dialog-panel',
disableClose: true,
data: { excludedClientIds }
data: { excludedClientIds, maxSharePercentage }
});
ref.afterClosed().subscribe((result) => {
if (result) {
@@ -8,7 +8,7 @@
<div class="fdrc-wrapper">
<!-- ══ Chart Header Card ════════════════════════════════════════════════ -->
@if (fixedDepositsAccountProductTemplate?.accountChart) {
@if (interestRateChartData.length > 0) {
<div class="fdrc-header-card">
<div class="fdrc-header-card__left">
<div class="fdrc-header-card__icon">
@@ -163,6 +163,18 @@
</div>
</div>
<!-- Senior Citizen -->
<div class="fdrc-calc-field fdrc-calc-field--senior">
<label class="fdrc-senior-label">
<input type="checkbox" [(ngModel)]="isSeniorCitizen" (change)="onSeniorCitizenChange()" />
<span class="fdrc-senior-text">
<i class="fas fa-user-shield"></i>
Senior Citizen
</span>
<span class="fdrc-senior-hint">Higher rate applied if available</span>
</label>
</div>
<div class="fdrc-calc-actions">
<button type="button" class="fdrc-calc-btn" (click)="calculateRate()">
<i class="fas fa-search"></i> Find Rate
@@ -186,34 +198,80 @@
<!-- Match Result -->
@if (matchResult) {
<div class="fdrc-result-card">
<div class="fdrc-result-card__check">
<i class="fas fa-check-circle"></i>
</div>
<div class="fdrc-result-card__body">
<!-- Selected Tenure Banner -->
<!-- Tenure Banner -->
<div class="fdrc-tenure-banner">
<i class="fas fa-calendar-check"></i>
<div class="fdrc-tenure-banner__content">
<span class="fdrc-tenure-banner__label">Your Selected FD Tenure</span>
<span class="fdrc-tenure-banner__value">{{ tenureValue }} {{ tenurePeriodUnit }}</span>
</div>
<span class="fdrc-tenure-banner__saved"> <i class="fas fa-save"></i> Will be saved </span>
<span class="fdrc-tenure-banner__saved"><i class="fas fa-save"></i> Will be saved</span>
</div>
<div class="fdrc-result-rate">
<span class="fdrc-result-rate__value">{{ matchResult.appliedRate | number: '1.2-2' }}%</span>
<span class="fdrc-result-rate__label">Annual Interest Rate</span>
</div>
<!-- Senior Citizen Status — always shown when checked -->
@if (isSeniorCitizen) {
@if ((matchResult.slab.seniorCitizenRate ?? 0) > 0) {
<div class="fdrc-sc-status fdrc-sc-status--applied">
<i class="fas fa-user-shield fdrc-sc-status__icon"></i>
<span class="fdrc-sc-status__text">Senior Citizen Rate Applied</span>
<span class="fdrc-sc-status__bonus">
+{{
(matchResult.slab.seniorCitizenRate ?? 0) - (matchResult.slab.annualInterestRate ?? 0)
| number: '1.2-2'
}}% bonus
</span>
</div>
} @else {
<div class="fdrc-sc-status fdrc-sc-status--notice">
<i class="fas fa-info-circle fdrc-sc-status__icon"></i>
<span class="fdrc-sc-status__text"
>Senior Citizen selected — no additional rate configured for this slab, normal rate applied</span
>
</div>
}
}
<!-- Rate Display -->
@if (isSeniorCitizen && (matchResult.slab.seniorCitizenRate ?? 0) > 0) {
<!-- Before / After comparison -->
<div class="fdrc-rate-compare">
<div class="fdrc-rate-compare__block">
<span class="fdrc-rate-compare__tag">Normal Rate</span>
<span class="fdrc-rate-compare__val fdrc-rate-compare__val--strike"
>{{ matchResult.slab.annualInterestRate | number: '1.2-2' }}%</span
>
<span class="fdrc-rate-compare__sub">p.a.</span>
</div>
<div class="fdrc-rate-compare__arrow"><i class="fas fa-long-arrow-alt-right"></i></div>
<div class="fdrc-rate-compare__block fdrc-rate-compare__block--sc">
<span class="fdrc-rate-compare__tag fdrc-rate-compare__tag--sc"
><i class="fas fa-user-shield"></i> Sr. Citizen Rate</span
>
<span class="fdrc-rate-compare__val fdrc-rate-compare__val--sc"
>{{ matchResult.appliedRate | number: '1.2-2' }}%</span
>
<span class="fdrc-rate-compare__sub">p.a.</span>
</div>
</div>
} @else {
<div class="fdrc-result-rate">
<span class="fdrc-result-rate__value">{{ matchResult.appliedRate | number: '1.2-2' }}%</span>
<span class="fdrc-result-rate__label">Annual Interest Rate p.a.</span>
</div>
}
<!-- Slab Info -->
<div class="fdrc-result-meta">
<div class="fdrc-result-meta__item">
<i class="fas fa-layer-group"></i>
<span
>Applicable Slab:
<strong>
{{ matchResult.slab.fromPeriod }}–{{ matchResult.slab.toPeriod ?? '∞' }}
{{ matchResult.slab.periodType?.value }}
</strong>
</span>
<strong
>{{ matchResult.slab.fromPeriod }}–{{ matchResult.slab.toPeriod ?? '∞' }}
{{ matchResult.slab.periodType?.value }}</strong
></span
>
</div>
@if (matchResult.slab.description) {
<div class="fdrc-result-meta__item">
@@ -230,18 +288,21 @@
>{{ matchResult.slab.amountRangeFrom | number }}–{{
matchResult.slab.amountRangeTo | number
}}</strong
>
</span>
></span
>
</div>
}
</div>
<!-- Maturity Summary (all FD types) -->
@if (matchResult.maturityAmount !== null) {
<!-- Maturity Summary -->
@if (matchResult.maturityAmount) {
<div class="fdrc-maturity-card">
<div class="fdrc-maturity-card__header">
<i class="fas fa-coins"></i>
<span>Maturity Summary</span>
@if (isSeniorCitizen && (matchResult.slab.seniorCitizenRate ?? 0) > 0) {
<span class="fdrc-sc-tag"><i class="fas fa-user-shield"></i> Sr. Citizen Rate</span>
}
</div>
<div class="fdrc-maturity-card__body">
<div class="fdrc-maturity-row">
@@ -252,15 +313,24 @@
<span class="fdrc-maturity-row__label">Tenure</span>
<span class="fdrc-maturity-row__value">{{ tenureValue }} {{ tenurePeriodUnit }}</span>
</div>
<div class="fdrc-maturity-row">
<span class="fdrc-maturity-row__label">Interest Rate</span>
<div
class="fdrc-maturity-row"
[class.fdrc-maturity-row--sc]="isSeniorCitizen && (matchResult.slab.seniorCitizenRate ?? 0) > 0"
>
<span class="fdrc-maturity-row__label">
@if (isSeniorCitizen && (matchResult.slab.seniorCitizenRate ?? 0) > 0) {
<i class="fas fa-user-shield"></i> Sr. Citizen Rate
} @else {
Interest Rate
}
</span>
<span class="fdrc-maturity-row__value">{{ matchResult.appliedRate | number: '1.2-2' }}% p.a.</span>
</div>
<div class="fdrc-maturity-row fdrc-maturity-row--interest">
<span class="fdrc-maturity-row__label">Interest Earned</span>
<span class="fdrc-maturity-row__value">₹ {{ matchResult.interestEarned | number: '1.2-2' }}</span>
</div>
@if (isSpecial && matchResult.estimatedMonthlyPayout !== null) {
@if (isSpecial && matchResult.estimatedMonthlyPayout) {
<div class="fdrc-maturity-row">
<span class="fdrc-maturity-row__label">Monthly Payout</span>
<span class="fdrc-maturity-row__value fdrc-maturity-row__value--highlight">
@@ -350,6 +420,22 @@
</td>
</ng-container>
<!-- Senior Citizen Rate Column -->
<ng-container matColumnDef="seniorCitizenRate">
<th mat-header-cell *matHeaderCellDef class="fdrc-th">
<i class="fas fa-user-shield" style="margin-right: 4px; color: #6366f1"></i>Sr. Citizen Rate
</th>
<td mat-cell *matCellDef="let slab" class="fdrc-td">
@if (slab.seniorCitizenRate > 0) {
<span class="fdrc-rate-badge fdrc-rate-badge--senior"
>{{ slab.seniorCitizenRate | number: '1.2-2' }}%</span
>
} @else {
<span class="fdrc-td--na">—</span>
}
</td>
</ng-container>
<!-- Description Column -->
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Description</th>
@@ -387,8 +387,7 @@ $red-light: #fee2e2;
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
margin-top: 22px;
flex: 0 0 100%;
}
.fdrc-calc-btn {
@@ -515,25 +514,153 @@ $red-light: #fee2e2;
// ── Result Card ───────────────────────────────────────────────────────────────
.fdrc-result-card {
display: flex;
align-items: flex-start;
gap: 16px;
margin-top: 16px;
padding: 18px 20px;
background: $green-light;
border: 1.5px solid rgba($green, 0.35);
border-radius: 12px;
&__check {
font-size: 26px;
color: $green;
flex-shrink: 0;
margin-top: 2px;
&__body {
display: flex;
flex-direction: column;
gap: 12px;
}
}
// ── Senior Citizen Status Banner ──────────────────────────────────────────
.fdrc-sc-status {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
&--applied {
background: #ede9fe;
border: 1.5px solid #a78bfa;
color: #4c1d95;
}
&__body {
&--notice {
background: #fef3c7;
border: 1.5px solid #fcd34d;
color: #78350f;
}
&__icon {
font-size: 16px;
flex-shrink: 0;
}
&__text {
flex: 1;
min-width: 0;
}
&__bonus {
margin-left: auto;
flex-shrink: 0;
background: #7c3aed;
color: #fff;
padding: 3px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
}
}
// ── Rate Comparison Block (Normal → Senior) ───────────────────────────────
.fdrc-rate-compare {
display: flex;
align-items: flex-end;
gap: 16px;
flex-wrap: wrap;
&__block {
display: flex;
flex-direction: column;
gap: 3px;
}
&__tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: $gray-200;
color: $gray-600;
border-radius: 999px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
white-space: nowrap;
width: fit-content;
&--sc {
background: #ede9fe;
color: #5b21b6;
}
}
&__val {
font-size: 30px;
font-weight: 800;
line-height: 1;
&--strike {
color: $gray-600;
text-decoration: line-through;
}
&--sc {
color: #5b21b6;
}
}
&__sub {
font-size: 11px;
color: $gray-600;
}
&__arrow {
font-size: 22px;
color: $gray-600;
padding-bottom: 8px;
flex-shrink: 0;
}
}
// ── SC Tag (maturity card header) ─────────────────────────────────────────
.fdrc-sc-tag {
display: inline-flex;
align-items: center;
gap: 4px;
margin-left: auto;
padding: 3px 10px;
background: #ede9fe;
color: #5b21b6;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
white-space: nowrap;
}
// ── SC Maturity Row ───────────────────────────────────────────────────────
.fdrc-maturity-row--sc {
.fdrc-maturity-row__label {
color: #5b21b6;
font-weight: 600;
}
.fdrc-maturity-row__value {
color: #5b21b6;
font-weight: 700;
}
}
@@ -754,6 +881,151 @@ $red-light: #fee2e2;
font-weight: 700;
}
.fdrc-rate-badge--senior {
background: #ede9fe;
color: #5b21b6;
}
// ── Senior Citizen Rate Comparison View ───────────────────────────────────
.fdrc-senior-rate-view {
margin-bottom: 12px;
&__row {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 10px;
flex-wrap: wrap;
}
&__normal {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
opacity: 0.65;
}
&__senior {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
&__badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: $gray-200;
color: $gray-600;
border-radius: 999px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
white-space: nowrap;
&--sr {
background: #ede9fe;
color: #5b21b6;
}
}
&__strike {
font-size: 24px;
font-weight: 700;
color: $gray-600;
text-decoration: line-through;
line-height: 1;
}
&__applied {
font-size: 32px;
font-weight: 800;
color: #5b21b6;
line-height: 1;
}
&__arrow {
font-size: 18px;
color: $gray-600;
flex-shrink: 0;
align-self: center;
margin-top: 16px;
}
&__bonus {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 14px;
background: #ede9fe;
color: #5b21b6;
border-radius: 8px;
font-size: 12px;
font-weight: 700;
i {
font-size: 13px;
}
}
}
.fdrc-senior-badge {
display: inline-flex;
align-items: center;
gap: 4px;
margin-left: 8px;
padding: 2px 8px;
background: #ede9fe;
color: #5b21b6;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
}
.fdrc-calc-field--senior {
flex: 0 0 100%;
display: flex;
align-items: center;
}
.fdrc-senior-label {
display: inline-flex;
align-items: center;
gap: 8px;
cursor: pointer;
user-select: none;
[type='checkbox'] {
width: 16px;
height: 16px;
accent-color: #6366f1;
cursor: pointer;
}
}
.fdrc-senior-text {
display: inline-flex;
align-items: center;
gap: 5px;
font-weight: 600;
color: #4338ca;
font-size: 13px;
i {
color: #6366f1;
}
}
.fdrc-senior-hint {
font-size: 11px;
color: #94a3b8;
}
.fdrc-incentive-btn {
display: inline-flex;
align-items: center;
@@ -71,6 +71,8 @@ export interface SlabMatchResult {
export class FixedDepositAccountInterestRateChartStepComponent implements OnChanges {
@Input() fixedDepositsAccountTemplate: any;
@Input() fixedDepositsAccountProductTemplate: any;
/** Active CBS base interest rate versions — when provided, overrides the product chart slabs */
@Input() cbsBaseRates: any[] = [];
@Output() tenureSelected = new EventEmitter<{
depositPeriod: number;
@@ -79,6 +81,7 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
maturityAmount?: number;
interestEarned?: number;
appliedRate?: number;
isSeniorCitizen?: boolean;
}>();
interestRateChartData: any[] = [];
@@ -87,9 +90,12 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
'period',
'amountRange',
'interest',
'seniorCitizenRate',
'description',
'actions'
];
isSeniorCitizen = false;
incentivesDisplayedColumns: string[] = [
'entityType',
'attributeName',
@@ -151,17 +157,26 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
ngOnChanges() {
if (this.fixedDepositsAccountProductTemplate) {
this.interestRateChartData = this.fixedDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [];
const fdType = this.fixedDepositsAccountProductTemplate?.fdType;
const activeBase = (this.cbsBaseRates || []).find((r) => r.fdType === fdType && r.isActive);
this.interestRateChartData = activeBase?.slabs
? this.normalizeCbsSlabs(activeBase.slabs)
: (this.fixedDepositsAccountProductTemplate.accountChart?.chartSlabs ?? []);
if (this.chartsTableRef) {
this.chartsTableRef.renderRows();
}
// Reset calculator when product changes
this.resetCalculator();
// Set default period unit based on FD type
this.tenurePeriodUnit = this.allowedPeriodUnits[0];
}
}
private normalizeCbsSlabs(slabs: any[]): any[] {
return (slabs || []).map((s) => ({
...s,
periodType: { id: s.periodTypeEnum, value: s.periodTypeLabel }
}));
}
// Convert entered tenure to Days for slab matching
private toDays(value: number, unit: FdPeriodUnit): number {
if (unit === 'Days') return value;
@@ -229,7 +244,10 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
return;
}
const rate = matchedSlab.annualInterestRate ?? 0;
const rate =
this.isSeniorCitizen && (matchedSlab.seniorCitizenRate ?? 0) > 0
? (matchedSlab.seniorCitizenRate ?? 0)
: (matchedSlab.annualInterestRate ?? 0);
const result: SlabMatchResult = {
slab: matchedSlab,
slabIndex: matchedIndex,
@@ -266,7 +284,8 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
depositAmount: this.principalAmount || undefined,
maturityAmount: result.maturityAmount,
interestEarned: result.interestEarned,
appliedRate: result.appliedRate
appliedRate: result.appliedRate,
isSeniorCitizen: this.isSeniorCitizen
});
}
}
@@ -294,12 +313,20 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
this.validationWarning = null;
}
onSeniorCitizenChange() {
if (this.tenureValue && this.tenureValue > 0) {
this.calculateRate();
} else {
this.matchResult = null;
}
}
isMatchedSlab(index: number): boolean {
return this.matchResult?.slabIndex === index;
}
get chartName(): string {
return this.fixedDepositsAccountProductTemplate?.accountChart?.name || '';
return this.fixedDepositsAccountProductTemplate?.accountChart?.name || 'CBS Base Interest Rate';
}
get chartFromDate(): any {
@@ -488,7 +488,26 @@
<div class="preview-body">
<!-- Selected Tenure + Rate Banner -->
@if (selectedTenure && matchedSlabIndex >= 0) {
<div class="preview-tenure-banner">
<!-- Senior Citizen Status Banner -->
@if (isSeniorCitizenSelected) {
@if (seniorCitizenBonus > 0) {
<div class="preview-sc-status preview-sc-status--applied">
<i class="fas fa-user-shield"></i>
<span>Senior Citizen Rate Applied</span>
<span class="preview-sc-status__bonus">+{{ seniorCitizenBonus | formatNumber }}% bonus</span>
</div>
} @else {
<div class="preview-sc-status preview-sc-status--notice">
<i class="fas fa-info-circle"></i>
<span>Senior Citizen selected — no additional rate configured for this slab</span>
</div>
}
}
<div
class="preview-tenure-banner"
[class.preview-tenure-banner--sc]="isSeniorCitizenSelected && seniorCitizenBonus > 0"
>
<div class="preview-tenure-banner__left">
<i class="fas fa-calendar-check"></i>
<div>
@@ -498,9 +517,17 @@
</div>
<div class="preview-tenure-banner__divider"></div>
<div class="preview-tenure-banner__right">
<span class="preview-tenure-banner__rate-label">Applicable Interest Rate</span>
<span class="preview-tenure-banner__rate"
>{{ interestRateChartData[matchedSlabIndex]?.annualInterestRate | formatNumber }}%</span
<span class="preview-tenure-banner__rate-label">
@if (isSeniorCitizenSelected && seniorCitizenBonus > 0) {
<i class="fas fa-user-shield" style="color: #5b21b6; margin-right: 4px"></i>Sr. Citizen Rate
} @else {
Applicable Interest Rate
}
</span>
<span
class="preview-tenure-banner__rate"
[style.color]="isSeniorCitizenSelected && seniorCitizenBonus > 0 ? '#5b21b6' : ''"
>{{ appliedInterestRate | formatNumber }}%</span
>
</div>
<div class="preview-tenure-banner__slab">
@@ -253,6 +253,54 @@
border-left: 3px solid #16a34a;
}
// ── Senior Citizen Status Banner (preview) ────────────────────────────────────
.preview-sc-status {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 14px;
border-radius: 8px;
margin-bottom: 10px;
font-size: 13px;
font-weight: 600;
&--applied {
background: #ede9fe;
border: 1.5px solid #a78bfa;
color: #4c1d95;
}
&--notice {
background: #fef3c7;
border: 1.5px solid #fcd34d;
color: #78350f;
}
&__bonus {
margin-left: auto;
flex-shrink: 0;
background: #7c3aed;
color: #fff;
padding: 3px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
}
}
.preview-tenure-banner--sc {
border-color: #a78bfa;
background: #f5f3ff;
.preview-tenure-banner__left > i {
color: #5b21b6;
}
.preview-tenure-banner__divider {
background: #a78bfa;
}
}
// ── Legend below table ────────────────────────────────────────────────────────
.preview-slab-legend {
display: flex;
@@ -85,8 +85,26 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
maturityAmount?: number;
interestEarned?: number;
appliedRate?: number;
isSeniorCitizen?: boolean;
} | null = null;
get appliedInterestRate(): number {
if (this.selectedTenure?.appliedRate != null) return this.selectedTenure.appliedRate;
if (this.matchedSlabIndex >= 0) return this.interestRateChartData[this.matchedSlabIndex]?.annualInterestRate ?? 0;
return 0;
}
get isSeniorCitizenSelected(): boolean {
return !!this.selectedTenure?.isSeniorCitizen;
}
get seniorCitizenBonus(): number {
if (!this.isSeniorCitizenSelected || this.selectedTenure?.appliedRate == null) return 0;
if (this.matchedSlabIndex < 0) return 0;
const normalRate = this.interestRateChartData[this.matchedSlabIndex]?.annualInterestRate ?? 0;
return this.selectedTenure.appliedRate - normalRate;
}
/** Charges Displayed Columns */
chargesDisplayedColumns: string[] = [
'name',
@@ -75,6 +75,7 @@
[recurringDepositsAccountTemplate]="recurringDepositsAccountTemplate"
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
[submittedOnDate]="submittedOnDate"
[cbsBaseRates]="cbsBaseRates"
(tenureSelected)="handleTenureSelected($event)"
>
</mifosx-recurring-deposits-account-interest-rate-chart-step>
@@ -105,6 +106,8 @@
[rdMaturityAmount]="rdMaturityAmount"
[rdInterestEarned]="rdInterestEarned"
[rdDaysInYear]="rdDaysInYear"
[rdAppliedRate]="rdAppliedRate"
[rdIsSeniorCitizen]="rdIsSeniorCitizen"
(submitEvent)="submit()"
>
</mifosx-recurring-deposits-account-preview-step>
@@ -77,10 +77,14 @@ export class CreateRecurringDepositsAccountComponent {
recurringDepositsAccountTemplate: any;
/** Recurring Deposit Account Product Template */
recurringDepositsAccountProductTemplate: any;
/** Active CBS base interest rate versions */
cbsBaseRates: any[] = [];
/** Selected tenure value and frequency from calculator */
selectedTenureValue: number | null = null;
selectedTenureFrequencyId: number | null = null;
selectedDepositAmount: number | null = null;
rdAppliedRate: number | null = null;
rdIsSeniorCitizen = false;
constructor() {
this.route.data.subscribe((data: { recurringDepositsAccountTemplate: any }) => {
@@ -94,6 +98,10 @@ export class CreateRecurringDepositsAccountComponent {
*/
setTemplate($event: any) {
this.recurringDepositsAccountProductTemplate = $event;
this.productsService.getFdBaseInterestRates().subscribe({
next: (rates: any[]) => (this.cbsBaseRates = rates || []),
error: () => (this.cbsBaseRates = [])
});
}
/** Captures tenure selection from calculator */
@@ -101,6 +109,8 @@ export class CreateRecurringDepositsAccountComponent {
this.selectedTenureValue = $event.depositPeriod;
this.selectedTenureFrequencyId = $event.depositPeriodFrequencyId;
this.selectedDepositAmount = $event.depositAmount;
this.rdAppliedRate = $event.appliedRate ?? null;
this.rdIsSeniorCitizen = $event.isSeniorCitizen ?? false;
}
/** Get Recurring Deposit Account Details Form Data */
@@ -283,6 +293,13 @@ export class CreateRecurringDepositsAccountComponent {
recurringDepositAccount.mandatoryRecommendedDepositAmount = this.selectedDepositAmount;
}
// Senior citizen rate override
if (this.rdIsSeniorCitizen && this.rdAppliedRate != null && this.rdAppliedRate > 0) {
recurringDepositAccount.isSeniorCitizen = true;
recurringDepositAccount.seniorCitizenInterestRate = this.rdAppliedRate;
recurringDepositAccount.nominalAnnualInterestRate = this.rdAppliedRate;
}
const nominees = this.recurringDepositsAccountCurrencyStep?.nominees ?? [];
if (nominees.length > 0) {
recurringDepositAccount.nominees = nominees.map((n: any) => ({
@@ -365,6 +365,7 @@
color="primary"
type="button"
class="soa-add-btn"
[disabled]="totalJointSharePercentage >= 100"
(click)="openAddJointHolderDialog()"
>
<mat-icon>person_add</mat-icon> Add Holder
@@ -434,11 +434,12 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
...(this.primaryClientId ? [this.primaryClientId] : []),
...this.jointHolders.map((h: any) => h.clientId).filter(Boolean)
];
const maxSharePercentage = Math.max(0, 100 - this.totalJointSharePercentage);
const ref = this.dialog.open(AddRelatedClientDialogComponent, {
width: '500px',
panelClass: 'arc-dialog-panel',
disableClose: true,
data: { excludedClientIds }
data: { excludedClientIds, maxSharePercentage }
});
ref.afterClosed().subscribe((result) => {
if (result) {
@@ -103,6 +103,18 @@
</div>
</div>
<!-- Senior Citizen -->
<div class="fdrc-calc-field fdrc-calc-field--senior">
<label class="fdrc-senior-label">
<input type="checkbox" [(ngModel)]="isSeniorCitizen" (change)="onSeniorCitizenChange()" />
<span class="fdrc-senior-text">
<i class="fas fa-user-shield"></i>
Senior Citizen
</span>
<span class="fdrc-senior-hint">Higher rate applied if available</span>
</label>
</div>
<div class="fdrc-calc-actions">
<button type="button" class="fdrc-calc-btn" (click)="calculateRate()">
<i class="fas fa-search"></i> Find Rate
@@ -126,34 +138,80 @@
<!-- Match Result -->
@if (matchResult) {
<div class="fdrc-result-card">
<div class="fdrc-result-card__check">
<i class="fas fa-check-circle"></i>
</div>
<div class="fdrc-result-card__body">
<!-- Selected Tenure Banner -->
<!-- Tenure Banner -->
<div class="fdrc-tenure-banner">
<i class="fas fa-calendar-check"></i>
<div class="fdrc-tenure-banner__content">
<span class="fdrc-tenure-banner__label">Your Selected RD Tenure</span>
<span class="fdrc-tenure-banner__value">{{ tenureValue }} {{ tenurePeriodUnit }}</span>
</div>
<span class="fdrc-tenure-banner__saved"> <i class="fas fa-save"></i> Will be saved </span>
<span class="fdrc-tenure-banner__saved"><i class="fas fa-save"></i> Will be saved</span>
</div>
<div class="fdrc-result-rate">
<span class="fdrc-result-rate__value">{{ matchResult.appliedRate | number: '1.2-2' }}%</span>
<span class="fdrc-result-rate__label">Annual Interest Rate</span>
</div>
<!-- Senior Citizen Status — always shown when checked -->
@if (isSeniorCitizen) {
@if ((matchResult.slab.seniorCitizenRate ?? 0) > 0) {
<div class="fdrc-sc-status fdrc-sc-status--applied">
<i class="fas fa-user-shield fdrc-sc-status__icon"></i>
<span class="fdrc-sc-status__text">Senior Citizen Rate Applied</span>
<span class="fdrc-sc-status__bonus">
+{{
(matchResult.slab.seniorCitizenRate ?? 0) - (matchResult.slab.annualInterestRate ?? 0)
| number: '1.2-2'
}}% bonus
</span>
</div>
} @else {
<div class="fdrc-sc-status fdrc-sc-status--notice">
<i class="fas fa-info-circle fdrc-sc-status__icon"></i>
<span class="fdrc-sc-status__text"
>Senior Citizen selected — no additional rate configured for this slab, normal rate applied</span
>
</div>
}
}
<!-- Rate Display -->
@if (isSeniorCitizen && (matchResult.slab.seniorCitizenRate ?? 0) > 0) {
<!-- Before / After comparison -->
<div class="fdrc-rate-compare">
<div class="fdrc-rate-compare__block">
<span class="fdrc-rate-compare__tag">Normal Rate</span>
<span class="fdrc-rate-compare__val fdrc-rate-compare__val--strike"
>{{ matchResult.slab.annualInterestRate | number: '1.2-2' }}%</span
>
<span class="fdrc-rate-compare__sub">p.a.</span>
</div>
<div class="fdrc-rate-compare__arrow"><i class="fas fa-long-arrow-alt-right"></i></div>
<div class="fdrc-rate-compare__block fdrc-rate-compare__block--sc">
<span class="fdrc-rate-compare__tag fdrc-rate-compare__tag--sc"
><i class="fas fa-user-shield"></i> Sr. Citizen Rate</span
>
<span class="fdrc-rate-compare__val fdrc-rate-compare__val--sc"
>{{ matchResult.appliedRate | number: '1.2-2' }}%</span
>
<span class="fdrc-rate-compare__sub">p.a.</span>
</div>
</div>
} @else {
<div class="fdrc-result-rate">
<span class="fdrc-result-rate__value">{{ matchResult.appliedRate | number: '1.2-2' }}%</span>
<span class="fdrc-result-rate__label">Annual Interest Rate p.a.</span>
</div>
}
<!-- Slab Info -->
<div class="fdrc-result-meta">
<div class="fdrc-result-meta__item">
<i class="fas fa-layer-group"></i>
<span
>Applicable Slab:
<strong>
{{ matchResult.slab.fromPeriod }}–{{ matchResult.slab.toPeriod ?? '∞' }}
{{ matchResult.slab.periodType?.value }}
</strong>
</span>
<strong
>{{ matchResult.slab.fromPeriod }}–{{ matchResult.slab.toPeriod ?? '∞' }}
{{ matchResult.slab.periodType?.value }}</strong
></span
>
</div>
@if (matchResult.slab.description) {
<div class="fdrc-result-meta__item">
@@ -164,11 +222,14 @@
</div>
<!-- Maturity Summary -->
@if (matchResult.maturityAmount !== null) {
@if (matchResult.maturityAmount) {
<div class="fdrc-maturity-card">
<div class="fdrc-maturity-card__header">
<i class="fas fa-coins"></i>
<span>RD Maturity Summary (Estimate)</span>
@if (isSeniorCitizen && (matchResult.slab.seniorCitizenRate ?? 0) > 0) {
<span class="fdrc-sc-tag"><i class="fas fa-user-shield"></i> Sr. Citizen Rate</span>
}
</div>
<div class="fdrc-maturity-card__body">
<div class="fdrc-maturity-row">
@@ -179,8 +240,17 @@
<span class="fdrc-maturity-row__label">Tenure</span>
<span class="fdrc-maturity-row__value">{{ tenureValue }} {{ tenurePeriodUnit }}</span>
</div>
<div class="fdrc-maturity-row">
<span class="fdrc-maturity-row__label">Interest Rate</span>
<div
class="fdrc-maturity-row"
[class.fdrc-maturity-row--sc]="isSeniorCitizen && (matchResult.slab.seniorCitizenRate ?? 0) > 0"
>
<span class="fdrc-maturity-row__label">
@if (isSeniorCitizen && (matchResult.slab.seniorCitizenRate ?? 0) > 0) {
<i class="fas fa-user-shield"></i> Sr. Citizen Rate
} @else {
Interest Rate
}
</span>
<span class="fdrc-maturity-row__value">{{ matchResult.appliedRate | number: '1.2-2' }}% p.a.</span>
</div>
<div class="fdrc-maturity-row fdrc-maturity-row--interest">
@@ -189,9 +259,9 @@
</div>
<div class="fdrc-maturity-row fdrc-maturity-row--total">
<span class="fdrc-maturity-row__label">Estimated Maturity Amount</span>
<span class="fdrc-maturity-row__value fdrc-maturity-row__value--big">
₹ {{ matchResult.maturityAmount | number: '1.2-2' }}
</span>
<span class="fdrc-maturity-row__value fdrc-maturity-row__value--big"
>₹ {{ matchResult.maturityAmount | number: '1.2-2' }}</span
>
</div>
</div>
</div>
@@ -263,6 +333,22 @@
</td>
</ng-container>
<!-- Senior Citizen Rate Column -->
<ng-container matColumnDef="seniorCitizenRate">
<th mat-header-cell *matHeaderCellDef class="fdrc-th">
<i class="fas fa-user-shield" style="margin-right: 4px; color: #6366f1"></i>Sr. Citizen Rate
</th>
<td mat-cell *matCellDef="let slab" class="fdrc-td">
@if (slab.seniorCitizenRate > 0) {
<span class="fdrc-rate-badge fdrc-rate-badge--senior"
>{{ slab.seniorCitizenRate | number: '1.2-2' }}%</span
>
} @else {
<span class="fdrc-td--na">—</span>
}
</td>
</ng-container>
<!-- Description Column -->
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Description</th>
@@ -337,8 +337,7 @@ $red-light: #fee2e2;
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
margin-top: 22px;
flex: 0 0 100%;
}
.fdrc-calc-btn {
@@ -465,25 +464,157 @@ $red-light: #fee2e2;
// ── Result Card ───────────────────────────────────────────────────────────────
.fdrc-result-card {
display: flex;
align-items: flex-start;
gap: 16px;
margin-top: 16px;
padding: 18px 20px;
background: $green-light;
border: 1.5px solid rgba($green, 0.35);
border-radius: 12px;
&__check {
font-size: 26px;
color: $green;
flex-shrink: 0;
margin-top: 2px;
&__body {
display: flex;
flex-direction: column;
gap: 12px;
}
}
// ── Senior Citizen Status Banner ──────────────────────────────────────────
.fdrc-sc-status {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
&--applied {
background: #ede9fe;
border: 1.5px solid #a78bfa;
color: #4c1d95;
}
&__body {
&--notice {
background: #fef3c7;
border: 1.5px solid #fcd34d;
color: #78350f;
}
&__icon {
font-size: 16px;
flex-shrink: 0;
}
&__text {
flex: 1;
min-width: 0;
}
&__bonus {
margin-left: auto;
flex-shrink: 0;
background: #7c3aed;
color: #fff;
padding: 3px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
}
}
// ── Rate Comparison Block (Normal → Senior) ───────────────────────────────
.fdrc-rate-compare {
display: flex;
align-items: flex-end;
gap: 16px;
flex-wrap: wrap;
&__block {
display: flex;
flex-direction: column;
gap: 3px;
&--sc {
// senior block styling via child classes
}
}
&__tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: $gray-200;
color: $gray-600;
border-radius: 999px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
white-space: nowrap;
width: fit-content;
&--sc {
background: #ede9fe;
color: #5b21b6;
}
}
&__val {
font-size: 30px;
font-weight: 800;
line-height: 1;
&--strike {
color: $gray-600;
text-decoration: line-through;
}
&--sc {
color: #5b21b6;
}
}
&__sub {
font-size: 11px;
color: $gray-600;
}
&__arrow {
font-size: 22px;
color: $gray-600;
padding-bottom: 8px;
flex-shrink: 0;
}
}
// ── SC Tag (maturity card header) ─────────────────────────────────────────
.fdrc-sc-tag {
display: inline-flex;
align-items: center;
gap: 4px;
margin-left: auto;
padding: 3px 10px;
background: #ede9fe;
color: #5b21b6;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
white-space: nowrap;
}
// ── SC Maturity Row ───────────────────────────────────────────────────────
.fdrc-maturity-row--sc {
.fdrc-maturity-row__label {
color: #5b21b6;
font-weight: 600;
}
.fdrc-maturity-row__value {
color: #5b21b6;
font-weight: 700;
}
}
@@ -730,6 +861,151 @@ $red-light: #fee2e2;
font-weight: 700;
}
.fdrc-rate-badge--senior {
background: #ede9fe;
color: #5b21b6;
}
// ── Senior Citizen Rate Comparison View ───────────────────────────────────
.fdrc-senior-rate-view {
margin-bottom: 12px;
&__row {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 10px;
flex-wrap: wrap;
}
&__normal {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
opacity: 0.65;
}
&__senior {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
&__badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: $gray-200;
color: $gray-600;
border-radius: 999px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
white-space: nowrap;
&--sr {
background: #ede9fe;
color: #5b21b6;
}
}
&__strike {
font-size: 24px;
font-weight: 700;
color: $gray-600;
text-decoration: line-through;
line-height: 1;
}
&__applied {
font-size: 32px;
font-weight: 800;
color: #5b21b6;
line-height: 1;
}
&__arrow {
font-size: 18px;
color: $gray-600;
flex-shrink: 0;
align-self: center;
margin-top: 16px;
}
&__bonus {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 14px;
background: #ede9fe;
color: #5b21b6;
border-radius: 8px;
font-size: 12px;
font-weight: 700;
i {
font-size: 13px;
}
}
}
.fdrc-senior-badge {
display: inline-flex;
align-items: center;
gap: 4px;
margin-left: 8px;
padding: 2px 8px;
background: #ede9fe;
color: #5b21b6;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
}
.fdrc-calc-field--senior {
flex: 0 0 100%;
display: flex;
align-items: center;
}
.fdrc-senior-label {
display: inline-flex;
align-items: center;
gap: 8px;
cursor: pointer;
user-select: none;
[type='checkbox'] {
width: 16px;
height: 16px;
accent-color: #6366f1;
cursor: pointer;
}
}
.fdrc-senior-text {
display: inline-flex;
align-items: center;
gap: 5px;
font-weight: 600;
color: #4338ca;
font-size: 13px;
i {
color: #6366f1;
}
}
.fdrc-senior-hint {
font-size: 11px;
color: #94a3b8;
}
.fdrc-incentive-btn {
display: inline-flex;
align-items: center;
@@ -84,6 +84,8 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
@Input() recurringDepositsAccountTemplate: any;
@Input() recurringDepositsAccountProductTemplate: any;
@Input() submittedOnDate: Date | null = null;
/** Active CBS base interest rate versions — when provided, overrides the product chart slabs */
@Input() cbsBaseRates: any[] = [];
@Output() tenureSelected = new EventEmitter<{
depositPeriod: number;
@@ -92,9 +94,15 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
maturityAmount?: number;
interestEarned?: number;
daysInYear?: number;
appliedRate?: number;
isSeniorCitizen?: boolean;
}>();
get interestRateChartData(): any[] {
const activeBase = (this.cbsBaseRates || []).find((r) => r.fdType === 'DOUBLE' && r.isActive);
if (activeBase?.slabs?.length) {
return this.normalizeCbsSlabs(activeBase.slabs);
}
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
if (!template) return [];
return (
@@ -107,13 +115,23 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
);
}
private normalizeCbsSlabs(slabs: any[]): any[] {
return (slabs || []).map((s) => ({
...s,
periodType: { id: s.periodTypeEnum, value: s.periodTypeLabel }
}));
}
chartSlabsDisplayedColumns: string[] = [
'period',
'amountRange',
'interest',
'seniorCitizenRate',
'description',
'actions'
];
isSeniorCitizen = false;
incentivesDisplayedColumns: string[] = [
'entityType',
'attributeName',
@@ -208,7 +226,10 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
return;
}
const rate = matchedSlab.annualInterestRate ?? 0;
const rate =
this.isSeniorCitizen && (matchedSlab.seniorCitizenRate ?? 0) > 0
? (matchedSlab.seniorCitizenRate ?? 0)
: (matchedSlab.annualInterestRate ?? 0);
const result: SlabMatchResult = {
slab: matchedSlab,
slabIndex: matchedIndex,
@@ -271,7 +292,9 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
depositAmount: this.principalAmount || undefined,
maturityAmount: this.matchResult.maturityAmount,
interestEarned: this.matchResult.interestEarned,
daysInYear: this.matchResult.daysInYear
daysInYear: this.matchResult.daysInYear,
appliedRate: this.matchResult.appliedRate,
isSeniorCitizen: this.isSeniorCitizen
});
}
}
@@ -298,6 +321,14 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
this.validationWarning = null;
}
onSeniorCitizenChange() {
if (this.tenureValue && this.tenureValue > 0) {
this.calculateRate();
} else {
this.matchResult = null;
}
}
isMatchedSlab(index: number): boolean {
return this.matchResult?.slabIndex === index;
}
@@ -609,8 +609,16 @@
>
</div>
<div class="preview-field">
<span class="preview-label">Applicable Interest Rate</span>
<span class="preview-value">{{ rdApplicableSlab?.annualInterestRate | formatNumber }}%</span>
<span class="preview-label">
@if (rdIsSeniorCitizen && rdSeniorCitizenBonus > 0) {
<i class="fas fa-user-shield" style="color: #5b21b6; margin-right: 4px"></i>Sr. Citizen Rate
} @else {
Applicable Interest Rate
}
</span>
<span class="preview-value" [style.color]="rdIsSeniorCitizen && rdSeniorCitizenBonus > 0 ? '#5b21b6' : ''">
{{ rdAppliedInterestRate | formatNumber }}%
</span>
</div>
<div class="preview-field">
<span class="preview-label">Tenure</span>
@@ -652,7 +660,26 @@
<div class="preview-body">
<!-- Selected Tenure + Rate Banner -->
@if (rdTenureDays && matchedSlabIndex >= 0) {
<div class="preview-tenure-banner">
<!-- Senior Citizen Status Banner -->
@if (rdIsSeniorCitizen) {
@if (rdSeniorCitizenBonus > 0) {
<div class="preview-sc-status preview-sc-status--applied">
<i class="fas fa-user-shield"></i>
<span>Senior Citizen Rate Applied</span>
<span class="preview-sc-status__bonus">+{{ rdSeniorCitizenBonus | formatNumber }}% bonus</span>
</div>
} @else {
<div class="preview-sc-status preview-sc-status--notice">
<i class="fas fa-info-circle"></i>
<span>Senior Citizen selected — no additional rate configured for this slab</span>
</div>
}
}
<div
class="preview-tenure-banner"
[class.preview-tenure-banner--sc]="rdIsSeniorCitizen && rdSeniorCitizenBonus > 0"
>
<div class="preview-tenure-banner__left">
<i class="fas fa-calendar-check"></i>
<div>
@@ -662,9 +689,17 @@
</div>
<div class="preview-tenure-banner__divider"></div>
<div class="preview-tenure-banner__right">
<span class="preview-tenure-banner__rate-label">Applicable Interest Rate</span>
<span class="preview-tenure-banner__rate"
>{{ interestRateChartData[matchedSlabIndex]?.annualInterestRate | formatNumber }}%</span
<span class="preview-tenure-banner__rate-label">
@if (rdIsSeniorCitizen && rdSeniorCitizenBonus > 0) {
<i class="fas fa-user-shield" style="color: #5b21b6; margin-right: 4px"></i>Sr. Citizen Rate
} @else {
Applicable Interest Rate
}
</span>
<span
class="preview-tenure-banner__rate"
[style.color]="rdIsSeniorCitizen && rdSeniorCitizenBonus > 0 ? '#5b21b6' : ''"
>{{ rdAppliedInterestRate | formatNumber }}%</span
>
</div>
<div class="preview-tenure-banner__slab">
@@ -283,6 +283,54 @@
border-left: 3px solid #16a34a;
}
// ── Senior Citizen Status Banner (preview) ────────────────────────────────────
.preview-sc-status {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 14px;
border-radius: 8px;
margin-bottom: 10px;
font-size: 13px;
font-weight: 600;
&--applied {
background: #ede9fe;
border: 1.5px solid #a78bfa;
color: #4c1d95;
}
&--notice {
background: #fef3c7;
border: 1.5px solid #fcd34d;
color: #78350f;
}
&__bonus {
margin-left: auto;
flex-shrink: 0;
background: #7c3aed;
color: #fff;
padding: 3px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
}
}
.preview-tenure-banner--sc {
border-color: #a78bfa;
background: #f5f3ff;
.preview-tenure-banner__left > i {
color: #5b21b6;
}
.preview-tenure-banner__divider {
background: #a78bfa;
}
}
// ── Legend below table ────────────────────────────────────────────────────────
.preview-slab-legend {
display: flex;
@@ -80,6 +80,19 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
@Input() rdMaturityAmount: number | null = null;
@Input() rdInterestEarned: number | null = null;
@Input() rdDaysInYear: number | null = null;
@Input() rdAppliedRate: number | null = null;
@Input() rdIsSeniorCitizen = false;
get rdAppliedInterestRate(): number {
if (this.rdAppliedRate != null) return this.rdAppliedRate;
return this.rdApplicableSlab?.annualInterestRate ?? 0;
}
get rdSeniorCitizenBonus(): number {
if (!this.rdIsSeniorCitizen || this.rdAppliedRate == null) return 0;
const normalRate = this.rdApplicableSlab?.annualInterestRate ?? 0;
return this.rdAppliedRate - normalRate;
}
/** Output the submit action */
@Output() submitEvent = new EventEmitter();