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
+355
@@ -0,0 +1,355 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="page-layout">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>CBS Base Interest Rate</h2>
|
||||
<p class="page-subtitle">
|
||||
Manage master CBS interest rate slabs by deposit type. Saving a new version automatically
|
||||
<strong>deactivates</strong> the current active version and <strong>activates</strong> the new one. This chart
|
||||
is used as the reference for both Fixed Deposit and Recurring Deposit products.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-tab-group animationDuration="150ms">
|
||||
@for (fdType of fdTypes; track fdType) {
|
||||
<mat-tab [label]="fdType">
|
||||
<div class="tab-content">
|
||||
@if (!showPreview[fdType]) {
|
||||
<!-- ── Editable Slabs Table ── -->
|
||||
<div class="section-header">
|
||||
<h3>CBS Base Interest Rate Slabs</h3>
|
||||
<button type="button" mat-raised-button color="accent" (click)="addSlab(fdType)">
|
||||
<mat-icon>add</mat-icon> Add Slab
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (slabs(fdType).length === 0) {
|
||||
<p class="empty-text">No slabs defined. Click "Add Slab" to begin.</p>
|
||||
} @else {
|
||||
<form [formGroup]="forms[fdType]">
|
||||
<table mat-table [dataSource]="slabs(fdType).controls" class="slabs-table">
|
||||
<ng-container matColumnDef="srNo">
|
||||
<th mat-header-cell *matHeaderCellDef>#</th>
|
||||
<td mat-cell *matCellDef="let row">{{ row.get('srNo').value }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>Description</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-form-field class="cell-field">
|
||||
<input matInput [formControl]="row.get('description')" placeholder="e.g. 7 Days to 14 Days" />
|
||||
</mat-form-field>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="periodType">
|
||||
<th mat-header-cell *matHeaderCellDef>Period Type</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-form-field class="cell-field cell-field--sm">
|
||||
<mat-select [formControl]="row.get('periodTypeEnum')">
|
||||
@for (pt of periodTypes; track pt.id) {
|
||||
<mat-option [value]="pt.id">{{ pt.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="fromPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-form-field class="cell-field cell-field--xs">
|
||||
<input matInput type="number" min="0" [formControl]="row.get('fromPeriod')" />
|
||||
</mat-form-field>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="toPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-form-field class="cell-field cell-field--xs">
|
||||
<input matInput type="number" min="0" [formControl]="row.get('toPeriod')" placeholder="∞" />
|
||||
</mat-form-field>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="annualRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Annual Rate (%)</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-form-field class="cell-field cell-field--xs">
|
||||
<input
|
||||
matInput
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
[formControl]="row.get('annualInterestRate')"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="seniorRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Senior Citizen (%)</th>
|
||||
<td mat-cell *matCellDef="let row">
|
||||
<mat-form-field class="cell-field cell-field--xs">
|
||||
<input
|
||||
matInput
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.01"
|
||||
[formControl]="row.get('seniorCitizenRate')"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let row; let i = index">
|
||||
<button type="button" mat-icon-button color="warn" (click)="removeSlab(fdType, i)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="slabColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: slabColumns"></tr>
|
||||
</table>
|
||||
</form>
|
||||
}
|
||||
|
||||
<!-- ── Version Label & Remarks ── -->
|
||||
<form [formGroup]="forms[fdType]" class="version-form">
|
||||
<mat-form-field class="version-field">
|
||||
<mat-label>New Version Label</mat-label>
|
||||
<input matInput formControlName="versionLabel" placeholder="e.g. v2.0 May 2026" />
|
||||
<mat-error>Version label is required.</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="version-field">
|
||||
<mat-label>Remarks (optional)</mat-label>
|
||||
<input matInput formControlName="remarks" />
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
||||
<div class="actions-row">
|
||||
<button
|
||||
type="button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
[disabled]="forms[fdType].invalid || slabs(fdType).length === 0"
|
||||
(click)="openPreview(fdType)"
|
||||
>
|
||||
Preview & Save
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- ── Preview Panel ── -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-heading">
|
||||
<h3>CBS Base Interest Rate — Save Preview ({{ fdType }})</h3>
|
||||
<p class="page-subtitle">
|
||||
Review the change before confirming. The current active version will be
|
||||
<strong class="text-red">deactivated</strong>, and the new version will be
|
||||
<strong class="text-green">activated</strong>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="preview-grid">
|
||||
<!-- Current Active (will deactivate) -->
|
||||
<div class="preview-card preview-card--deactivate">
|
||||
<div class="preview-card-header">
|
||||
<span class="active-badge">Currently Active</span>
|
||||
<span class="deactivate-arrow">→ Will be DEACTIVATED</span>
|
||||
</div>
|
||||
@if (activeRates[fdType]) {
|
||||
<p class="version-meta">{{ activeRates[fdType].versionLabel }}</p>
|
||||
@if (activeRates[fdType].remarks) {
|
||||
<p class="remarks-text">{{ activeRates[fdType].remarks }}</p>
|
||||
}
|
||||
<table mat-table [dataSource]="activeRates[fdType].slabs" class="preview-table">
|
||||
<ng-container matColumnDef="srNo">
|
||||
<th mat-header-cell *matHeaderCellDef>#</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.srNo }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>Description</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.description }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="periodType">
|
||||
<th mat-header-cell *matHeaderCellDef>Period</th>
|
||||
<td mat-cell *matCellDef="let s">{{ getPeriodLabel(s.periodTypeEnum) }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fromPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.fromPeriod }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="toPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.toPeriod ?? '∞' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="annualRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Rate (%)</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.annualInterestRate | number: '1.2-2' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="seniorRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Senior (%)</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.seniorCitizenRate | number: '1.2-2' }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="historySlabColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: historySlabColumns"></tr>
|
||||
</table>
|
||||
} @else {
|
||||
<p class="empty-text">No active version currently exists.</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- New Version (will activate) -->
|
||||
<div class="preview-card preview-card--activate">
|
||||
<div class="preview-card-header">
|
||||
<span class="inactive-badge">New Version</span>
|
||||
<span class="activate-arrow">→ Will be ACTIVATED</span>
|
||||
</div>
|
||||
<p class="version-meta">{{ forms[fdType].value.versionLabel }}</p>
|
||||
@if (forms[fdType].value.remarks) {
|
||||
<p class="remarks-text">{{ forms[fdType].value.remarks }}</p>
|
||||
}
|
||||
<table mat-table [dataSource]="slabs(fdType).value" class="preview-table">
|
||||
<ng-container matColumnDef="srNo">
|
||||
<th mat-header-cell *matHeaderCellDef>#</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.srNo }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>Description</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.description }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="periodType">
|
||||
<th mat-header-cell *matHeaderCellDef>Period</th>
|
||||
<td mat-cell *matCellDef="let s">{{ getPeriodLabel(s.periodTypeEnum) }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fromPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.fromPeriod }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="toPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.toPeriod ?? '∞' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="annualRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Rate (%)</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.annualInterestRate | number: '1.2-2' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="seniorRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Senior (%)</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.seniorCitizenRate | number: '1.2-2' }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="historySlabColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: historySlabColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-row">
|
||||
<button type="button" mat-stroked-button (click)="cancelPreview(fdType)">Cancel</button>
|
||||
<button
|
||||
type="button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
[disabled]="isSaving[fdType]"
|
||||
(click)="confirmSave(fdType)"
|
||||
>
|
||||
{{ isSaving[fdType] ? 'Saving...' : 'Confirm & Activate' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (saveMessage[fdType]) {
|
||||
<p class="save-message">{{ saveMessage[fdType] }}</p>
|
||||
}
|
||||
|
||||
<!-- ── History ── -->
|
||||
<div class="history-section">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<h3>CBS Base Interest Rate — Version History</h3>
|
||||
<p class="page-subtitle">All past versions for {{ fdType }} deposit type.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (isLoading) {
|
||||
<p>Loading history...</p>
|
||||
} @else if (historyForType(fdType).length === 0) {
|
||||
<p class="empty-text">No history found.</p>
|
||||
} @else {
|
||||
<mat-accordion multi>
|
||||
@for (version of historyForType(fdType); track version.id) {
|
||||
<mat-expansion-panel [expanded]="version.isActive">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
<span class="version-header">
|
||||
<strong>{{ version.versionLabel }}</strong>
|
||||
<span [class]="version.isActive ? 'active-badge' : 'inactive-badge'">
|
||||
{{ version.isActive ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
</span>
|
||||
</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
<span class="meta-text">From {{ version.effectiveFromDate }}</span>
|
||||
<span class="meta-text">To {{ version.effectiveToDate || 'Current' }}</span>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
@if (version.remarks) {
|
||||
<p class="remarks-text">{{ version.remarks }}</p>
|
||||
}
|
||||
|
||||
<table mat-table [dataSource]="version.slabs" class="history-table">
|
||||
<ng-container matColumnDef="srNo">
|
||||
<th mat-header-cell *matHeaderCellDef>#</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.srNo }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>Description</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.description }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="periodType">
|
||||
<th mat-header-cell *matHeaderCellDef>Period</th>
|
||||
<td mat-cell *matCellDef="let s">{{ getPeriodLabel(s.periodTypeEnum) }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fromPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.fromPeriod }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="toPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.toPeriod ?? '∞' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="annualRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Annual Rate (%)</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.annualInterestRate | number: '1.2-2' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="seniorRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Senior Citizen (%)</th>
|
||||
<td mat-cell *matCellDef="let s">{{ s.seniorCitizenRate | number: '1.2-2' }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="historySlabColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: historySlabColumns"></tr>
|
||||
</table>
|
||||
</mat-expansion-panel>
|
||||
}
|
||||
</mat-accordion>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
}
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.page-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: 1rem 0 0.5rem;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 0.25rem 0 0;
|
||||
color: #6b7280;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
padding: 1.25rem 0.25rem;
|
||||
}
|
||||
|
||||
/* ── Section Header ── */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
color: #9ca3af;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ── Slabs Table ── */
|
||||
.slabs-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cell-field {
|
||||
width: 100%;
|
||||
min-width: 140px;
|
||||
|
||||
&--sm {
|
||||
min-width: 110px;
|
||||
}
|
||||
|
||||
&--xs {
|
||||
min-width: 80px;
|
||||
width: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Version Form ── */
|
||||
.version-form {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.version-field {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
/* ── Actions ── */
|
||||
.actions-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.save-message {
|
||||
color: #16a34a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── Preview Section ── */
|
||||
.preview-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.preview-heading h3 {
|
||||
margin: 0 0 0.25rem;
|
||||
}
|
||||
|
||||
.preview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
|
||||
@media (width <= 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-card {
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
border: 2px solid transparent;
|
||||
|
||||
&--deactivate {
|
||||
background: #fff7f7;
|
||||
border-color: #fca5a5;
|
||||
}
|
||||
|
||||
&--activate {
|
||||
background: #f0fdf4;
|
||||
border-color: #86efac;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.deactivate-arrow {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.activate-arrow {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.text-red {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.text-green {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.preview-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ── History ── */
|
||||
.history-section {
|
||||
margin-top: 0.5rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.version-header {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.meta-text {
|
||||
margin-left: 0.75rem;
|
||||
color: #6b7280;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.history-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.remarks-text {
|
||||
margin: 0 0 0.75rem;
|
||||
color: #374151;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.version-meta {
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin: 0 0 0.25rem;
|
||||
}
|
||||
|
||||
/* ── Badges ── */
|
||||
.active-badge,
|
||||
.inactive-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.2rem 0.6rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.active-badge {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.inactive-badge {
|
||||
background: #f3f4f6;
|
||||
color: #4b5563;
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import {
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { ProductsService } from 'app/products/products.service';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-base-fd-interest-rate-chart',
|
||||
templateUrl: './base-fd-interest-rate-chart.component.html',
|
||||
styleUrls: ['./base-fd-interest-rate-chart.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatTabsModule,
|
||||
MatIconButton,
|
||||
MatIconModule,
|
||||
MatExpansionModule,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
DecimalPipe
|
||||
]
|
||||
})
|
||||
export class BaseFdInterestRateChartComponent implements OnInit {
|
||||
private fb = inject(FormBuilder);
|
||||
private productsService = inject(ProductsService);
|
||||
|
||||
readonly fdTypes = [
|
||||
'SINGLE',
|
||||
'DOUBLE',
|
||||
'SPECIAL'
|
||||
];
|
||||
readonly periodTypes = [
|
||||
{ id: 0, label: 'Days' },
|
||||
{ id: 1, label: 'Weeks' },
|
||||
{ id: 2, label: 'Months' },
|
||||
{ id: 3, label: 'Years' }
|
||||
];
|
||||
readonly slabColumns = [
|
||||
'srNo',
|
||||
'description',
|
||||
'periodType',
|
||||
'fromPeriod',
|
||||
'toPeriod',
|
||||
'annualRate',
|
||||
'seniorRate',
|
||||
'actions'
|
||||
];
|
||||
readonly historySlabColumns = [
|
||||
'srNo',
|
||||
'description',
|
||||
'periodType',
|
||||
'fromPeriod',
|
||||
'toPeriod',
|
||||
'annualRate',
|
||||
'seniorRate'
|
||||
];
|
||||
|
||||
forms: Record<string, FormGroup> = {};
|
||||
activeRates: Record<string, any> = {};
|
||||
historyRates: any[] = [];
|
||||
|
||||
isLoading = false;
|
||||
isSaving: Record<string, boolean> = {};
|
||||
saveMessage: Record<string, string> = {};
|
||||
showPreview: Record<string, boolean> = {};
|
||||
|
||||
ngOnInit(): void {
|
||||
this.fdTypes.forEach((type) => {
|
||||
this.forms[type] = this.buildForm();
|
||||
this.isSaving[type] = false;
|
||||
this.saveMessage[type] = '';
|
||||
this.showPreview[type] = false;
|
||||
});
|
||||
this.loadAll();
|
||||
}
|
||||
|
||||
slabs(fdType: string): FormArray {
|
||||
return this.forms[fdType].get('slabs') as FormArray;
|
||||
}
|
||||
|
||||
addSlab(fdType: string): void {
|
||||
const arr = this.slabs(fdType);
|
||||
arr.push(this.buildSlabRow({ srNo: arr.length + 1 }));
|
||||
}
|
||||
|
||||
removeSlab(fdType: string, index: number): void {
|
||||
this.slabs(fdType).removeAt(index);
|
||||
this.slabs(fdType).controls.forEach((c, i) => c.patchValue({ srNo: i + 1 }));
|
||||
}
|
||||
|
||||
openPreview(fdType: string): void {
|
||||
this.showPreview[fdType] = true;
|
||||
this.saveMessage[fdType] = '';
|
||||
}
|
||||
|
||||
cancelPreview(fdType: string): void {
|
||||
this.showPreview[fdType] = false;
|
||||
}
|
||||
|
||||
confirmSave(fdType: string): void {
|
||||
this.isSaving[fdType] = true;
|
||||
const { versionLabel, remarks, slabs } = this.forms[fdType].value;
|
||||
this.productsService.saveFdBaseInterestRates({ fdType, versionLabel, remarks, slabs }).subscribe({
|
||||
next: () => {
|
||||
this.saveMessage[fdType] = 'New version activated. Previous version deactivated.';
|
||||
this.showPreview[fdType] = false;
|
||||
this.isSaving[fdType] = false;
|
||||
this.loadAll();
|
||||
},
|
||||
error: () => {
|
||||
this.saveMessage[fdType] = 'Unable to save. Please try again.';
|
||||
this.isSaving[fdType] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getPeriodLabel(enumVal: number): string {
|
||||
return this.periodTypes.find((p) => p.id === enumVal)?.label ?? String(enumVal);
|
||||
}
|
||||
|
||||
historyForType(fdType: string): any[] {
|
||||
return this.historyRates
|
||||
.filter((v) => v.fdType === fdType)
|
||||
.sort((a, b) => {
|
||||
if (a.isActive && !b.isActive) return -1;
|
||||
if (!a.isActive && b.isActive) return 1;
|
||||
const dateA = a.effectiveFromDate ? new Date(a.effectiveFromDate).getTime() : 0;
|
||||
const dateB = b.effectiveFromDate ? new Date(b.effectiveFromDate).getTime() : 0;
|
||||
return dateB - dateA;
|
||||
});
|
||||
}
|
||||
|
||||
private loadAll(): void {
|
||||
this.isLoading = true;
|
||||
this.fdTypes.forEach((type) => {
|
||||
this.productsService.getFdBaseInterestRates(type).subscribe({
|
||||
next: (rates: any[]) => {
|
||||
const active = (rates || []).find((r: any) => r.isActive);
|
||||
this.activeRates[type] = active ?? null;
|
||||
const arr = this.slabs(type);
|
||||
arr.clear();
|
||||
(active?.slabs ?? []).forEach((s: any) => arr.push(this.buildSlabRow(s)));
|
||||
this.forms[type].patchValue({ versionLabel: '', remarks: '' });
|
||||
},
|
||||
error: () => {
|
||||
this.activeRates[type] = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
this.productsService.getFdBaseInterestRateHistory().subscribe({
|
||||
next: (h: any[]) => {
|
||||
this.historyRates = h || [];
|
||||
this.isLoading = false;
|
||||
},
|
||||
error: () => {
|
||||
this.historyRates = [];
|
||||
this.isLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private buildForm(): FormGroup {
|
||||
return this.fb.group({
|
||||
versionLabel: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
remarks: [''],
|
||||
slabs: this.fb.array([])
|
||||
});
|
||||
}
|
||||
|
||||
private buildSlabRow(d?: any): FormGroup {
|
||||
return this.fb.group({
|
||||
srNo: [d?.srNo ?? 1],
|
||||
description: [
|
||||
d?.description ?? '',
|
||||
Validators.required
|
||||
],
|
||||
periodTypeEnum: [
|
||||
d?.periodTypeEnum ?? 2,
|
||||
Validators.required
|
||||
],
|
||||
fromPeriod: [
|
||||
d?.fromPeriod ?? 0,
|
||||
[
|
||||
Validators.required,
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
toPeriod: [d?.toPeriod ?? null],
|
||||
annualInterestRate: [
|
||||
d?.annualInterestRate ?? 0,
|
||||
[
|
||||
Validators.required,
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
seniorCitizenRate: [
|
||||
d?.seniorCitizenRate ?? 0,
|
||||
Validators.min(0)
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,7 @@ import { EditProductMixComponent } from './products-mix/edit-product-mix/edit-pr
|
||||
import { ChargesTemplateAndResolver } from './charges/charges-template-and-resolver';
|
||||
import { EditFixedDepositProductComponent } from './fixed-deposit-products/edit-fixed-deposit-product/edit-fixed-deposit-product.component';
|
||||
import { FixedDepositProductAndTemplateResolver } from './fixed-deposit-products/edit-fixed-deposit-product/fixed-deposit-product-and-template.resolver';
|
||||
import { BaseFdInterestRateChartComponent } from './fixed-deposit-products/base-fd-interest-rate-chart/base-fd-interest-rate-chart.component';
|
||||
import { CollateralResolver } from './collaterals/collateral.resolver';
|
||||
import { CollateralsResolver } from './collaterals/collaterals.resolver';
|
||||
import { CollateralTemplateResolver } from './collaterals/collaterals-template.resolver';
|
||||
@@ -727,6 +728,11 @@ const routes: Routes = [
|
||||
path: 'fixed-deposit-products',
|
||||
data: { title: 'Fixed Deposit Products', breadcrumb: 'Fixed Deposit Products' },
|
||||
children: [
|
||||
{
|
||||
path: 'base-interest-rates',
|
||||
component: BaseFdInterestRateChartComponent,
|
||||
data: { title: 'CBS Base Interest Rate', breadcrumb: 'CBS Base Interest Rate' }
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: CreateFixedDepositProductComponent,
|
||||
|
||||
@@ -87,6 +87,25 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a
|
||||
class="pd-card"
|
||||
[routerLink]="['fixed-deposit-products', 'base-interest-rates']"
|
||||
*mifosxHasPermission="'READ_FIXEDDEPOSITPRODUCT'"
|
||||
>
|
||||
<div class="pd-card__icon pd-card__icon--amber">
|
||||
<i class="fas fa-percentage"></i>
|
||||
</div>
|
||||
<div class="pd-card__body">
|
||||
<h3 class="pd-card__title">CBS Base Interest Rate</h3>
|
||||
<p class="pd-card__desc">
|
||||
Manage and activate CBS base interest rate slabs used for Fixed and Recurring Deposit products
|
||||
</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right pd-card__arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div #recurringDepositProducts>
|
||||
<a
|
||||
class="pd-card"
|
||||
|
||||
@@ -84,7 +84,6 @@ import { ViewTaxGroupComponent } from './manage-tax-groups/view-tax-group/view-t
|
||||
import { ShareProductsDividendsComponent } from './share-products/dividends-share-product/dividends.components';
|
||||
import { ViewRecurringDepositProductComponent } from './recurring-deposit-products/view-recurring-deposit-product/view-recurring-deposit-product.component';
|
||||
import { CreateRecurringDepositProductComponent } from './recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component';
|
||||
import { BaseRecurringDepositInterestRateChartComponent } from './recurring-deposit-products/base-recurring-deposit-interest-rate-chart/base-recurring-deposit-interest-rate-chart.component';
|
||||
import { RecurringDepositProductAccountingStepComponent } from './recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component';
|
||||
import { RecurringDepositProductChargesStepComponent } from './recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-charges-step/recurring-deposit-product-charges-step.component';
|
||||
import { RecurringDepositProductCurrencyStepComponent } from './recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-currency-step/recurring-deposit-product-currency-step.component';
|
||||
@@ -214,7 +213,6 @@ import { AdvancedAccountingMappingRuleComponent } from './loan-products/loan-pro
|
||||
DepositProductIncentiveFormDialogComponent,
|
||||
ViewRecurringDepositProductComponent,
|
||||
CreateRecurringDepositProductComponent,
|
||||
BaseRecurringDepositInterestRateChartComponent,
|
||||
RecurringDepositProductAccountingStepComponent,
|
||||
RecurringDepositProductChargesStepComponent,
|
||||
RecurringDepositProductCurrencyStepComponent,
|
||||
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="page-layout">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>CBS Base Recurring Deposit Interest Chart</h2>
|
||||
<p class="page-subtitle">This chart is the master template used when creating a recurring deposit product.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (recurringDepositProductsTemplate?.chartTemplate) {
|
||||
<mifosx-recurring-deposit-product-interest-rate-chart-step
|
||||
#stepComponent
|
||||
[recurringDepositProductsTemplate]="recurringDepositProductsTemplate"
|
||||
[rdInterestRates]="rdInterestRates"
|
||||
>
|
||||
</mifosx-recurring-deposit-product-interest-rate-chart-step>
|
||||
|
||||
<div class="actions-row">
|
||||
<button
|
||||
type="button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
[disabled]="isSaving || !stepComponent"
|
||||
(click)="save(stepComponent)"
|
||||
>
|
||||
{{ isSaving ? 'Saving...' : 'Save CBS Base Chart' }}
|
||||
</button>
|
||||
@if (saveMessage) {
|
||||
<span class="save-message">{{ saveMessage }}</span>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<p>Loading CBS base chart...</p>
|
||||
}
|
||||
|
||||
<div class="history-section">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<h3>Chart History</h3>
|
||||
<p class="section-subtitle">Previous CBS base chart versions are listed here for comparison and audit.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (isHistoryLoading) {
|
||||
<p>Loading history...</p>
|
||||
} @else if (rdInterestRateHistory.length === 0) {
|
||||
<p>No history found for the CBS base chart.</p>
|
||||
} @else {
|
||||
<mat-accordion multi>
|
||||
@for (version of rdInterestRateHistory; track version.id) {
|
||||
<mat-expansion-panel [expanded]="version.isActive">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
<span class="version-header">
|
||||
<strong>{{ version.versionLabel }}</strong>
|
||||
<span [class]="version.isActive ? 'active-badge' : 'inactive-badge'">
|
||||
{{ version.isActive ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
</span>
|
||||
</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
<span class="meta-text">From {{ version.effectiveFromDate }}</span>
|
||||
<span class="meta-text">To {{ version.effectiveToDate || 'Current' }}</span>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
@if (version.remarks) {
|
||||
<p class="remarks-text">{{ version.remarks }}</p>
|
||||
}
|
||||
|
||||
<table class="history-table" mat-table [dataSource]="version.slabs">
|
||||
<ng-container matColumnDef="srNo">
|
||||
<th mat-header-cell *matHeaderCellDef>Sr.</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.srNo }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>Description</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.description }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="fromPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.fromPeriod }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="toPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
||||
<td mat-cell *matCellDef="let slab">
|
||||
{{ slab.toPeriod !== null && slab.toPeriod !== undefined ? slab.toPeriod : 'N/A' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="periodTypeLabel">
|
||||
<th mat-header-cell *matHeaderCellDef>Period Type</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="annualInterestRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Annual Rate (%)</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.annualInterestRate | number: '1.2-2' }}</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="historySlabColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: historySlabColumns"></tr>
|
||||
</table>
|
||||
</mat-expansion-panel>
|
||||
}
|
||||
</mat-accordion>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.page-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 0.25rem 0 0;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.actions-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.save-message {
|
||||
color: #2563eb;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.history-section {
|
||||
margin-top: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
margin: 0.25rem 0 0;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.version-header {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.meta-text {
|
||||
margin-left: 0.75rem;
|
||||
color: #6b7280;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.active-badge,
|
||||
.inactive-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.active-badge {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.inactive-badge {
|
||||
background: #f3f4f6;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.remarks-text {
|
||||
margin: 0 0 0.75rem;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.history-table {
|
||||
width: 100%;
|
||||
}
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { ProductsService } from 'app/products/products.service';
|
||||
import { RecurringDepositProductInterestRateChartStepComponent } from '../recurring-deposit-product-stepper/recurring-deposit-product-interest-rate-chart-step/recurring-deposit-product-interest-rate-chart-step.component';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
import {
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-base-recurring-deposit-interest-rate-chart',
|
||||
templateUrl: './base-recurring-deposit-interest-rate-chart.component.html',
|
||||
styleUrls: ['./base-recurring-deposit-interest-rate-chart.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatButton,
|
||||
MatExpansionModule,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
DecimalPipe,
|
||||
RecurringDepositProductInterestRateChartStepComponent
|
||||
]
|
||||
})
|
||||
export class BaseRecurringDepositInterestRateChartComponent implements OnInit {
|
||||
private productsService = inject(ProductsService);
|
||||
|
||||
recurringDepositProductsTemplate: any;
|
||||
rdInterestRates: any[] = [];
|
||||
rdInterestRateHistory: any[] = [];
|
||||
readonly historySlabColumns = [
|
||||
'srNo',
|
||||
'description',
|
||||
'fromPeriod',
|
||||
'toPeriod',
|
||||
'periodTypeLabel',
|
||||
'annualInterestRate'
|
||||
];
|
||||
isSaving = false;
|
||||
isHistoryLoading = false;
|
||||
saveMessage = '';
|
||||
|
||||
ngOnInit(): void {
|
||||
this.productsService.getRecurringDepositProductsTemplate().subscribe({
|
||||
next: (template: any) => (this.recurringDepositProductsTemplate = template),
|
||||
error: () => (this.recurringDepositProductsTemplate = null)
|
||||
});
|
||||
|
||||
this.loadBaseRates();
|
||||
this.loadBaseRateHistory();
|
||||
}
|
||||
|
||||
get chartStep(): RecurringDepositProductInterestRateChartStepComponent | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
save(stepComponent: RecurringDepositProductInterestRateChartStepComponent): void {
|
||||
this.isSaving = true;
|
||||
this.saveMessage = '';
|
||||
const payload = stepComponent.modifiedRdInterestRatePayload;
|
||||
this.productsService.saveRdBaseInterestRates(payload).subscribe({
|
||||
next: () => {
|
||||
this.saveMessage = 'CBS base chart saved successfully.';
|
||||
this.loadBaseRates();
|
||||
this.loadBaseRateHistory();
|
||||
this.isSaving = false;
|
||||
},
|
||||
error: () => {
|
||||
this.saveMessage = 'Unable to save CBS base chart.';
|
||||
this.isSaving = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private loadBaseRates(): void {
|
||||
this.productsService.getRdBaseInterestRates().subscribe({
|
||||
next: (rates: any[]) => (this.rdInterestRates = rates || []),
|
||||
error: () => (this.rdInterestRates = [])
|
||||
});
|
||||
}
|
||||
|
||||
private loadBaseRateHistory(): void {
|
||||
this.isHistoryLoading = true;
|
||||
this.productsService.getRdBaseInterestRateHistory().subscribe({
|
||||
next: (history: any[]) => {
|
||||
this.rdInterestRateHistory = history || [];
|
||||
this.isHistoryLoading = false;
|
||||
},
|
||||
error: () => {
|
||||
this.rdInterestRateHistory = [];
|
||||
this.isHistoryLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user