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
@@ -106,20 +106,38 @@
<div class="arc-step-label arc-step-label--mt">
<span class="arc-step-badge">2</span>
<span>Ownership Share</span>
@if (maxShare < 100) {
<span class="arc-remaining-badge">Max available: {{ maxShare }}%</span>
}
</div>
<div class="arc-share-row">
<mat-form-field appearance="outline" class="arc-share-field">
<mat-label>Share Percentage</mat-label>
<input matInput type="number" formControlName="sharePercentage" min="1" max="100" />
<input matInput type="number" formControlName="sharePercentage" min="1" [max]="maxShare" />
<span matSuffix class="arc-percent-suffix">%</span>
<mat-error>Enter a value between 1 and 100</mat-error>
@if (form.get('sharePercentage').hasError('max')) {
<mat-error>Maximum available is {{ maxShare }}%</mat-error>
} @else {
<mat-error>Enter a value between 1 and {{ maxShare }}</mat-error>
}
</mat-form-field>
<div class="arc-share-hints">
@for (hint of [25, 33, 50, 100]; track hint) {
<button type="button" class="arc-hint-chip" (click)="form.get('sharePercentage').setValue(hint)">
{{ hint }}%
@if (hint <= maxShare) {
<button type="button" class="arc-hint-chip" (click)="form.get('sharePercentage').setValue(hint)">
{{ hint }}%
</button>
}
}
@if (maxShare > 0 && ![25, 33, 50, 100].includes(maxShare)) {
<button
type="button"
class="arc-hint-chip arc-hint-chip--remaining"
(click)="form.get('sharePercentage').setValue(maxShare)"
>
{{ maxShare }}%
</button>
}
</div>
@@ -102,6 +102,19 @@ mifosx-add-related-client-dialog {
}
}
.arc-remaining-badge {
margin-left: auto;
font-size: 11px;
font-weight: 700;
padding: 2px 10px;
border-radius: 999px;
background: #fef3c7;
color: #92400e;
border: 1px solid #fcd34d;
text-transform: none;
letter-spacing: 0;
}
.arc-step-badge {
width: 20px;
height: 20px;
@@ -350,6 +363,18 @@ mifosx-add-related-client-dialog {
background: $primary-light;
color: $primary;
}
&--remaining {
border-color: #f59e0b;
background: #fffbeb;
color: #92400e;
&:hover {
border-color: #d97706;
background: #fef3c7;
color: #78350f;
}
}
}
// ── Actions ───────────────────────────────────────────────────────────────────
@@ -32,16 +32,21 @@ export class AddRelatedClientDialogComponent implements OnDestroy {
private fb = inject(UntypedFormBuilder);
private clientsService = inject(ClientsService);
dialogRef = inject(MatDialogRef<AddRelatedClientDialogComponent>);
data: { excludedClientIds?: number[] } = inject(MAT_DIALOG_DATA, { optional: true }) ?? {};
data: { excludedClientIds?: number[]; maxSharePercentage?: number } =
inject(MAT_DIALOG_DATA, { optional: true }) ?? {};
get maxShare(): number {
return this.data?.maxSharePercentage ?? 100;
}
form: UntypedFormGroup = this.fb.group({
searchText: [''],
sharePercentage: [
null,
this.data?.maxSharePercentage ?? null,
[
Validators.required,
Validators.min(1),
Validators.max(100)
Validators.max(this.data?.maxSharePercentage ?? 100)
]
],
signingAuthority: [null],
@@ -259,6 +259,7 @@
color="primary"
type="button"
class="soa-add-btn"
[disabled]="totalJointSharePercentage >= 100"
(click)="openAddJointHolderDialog()"
>
<mat-icon>person_add</mat-icon> Add Holder
@@ -761,6 +762,7 @@
color="primary"
type="button"
class="ni-add-btn"
[disabled]="totalSharePercentage >= 100"
(click)="openAddRelatedClientDialog()"
>
<mat-icon>person_add</mat-icon> Add Partner
@@ -364,11 +364,12 @@ export class CurrentAccountDetailsStepComponent 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) {
@@ -443,11 +444,12 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
...(this.primaryClientId ? [this.primaryClientId] : []),
...this.relatedClients.map((c: any) => c.clientId).filter(Boolean)
];
const maxSharePercentage = Math.max(0, 100 - this.totalSharePercentage);
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) {