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:
co-authored by
Claude Sonnet 4.6
parent
846b2484d7
commit
61b0244fa0
+1
@@ -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>
|
||||
|
||||
+22
@@ -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(
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+2
-1
@@ -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) {
|
||||
|
||||
+108
-22
@@ -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>
|
||||
|
||||
+284
-12
@@ -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;
|
||||
|
||||
+33
-6
@@ -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 {
|
||||
|
||||
+31
-4
@@ -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">
|
||||
|
||||
+48
@@ -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;
|
||||
|
||||
+18
@@ -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',
|
||||
|
||||
+3
@@ -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>
|
||||
|
||||
+17
@@ -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) => ({
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+2
-1
@@ -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) {
|
||||
|
||||
+106
-20
@@ -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>
|
||||
|
||||
+288
-12
@@ -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;
|
||||
|
||||
+33
-2
@@ -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;
|
||||
}
|
||||
|
||||
+41
-6
@@ -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">
|
||||
|
||||
+48
@@ -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;
|
||||
|
||||
+13
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user