update fd account
This commit is contained in:
+6
-50
@@ -45,38 +45,15 @@
|
||||
</mifosx-fixed-deposit-account-details-step>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 2: Currency -->
|
||||
@if (fixedDepositsAccountProductTemplate) {
|
||||
<!-- Step 2: Terms -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
|
||||
<ng-template matStepLabel>Currency</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-account-terms-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
<mifosx-fixed-deposit-account-currency-step
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-terms-step>
|
||||
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext>
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 3: Settings -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.SETTINGS' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-account-settings-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-settings-step>
|
||||
</mifosx-fixed-deposit-account-currency-step>
|
||||
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
@@ -97,6 +74,7 @@
|
||||
<mifosx-fixed-deposit-account-interest-rate-chart-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
(tenureSelected)="onTenureSelected($event)"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-interest-rate-chart-step>
|
||||
|
||||
@@ -112,29 +90,6 @@
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 5: Charges -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-account-charges-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
[fixedDepositAccountFormValid]="fixedDepositAccountFormValid"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-charges-step>
|
||||
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext>
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 6: Preview -->
|
||||
<mat-step state="preview" completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
|
||||
@@ -142,6 +97,7 @@
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
[fixedDepositAccountData]="fixedDepositAccount"
|
||||
[selectedTenure]="selectedTenure"
|
||||
(submitEvent)="submit()"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-preview-step>
|
||||
|
||||
+16
-37
@@ -16,9 +16,7 @@ import { SettingsService } from 'app/settings/settings.service';
|
||||
|
||||
/** Custom Components */
|
||||
import { FixedDepositAccountDetailsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component';
|
||||
import { FixedDepositAccountTermsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-terms-step/fixed-deposit-account-terms-step.component';
|
||||
import { FixedDepositAccountSettingsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-settings-step/fixed-deposit-account-settings-step.component';
|
||||
import { FixedDepositAccountChargesStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-charges-step/fixed-deposit-account-charges-step.component';
|
||||
import { FixedDepositAccountCurrencyStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-currency-step/fixed-deposit-account-currency-step.component';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import {
|
||||
MatStepper,
|
||||
@@ -50,10 +48,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
FixedDepositAccountDetailsStepComponent,
|
||||
FixedDepositAccountTermsStepComponent,
|
||||
FixedDepositAccountSettingsStepComponent,
|
||||
FixedDepositAccountInterestRateChartStepComponent,
|
||||
FixedDepositAccountChargesStepComponent,
|
||||
FixedDepositAccountCurrencyStepComponent,
|
||||
FixedDepositAccountPreviewStepComponent
|
||||
]
|
||||
})
|
||||
@@ -66,17 +62,15 @@ export class CreateFixedDepositAccountComponent {
|
||||
|
||||
@ViewChild(FixedDepositAccountDetailsStepComponent, { static: true })
|
||||
fixedDepositsAccountDetailsStep: FixedDepositAccountDetailsStepComponent;
|
||||
@ViewChild(FixedDepositAccountTermsStepComponent)
|
||||
fixedDepositAccountTermsStep: FixedDepositAccountTermsStepComponent;
|
||||
@ViewChild(FixedDepositAccountSettingsStepComponent)
|
||||
fixedDepositAccountSettingsStep: FixedDepositAccountSettingsStepComponent;
|
||||
@ViewChild(FixedDepositAccountChargesStepComponent)
|
||||
fixedDepositAccountChargesStep: FixedDepositAccountChargesStepComponent;
|
||||
@ViewChild(FixedDepositAccountCurrencyStepComponent)
|
||||
fixedDepositAccountCurrencyStep: FixedDepositAccountCurrencyStepComponent;
|
||||
|
||||
/** Fixed Deposits Account Template */
|
||||
fixedDepositsAccountTemplate: any;
|
||||
/** Fixed Deposit Account Product Template */
|
||||
fixedDepositsAccountProductTemplate: any;
|
||||
/** Tenure selected from the interest rate chart step */
|
||||
selectedTenure: { depositPeriod: number; depositPeriodFrequencyId: number } | null = null;
|
||||
|
||||
/**
|
||||
* Fetches FD account template from `resolve`
|
||||
@@ -100,36 +94,22 @@ export class CreateFixedDepositAccountComponent {
|
||||
this.fixedDepositsAccountProductTemplate = $event;
|
||||
}
|
||||
|
||||
onTenureSelected($event: { depositPeriod: number; depositPeriodFrequencyId: number }) {
|
||||
this.selectedTenure = $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Fixed Deposit Account Details Form Data
|
||||
*/
|
||||
get fixedDepositAccountDetailsForm() {
|
||||
return this.fixedDepositsAccountDetailsStep.fixedDepositAccountDetailsForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Fixed Deposit Account Terms Form Data
|
||||
*/
|
||||
get fixedDepositAccountTermsForm() {
|
||||
return this.fixedDepositAccountTermsStep.fixedDepositAccountTermsForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves Fixed Deposit Account Settings Form Data
|
||||
*/
|
||||
get fixedDepositAccountSettingsForm() {
|
||||
return this.fixedDepositAccountSettingsStep.fixedDepositAccountSettingsForm;
|
||||
return this.fixedDepositsAccountDetailsStep?.fixedDepositAccountDetailsForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks stepper validity.
|
||||
*/
|
||||
get fixedDepositAccountFormValid() {
|
||||
return (
|
||||
this.fixedDepositAccountDetailsForm.valid &&
|
||||
this.fixedDepositAccountTermsForm.valid &&
|
||||
this.fixedDepositAccountSettingsForm.valid
|
||||
);
|
||||
return !!this.fixedDepositAccountDetailsForm?.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,10 +117,8 @@ export class CreateFixedDepositAccountComponent {
|
||||
*/
|
||||
get fixedDepositAccount() {
|
||||
return {
|
||||
...this.fixedDepositsAccountDetailsStep.fixedDepositAccountDetails,
|
||||
...this.fixedDepositAccountTermsStep.fixedDepositAccountTerms,
|
||||
...this.fixedDepositAccountSettingsStep.fixedDepositAccountSettings,
|
||||
...this.fixedDepositAccountChargesStep.fixedDepositAccountCharges
|
||||
...this.fixedDepositsAccountDetailsStep?.fixedDepositAccountDetails,
|
||||
...this.fixedDepositAccountCurrencyStep?.currencyStepData
|
||||
};
|
||||
}
|
||||
|
||||
@@ -153,6 +131,7 @@ export class CreateFixedDepositAccountComponent {
|
||||
const monthDayFormat = 'dd MMMM';
|
||||
const fixedDepositAccount = {
|
||||
...this.fixedDepositAccount,
|
||||
...(this.selectedTenure ?? {}),
|
||||
clientId: this.fixedDepositsAccountTemplate.clientId,
|
||||
charges: this.fixedDepositAccount.charges.map((charge: any) => ({
|
||||
chargeId: charge.id,
|
||||
@@ -163,7 +142,7 @@ export class CreateFixedDepositAccountComponent {
|
||||
feeInterval: charge.feeInterval
|
||||
})),
|
||||
submittedOnDate: this.dateUtils.formatDate(this.fixedDepositAccount.submittedOnDate, dateFormat),
|
||||
charts: [{ chartSlabs: this.fixedDepositsAccountProductTemplate.accountChart.chartSlabs }],
|
||||
charts: [{ chartSlabs: this.fixedDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [] }],
|
||||
dateFormat,
|
||||
monthDayFormat,
|
||||
locale
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<!--
|
||||
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="facc-section">
|
||||
<div class="facc-section__header">
|
||||
<i class="fas fa-coins"></i>
|
||||
<span>Currency & Account Status Configuration</span>
|
||||
<span class="facc-badge facc-badge--from">From Product</span>
|
||||
</div>
|
||||
<div class="facc-body">
|
||||
<div class="facc-grid">
|
||||
<mat-form-field appearance="outline" class="facc-field">
|
||||
<mat-label>Currency</mat-label>
|
||||
<input matInput [value]="fixedDepositsAccountProductTemplate?.currency?.name || ''" readonly />
|
||||
<mat-hint>Inherited from selected product</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="facc-field">
|
||||
<mat-label>Decimal Places</mat-label>
|
||||
<input matInput [value]="fixedDepositsAccountProductTemplate?.currency?.decimalPlaces ?? ''" readonly />
|
||||
<mat-hint>Inherited from selected product</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="facc-alert">
|
||||
<div class="facc-alert__content">
|
||||
<h4>Auto Debit Facility</h4>
|
||||
<p>Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.</p>
|
||||
</div>
|
||||
<mat-slide-toggle [(ngModel)]="autoDebitFacility" color="primary"></mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="facc-alert">
|
||||
<div class="facc-alert__content">
|
||||
<h4>Dormancy Alert</h4>
|
||||
<p>Enable automated notifications when an account becomes inactive based on product rules.</p>
|
||||
</div>
|
||||
<span class="facc-yn" [class.facc-yn--yes]="fixedDepositsAccountProductTemplate?.dormancyAlert">
|
||||
{{ fixedDepositsAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
@if (fixedDepositsAccountProductTemplate?.dormancyAlert) {
|
||||
<mifosx-dormancy-alert-panel
|
||||
[productTemplate]="fixedDepositsAccountProductTemplate"
|
||||
></mifosx-dormancy-alert-panel>
|
||||
}
|
||||
|
||||
<div class="facc-alert">
|
||||
<div class="facc-alert__content">
|
||||
<h4>Nomination Facility</h4>
|
||||
<p>Allow account holders to designate individuals to receive funds in the event of account holder's death.</p>
|
||||
</div>
|
||||
<span class="facc-yn" [class.facc-yn--yes]="fixedDepositsAccountProductTemplate?.nominationFacility">
|
||||
{{ fixedDepositsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
@if (fixedDepositsAccountProductTemplate?.nominationFacility) {
|
||||
<mifosx-nominee-form (nomineeChange)="onNomineeChange($event)"></mifosx-nominee-form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
.facc-section {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
&__header {
|
||||
background-color: #f8fafc;
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
|
||||
i {
|
||||
color: #1976d2;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.facc-body {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.facc-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.facc-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.facc-alert {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 1.25rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
background-color: #fff;
|
||||
gap: 1.5rem;
|
||||
|
||||
&--editable {
|
||||
border-color: #bfdbfe;
|
||||
background-color: #eff6ff;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 0.8rem;
|
||||
color: #64748b;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.facc-badge {
|
||||
padding: 0.2rem 0.6rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
|
||||
&--from {
|
||||
background-color: #dbeafe;
|
||||
color: #1e40af;
|
||||
}
|
||||
}
|
||||
|
||||
.facc-yn {
|
||||
padding: 0.25rem 1rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
background-color: #fee2e2;
|
||||
color: #991b1b;
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
|
||||
&--yes {
|
||||
background-color: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
}
|
||||
|
||||
:host ::ng-deep {
|
||||
.mat-mdc-form-field-subscript-wrapper {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { NomineeFormComponent } from 'app/shared/nominee-form/nominee-form.component';
|
||||
import { DormancyAlertPanelComponent } from 'app/shared/dormancy-alert-panel/dormancy-alert-panel.component';
|
||||
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-fixed-deposit-account-currency-step',
|
||||
templateUrl: './fixed-deposit-account-currency-step.component.html',
|
||||
styleUrls: ['./fixed-deposit-account-currency-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
NomineeFormComponent,
|
||||
DormancyAlertPanelComponent,
|
||||
MatSlideToggle,
|
||||
FormsModule
|
||||
]
|
||||
})
|
||||
export class FixedDepositAccountCurrencyStepComponent implements OnChanges {
|
||||
@Input() fixedDepositsAccountProductTemplate: any;
|
||||
|
||||
nomineeData: any = null;
|
||||
|
||||
autoDebitFacility = false;
|
||||
|
||||
// Editable feature toggles (pre-filled from product, overridable by user)
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['fixedDepositsAccountProductTemplate']?.currentValue) {
|
||||
const pt = changes['fixedDepositsAccountProductTemplate'].currentValue;
|
||||
this.autoDebitFacility = !!pt.autoDebitFacility;
|
||||
}
|
||||
}
|
||||
|
||||
onNomineeChange(event: any): void {
|
||||
this.nomineeData = event;
|
||||
}
|
||||
|
||||
/** Called by parent to collect currency step values */
|
||||
get currencyStepData() {
|
||||
return {
|
||||
autoDebitFacility: this.autoDebitFacility,
|
||||
...(this.nomineeData ? { nominee: this.nomineeData } : {})
|
||||
};
|
||||
}
|
||||
}
|
||||
+869
-53
@@ -7,67 +7,883 @@
|
||||
-->
|
||||
|
||||
<form [formGroup]="fixedDepositAccountDetailsForm">
|
||||
<div class="layout-row-wrap gap-2px responsive-column">
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Product Name' | translate }}</mat-label>
|
||||
<mat-select formControlName="productId" required>
|
||||
@for (product of productData; track product) {
|
||||
<mat-option [value]="product.id">
|
||||
{{ product.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@if (isProductSelected) {
|
||||
<mat-form-field class="flex-48" (click)="submittedOnDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.Submitted On' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="submittedOnDatePicker"
|
||||
formControlName="submittedOnDate"
|
||||
required
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="submittedOnDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #submittedOnDatePicker></mat-datepicker>
|
||||
<!-- ══ Account Details ══════════════════════════════════════════ -->
|
||||
<div class="rad-section">
|
||||
<div class="rad-section__header">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<h3>Account Details</h3>
|
||||
</div>
|
||||
<div class="rad-grid">
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>{{ 'labels.inputs.Product Name' | translate }}</mat-label>
|
||||
<mat-select formControlName="productId" required>
|
||||
@for (product of productData; track product) {
|
||||
<mat-option [value]="product.id">{{ product.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Submission Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
{{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
@if (isProductSelected) {
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Field Officer' | translate }}</mat-label>
|
||||
<mat-select formControlName="fieldOfficerId">
|
||||
@for (officer of fieldOfficerData; track officer) {
|
||||
<mat-option [value]="officer.id">
|
||||
{{ officer.displayName }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
}
|
||||
@if (isProductSelected) {
|
||||
<mat-form-field appearance="outline" class="rad-field" (click)="submittedOnDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.Submitted On' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="submittedOnDatePicker"
|
||||
formControlName="submittedOnDate"
|
||||
required
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="submittedOnDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #submittedOnDatePicker></mat-datepicker>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Submitted On' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@if (isProductSelected) {
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.External id' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
formControlName="externalId"
|
||||
matTooltip="{{ 'tooltips.Provides an external id' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
}
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>{{ 'labels.inputs.Field Officer' | translate }}</mat-label>
|
||||
<mat-select formControlName="fieldOfficerId">
|
||||
@for (officer of fieldOfficerData; track officer) {
|
||||
<mat-option [value]="officer.id">{{ officer.displayName }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>{{ 'labels.inputs.External id' | translate }}</mat-label>
|
||||
<input matInput formControlName="externalId" />
|
||||
</mat-form-field>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (isProductSelected && productTemplate) {
|
||||
<!-- ══ Product Classification (read-only) ══════════════════════ -->
|
||||
<div class="rad-section rad-section--mt">
|
||||
<div class="rad-section__header">
|
||||
<i class="fas fa-tags"></i>
|
||||
<h3>Product Classification</h3>
|
||||
<span class="rad-badge">From Product</span>
|
||||
</div>
|
||||
<div class="rad-grid rad-grid--classification">
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>Account Type</mat-label>
|
||||
<mat-select [(ngModel)]="selectedAccountType" [ngModelOptions]="{ standalone: true }">
|
||||
@for (opt of accountTypeOptions; track opt) {
|
||||
<mat-option [value]="opt">{{ opt }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
@if (productTemplate?.fdType) {
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>FD Type</mat-label>
|
||||
<input matInput [value]="getFdTypeLabel(productTemplate.fdType)" readonly />
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>Customer Segment</mat-label>
|
||||
<mat-select [(ngModel)]="selectedCustomerSegment" [ngModelOptions]="{ standalone: true }">
|
||||
@for (opt of customerSegmentOptions; track opt) {
|
||||
<mat-option [value]="opt">{{ opt }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>Account No</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[value]="productTemplate?.previewAccountNo || fixedDepositsAccountTemplate?.accountNo || 'Auto-generated'"
|
||||
readonly
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
@if (productTemplate.effectiveDate) {
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>Effective Date</mat-label>
|
||||
<input matInput [value]="formatTemplateDate(productTemplate.effectiveDate)" readonly />
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
@if (productTemplate.endDate) {
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>End Date</mat-label>
|
||||
<input matInput [value]="formatTemplateDate(productTemplate.endDate)" readonly />
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
<div class="rad-field rad-info-row">
|
||||
<span class="rad-info-label">Deposit Account Link</span>
|
||||
<span class="rad-badge" [class.rad-badge--yes]="productTemplate.shareLinkingEnabled">
|
||||
{{ productTemplate.shareLinkingEnabled ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ Currency (read-only) ════════════════════════════════════ -->
|
||||
<div class="rad-section rad-section--mt">
|
||||
<div class="rad-section__header">
|
||||
<i class="fas fa-coins"></i>
|
||||
<h3>Currency</h3>
|
||||
<span class="rad-badge">From Product</span>
|
||||
</div>
|
||||
<div class="rad-grid rad-grid--classification">
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
|
||||
<input matInput [value]="currencyName" readonly />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
|
||||
<input matInput [value]="decimalPlaces" readonly />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rad-field">
|
||||
<mat-label>{{ 'labels.inputs.Currency In Multiples Of' | translate }}</mat-label>
|
||||
<input matInput [value]="inMultiplesOf" readonly />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ Deposit Account Link ══════════════════════════════════════════ -->
|
||||
@if (productTemplate?.shareLinkingEnabled) {
|
||||
<div class="dal-card">
|
||||
<div class="dal-card__header">
|
||||
<div class="dal-card__header-left">
|
||||
<div class="dal-card__icon">
|
||||
<mat-icon>link</mat-icon>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="dal-card__title">Deposit Account Link</h3>
|
||||
<span class="dal-card__sub">Link an existing Savings or Current account to this FD</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dal-card__body">
|
||||
@if (dalLoading) {
|
||||
<div class="dal-loading">
|
||||
<mat-icon class="dal-loading__icon">hourglass_top</mat-icon>
|
||||
<span>Loading accounts…</span>
|
||||
</div>
|
||||
} @else if (selectedLinkedAccount) {
|
||||
<!-- ── Selected Account Card ── -->
|
||||
<div class="dal-selected">
|
||||
<div class="dal-selected__info">
|
||||
<span
|
||||
class="dal-type-badge"
|
||||
[class.dal-type-badge--savings]="selectedLinkedAccount.accountType === 'SAVINGS'"
|
||||
[class.dal-type-badge--current]="selectedLinkedAccount.accountType === 'CURRENT'"
|
||||
>
|
||||
<mat-icon>{{
|
||||
selectedLinkedAccount.accountType === 'SAVINGS' ? 'savings' : 'account_balance'
|
||||
}}</mat-icon>
|
||||
{{ selectedLinkedAccount.accountType === 'SAVINGS' ? 'Savings' : 'Current' }}
|
||||
</span>
|
||||
<div class="dal-selected__details">
|
||||
<span class="dal-selected__accno">{{ selectedLinkedAccount.accountNo }}</span>
|
||||
<span class="dal-selected__product">{{ selectedLinkedAccount.productName }}</span>
|
||||
</div>
|
||||
<div class="dal-selected__balance">
|
||||
<span class="dal-selected__bal-label">Balance</span>
|
||||
<span class="dal-selected__bal-amount"
|
||||
>{{ selectedLinkedAccount.currencyCode }}
|
||||
{{ selectedLinkedAccount.balance | number: '1.2-2' }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="warn"
|
||||
type="button"
|
||||
class="dal-change-btn"
|
||||
(click)="clearLinkedAccount()"
|
||||
>
|
||||
<mat-icon>swap_horiz</mat-icon> Change
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- ── Step 1: Choose account type ── -->
|
||||
@if (!dalSelectedType) {
|
||||
<div class="dal-type-step">
|
||||
<p class="dal-type-step__label">Select account type to link:</p>
|
||||
<div class="dal-type-grid">
|
||||
<button type="button" class="dal-type-btn dal-type-btn--savings" (click)="selectDalType('SAVINGS')">
|
||||
<mat-icon>savings</mat-icon>
|
||||
<span class="dal-type-btn__name">Savings Account</span>
|
||||
<span class="dal-type-btn__count"
|
||||
>{{ dalAllSavings.length }} account{{ dalAllSavings.length !== 1 ? 's' : '' }}</span
|
||||
>
|
||||
</button>
|
||||
<button type="button" class="dal-type-btn dal-type-btn--current" (click)="selectDalType('CURRENT')">
|
||||
<mat-icon>account_balance</mat-icon>
|
||||
<span class="dal-type-btn__name">Current Account</span>
|
||||
<span class="dal-type-btn__count"
|
||||
>{{ dalAllCurrent.length }} account{{ dalAllCurrent.length !== 1 ? 's' : '' }}</span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- ── Step 2: Search & pick from accounts ── -->
|
||||
<div class="dal-picker">
|
||||
<div class="dal-picker__topbar">
|
||||
<button
|
||||
type="button"
|
||||
mat-icon-button
|
||||
class="dal-back-btn"
|
||||
matTooltip="Back"
|
||||
(click)="dalSelectedType = null"
|
||||
>
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
<span class="dal-picker__title">
|
||||
<mat-icon>{{ dalSelectedType === 'SAVINGS' ? 'savings' : 'account_balance' }}</mat-icon>
|
||||
{{ dalSelectedType === 'SAVINGS' ? 'Savings Accounts' : 'Current Accounts' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline" class="dal-search-field">
|
||||
<mat-label>Search by Account No. or Product</mat-label>
|
||||
<mat-icon matPrefix>search</mat-icon>
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="dalSearch"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
placeholder="Type to filter…"
|
||||
/>
|
||||
@if (dalSearch) {
|
||||
<button matSuffix mat-icon-button type="button" (click)="dalSearch = ''">
|
||||
<mat-icon>clear</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
@if (dalFilteredAccounts.length === 0) {
|
||||
<div class="dal-empty">
|
||||
<mat-icon>search_off</mat-icon>
|
||||
<span>No {{ dalSelectedType === 'SAVINGS' ? 'Savings' : 'Current' }} accounts found.</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="dal-list">
|
||||
@for (account of dalFilteredAccounts; track account.id) {
|
||||
<div class="dal-item" (click)="selectLinkedAccount(account)">
|
||||
<div class="dal-item__details">
|
||||
<span class="dal-item__accno">{{ account.accountNo }}</span>
|
||||
<span class="dal-item__product">{{ account.productName }}</span>
|
||||
</div>
|
||||
<div class="dal-item__right">
|
||||
<span
|
||||
class="dal-item__status"
|
||||
[class.dal-item__status--active]="account.status === 'Active'"
|
||||
>{{ account.status }}</span
|
||||
>
|
||||
<span class="dal-item__balance"
|
||||
>{{ account.currencyCode }} {{ account.balance | number: '1.2-2' }}</span
|
||||
>
|
||||
</div>
|
||||
<mat-icon class="dal-item__arrow">chevron_right</mat-icon>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<!-- ══ Status of Account (RBI) ═══════════════════════════════════════ -->
|
||||
@if (isProductSelected) {
|
||||
<div class="soa-card">
|
||||
<div class="soa-card__header">
|
||||
<div class="soa-card__header-left">
|
||||
<div class="soa-card__icon">
|
||||
<i class="fas fa-id-card"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="soa-card__title">Status of Account</h3>
|
||||
<span class="soa-card__sub">As per RBI guidelines — select account holder type</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="soa-pill">RBI Compliant</span>
|
||||
</div>
|
||||
|
||||
<!-- Auto-detected client info bar -->
|
||||
@if (clientDob) {
|
||||
<div class="soa-client-info-bar">
|
||||
<div class="soa-client-info-bar__item">
|
||||
<mat-icon>cake</mat-icon>
|
||||
<span
|
||||
>Date of Birth: <strong>{{ formatTemplateDate(clientDob) }}</strong></span
|
||||
>
|
||||
</div>
|
||||
<div class="soa-client-info-bar__item">
|
||||
<mat-icon>person_pin</mat-icon>
|
||||
<span
|
||||
>Age: <strong>{{ clientAge }} years</strong></span
|
||||
>
|
||||
</div>
|
||||
@if (clientAge !== null && clientAge < 18) {
|
||||
<span class="soa-auto-pill soa-auto-pill--minor">
|
||||
<mat-icon>child_care</mat-icon> Auto-detected: Minor
|
||||
</span>
|
||||
} @else if (clientAge !== null && clientAge >= 60) {
|
||||
<span class="soa-auto-pill soa-auto-pill--senior">
|
||||
<mat-icon>elderly</mat-icon> Auto-detected: Senior Citizen
|
||||
</span>
|
||||
} @else {
|
||||
<span class="soa-auto-pill soa-auto-pill--ok"><mat-icon>check_circle</mat-icon> Age Verified</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Status selector tiles -->
|
||||
<div class="soa-tiles">
|
||||
@for (opt of accountStatusOptions; track opt.value) {
|
||||
<button
|
||||
type="button"
|
||||
class="soa-tile"
|
||||
[class.soa-tile--selected]="selectedAccountStatus === opt.value"
|
||||
[class.soa-tile--disabled]="isTileDisabled(opt.value)"
|
||||
[disabled]="isTileDisabled(opt.value)"
|
||||
(click)="selectAccountStatus(opt.value)"
|
||||
[title]="getTileTooltip(opt.value)"
|
||||
>
|
||||
<mat-icon class="soa-tile__icon" [style.color]="isTileDisabled(opt.value) ? '#cbd5e1' : opt.color">
|
||||
{{ opt.icon }}
|
||||
</mat-icon>
|
||||
<span class="soa-tile__label">{{ opt.label }}</span>
|
||||
@if (isTileDisabled(opt.value)) {
|
||||
<span class="soa-tile__lock"><mat-icon>lock</mat-icon></span>
|
||||
}
|
||||
@if (selectedAccountStatus === opt.value) {
|
||||
<mat-icon class="soa-tile__check">check_circle</mat-icon>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- ── JOINT ACCOUNT ─────────────────────────────────────────── -->
|
||||
@if (selectedAccountStatus === 'JOINT') {
|
||||
<div class="soa-block">
|
||||
<div class="soa-block__head">
|
||||
<div class="soa-block__head-left">
|
||||
<span class="soa-block__letter" style="background: #8b5cf6">A</span>
|
||||
<div>
|
||||
<span class="soa-block__title">Joint Account Holders</span>
|
||||
<span class="soa-block__hint">Add all co-applicants linked to this joint FD account</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="soa-add-btn"
|
||||
(click)="openAddJointHolderDialog()"
|
||||
>
|
||||
<mat-icon>person_add</mat-icon> Add Holder
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="soa-table-wrap">
|
||||
<table class="soa-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="soa-th soa-th--sr">Sr.</th>
|
||||
<th class="soa-th">Holder Name</th>
|
||||
<th class="soa-th">CIF No.</th>
|
||||
<th class="soa-th soa-th--center">Ownership %</th>
|
||||
<th class="soa-th soa-th--center">Signing Authority</th>
|
||||
<th class="soa-th soa-th--center">Role</th>
|
||||
<th class="soa-th soa-th--center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="soa-tr soa-tr--primary">
|
||||
<td class="soa-td soa-td--sr">1</td>
|
||||
<td class="soa-td">
|
||||
<div class="soa-client-cell">
|
||||
<div class="soa-avatar soa-avatar--primary">
|
||||
{{ primaryClientName ? primaryClientName[0].toUpperCase() : '?' }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="soa-client-name">{{ primaryClientName || 'Primary Holder' }}</span>
|
||||
<span class="soa-primary-tag">Primary Account Holder</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="soa-td">
|
||||
<span class="soa-mono">{{ primaryClientCif || '—' }}</span>
|
||||
</td>
|
||||
<td class="soa-td soa-td--center">
|
||||
<input
|
||||
class="soa-pct-input"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
[(ngModel)]="primaryHolderShare"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
placeholder="0"
|
||||
/>
|
||||
<span class="soa-pct-unit">%</span>
|
||||
</td>
|
||||
<td class="soa-td soa-td--center">
|
||||
<span class="soa-sign-toggle soa-sign-toggle--mandatory">
|
||||
<mat-icon>lock</mat-icon> Mandatory
|
||||
</span>
|
||||
</td>
|
||||
<td class="soa-td soa-td--center">Primary Account Holder</td>
|
||||
<td class="soa-td soa-td--center"><span class="soa-no-action">—</span></td>
|
||||
</tr>
|
||||
|
||||
@for (h of jointHolders; track h; let i = $index) {
|
||||
<tr class="soa-tr" [class.soa-tr--mandatory]="h.isMandatory">
|
||||
<td class="soa-td soa-td--sr">{{ i + 2 }}</td>
|
||||
<td class="soa-td">
|
||||
<div class="soa-client-cell">
|
||||
<div class="soa-avatar" [class.soa-avatar--mandatory]="h.isMandatory">
|
||||
{{ h.clientName[0].toUpperCase() }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="soa-client-name">{{ h.clientName }}</span>
|
||||
@if (h.isMandatory) {
|
||||
<span class="soa-mandatory-tag">Mandatory Signatory</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="soa-td">
|
||||
<span class="soa-mono">{{ h.clientCif || '—' }}</span>
|
||||
</td>
|
||||
<td class="soa-td soa-td--center">
|
||||
<span class="soa-pct-badge" [class.soa-pct-badge--full]="h.sharePercentage === 100">
|
||||
{{ h.sharePercentage }}%
|
||||
</span>
|
||||
</td>
|
||||
<td class="soa-td soa-td--center">
|
||||
<button
|
||||
type="button"
|
||||
class="soa-sign-toggle"
|
||||
[class.soa-sign-toggle--mandatory]="h.isMandatory"
|
||||
[class.soa-sign-toggle--optional]="!h.isMandatory"
|
||||
(click)="toggleJointHolderMandatory(h)"
|
||||
>
|
||||
<mat-icon>{{ h.isMandatory ? 'lock' : 'lock_open' }}</mat-icon>
|
||||
{{ h.isMandatory ? 'Mandatory' : 'Optional' }}
|
||||
</button>
|
||||
</td>
|
||||
<td class="soa-td soa-td--center">Co-Holder</td>
|
||||
<td class="soa-td soa-td--center">
|
||||
<button mat-icon-button color="warn" type="button" (click)="removeJointHolder(i)">
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="soa-tr-total">
|
||||
<td colspan="3" class="soa-td soa-td--total-label">Total Ownership</td>
|
||||
<td class="soa-td soa-td--center">
|
||||
<span
|
||||
class="soa-pct-badge"
|
||||
[class.soa-pct-badge--ok]="totalJointSharePercentage === 100"
|
||||
[class.soa-pct-badge--warn]="totalJointSharePercentage !== 100"
|
||||
>
|
||||
{{ totalJointSharePercentage }}%
|
||||
</span>
|
||||
</td>
|
||||
<td class="soa-td soa-td--center">
|
||||
<span class="soa-mandatory-count">
|
||||
<mat-icon>lock</mat-icon> {{ mandatoryJointCount + 1 }} Mandatory
|
||||
</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if (jointHolders.length === 0) {
|
||||
<div class="soa-empty" style="margin-top: 8px">
|
||||
<mat-icon>group_add</mat-icon>
|
||||
<span>Click <strong>Add Holder</strong> to add joint account holders.</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (totalJointSharePercentage !== 100) {
|
||||
<div class="soa-alert soa-alert--warn" style="margin-top: 8px">
|
||||
<mat-icon>warning_amber</mat-icon>
|
||||
Total ownership is {{ totalJointSharePercentage }}%. It must equal 100%.
|
||||
</div>
|
||||
} @else {
|
||||
<div class="soa-alert soa-alert--ok" style="margin-top: 8px">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
Ownership allocation is complete (100%).
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="soa-block soa-block--sep">
|
||||
<div class="soa-block__head">
|
||||
<div class="soa-block__head-left">
|
||||
<span class="soa-block__letter" style="background: #7c3aed">B</span>
|
||||
<div>
|
||||
<span class="soa-block__title">Mode of Operation</span>
|
||||
<span class="soa-block__hint">Auto-set from mandatory holders — override manually if required</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (modeOfOperation) {
|
||||
<span class="soa-auto-badge"><mat-icon>auto_awesome</mat-icon> Auto</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (jointHolders.length === 0) {
|
||||
<div class="soa-empty">
|
||||
<mat-icon>how_to_vote</mat-icon>
|
||||
<span>Add joint holders in Section A first.</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="soa-mode-grid">
|
||||
@for (opt of modeOfOperationOptions; track opt.value) {
|
||||
<button
|
||||
type="button"
|
||||
class="soa-mode-card"
|
||||
[class.soa-mode-card--selected]="modeOfOperation === opt.value"
|
||||
(click)="modeOfOperation = opt.value"
|
||||
>
|
||||
<mat-icon class="soa-mode-card__icon">{{ opt.icon }}</mat-icon>
|
||||
<div class="soa-mode-card__code">{{ opt.label }}</div>
|
||||
<div class="soa-mode-card__desc">{{ opt.sublabel }}</div>
|
||||
@if (modeOfOperation === opt.value) {
|
||||
<mat-icon class="soa-mode-card__check">check_circle</mat-icon>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="soa-mode-summary" [class.soa-mode-summary--set]="modeOfOperation">
|
||||
@if (modeOfOperation) {
|
||||
<mat-icon>how_to_vote</mat-icon>
|
||||
<span
|
||||
>Mode of Operation: <strong>{{ selectedModeLabel }}</strong></span
|
||||
>
|
||||
<span class="soa-mode-summary__from">
|
||||
({{ mandatoryJointCount }} Mandatory + {{ jointHolders.length - mandatoryJointCount }} Optional)
|
||||
</span>
|
||||
} @else {
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>Set Mandatory/Optional for holders above to auto-select mode.</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ── MINOR ACCOUNT ─────────────────────────────────────────── -->
|
||||
@if (selectedAccountStatus === 'MINOR') {
|
||||
<div class="soa-block">
|
||||
<div class="soa-block__head">
|
||||
<div class="soa-block__head-left">
|
||||
<span class="soa-block__letter" style="background: #f59e0b">
|
||||
<mat-icon style="font-size: 14px; height: 14px; width: 14px">child_care</mat-icon>
|
||||
</span>
|
||||
<div>
|
||||
<span class="soa-block__title">Minor Account Details</span>
|
||||
<span class="soa-block__hint">As per RBI — guardian must be an existing registered client</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="soa-pill soa-pill--minor">Guardian Required</span>
|
||||
</div>
|
||||
|
||||
<div class="soa-minor-grid">
|
||||
<mat-form-field appearance="outline" class="soa-field" (click)="minorDobPicker.open()">
|
||||
<mat-label>Minor's Date of Birth</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[matDatepicker]="minorDobPicker"
|
||||
[max]="maxDate"
|
||||
[(ngModel)]="minorDob"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="minorDobPicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #minorDobPicker></mat-datepicker>
|
||||
@if (clientDob) {
|
||||
<mat-hint>Auto-filled from client profile</mat-hint>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="soa-field">
|
||||
<mat-label>Age</mat-label>
|
||||
<input matInput [value]="clientAge !== null ? clientAge + ' years' : '—'" readonly />
|
||||
<mat-hint>Must be below 18 years</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="soa-field">
|
||||
<mat-label>Date of Majority (Auto)</mat-label>
|
||||
<input matInput [value]="majorityDate" readonly />
|
||||
<mat-hint>Auto-calculated: DOB + 18 years</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="soa-field">
|
||||
<mat-label>Guardian Relationship</mat-label>
|
||||
<mat-select [(ngModel)]="guardianRelationship" [ngModelOptions]="{ standalone: true }">
|
||||
@for (rel of guardianRelationshipOptions; track rel) {
|
||||
<mat-option [value]="rel">{{ rel }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="soa-guardian-search">
|
||||
<div class="soa-guardian-search__label">
|
||||
<mat-icon>manage_accounts</mat-icon>
|
||||
<span>Search & Link Guardian (existing client)</span>
|
||||
</div>
|
||||
<mifosx-introducer-search (introducerChange)="onGuardianChange($event)"></mifosx-introducer-search>
|
||||
|
||||
@if (guardianName) {
|
||||
<div class="soa-guardian-selected">
|
||||
<mat-icon class="soa-guardian-selected__icon">verified_user</mat-icon>
|
||||
<div>
|
||||
<div class="soa-guardian-selected__name">{{ guardianName }}</div>
|
||||
<div class="soa-guardian-selected__cif">CIF: {{ guardianCif || 'N/A' }}</div>
|
||||
</div>
|
||||
<span class="soa-pill soa-pill--ok">Guardian Linked</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="soa-info-banner soa-info-banner--minor">
|
||||
<mat-icon>info</mat-icon>
|
||||
<ul>
|
||||
<li>Minor below 10 years — Guardian must operate the account.</li>
|
||||
<li>Minor above 10 years — Can operate independently (bank discretion).</li>
|
||||
<li>Account must be converted to regular savings on attaining majority.</li>
|
||||
<li>Fresh KYC of the minor must be done on majority.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ── SENIOR CITIZEN ────────────────────────────────────────── -->
|
||||
@if (selectedAccountStatus === 'SENIOR_CITIZEN') {
|
||||
<div class="soa-block">
|
||||
<div class="soa-block__head">
|
||||
<div class="soa-block__head-left">
|
||||
<span class="soa-block__letter" style="background: #10b981">
|
||||
<mat-icon style="font-size: 14px; height: 14px; width: 14px">elderly</mat-icon>
|
||||
</span>
|
||||
<div>
|
||||
<span class="soa-block__title">Senior Citizen Account</span>
|
||||
<span class="soa-block__hint">Age ≥ 60 years — eligible for preferential banking services</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="soa-pill soa-pill--senior">Age ≥ 60</span>
|
||||
</div>
|
||||
|
||||
<div class="soa-minor-grid" style="margin-bottom: 16px">
|
||||
<mat-form-field appearance="outline" class="soa-field">
|
||||
<mat-label>Date of Birth</mat-label>
|
||||
<input matInput [value]="formatTemplateDate(clientDob)" readonly />
|
||||
<mat-hint>From client profile</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="soa-field">
|
||||
<mat-label>Age</mat-label>
|
||||
<input matInput [value]="clientAge !== null ? clientAge + ' years' : '—'" readonly />
|
||||
<mat-hint>Age ≥ 60 required for Senior Citizen</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="soa-sc-benefits">
|
||||
<div class="soa-sc-benefit">
|
||||
<mat-icon>priority_high</mat-icon>
|
||||
<div>
|
||||
<strong>Priority Service</strong>
|
||||
<span>Dedicated counters / assisted banking at branches</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="soa-sc-benefit">
|
||||
<mat-icon>percent</mat-icon>
|
||||
<div>
|
||||
<strong>Preferential Interest Rate</strong>
|
||||
<span>Additional 0.25%–0.50% on deposits (as per product)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="soa-sc-benefit">
|
||||
<mat-icon>health_and_safety</mat-icon>
|
||||
<div>
|
||||
<strong>Doorstep Banking</strong>
|
||||
<span>Eligible for home banking services (RBI circular)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="soa-sc-benefit">
|
||||
<mat-icon>gavel</mat-icon>
|
||||
<div>
|
||||
<strong>Nomination Mandatory</strong>
|
||||
<span>RBI mandate — ensure nomination is registered</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="soa-info-banner soa-info-banner--senior">
|
||||
<mat-icon>info</mat-icon>
|
||||
<ul>
|
||||
<li>Age proof (DOB document) mandatory at time of account opening.</li>
|
||||
<li>Nomination registration is compulsory (Form DA-1).</li>
|
||||
<li>Bank must offer dedicated helpline and priority grievance redressal.</li>
|
||||
<li>Door-step banking services must be offered (RBI Master Direction 2023).</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ── INDIVIDUAL MALE ───────────────────────────────────────── -->
|
||||
@if (selectedAccountStatus === 'INDIVIDUAL_MALE') {
|
||||
<div class="soa-block">
|
||||
<div class="soa-block__head">
|
||||
<div class="soa-block__head-left">
|
||||
<span class="soa-block__letter" style="background: #3b82f6">
|
||||
<mat-icon style="font-size: 14px; height: 14px; width: 14px">person</mat-icon>
|
||||
</span>
|
||||
<div>
|
||||
<span class="soa-block__title">Individual Account (Male)</span>
|
||||
<span class="soa-block__hint"
|
||||
>Select special category if applicable — governs applicable schemes & benefits</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<span class="soa-pill" style="background: #dbeafe; color: #1d4ed8; border-color: #bfdbfe"
|
||||
>Special Category</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="soa-category-grid">
|
||||
@for (opt of specialCategoryOptions; track opt.value) {
|
||||
<button
|
||||
type="button"
|
||||
class="soa-cat-tile"
|
||||
[class.soa-cat-tile--selected]="selectedSpecialCategory === opt.value"
|
||||
[class.soa-cat-tile--male]="true"
|
||||
(click)="selectSpecialCategory(opt.value)"
|
||||
>
|
||||
<mat-icon class="soa-cat-tile__icon">{{ opt.icon }}</mat-icon>
|
||||
<span class="soa-cat-tile__label">{{ opt.label }}</span>
|
||||
@if (selectedSpecialCategory === opt.value) {
|
||||
<mat-icon class="soa-cat-tile__check">check_circle</mat-icon>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="soa-info-banner soa-info-banner--male">
|
||||
<mat-icon>info</mat-icon>
|
||||
<ul>
|
||||
<li>Valid KYC documents required (Aadhaar / PAN / Passport).</li>
|
||||
<li>Nomination registration is mandatory (Form DA-1).</li>
|
||||
<li>Defence / Ex-Serviceman — eligible for special savings schemes (DSSP).</li>
|
||||
<li>NRI — FCNR / NRE / NRO account rules apply as per FEMA guidelines.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ── INDIVIDUAL FEMALE ──────────────────────────────────────── -->
|
||||
@if (selectedAccountStatus === 'INDIVIDUAL_FEMALE') {
|
||||
<div class="soa-block">
|
||||
<div class="soa-block__head">
|
||||
<div class="soa-block__head-left">
|
||||
<span class="soa-block__letter" style="background: #ec4899">
|
||||
<mat-icon style="font-size: 14px; height: 14px; width: 14px">person_outline</mat-icon>
|
||||
</span>
|
||||
<div>
|
||||
<span class="soa-block__title">Individual Account (Female)</span>
|
||||
<span class="soa-block__hint"
|
||||
>Select weaker section category if applicable — governs PMJDY & priority schemes</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<span class="soa-pill" style="background: #fce7f3; color: #be185d; border-color: #f9a8d4"
|
||||
>Weaker Section</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="soa-category-grid">
|
||||
@for (opt of weakerSectionOptions; track opt.value) {
|
||||
<button
|
||||
type="button"
|
||||
class="soa-cat-tile"
|
||||
[class.soa-cat-tile--selected]="selectedWeakerSection === opt.value"
|
||||
[class.soa-cat-tile--female]="true"
|
||||
(click)="selectWeakerSection(opt.value)"
|
||||
>
|
||||
<mat-icon class="soa-cat-tile__icon">{{ opt.icon }}</mat-icon>
|
||||
<span class="soa-cat-tile__label">{{ opt.label }}</span>
|
||||
@if (selectedWeakerSection === opt.value) {
|
||||
<mat-icon class="soa-cat-tile__check">check_circle</mat-icon>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="soa-info-banner soa-info-banner--female">
|
||||
<mat-icon>info</mat-icon>
|
||||
<ul>
|
||||
<li>Valid KYC documents required (Aadhaar / PAN / Passport).</li>
|
||||
<li>Nomination registration is mandatory (Form DA-1).</li>
|
||||
<li>Eligible for PMJDY overdraft facilities (subject to rules).</li>
|
||||
<li>Financial literacy & priority processing for women applicants.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ── SINGLE ACCOUNT ────────────────────────────────────────── -->
|
||||
@if (selectedAccountStatus === 'SINGLE') {
|
||||
<div class="soa-block">
|
||||
<div class="soa-block__head">
|
||||
<div class="soa-block__head-left">
|
||||
<span class="soa-block__letter" style="background: #0891b2">
|
||||
<mat-icon style="font-size: 14px; height: 14px; width: 14px">account_circle</mat-icon>
|
||||
</span>
|
||||
<div>
|
||||
<span class="soa-block__title">Single Account</span>
|
||||
<span class="soa-block__hint">Standard individual account operated by a single person</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="soa-pill" style="background: #ecfeff; color: #0e7490; border-color: #cffafe"
|
||||
>Single Holder</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="soa-info-banner soa-info-banner--single">
|
||||
<mat-icon>info</mat-icon>
|
||||
<ul>
|
||||
<li>Only one primary holder allowed.</li>
|
||||
<li>Nomination is highly recommended.</li>
|
||||
<li>Full independent operation of account.</li>
|
||||
<li>Convertible to joint account later if required.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious disabled>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
|
||||
+1547
-2
File diff suppressed because it is too large
Load Diff
+479
-24
@@ -7,16 +7,21 @@
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, Input, Output, EventEmitter, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Component, OnInit, Input, Output, EventEmitter, inject, ChangeDetectorRef } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, FormsModule } from '@angular/forms';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
/** Custom Services */
|
||||
import { FixedDepositsService } from '../../fixed-deposits.service';
|
||||
import { ClientsService } from 'app/clients/clients.service';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { AddRelatedClientDialogComponent } from 'app/current-accounts/current-account-stepper/current-account-details-step/add-related-client-dialog/add-related-client-dialog.component';
|
||||
|
||||
/**
|
||||
* Fixed Deposits Account Details Step
|
||||
@@ -30,13 +35,19 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
MatTooltip,
|
||||
MatStepperPrevious,
|
||||
FaIconComponent,
|
||||
MatStepperNext
|
||||
MatStepperNext,
|
||||
FormsModule,
|
||||
MatIcon
|
||||
]
|
||||
})
|
||||
export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private fixedDepositsService = inject(FixedDepositsService);
|
||||
private settingsService = inject(SettingsService);
|
||||
private dateUtils = inject(Dates);
|
||||
private cdr = inject(ChangeDetectorRef);
|
||||
private clientsService = inject(ClientsService);
|
||||
private dialog = inject(MatDialog);
|
||||
|
||||
/** Fixed Deposits Account Template */
|
||||
@Input() fixedDepositsAccountTemplate: any;
|
||||
@@ -49,22 +60,194 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
||||
productData: any;
|
||||
/** Field Officer Data */
|
||||
fieldOfficerData: any;
|
||||
/** For edit savings form */
|
||||
/** For edit fixed deposits form */
|
||||
isFieldOfficerPatched = false;
|
||||
/** Fixed Deposits Account Details Form */
|
||||
fixedDepositAccountDetailsForm: UntypedFormGroup;
|
||||
fixedDepositAccountDetailsForm!: UntypedFormGroup;
|
||||
|
||||
isProductSelected = false;
|
||||
|
||||
/** Fixed Deposits Account Template with product data */
|
||||
@Output() fixedDepositsAccountProductTemplate = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Sets fixed deposits account details form.
|
||||
* @param {FormBuilder} formBuilder Form Builder.
|
||||
* @param {FixedDepositsService} fixedDepositsService Fixed Deposits Service.
|
||||
* @param {SettingsService} settingsService Settings Service
|
||||
*/
|
||||
/** Local copy of product template for displaying classification info */
|
||||
productTemplate: any = null;
|
||||
accountTypeOptions: string[] = [];
|
||||
|
||||
readonly fdTypeLabels: Record<string, string> = {
|
||||
SINGLE: 'FD Single (Days < 1 Year)',
|
||||
DOUBLE: 'FD Double (≥ 1 Year)',
|
||||
SPECIAL: 'FD Special (Monthly Interest Payout)'
|
||||
};
|
||||
|
||||
getFdTypeLabel(fdType: string): string {
|
||||
return this.fdTypeLabels[fdType] || fdType;
|
||||
}
|
||||
customerSegmentOptions: string[] = [];
|
||||
selectedAccountType: string | null = null;
|
||||
selectedCustomerSegment: string | null = null;
|
||||
|
||||
// ── Client info (auto-fetched) ─────────────────────────────────────────
|
||||
clientDob: Date | null = null;
|
||||
clientAge: number | null = null;
|
||||
clientGender: string | null = null;
|
||||
primaryClientName = '';
|
||||
primaryClientCif = '';
|
||||
primaryClientId: number | null = null;
|
||||
primaryHolderShare = 0;
|
||||
|
||||
// ── Status of Account (RBI) ────────────────────────────────────────────
|
||||
readonly accountStatusOptions = [
|
||||
{ value: 'INDIVIDUAL_MALE', label: 'Individual (Male)', icon: 'person', color: '#3b82f6' },
|
||||
{ value: 'INDIVIDUAL_FEMALE', label: 'Individual (Female)', icon: 'person_outline', color: '#ec4899' },
|
||||
{ value: 'SINGLE', label: 'Single Account', icon: 'account_circle', color: '#0891b2' },
|
||||
{ value: 'JOINT', label: 'Joint Account', icon: 'group', color: '#8b5cf6' },
|
||||
{ value: 'MINOR', label: 'Minor Account', icon: 'child_care', color: '#f59e0b' },
|
||||
{ value: 'SENIOR_CITIZEN', label: 'Senior Citizen', icon: 'elderly', color: '#10b981' }
|
||||
];
|
||||
|
||||
selectedAccountStatus = '';
|
||||
|
||||
// ── Joint Account ──────────────────────────────────────────────────────
|
||||
jointHolders: Array<{
|
||||
clientId: number;
|
||||
clientName: string;
|
||||
clientCif: string;
|
||||
sharePercentage: number;
|
||||
isMandatory: boolean;
|
||||
signingAuthority?: string;
|
||||
role?: string;
|
||||
}> = [];
|
||||
|
||||
modeOfOperation = '';
|
||||
|
||||
readonly modeOfOperationOptions = [
|
||||
{ value: 'EITHER_OR_SURVIVOR', label: 'E or S', sublabel: 'Either or Survivor', icon: 'how_to_vote' },
|
||||
{ value: 'FORMER_OR_SURVIVOR', label: 'F or S', sublabel: 'Former or Survivor', icon: 'person' },
|
||||
{ value: 'JOINTLY', label: 'Jointly', sublabel: 'All Must Sign', icon: 'groups' },
|
||||
{ value: 'ANYONE_OR_SURVIVOR', label: 'Anyone', sublabel: 'Anyone or Survivor', icon: 'group' }
|
||||
];
|
||||
|
||||
// ── Individual Male — Special Category ────────────────────────────────
|
||||
selectedSpecialCategory = '';
|
||||
|
||||
readonly specialCategoryOptions = [
|
||||
{ value: 'DEFENCE', label: 'Defence Personnel', icon: 'military_tech' },
|
||||
{ value: 'EX_SERVICEMAN', label: 'Ex-Serviceman', icon: 'shield' },
|
||||
{ value: 'GOVT_EMPLOYEE', label: 'Government Employee', icon: 'account_balance' },
|
||||
{ value: 'PHYSICALLY_CHALLENGED', label: 'Physically Challenged', icon: 'accessible' },
|
||||
{ value: 'NRI', label: 'NRI (Non-Resident Indian)', icon: 'flight' },
|
||||
{ value: 'NONE', label: 'None / General', icon: 'person' }
|
||||
];
|
||||
|
||||
// ── Individual Female — Weaker Section ────────────────────────────────
|
||||
selectedWeakerSection = '';
|
||||
|
||||
readonly weakerSectionOptions = [
|
||||
{ value: 'BPL', label: 'BPL (Below Poverty Line)', icon: 'volunteer_activism' },
|
||||
{ value: 'SC_ST', label: 'SC / ST Category', icon: 'groups' },
|
||||
{ value: 'MINORITY', label: 'Minority Community', icon: 'diversity_3' },
|
||||
{ value: 'SHG', label: 'Self Help Group Member', icon: 'handshake' },
|
||||
{ value: 'PMJDY', label: 'PMJDY Beneficiary', icon: 'savings' },
|
||||
{ value: 'NONE', label: 'None / General', icon: 'person_outline' }
|
||||
];
|
||||
|
||||
// ── Minor Account ──────────────────────────────────────────────────────
|
||||
guardianClientId: number | null = null;
|
||||
guardianName = '';
|
||||
guardianCif = '';
|
||||
guardianRelationship = '';
|
||||
minorDob: Date | null = null;
|
||||
|
||||
readonly guardianRelationshipOptions = [
|
||||
'Father',
|
||||
'Mother',
|
||||
'Legal Guardian',
|
||||
'Court Appointed Guardian'
|
||||
];
|
||||
|
||||
// ── Deposit Account Link ───────────────────────────────────────────────
|
||||
dalSelectedType: 'SAVINGS' | 'CURRENT' | null = null;
|
||||
dalAllSavings: any[] = [];
|
||||
dalAllCurrent: any[] = [];
|
||||
dalSearch = '';
|
||||
dalLoading = false;
|
||||
selectedLinkedAccount: any = null;
|
||||
currencyFlags: any = null;
|
||||
|
||||
get currencyName(): string {
|
||||
return this.productTemplate?.currency?.name || '';
|
||||
}
|
||||
|
||||
get decimalPlaces(): number {
|
||||
return this.productTemplate?.currency?.decimalPlaces ?? 0;
|
||||
}
|
||||
|
||||
get inMultiplesOf(): number {
|
||||
return this.productTemplate?.currency?.inMultiplesOf ?? 0;
|
||||
}
|
||||
|
||||
get dalAccounts(): any[] {
|
||||
return this.dalSelectedType === 'SAVINGS' ? this.dalAllSavings : this.dalAllCurrent;
|
||||
}
|
||||
|
||||
get dalFilteredAccounts(): any[] {
|
||||
const q = this.dalSearch.toLowerCase().trim();
|
||||
if (!q) return this.dalAccounts;
|
||||
return this.dalAccounts.filter(
|
||||
(a) => a.accountNo.toLowerCase().includes(q) || a.productName.toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
|
||||
selectDalType(type: 'SAVINGS' | 'CURRENT'): void {
|
||||
this.dalSelectedType = type;
|
||||
this.dalSearch = '';
|
||||
this.selectedLinkedAccount = null;
|
||||
}
|
||||
|
||||
selectLinkedAccount(account: any): void {
|
||||
this.selectedLinkedAccount = account;
|
||||
}
|
||||
|
||||
clearLinkedAccount(): void {
|
||||
this.selectedLinkedAccount = null;
|
||||
this.dalSelectedType = null;
|
||||
this.dalSearch = '';
|
||||
}
|
||||
|
||||
private loadClientAccounts(): void {
|
||||
const clientId = String(this.fixedDepositsAccountTemplate?.clientId);
|
||||
if (!clientId) return;
|
||||
this.dalLoading = true;
|
||||
this.clientsService.getClientAccountData(clientId).subscribe({
|
||||
next: (data: any) => {
|
||||
this.dalAllSavings = (data?.savingsAccounts ?? []).map((a: any) => ({
|
||||
id: a.id,
|
||||
accountNo: a.accountNo,
|
||||
productName: a.productName ?? '',
|
||||
accountType: 'SAVINGS' as const,
|
||||
status: a.status?.value ?? '',
|
||||
balance: a.accountBalance ?? 0,
|
||||
currencyCode: a.currency?.code ?? ''
|
||||
}));
|
||||
this.dalAllCurrent = (data?.currentAccounts ?? []).map((a: any) => ({
|
||||
id: a.id,
|
||||
accountNo: a.accountNo,
|
||||
productName: a.productName ?? '',
|
||||
accountType: 'CURRENT' as const,
|
||||
status: a.status?.value ?? '',
|
||||
balance: a.accountBalance ?? 0,
|
||||
currencyCode: a.currency?.code ?? ''
|
||||
}));
|
||||
this.dalLoading = false;
|
||||
this.cdr.detectChanges();
|
||||
},
|
||||
error: () => {
|
||||
this.dalLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.createFixedDepositsAccountDetailsForm();
|
||||
}
|
||||
@@ -78,12 +261,109 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
||||
this.fixedDepositAccountDetailsForm.patchValue({
|
||||
productId: this.fixedDepositsAccountTemplate.depositProductId,
|
||||
submittedOnDate:
|
||||
this.fixedDepositsAccountTemplate.timeline.submittedOnDate &&
|
||||
this.fixedDepositsAccountTemplate.timeline?.submittedOnDate &&
|
||||
new Date(this.fixedDepositsAccountTemplate.timeline.submittedOnDate),
|
||||
externalId: this.fixedDepositsAccountTemplate.externalId || ''
|
||||
externalId: this.fixedDepositsAccountTemplate.externalId
|
||||
});
|
||||
} else {
|
||||
this.fixedDepositAccountDetailsForm.patchValue({
|
||||
submittedOnDate: this.settingsService.businessDate
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-detect account status from client DOB + capture primary holder info
|
||||
const clientId = this.fixedDepositsAccountTemplate?.clientId || this.fixedDepositsAccountTemplate?.id;
|
||||
if (clientId) {
|
||||
this.primaryClientId = clientId;
|
||||
this.clientsService.getClientData(String(clientId)).subscribe((client: any) => {
|
||||
this.primaryClientName = client?.displayName ?? client?.fullname ?? '';
|
||||
this.primaryClientCif = client?.cifNumber ?? client?.cifNo ?? '';
|
||||
if (client?.dateOfBirth) {
|
||||
const dob = this.parseClientDob(client.dateOfBirth);
|
||||
if (dob) {
|
||||
this.clientDob = dob;
|
||||
this.clientAge = this.calculateAge(dob);
|
||||
this.clientGender = client.gender?.name?.toLowerCase() ?? null;
|
||||
this.autoDetectAccountStatus();
|
||||
}
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse DOB from backend — handles array [yyyy, mm, dd] or string */
|
||||
private parseClientDob(dob: any): Date | null {
|
||||
if (!dob) return null;
|
||||
if (Array.isArray(dob) && dob.length >= 3) {
|
||||
return new Date(dob[0], dob[1] - 1, dob[2]);
|
||||
}
|
||||
const d = new Date(dob);
|
||||
return isNaN(d.getTime()) ? null : d;
|
||||
}
|
||||
|
||||
/** Calculate full years from a date to today */
|
||||
private calculateAge(dob: Date): number {
|
||||
const today = this.settingsService.businessDate ?? new Date();
|
||||
let age = today.getFullYear() - dob.getFullYear();
|
||||
const monthDiff = today.getMonth() - dob.getMonth();
|
||||
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dob.getDate())) {
|
||||
age--;
|
||||
}
|
||||
return age;
|
||||
}
|
||||
|
||||
isTileDisabled(value: string): boolean {
|
||||
if (value === 'MINOR') return this.clientAge === null || this.clientAge >= 18;
|
||||
if (value === 'SENIOR_CITIZEN') return this.clientAge === null || this.clientAge < 60;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Handle Account Status selection */
|
||||
selectAccountStatus(status: string): void {
|
||||
if (!this.isTileDisabled(status)) {
|
||||
this.selectedAccountStatus = status;
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
/** Handle Special Category selection */
|
||||
selectSpecialCategory(category: string): void {
|
||||
this.selectedSpecialCategory = category;
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
/** Handle Weaker Section selection */
|
||||
selectWeakerSection(section: string): void {
|
||||
this.selectedWeakerSection = section;
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
getTileTooltip(value: string): string {
|
||||
if (value === 'MINOR' && this.clientAge !== null && this.clientAge >= 18) {
|
||||
return `Age ${this.clientAge} yrs — Minor only allowed below 18 years`;
|
||||
}
|
||||
if (value === 'SENIOR_CITIZEN' && this.clientAge !== null && this.clientAge < 60) {
|
||||
return `Age ${this.clientAge} yrs — Senior Citizen only allowed at 60+ years`;
|
||||
}
|
||||
if (value === 'MINOR' && this.clientAge === null) return 'DOB not available — cannot verify age';
|
||||
if (value === 'SENIOR_CITIZEN' && this.clientAge === null) return 'DOB not available — cannot verify age';
|
||||
return '';
|
||||
}
|
||||
|
||||
private autoDetectAccountStatus(): void {
|
||||
if (this.clientAge === null) return;
|
||||
if (this.clientAge < 18) {
|
||||
this.selectedAccountStatus = 'MINOR';
|
||||
this.minorDob = this.clientDob;
|
||||
} else if (this.clientAge >= 60) {
|
||||
this.selectedAccountStatus = 'SENIOR_CITIZEN';
|
||||
} else if (this.clientGender === 'female') {
|
||||
this.selectedAccountStatus = 'INDIVIDUAL_FEMALE';
|
||||
} else {
|
||||
this.selectedAccountStatus = 'INDIVIDUAL_MALE';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,34 +388,209 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
||||
* Fetches fixed deposits account product template on productId value changes
|
||||
*/
|
||||
buildDependencies() {
|
||||
const clientId = this.fixedDepositsAccountTemplate.clientId;
|
||||
this.fixedDepositAccountDetailsForm.get('productId').valueChanges.subscribe((productId: string) => {
|
||||
if (!this.fixedDepositsAccountTemplate) return;
|
||||
const clientId = this.fixedDepositsAccountTemplate.clientId || this.fixedDepositsAccountTemplate.id;
|
||||
this.fixedDepositAccountDetailsForm.get('productId')!.valueChanges.subscribe((productId: string) => {
|
||||
this.fixedDepositsService.getFixedDepositsAccountTemplate(clientId, productId).subscribe((response: any) => {
|
||||
if (!response.currency) {
|
||||
const selectedProduct = this.fixedDepositsAccountTemplate.productOptions?.find(
|
||||
(product: any) => product.id == productId
|
||||
);
|
||||
if (selectedProduct?.currency) {
|
||||
response.currency = selectedProduct.currency;
|
||||
} else if (this.fixedDepositsAccountTemplate.currency) {
|
||||
response.currency = this.fixedDepositsAccountTemplate.currency;
|
||||
}
|
||||
}
|
||||
this.productTemplate = response;
|
||||
this.accountTypeOptions = this.extractOptions(response.accountTypeOptions, response.accountType);
|
||||
this.customerSegmentOptions = this.extractOptions(response.customerSegmentOptions, response.customerSegment);
|
||||
this.selectedAccountType = response.accountType ?? null;
|
||||
this.selectedCustomerSegment = response.customerSegment ?? null;
|
||||
this.fixedDepositsAccountProductTemplate.emit(response);
|
||||
this.isProductSelected = true;
|
||||
this.fieldOfficerData = response.fieldOfficerOptions;
|
||||
this.isProductSelected = true;
|
||||
if (response.shareLinkingEnabled) {
|
||||
this.loadClientAccounts();
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
if (!this.isFieldOfficerPatched && this.fixedDepositsAccountTemplate.fieldOfficerId) {
|
||||
this.fixedDepositAccountDetailsForm
|
||||
.get('fieldOfficerId')
|
||||
.get('fieldOfficerId')!
|
||||
.patchValue(this.fixedDepositsAccountTemplate.fieldOfficerId);
|
||||
this.isFieldOfficerPatched = true;
|
||||
} else {
|
||||
this.fixedDepositAccountDetailsForm.get('fieldOfficerId').patchValue('');
|
||||
this.fixedDepositAccountDetailsForm.get('fieldOfficerId')!.patchValue('');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
hasValue(val: any): boolean {
|
||||
return val !== null && val !== undefined && val !== '';
|
||||
}
|
||||
|
||||
private extractOptions(options: any[], fallback?: string): string[] {
|
||||
if (Array.isArray(options) && options.length > 0) {
|
||||
return options.map((o: any) => o.value ?? o.name ?? o);
|
||||
}
|
||||
return fallback ? [fallback] : [];
|
||||
}
|
||||
|
||||
formatTemplateDate(value: any): string {
|
||||
if (!value) return '';
|
||||
let dateValue: Date | null = null;
|
||||
if (value instanceof Date) {
|
||||
dateValue = value;
|
||||
} else if (Array.isArray(value) && value.length >= 3) {
|
||||
dateValue = new Date(value[0], value[1] - 1, value[2]);
|
||||
} else {
|
||||
dateValue = new Date(value);
|
||||
}
|
||||
if (!dateValue || Number.isNaN(dateValue.getTime())) {
|
||||
return Array.isArray(value) ? value.join('/') : String(value);
|
||||
}
|
||||
return this.dateUtils.formatDate(dateValue, 'M/d/yyyy') || '';
|
||||
}
|
||||
|
||||
onGuardianChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void {
|
||||
this.guardianClientId = event?.clientId ?? null;
|
||||
this.guardianName = event?.displayName ?? '';
|
||||
this.guardianCif = event?.cifNumber ?? '';
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
// ── Joint Account helpers ──────────────────────────────────────────────
|
||||
openAddJointHolderDialog(): void {
|
||||
const ref = this.dialog.open(AddRelatedClientDialogComponent, {
|
||||
width: '500px',
|
||||
panelClass: 'arc-dialog-panel',
|
||||
disableClose: true
|
||||
});
|
||||
ref.afterClosed().subscribe((result) => {
|
||||
if (result) {
|
||||
const duplicate = this.jointHolders.some((h) => h.clientCif && h.clientCif === result.clientCif);
|
||||
if (duplicate) {
|
||||
alert(`Account holder with CIF ${result.clientCif} is already added.`);
|
||||
return;
|
||||
}
|
||||
this.jointHolders = [
|
||||
...this.jointHolders,
|
||||
{ ...result, isMandatory: false }];
|
||||
this.autoSetModeOfOperation();
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeJointHolder(index: number): void {
|
||||
this.jointHolders = this.jointHolders.filter((_, i) => i !== index);
|
||||
this.autoSetModeOfOperation();
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
toggleJointHolderMandatory(holder: any): void {
|
||||
holder.isMandatory = !holder.isMandatory;
|
||||
this.autoSetModeOfOperation();
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
|
||||
autoSetModeOfOperation(): void {
|
||||
const mandatory = this.jointHolders.filter((h) => h.isMandatory).length;
|
||||
if (mandatory === 0) {
|
||||
this.modeOfOperation = 'EITHER_OR_SURVIVOR';
|
||||
} else if (mandatory === 1) {
|
||||
this.modeOfOperation = 'FORMER_OR_SURVIVOR';
|
||||
} else {
|
||||
this.modeOfOperation = 'JOINTLY';
|
||||
}
|
||||
}
|
||||
|
||||
get totalJointSharePercentage(): number {
|
||||
return (this.primaryHolderShare || 0) + this.jointHolders.reduce((sum, h) => sum + (h.sharePercentage || 0), 0);
|
||||
}
|
||||
|
||||
get mandatoryJointCount(): number {
|
||||
return this.jointHolders.filter((h) => h.isMandatory).length;
|
||||
}
|
||||
|
||||
get selectedModeLabel(): string {
|
||||
return this.modeOfOperationOptions.find((o) => o.value === this.modeOfOperation)?.sublabel ?? '';
|
||||
}
|
||||
|
||||
get majorityDate(): string {
|
||||
const dob = this.minorDob ?? this.clientDob;
|
||||
if (!dob) return '';
|
||||
const d = new Date(dob);
|
||||
d.setFullYear(d.getFullYear() + 18);
|
||||
return this.dateUtils.formatDate(d, 'M/d/yyyy') || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fixed deposits account details form value.
|
||||
*/
|
||||
get fixedDepositAccountDetails() {
|
||||
const fixedDepositAccountDetails = this.fixedDepositAccountDetailsForm.value;
|
||||
for (const key in fixedDepositAccountDetails) {
|
||||
if (fixedDepositAccountDetails[key] === '') {
|
||||
delete fixedDepositAccountDetails[key];
|
||||
}
|
||||
const value = this.fixedDepositAccountDetailsForm.value;
|
||||
|
||||
if (this.selectedAccountType) {
|
||||
value.accountType = this.selectedAccountType;
|
||||
}
|
||||
return fixedDepositAccountDetails;
|
||||
if (this.selectedCustomerSegment) {
|
||||
value.customerSegment = this.selectedCustomerSegment;
|
||||
}
|
||||
|
||||
if (this.selectedAccountStatus) {
|
||||
value.accountStatus = this.selectedAccountStatus;
|
||||
}
|
||||
|
||||
if (this.selectedAccountStatus === 'JOINT') {
|
||||
value.modeOfOperation = this.modeOfOperation || null;
|
||||
value.jointHolders = [
|
||||
{
|
||||
clientId: this.primaryClientId,
|
||||
displayName: this.primaryClientName,
|
||||
cifNumber: this.primaryClientCif,
|
||||
sharePercentage: this.primaryHolderShare,
|
||||
isMandatory: true,
|
||||
isPrimary: true,
|
||||
signingAuthority: 'Mandatory',
|
||||
role: 'Primary Account Holder'
|
||||
},
|
||||
...this.jointHolders.map((h) => ({
|
||||
clientId: h.clientId,
|
||||
displayName: h.clientName,
|
||||
cifNumber: h.clientCif,
|
||||
sharePercentage: h.sharePercentage,
|
||||
isMandatory: h.isMandatory,
|
||||
isPrimary: false,
|
||||
signingAuthority: h.signingAuthority || (h.isMandatory ? 'Mandatory' : 'Optional'),
|
||||
role: h.role || 'Co-Holder'
|
||||
}))
|
||||
];
|
||||
}
|
||||
|
||||
if (this.selectedAccountStatus === 'INDIVIDUAL_MALE' && this.selectedSpecialCategory) {
|
||||
value.specialCategory = this.selectedSpecialCategory;
|
||||
}
|
||||
|
||||
if (this.selectedAccountStatus === 'INDIVIDUAL_FEMALE' && this.selectedWeakerSection) {
|
||||
value.weakerSection = this.selectedWeakerSection;
|
||||
}
|
||||
|
||||
if (this.selectedAccountStatus === 'MINOR') {
|
||||
value.guardianClientId = this.guardianClientId;
|
||||
value.guardianName = this.guardianName;
|
||||
value.guardianCifNumber = this.guardianCif;
|
||||
value.guardianRelationship = this.guardianRelationship;
|
||||
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'yyyy-MM-dd') : null;
|
||||
}
|
||||
|
||||
if (this.selectedLinkedAccount) {
|
||||
value.linkedAccountId = this.selectedLinkedAccount.id;
|
||||
value.linkedAccountType = this.selectedLinkedAccount.accountType;
|
||||
value.linkedAccountNo = this.selectedLinkedAccount.accountNo;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
+412
-154
@@ -6,161 +6,419 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="layout-column gap-2px">
|
||||
<div class="flex-fill" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.name">
|
||||
<span class="flex-40">{{ 'labels.inputs.name' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate.accountChart.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill">
|
||||
<span class="flex-40">{{ 'labels.inputs.Valid from Date' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.fromDate | dateFormat }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.endDate">
|
||||
<span class="flex-40">{{ 'labels.inputs.End Date' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.endDate | dateFormat }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.description">
|
||||
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.description }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill">
|
||||
<span class="flex-40">{{ 'labels.inputs.Primary Grouping by Amount' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositsAccountProductTemplate?.accountChart.isPrimaryGroupingByAmount ? 'Yes' : 'No'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<table class="mat-elevation-z1" mat-table [dataSource]="interestRateChartData" #chartsTable multiTemplateDataRows>
|
||||
<ng-container matColumnDef="period">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
{{ chartSlab.fromPeriod }}-{{ chartSlab.toPeriod }} {{ chartSlab.periodType.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="amountRange">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount Range' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.amountRangeFrom }}-{{ chartSlab.amountRangeTo }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="interest">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.annualInterestRate }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Description' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.description }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
(click)="expandChartSlabIndex = expandChartSlabIndex === chartSlabIndex ? null : chartSlabIndex"
|
||||
>
|
||||
<span *ngIf="expandChartSlabIndex !== chartSlabIndex">
|
||||
<fa-icon icon="eye" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.View Incentives' | translate }}
|
||||
</span>
|
||||
<span *ngIf="expandChartSlabIndex === chartSlabIndex">
|
||||
<fa-icon icon="eye-slash" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Hide Incentives' | translate }}
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="incentives">
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
|
||||
[attr.colspan]="chartSlabsDisplayedColumns.length"
|
||||
>
|
||||
<div
|
||||
class="incentives layout-row-wrap flex-fill"
|
||||
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
||||
>
|
||||
<mat-card class="layout-row-wrap flex-fill">
|
||||
<h4 class="m-b-10 flex-13">
|
||||
{{ 'labels.heading.Incentives' | translate }}
|
||||
</h4>
|
||||
|
||||
<table
|
||||
class="flex-fill mat-elevation-z1"
|
||||
mat-table
|
||||
[dataSource]="chartSlab.incentives"
|
||||
*ngIf="chartSlab.incentives.length"
|
||||
>
|
||||
<ng-container matColumnDef="entityType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.entityType.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="attributeName">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.attributeName.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="conditionType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.conditionType.value | titlecase }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="attributeValue">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive" [ngSwitch]="incentive.attributeName">
|
||||
{{ incentive.attributeValueDesc }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="incentiveType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.incentiveType.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.amount }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
||||
</table>
|
||||
</mat-card>
|
||||
<div class="fdrc-wrapper">
|
||||
<!-- ══ Chart Header Card ════════════════════════════════════════════════ -->
|
||||
@if (fixedDepositsAccountProductTemplate?.accountChart) {
|
||||
<div class="fdrc-header-card">
|
||||
<div class="fdrc-header-card__left">
|
||||
<div class="fdrc-header-card__icon">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<div>
|
||||
<div class="fdrc-header-card__title">{{ chartName || 'Interest Rate Chart' }}</div>
|
||||
@if (chartDescription) {
|
||||
<div class="fdrc-header-card__desc">{{ chartDescription }}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="fdrc-header-card__meta">
|
||||
@if (fdType) {
|
||||
<span
|
||||
class="fdrc-pill fdrc-pill--type"
|
||||
[class.fdrc-pill--single]="fdType === 'SINGLE'"
|
||||
[class.fdrc-pill--double]="fdType === 'DOUBLE'"
|
||||
[class.fdrc-pill--special]="fdType === 'SPECIAL'"
|
||||
>
|
||||
<i class="fas fa-tag"></i>
|
||||
{{ fdTypeLabel }}
|
||||
</span>
|
||||
}
|
||||
<div class="fdrc-header-card__dates">
|
||||
<span class="fdrc-meta-item">
|
||||
<i class="fas fa-calendar-check"></i>
|
||||
From: <strong>{{ chartFromDate | dateFormat }}</strong>
|
||||
</span>
|
||||
@if (chartEndDate) {
|
||||
<span class="fdrc-meta-item">
|
||||
<i class="fas fa-calendar-times"></i>
|
||||
To: <strong>{{ chartEndDate | dateFormat }}</strong>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
@if (isPrimaryGroupingByAmount) {
|
||||
<span class="fdrc-pill fdrc-pill--amount"> <i class="fas fa-layer-group"></i> Grouped by Amount </span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chartSlabsDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns" class="incentives-row"></tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- ══ FD Type Rule Banner ══════════════════════════════════════════════ -->
|
||||
@if (fdType) {
|
||||
<div
|
||||
class="fdrc-rule-banner"
|
||||
[class.fdrc-rule-banner--single]="fdType === 'SINGLE'"
|
||||
[class.fdrc-rule-banner--double]="fdType === 'DOUBLE'"
|
||||
[class.fdrc-rule-banner--special]="fdType === 'SPECIAL'"
|
||||
>
|
||||
<div class="fdrc-rule-banner__icon">
|
||||
@if (fdType === 'SINGLE') {
|
||||
<i class="fas fa-hourglass-start"></i>
|
||||
}
|
||||
@if (fdType === 'DOUBLE') {
|
||||
<i class="fas fa-hourglass-end"></i>
|
||||
}
|
||||
@if (fdType === 'SPECIAL') {
|
||||
<i class="fas fa-star"></i>
|
||||
}
|
||||
</div>
|
||||
<div class="fdrc-rule-banner__content">
|
||||
@if (fdType === 'SINGLE') {
|
||||
<strong>FD Single — Short Term</strong>
|
||||
<span>Tenure must be less than 1 year (1 to 364 days). Enter tenure in Days only.</span>
|
||||
}
|
||||
@if (fdType === 'DOUBLE') {
|
||||
<strong>FD Double — Long Term</strong>
|
||||
<span>Tenure must be 1 year or more. Enter tenure in Months or Years (minimum 12 months).</span>
|
||||
}
|
||||
@if (fdType === 'SPECIAL') {
|
||||
<strong>FD Special — Monthly Interest Payout</strong>
|
||||
<span
|
||||
>Interest is paid out monthly. Enter your principal to estimate monthly income. Any tenure is allowed.</span
|
||||
>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="layout-row responsive-column align-center gap-2px margin-t">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext>
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
<!-- ══ Tenure Calculator ════════════════════════════════════════════════ -->
|
||||
<div class="fdrc-calc-card">
|
||||
<div class="fdrc-calc-card__header">
|
||||
<div class="fdrc-calc-card__header-left">
|
||||
<div class="fdrc-calc-icon">
|
||||
<i class="fas fa-calculator"></i>
|
||||
</div>
|
||||
<div>
|
||||
<span class="fdrc-calc-card__title">Rate Calculator</span>
|
||||
<span class="fdrc-calc-card__sub">Enter your deposit tenure to find the applicable interest rate</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdrc-calc-card__body">
|
||||
<div class="fdrc-calc-inputs">
|
||||
<!-- Tenure Value -->
|
||||
<div class="fdrc-calc-field">
|
||||
<label class="fdrc-calc-label">Deposit Tenure</label>
|
||||
<div class="fdrc-tenure-row">
|
||||
<input
|
||||
type="number"
|
||||
class="fdrc-tenure-input"
|
||||
[(ngModel)]="tenureValue"
|
||||
[min]="1"
|
||||
placeholder="Enter value"
|
||||
(keyup.enter)="calculateRate()"
|
||||
/>
|
||||
<!-- Period Unit Selector -->
|
||||
@if (allowedPeriodUnits.length === 1) {
|
||||
<span class="fdrc-period-badge">{{ allowedPeriodUnits[0] }}</span>
|
||||
} @else {
|
||||
<div class="fdrc-period-tabs">
|
||||
@for (unit of allowedPeriodUnits; track unit) {
|
||||
<button
|
||||
type="button"
|
||||
class="fdrc-period-tab"
|
||||
[class.fdrc-period-tab--active]="tenurePeriodUnit === unit"
|
||||
(click)="tenurePeriodUnit = unit; onPeriodUnitChange()"
|
||||
>
|
||||
{{ unit }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- FD type constraint hint -->
|
||||
@if (fdType === 'SINGLE') {
|
||||
<span class="fdrc-calc-hint"><i class="fas fa-info-circle"></i> Max 364 days allowed</span>
|
||||
}
|
||||
@if (fdType === 'DOUBLE') {
|
||||
<span class="fdrc-calc-hint"><i class="fas fa-info-circle"></i> Minimum 12 months required</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Principal Amount (SPECIAL only) -->
|
||||
@if (isSpecial) {
|
||||
<div class="fdrc-calc-field">
|
||||
<label class="fdrc-calc-label"
|
||||
>Principal Amount <span class="fdrc-calc-optional">(optional — for monthly payout estimate)</span></label
|
||||
>
|
||||
<div class="fdrc-amount-row">
|
||||
<span class="fdrc-amount-prefix">₹</span>
|
||||
<input
|
||||
type="number"
|
||||
class="fdrc-tenure-input fdrc-tenure-input--amount"
|
||||
[(ngModel)]="principalAmount"
|
||||
[min]="1"
|
||||
placeholder="e.g. 100000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="fdrc-calc-actions">
|
||||
<button type="button" class="fdrc-calc-btn" (click)="calculateRate()">
|
||||
<i class="fas fa-search"></i> Find Rate
|
||||
</button>
|
||||
@if (tenureValue || matchResult) {
|
||||
<button type="button" class="fdrc-calc-reset" (click)="resetCalculator()">
|
||||
<i class="fas fa-times"></i> Reset
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Validation Warning -->
|
||||
@if (validationWarning) {
|
||||
<div class="fdrc-alert fdrc-alert--warn">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<span>{{ validationWarning }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- 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 -->
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
</div>
|
||||
@if (matchResult.slab.description) {
|
||||
<div class="fdrc-result-meta__item">
|
||||
<i class="fas fa-tag"></i>
|
||||
<span>{{ matchResult.slab.description }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (matchResult.slab.amountRangeFrom || matchResult.slab.amountRangeTo) {
|
||||
<div class="fdrc-result-meta__item">
|
||||
<i class="fas fa-coins"></i>
|
||||
<span
|
||||
>Amount Range:
|
||||
<strong
|
||||
>{{ matchResult.slab.amountRangeFrom | number }}–{{
|
||||
matchResult.slab.amountRangeTo | number
|
||||
}}</strong
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Monthly Payout (SPECIAL) -->
|
||||
@if (isSpecial && matchResult.estimatedMonthlyPayout != null) {
|
||||
<div class="fdrc-payout-card">
|
||||
<i class="fas fa-wallet"></i>
|
||||
<div class="fdrc-payout-card__content">
|
||||
<span class="fdrc-payout-card__label">Estimated Monthly Payout</span>
|
||||
<span class="fdrc-payout-card__amount"
|
||||
>₹ {{ matchResult.estimatedMonthlyPayout | number: '1.2-2' }}</span
|
||||
>
|
||||
<span class="fdrc-payout-card__formula"
|
||||
>= ₹{{ principalAmount | number }} × {{ matchResult.appliedRate }}% ÷ 12</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (isSpecial && !matchResult.estimatedMonthlyPayout) {
|
||||
<div class="fdrc-payout-hint">
|
||||
<i class="fas fa-lightbulb"></i>
|
||||
Enter a principal amount above to see estimated monthly interest payout.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ Rate Chart Table ══════════════════════════════════════════════════ -->
|
||||
<div class="fdrc-table-card">
|
||||
<div class="fdrc-table-card__header">
|
||||
<div class="fdrc-table-card__title-row">
|
||||
<i class="fas fa-table"></i>
|
||||
<span class="fdrc-table-card__title">Interest Rate Slabs</span>
|
||||
@if (interestRateChartData.length) {
|
||||
<span class="fdrc-slab-count"
|
||||
>{{ interestRateChartData.length }} slab{{ interestRateChartData.length !== 1 ? 's' : '' }}</span
|
||||
>
|
||||
}
|
||||
</div>
|
||||
@if (matchResult) {
|
||||
<span class="fdrc-highlight-note"> <i class="fas fa-highlighter"></i> Matched slab is highlighted </span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="fdrc-table-wrap">
|
||||
<table mat-table [dataSource]="interestRateChartData" #chartsTable multiTemplateDataRows class="fdrc-table">
|
||||
<!-- Period Column -->
|
||||
<ng-container matColumnDef="period">
|
||||
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Period</th>
|
||||
<td mat-cell *matCellDef="let slab" class="fdrc-td fdrc-td--period">
|
||||
<span class="fdrc-period-range">
|
||||
{{ slab.fromPeriod }}
|
||||
@if (slab.toPeriod != null) {
|
||||
– {{ slab.toPeriod }}
|
||||
} @else {
|
||||
+
|
||||
}
|
||||
</span>
|
||||
<span class="fdrc-period-type">{{ slab.periodType?.value }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Amount Range Column -->
|
||||
<ng-container matColumnDef="amountRange">
|
||||
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Amount Range</th>
|
||||
<td mat-cell *matCellDef="let slab" class="fdrc-td">
|
||||
@if (slab.amountRangeFrom || slab.amountRangeTo) {
|
||||
<span>{{ slab.amountRangeFrom | number }} – {{ slab.amountRangeTo | number }}</span>
|
||||
} @else {
|
||||
<span class="fdrc-td--na">—</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Interest Column -->
|
||||
<ng-container matColumnDef="interest">
|
||||
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Interest Rate</th>
|
||||
<td mat-cell *matCellDef="let slab" class="fdrc-td">
|
||||
<span class="fdrc-rate-badge">{{ slab.annualInterestRate | number: '1.2-2' }}%</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Description Column -->
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Description</th>
|
||||
<td mat-cell *matCellDef="let slab" class="fdrc-td">
|
||||
{{ slab.description || '—' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Actions Column -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Incentives</th>
|
||||
<td mat-cell *matCellDef="let slab; let i = dataIndex" class="fdrc-td">
|
||||
@if (slab.incentives?.length) {
|
||||
<button
|
||||
type="button"
|
||||
class="fdrc-incentive-btn"
|
||||
(click)="expandChartSlabIndex = expandChartSlabIndex === i ? null : i"
|
||||
>
|
||||
<i
|
||||
class="fas"
|
||||
[class.fa-eye]="expandChartSlabIndex !== i"
|
||||
[class.fa-eye-slash]="expandChartSlabIndex === i"
|
||||
></i>
|
||||
{{ expandChartSlabIndex === i ? 'Hide' : 'View' }} ({{ slab.incentives.length }})
|
||||
</button>
|
||||
} @else {
|
||||
<span class="fdrc-td--na">None</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Incentives Expanded Row -->
|
||||
<ng-container matColumnDef="incentives">
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let slab; let i = dataIndex"
|
||||
[attr.colspan]="chartSlabsDisplayedColumns.length"
|
||||
class="fdrc-incentives-cell"
|
||||
>
|
||||
<div
|
||||
class="fdrc-incentives-wrap"
|
||||
[@expandChartSlab]="i === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
||||
>
|
||||
@if (slab.incentives?.length) {
|
||||
<div class="fdrc-incentives-inner">
|
||||
<div class="fdrc-incentives-title"><i class="fas fa-gift"></i> Incentives for this slab</div>
|
||||
<table mat-table [dataSource]="slab.incentives" class="fdrc-incentives-table">
|
||||
<ng-container matColumnDef="entityType">
|
||||
<th mat-header-cell *matHeaderCellDef>Entity Type</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.entityType?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeName">
|
||||
<th mat-header-cell *matHeaderCellDef>Attribute</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.attributeName?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="conditionType">
|
||||
<th mat-header-cell *matHeaderCellDef>Condition</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.conditionType?.value | titlecase }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeValue">
|
||||
<th mat-header-cell *matHeaderCellDef>Value</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.attributeValueDesc }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incentiveType">
|
||||
<th mat-header-cell *matHeaderCellDef>Type</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.incentiveType?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>Amount</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.amount }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns" class="fdrc-header-row"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: chartSlabsDisplayedColumns; let i = dataIndex"
|
||||
class="fdrc-data-row"
|
||||
[class.fdrc-data-row--matched]="isMatchedSlab(i)"
|
||||
></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns"
|
||||
class="fdrc-incentives-row"
|
||||
></tr>
|
||||
</table>
|
||||
|
||||
@if (!interestRateChartData.length) {
|
||||
<div class="fdrc-empty">
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
<span>No interest rate slabs defined for this product.</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+854
-14
@@ -6,26 +6,866 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.margin-t {
|
||||
margin-top: 2em;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-100: #f1f5f9;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-600: #64748b;
|
||||
$gray-700: #334155;
|
||||
$gray-900: #0f172a;
|
||||
$blue: #2563eb;
|
||||
$blue-light: #dbeafe;
|
||||
$green: #16a34a;
|
||||
$green-light: #dcfce7;
|
||||
$amber: #d97706;
|
||||
$amber-light: #fef3c7;
|
||||
$purple: #7c3aed;
|
||||
$purple-light: #ede9fe;
|
||||
$red: #dc2626;
|
||||
$red-light: #fee2e2;
|
||||
|
||||
.fdrc-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
// ── Chart Header Card ──────────────────────────────────────────────────────
|
||||
|
||||
tr.incentives-row {
|
||||
height: 0;
|
||||
.fdrc-header-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 12px;
|
||||
padding: 16px 20px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> td {
|
||||
padding: 0;
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
font-size: 12px;
|
||||
color: $gray-600;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__dates {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 12px;
|
||||
color: $gray-700;
|
||||
|
||||
i {
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pills ──────────────────────────────────────────────────────────────────
|
||||
|
||||
.fdrc-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
white-space: nowrap;
|
||||
|
||||
&--type {
|
||||
background: #f1f5f9;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
&--single {
|
||||
background: $amber-light;
|
||||
color: $amber;
|
||||
}
|
||||
|
||||
&--double {
|
||||
background: $purple-light;
|
||||
color: $purple;
|
||||
}
|
||||
|
||||
&--special {
|
||||
background: $green-light;
|
||||
color: $green;
|
||||
}
|
||||
|
||||
&--amount {
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
// ── FD Type Rule Banner ────────────────────────────────────────────────────
|
||||
|
||||
.fdrc-rule-banner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 14px;
|
||||
padding: 14px 18px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid;
|
||||
|
||||
&--single {
|
||||
background: $amber-light;
|
||||
border-color: $amber;
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
&--double {
|
||||
background: $purple-light;
|
||||
border-color: $purple;
|
||||
color: #6d28d9;
|
||||
}
|
||||
|
||||
&--special {
|
||||
background: $green-light;
|
||||
border-color: $green;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 18px;
|
||||
margin-top: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
|
||||
strong {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.incentives {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mat-card {
|
||||
border-radius: 0;
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Calculator Card ────────────────────────────────────────────────────────
|
||||
|
||||
.fdrc-calc-card {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 20px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
}
|
||||
|
||||
&__header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: $gray-900;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
font-size: 12px;
|
||||
color: $gray-600;
|
||||
display: block;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-calc-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fdrc-calc-inputs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.fdrc-calc-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
flex: 1 1 220px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.fdrc-calc-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $gray-700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.fdrc-calc-optional {
|
||||
font-weight: 400;
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
.fdrc-tenure-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fdrc-tenure-input {
|
||||
height: 42px;
|
||||
border: 1.5px solid $border;
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
background: #fff;
|
||||
width: 130px;
|
||||
transition: border-color 0.15s;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $blue;
|
||||
}
|
||||
|
||||
&--amount {
|
||||
width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-period-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
height: 42px;
|
||||
background: $gray-100;
|
||||
border: 1.5px solid $border;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
.fdrc-period-tabs {
|
||||
display: flex;
|
||||
border: 1.5px solid $border;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fdrc-period-tab {
|
||||
height: 42px;
|
||||
padding: 0 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
background: #fff;
|
||||
color: $gray-600;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 0.15s,
|
||||
color 0.15s;
|
||||
|
||||
& + & {
|
||||
border-left: 1px solid $border;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: $blue;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-amount-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fdrc-amount-prefix {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 42px;
|
||||
background: $gray-100;
|
||||
border: 1.5px solid $border;
|
||||
border-right: none;
|
||||
border-radius: 8px 0 0 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
.fdrc-amount-row .fdrc-tenure-input {
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
.fdrc-calc-hint {
|
||||
font-size: 11px;
|
||||
color: $amber;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.fdrc-calc-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.fdrc-calc-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
height: 42px;
|
||||
padding: 0 22px;
|
||||
background: $blue;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: #1d4ed8;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-calc-reset {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 42px;
|
||||
padding: 0 16px;
|
||||
background: transparent;
|
||||
color: $gray-600;
|
||||
border: 1.5px solid $border;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
color 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: $red;
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Alert ─────────────────────────────────────────────────────────────────
|
||||
|
||||
.fdrc-alert {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
margin-top: 16px;
|
||||
font-size: 13px;
|
||||
|
||||
i {
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&--warn {
|
||||
background: $amber-light;
|
||||
color: #b45309;
|
||||
border: 1px solid rgba($amber, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tenure Banner (inside result card) ───────────────────────────────────
|
||||
|
||||
.fdrc-tenure-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
background: #fff;
|
||||
border: 1.5px solid rgba($green, 0.4);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 14px;
|
||||
|
||||
> i {
|
||||
font-size: 18px;
|
||||
color: $green;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
color: $gray-900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
&__saved {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px 10px;
|
||||
background: $green-light;
|
||||
color: $green;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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 {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-result-rate {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&__value {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
color: $green;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 13px;
|
||||
color: #166534;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-result-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
font-size: 12px;
|
||||
color: $gray-700;
|
||||
|
||||
i {
|
||||
color: $green;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Monthly Payout (SPECIAL) ───────────────────────────────────────────────
|
||||
|
||||
.fdrc-payout-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-top: 14px;
|
||||
padding: 14px 16px;
|
||||
background: #fff;
|
||||
border: 1.5px solid rgba($green, 0.3);
|
||||
border-radius: 10px;
|
||||
|
||||
i {
|
||||
font-size: 22px;
|
||||
color: $green;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: $gray-600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
&__amount {
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: $green;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
&__formula {
|
||||
font-size: 11px;
|
||||
color: $gray-600;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-payout-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
color: $gray-600;
|
||||
font-style: italic;
|
||||
|
||||
i {
|
||||
color: $amber;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Rate Chart Table Card ─────────────────────────────────────────────────
|
||||
|
||||
.fdrc-table-card {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 20px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
i {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-slab-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fdrc-highlight-note {
|
||||
font-size: 12px;
|
||||
color: $green;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.fdrc-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.fdrc-table {
|
||||
width: 100%;
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
.fdrc-th {
|
||||
font-size: 11px !important;
|
||||
font-weight: 700 !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: $gray-700 !important;
|
||||
background: $gray-50;
|
||||
padding: 10px 16px !important;
|
||||
}
|
||||
|
||||
.fdrc-td {
|
||||
padding: 12px 16px !important;
|
||||
font-size: 13px;
|
||||
color: $gray-700;
|
||||
vertical-align: middle;
|
||||
|
||||
&--period {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&--na {
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-period-range {
|
||||
font-weight: 700;
|
||||
color: $gray-900;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.fdrc-period-type {
|
||||
font-size: 11px;
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
.fdrc-rate-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 10px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fdrc-incentive-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 12px;
|
||||
height: 32px;
|
||||
background: transparent;
|
||||
border: 1.5px solid $border;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $gray-700;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
color 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: $blue;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-header-row {
|
||||
background: $gray-50;
|
||||
}
|
||||
|
||||
.fdrc-data-row {
|
||||
transition: background 0.15s;
|
||||
|
||||
&:hover td {
|
||||
background: $gray-50;
|
||||
}
|
||||
|
||||
&--matched td {
|
||||
background: $green-light !important;
|
||||
border-left: 3px solid $green;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-incentives-row {
|
||||
height: 0;
|
||||
|
||||
> td {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-incentives-cell {
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fdrc-incentives-wrap {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fdrc-incentives-inner {
|
||||
padding: 12px 20px 16px;
|
||||
background: $gray-50;
|
||||
border-top: 1px dashed $border;
|
||||
}
|
||||
|
||||
.fdrc-incentives-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: $gray-700;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
|
||||
i {
|
||||
color: $purple;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-incentives-table {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fdrc-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 48px 20px;
|
||||
color: #94a3b8;
|
||||
|
||||
i {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Stepper Actions ───────────────────────────────────────────────────────
|
||||
|
||||
.fdrc-stepper-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-top: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
+203
-26
@@ -6,8 +6,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, Input, OnChanges, ViewChild } from '@angular/core';
|
||||
import { Component, Input, OnChanges, Output, EventEmitter, ViewChild } from '@angular/core';
|
||||
import {
|
||||
MatTableDataSource,
|
||||
MatTable,
|
||||
@@ -22,15 +21,20 @@ import {
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { NgIf, NgSwitch, TitleCasePipe } from '@angular/common';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
||||
import { NgIf, NgSwitch, TitleCasePipe, DecimalPipe } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Fixed Deposits Account Interest Rate Chart Step
|
||||
*/
|
||||
export type FdPeriodUnit = 'Days' | 'Months' | 'Years';
|
||||
|
||||
export interface SlabMatchResult {
|
||||
slab: any;
|
||||
slabIndex: number;
|
||||
appliedRate: number;
|
||||
estimatedMonthlyPayout?: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-fixed-deposit-account-interest-rate-chart-step',
|
||||
templateUrl: './fixed-deposit-account-interest-rate-chart-step.component.html',
|
||||
@@ -44,41 +48,38 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FormsModule,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
FaIconComponent,
|
||||
NgSwitch,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
MatStepperPrevious,
|
||||
MatStepperNext,
|
||||
TitleCasePipe,
|
||||
DateFormatPipe
|
||||
DateFormatPipe,
|
||||
DecimalPipe
|
||||
]
|
||||
})
|
||||
export class FixedDepositAccountInterestRateChartStepComponent implements OnChanges {
|
||||
/** Fixed deposits account template */
|
||||
@Input() fixedDepositsAccountTemplate: any;
|
||||
/** Fixed deposits account and product template */
|
||||
@Input() fixedDepositsAccountProductTemplate: any;
|
||||
|
||||
/** Interest Rate Chart Data */
|
||||
interestRateChartData: any = [];
|
||||
/** Columns to be displayed in interest rate chart table. */
|
||||
chartSlabsDisplayedColumns: any[] = [
|
||||
@Output() tenureSelected = new EventEmitter<{ depositPeriod: number; depositPeriodFrequencyId: number }>();
|
||||
|
||||
interestRateChartData: any[] = [];
|
||||
|
||||
chartSlabsDisplayedColumns: string[] = [
|
||||
'period',
|
||||
'amountRange',
|
||||
'interest',
|
||||
'description',
|
||||
'actions'
|
||||
];
|
||||
/** Columns to be displayed in incentives sub-table. */
|
||||
incentivesDisplayedColumns: string[] = [
|
||||
'entityType',
|
||||
'attributeName',
|
||||
@@ -87,20 +88,196 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
|
||||
'incentiveType',
|
||||
'amount'
|
||||
];
|
||||
/** Additional Column to disblac incentives table */
|
||||
chartSlabsIncentivesDisplayedColumns: string[] = ['incentives'];
|
||||
/** Expand Chart Slab Index used in the view */
|
||||
expandChartSlabIndex: number;
|
||||
|
||||
/** Interest Rate charts table reference */
|
||||
expandChartSlabIndex: number | null = null;
|
||||
|
||||
@ViewChild('chartsTable', { static: true }) chartsTableRef: MatTable<Element>;
|
||||
|
||||
constructor() {}
|
||||
// ── Tenure Calculator ───────────────────────────────────────────────────
|
||||
tenureValue: number | null = null;
|
||||
tenurePeriodUnit: FdPeriodUnit = 'Days';
|
||||
principalAmount: number | null = null;
|
||||
|
||||
matchResult: SlabMatchResult | null = null;
|
||||
validationWarning: string | null = null;
|
||||
|
||||
// ── FD Type labels ──────────────────────────────────────────────────────
|
||||
readonly fdTypeLabels: Record<string, string> = {
|
||||
SINGLE: 'FD Single (Days < 1 Year)',
|
||||
DOUBLE: 'FD Double (≥ 1 Year)',
|
||||
SPECIAL: 'FD Special (Monthly Interest Payout)'
|
||||
};
|
||||
|
||||
get fdType(): string {
|
||||
return this.fixedDepositsAccountProductTemplate?.fdType || '';
|
||||
}
|
||||
|
||||
get fdTypeLabel(): string {
|
||||
return this.fdTypeLabels[this.fdType] || this.fdType;
|
||||
}
|
||||
|
||||
/** Period unit options driven by fdType */
|
||||
get allowedPeriodUnits(): FdPeriodUnit[] {
|
||||
if (this.fdType === 'SINGLE') return ['Days'];
|
||||
if (this.fdType === 'DOUBLE') return [
|
||||
'Months',
|
||||
'Years'
|
||||
];
|
||||
return [
|
||||
'Days',
|
||||
'Months',
|
||||
'Years'
|
||||
];
|
||||
}
|
||||
|
||||
get isSpecial(): boolean {
|
||||
return this.fdType === 'SPECIAL';
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.fixedDepositsAccountProductTemplate) {
|
||||
this.interestRateChartData = this.fixedDepositsAccountProductTemplate.accountChart.chartSlabs;
|
||||
this.chartsTableRef.renderRows();
|
||||
this.interestRateChartData = 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];
|
||||
}
|
||||
}
|
||||
|
||||
// Convert entered tenure to Days for slab matching
|
||||
private toDays(value: number, unit: FdPeriodUnit): number {
|
||||
if (unit === 'Days') return value;
|
||||
if (unit === 'Months') return value * 30;
|
||||
return value * 365;
|
||||
}
|
||||
|
||||
// Convert slab period (which is stored in its own periodType) to days
|
||||
private slabPeriodToDays(period: number, periodTypeValue: string): number {
|
||||
const p = (periodTypeValue || '').toLowerCase();
|
||||
if (p.includes('month')) return period * 30;
|
||||
if (p.includes('year')) return period * 365;
|
||||
return period; // days
|
||||
}
|
||||
|
||||
calculateRate() {
|
||||
this.validationWarning = null;
|
||||
this.matchResult = null;
|
||||
|
||||
if (!this.tenureValue || this.tenureValue <= 0) {
|
||||
this.validationWarning = 'Please enter a valid tenure value greater than 0.';
|
||||
return;
|
||||
}
|
||||
|
||||
// FD Type specific validation
|
||||
const enteredDays = this.toDays(this.tenureValue, this.tenurePeriodUnit);
|
||||
|
||||
if (this.fdType === 'SINGLE' && enteredDays >= 365) {
|
||||
this.validationWarning =
|
||||
'FD Single allows tenure less than 1 year (max 364 days). Please enter a shorter tenure.';
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.fdType === 'DOUBLE' && enteredDays < 365) {
|
||||
this.validationWarning =
|
||||
'FD Double requires tenure of at least 1 year (minimum 12 months). Please enter a longer tenure.';
|
||||
return;
|
||||
}
|
||||
|
||||
const slabs: any[] = this.interestRateChartData || [];
|
||||
if (!slabs.length) {
|
||||
this.validationWarning = 'No interest rate slabs are defined for this product.';
|
||||
return;
|
||||
}
|
||||
|
||||
let matchedSlab: any = null;
|
||||
let matchedIndex = -1;
|
||||
|
||||
for (let i = 0; i < slabs.length; i++) {
|
||||
const slab = slabs[i];
|
||||
const periodTypeValue = slab.periodType?.value || 'Days';
|
||||
const fromDays = this.slabPeriodToDays(slab.fromPeriod ?? 0, periodTypeValue);
|
||||
const toDays = slab.toPeriod != null ? this.slabPeriodToDays(slab.toPeriod, periodTypeValue) : Infinity;
|
||||
|
||||
if (enteredDays >= fromDays && enteredDays <= toDays) {
|
||||
matchedSlab = slab;
|
||||
matchedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchedSlab) {
|
||||
this.validationWarning =
|
||||
'No applicable interest rate slab found for the entered tenure. Please check the rate chart below.';
|
||||
return;
|
||||
}
|
||||
|
||||
const rate = matchedSlab.annualInterestRate ?? 0;
|
||||
const result: SlabMatchResult = {
|
||||
slab: matchedSlab,
|
||||
slabIndex: matchedIndex,
|
||||
appliedRate: rate
|
||||
};
|
||||
|
||||
if (this.isSpecial && this.principalAmount && this.principalAmount > 0) {
|
||||
result.estimatedMonthlyPayout = (this.principalAmount * rate) / 1200;
|
||||
}
|
||||
|
||||
this.matchResult = result;
|
||||
|
||||
// Emit selected tenure to parent for backend submission
|
||||
const frequencyId = this.resolveFrequencyId(this.tenurePeriodUnit);
|
||||
if (frequencyId !== null) {
|
||||
this.tenureSelected.emit({ depositPeriod: this.tenureValue, depositPeriodFrequencyId: frequencyId });
|
||||
}
|
||||
}
|
||||
|
||||
private resolveFrequencyId(unit: FdPeriodUnit): number | null {
|
||||
const options: any[] = this.fixedDepositsAccountProductTemplate?.periodFrequencyTypeOptions ?? [];
|
||||
const unitLower = unit.toLowerCase();
|
||||
const match = options.find((o: any) => (o.value || '').toLowerCase().includes(unitLower));
|
||||
if (match) return match.id;
|
||||
// Fineract standard fallback: Days=0, Months=2, Years=3
|
||||
const fallback: Record<FdPeriodUnit, number> = { Days: 0, Months: 2, Years: 3 };
|
||||
return fallback[unit] ?? null;
|
||||
}
|
||||
|
||||
resetCalculator() {
|
||||
this.tenureValue = null;
|
||||
this.principalAmount = null;
|
||||
this.matchResult = null;
|
||||
this.validationWarning = null;
|
||||
}
|
||||
|
||||
onPeriodUnitChange() {
|
||||
this.matchResult = null;
|
||||
this.validationWarning = null;
|
||||
}
|
||||
|
||||
isMatchedSlab(index: number): boolean {
|
||||
return this.matchResult?.slabIndex === index;
|
||||
}
|
||||
|
||||
get chartName(): string {
|
||||
return this.fixedDepositsAccountProductTemplate?.accountChart?.name || '';
|
||||
}
|
||||
|
||||
get chartFromDate(): any {
|
||||
return this.fixedDepositsAccountProductTemplate?.accountChart?.fromDate;
|
||||
}
|
||||
|
||||
get chartEndDate(): any {
|
||||
return this.fixedDepositsAccountProductTemplate?.accountChart?.endDate;
|
||||
}
|
||||
|
||||
get chartDescription(): string {
|
||||
return this.fixedDepositsAccountProductTemplate?.accountChart?.description || '';
|
||||
}
|
||||
|
||||
get isPrimaryGroupingByAmount(): boolean {
|
||||
return !!this.fixedDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount;
|
||||
}
|
||||
}
|
||||
|
||||
+565
-394
@@ -6,416 +6,587 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="layout-row-wrap responsive-column">
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Details' | translate }}</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Product' | translate }}</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositAccountData.productId | find: fixedDepositsAccountTemplate.productOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Submitted On' | translate }}</span>
|
||||
<span class="flex-60">{{ fixedDepositAccountData.submittedOnDate | dateFormat }}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositAccountData.externalId">
|
||||
<span class="flex-40">{{ 'labels.inputs.External Id' | translate }}</span>
|
||||
<span class="flex-60">{{ fixedDepositAccountData.externalId }}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Field Officer' | translate }}</span>
|
||||
<span class="flex-60">{{
|
||||
(fixedDepositAccountData.fieldOfficerId
|
||||
| find: fixedDepositsAccountProductTemplate.fieldOfficerOptions : 'id' : 'displayName') || 'Unassigned'
|
||||
| translateKey: 'inputs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Currency' | translate }}</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Currency' | translate }}</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate.currency.code }}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Currency Multiple' | translate }}</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate.currency.inMultiplesOf }}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Decimal Places' | translate }}</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate.currency.decimalPlaces }}</span>
|
||||
</div>
|
||||
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Terms' | translate }}</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Fixed Deposit Amount' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositAccountData.depositAmount
|
||||
| currency: fixedDepositsAccountProductTemplate.currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Deposit Period' | translate }}:</span>
|
||||
<span class="flex-60"
|
||||
>{{ fixedDepositAccountData.depositPeriod }}
|
||||
{{
|
||||
fixedDepositAccountData.depositPeriodFrequencyId
|
||||
| find: fixedDepositsAccountProductTemplate.periodFrequencyTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Interest Compounding Period' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositAccountData.interestCompoundingPeriodType
|
||||
| find: fixedDepositsAccountProductTemplate.interestCompoundingPeriodTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Interest Posting Period' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositAccountData.interestPostingPeriodType
|
||||
| find: fixedDepositsAccountProductTemplate.interestPostingPeriodTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Interest Calculated using' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositAccountData.interestCalculationType
|
||||
| find: fixedDepositsAccountProductTemplate.interestCalculationTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Days in Year' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositAccountData.interestCalculationDaysInYearType
|
||||
| find: fixedDepositsAccountProductTemplate.interestCalculationDaysInYearTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Settings' | translate }}</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositAccountData.lockinPeriodFrequency">
|
||||
<span class="flex-40">{{ 'labels.inputs.Lock-in Period' | translate }}:</span>
|
||||
<span class="flex-60"
|
||||
>{{ fixedDepositAccountData.lockinPeriodFrequency }}
|
||||
{{
|
||||
fixedDepositAccountData.lockinPeriodFrequencyType
|
||||
| find: fixedDepositsAccountProductTemplate.lockinPeriodFrequencyTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate.minDepositTerm">
|
||||
<span class="flex-40">{{ 'labels.inputs.Minimum Deposit Term' | translate }}:</span>
|
||||
<span class="flex-60"
|
||||
>{{ fixedDepositsAccountProductTemplate.minDepositTerm }}
|
||||
{{ fixedDepositsAccountProductTemplate.minDepositTermType?.value | translateKey: 'catalogs' }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate.inMultiplesOfDepositTerm">
|
||||
<span class="flex-40">{{ 'labels.inputs.In Multiples Of' | translate }}:</span>
|
||||
<span class="flex-60"
|
||||
>{{ fixedDepositsAccountProductTemplate.inMultiplesOfDepositTerm }}
|
||||
{{ fixedDepositsAccountProductTemplate.inMultiplesOfDepositTermType?.value | translateKey: 'catalogs' }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate.maxDepositTerm">
|
||||
<span class="flex-40">{{ 'labels.inputs.Maximum Deposit Term' | translate }}</span>
|
||||
<span class="flex-60"
|
||||
>{{ fixedDepositsAccountProductTemplate.maxDepositTerm }}
|
||||
{{ fixedDepositsAccountProductTemplate.maxDepositTermType?.value | translateKey: 'catalogs' }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Transfer Interest to Savings Account' | translate }}?</span>
|
||||
<span class="flex-60">{{ fixedDepositAccountData.transferInterestToSavings | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositAccountData.transferInterestToSavings">
|
||||
<span class="flex-40">{{ 'labels.inputs.Linked Savings Account' | translate }}</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositAccountData.linkAccountId
|
||||
| find: fixedDepositsAccountProductTemplate.savingsAccounts : 'id' : 'accountNo'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositAccountData.maturityInstructionId">
|
||||
<span class="flex-40">{{ 'labels.inputs.Maturity Instructions' | translate }}</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositAccountData.maturityInstructionId
|
||||
| find: fixedDepositsAccountProductTemplate.maturityInstructionOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate.preClosurePenalApplicable | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate.preClosurePenalApplicable">
|
||||
<span class="flex-40">{{ 'labels.inputs.Penal Interest' | translate }} (%):</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositsAccountProductTemplate.preClosurePenalInterest +
|
||||
'% (' +
|
||||
fixedDepositsAccountProductTemplate.preClosurePenalInterestOnType?.id +
|
||||
')'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate.withHoldTax">
|
||||
<span class="flex-40">{{ 'labels.inputs.Withhold Tax is Applicable' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositAccountData.withHoldTax | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Interest Rate Chart' | translate }}</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.name">
|
||||
<span class="flex-40">{{ 'labels.inputs.name' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Valid from Date' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.fromDate | dateFormat }}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.endDate">
|
||||
<span class="flex-40">{{ 'labels.inputs.End Date' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.endDate | dateFormat }}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100" *ngIf="fixedDepositsAccountProductTemplate?.accountChart.description">
|
||||
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositsAccountProductTemplate?.accountChart.description }}</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-row flex-100">
|
||||
<span class="flex-40">{{ 'labels.inputs.Primary Grouping by Amount' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositsAccountProductTemplate?.accountChart.isPrimaryGroupingByAmount | yesNo
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<table class="mat-elevation-z1 irc-table" mat-table [dataSource]="interestRateChartData" multiTemplateDataRows>
|
||||
<ng-container matColumnDef="period">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
{{ chartSlab.fromPeriod }} - {{ chartSlab.toPeriod }} {{
|
||||
chartSlab.periodType.value | translateKey: 'catalogs'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="amountRange">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount Range' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
{{ chartSlab.amountRangeFrom | formatNumber }} - {{ chartSlab.amountRangeTo | formatNumber }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="interest">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.annualInterestRate | formatNumber }} %</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Description' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.description }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
(click)="expandChartSlabIndex = expandChartSlabIndex === chartSlabIndex ? null : chartSlabIndex"
|
||||
>
|
||||
<span *ngIf="expandChartSlabIndex !== chartSlabIndex">
|
||||
<fa-icon icon="eye" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.View Incentives' | translate }}
|
||||
</span>
|
||||
<span *ngIf="expandChartSlabIndex === chartSlabIndex">
|
||||
<fa-icon icon="eye-slash" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Hide Incentives' | translate }}
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="incentives">
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
|
||||
[attr.colspan]="chartSlabsDisplayedColumns.length"
|
||||
>
|
||||
<div
|
||||
class="layout-row-wrap flex-100 incentives"
|
||||
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
||||
>
|
||||
<mat-card class="layout-row-wrap flex-100">
|
||||
<h4 class="m-b-10 flex-13">
|
||||
{{ 'labels.heading.Incentives' | translate }}
|
||||
</h4>
|
||||
|
||||
<table
|
||||
class="flex-100 mat-elevation-z1"
|
||||
mat-table
|
||||
[dataSource]="chartSlab.incentives"
|
||||
*ngIf="chartSlab.incentives.length"
|
||||
>
|
||||
<ng-container matColumnDef="entityType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.entityType.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="attributeName">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.attributeName.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="conditionType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.conditionType.value | titlecase }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="attributeValue">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive" [ngSwitch]="incentive.attributeName">
|
||||
{{ incentive.attributeValueDesc }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="incentiveType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.incentiveType.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.amount | formatNumber }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
||||
</table>
|
||||
</mat-card>
|
||||
<div class="preview-wrapper">
|
||||
<!-- ══ 1. Account Details ════════════════════════════════════════ -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-info-circle preview-section__icon"></i>
|
||||
<span class="preview-section__title">Account Details</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Product</span>
|
||||
<span class="preview-value">{{
|
||||
fixedDepositAccountData?.productId | find: fixedDepositsAccountTemplate?.productOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Submitted On</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData?.submittedOnDate | dateFormat }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Field Officer</span>
|
||||
<span class="preview-value">{{
|
||||
(fixedDepositAccountData?.fieldOfficerId
|
||||
| find: fixedDepositsAccountProductTemplate?.fieldOfficerOptions : 'id' : 'displayName') || 'Unassigned'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">External ID</span>
|
||||
<span class="preview-value" [class.preview-muted]="!fixedDepositAccountData?.externalId">
|
||||
{{ fixedDepositAccountData?.externalId || 'Unassigned' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chartSlabsDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns" class="incentives-row"></tr>
|
||||
</table>
|
||||
<!-- ══ 1a. Product Classification ════════════════════════════════ -->
|
||||
@if (fixedDepositsAccountProductTemplate) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-tags preview-section__icon"></i>
|
||||
<span class="preview-section__title">Product Classification</span>
|
||||
<span class="preview-section__badge preview-section__badge--active" style="margin-left: auto"
|
||||
>From Product</span
|
||||
>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@if (fixedDepositAccountData?.accountType) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Account Type</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData.accountType }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (fixedDepositAccountData?.customerSegment) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Customer Segment</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData.customerSegment }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (fixedDepositsAccountProductTemplate?.fdType) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">FD Type</span>
|
||||
<span class="preview-value">{{ getFdTypeLabel(fixedDepositsAccountProductTemplate.fdType) }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Account No</span>
|
||||
<span class="preview-value preview-mono">
|
||||
{{
|
||||
fixedDepositsAccountProductTemplate?.previewAccountNo ||
|
||||
fixedDepositsAccountTemplate?.accountNo ||
|
||||
'Auto-generated'
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
@if (fixedDepositsAccountProductTemplate?.effectiveDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Effective To</span>
|
||||
<span class="preview-value">{{ fixedDepositsAccountProductTemplate.effectiveDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (fixedDepositsAccountProductTemplate?.endDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">End Date</span>
|
||||
<span class="preview-value">{{ fixedDepositsAccountProductTemplate.endDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Share Linking Enabled</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="fixedDepositsAccountProductTemplate?.shareLinkingEnabled"
|
||||
>
|
||||
{{ fixedDepositsAccountProductTemplate?.shareLinkingEnabled ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
@if (fixedDepositsAccountProductTemplate?.introducerRequired !== undefined) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Introducer Required</span>
|
||||
<span
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="fixedDepositsAccountProductTemplate?.introducerRequired"
|
||||
>
|
||||
{{ fixedDepositsAccountProductTemplate?.introducerRequired ? 'Yes' : 'No' }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div *ngIf="fixedDepositAccountData.charges.length" class="layout-row-wrap responsive-column flex-100">
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Charges' | translate }}</h3>
|
||||
<!-- ══ 1b. Status of Account (RBI) ══════════════════════════════ -->
|
||||
@if (fixedDepositAccountData?.accountStatus) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-id-card preview-section__icon"></i>
|
||||
<span class="preview-section__title">Status of Account</span>
|
||||
<span class="preview-section__badge">RBI Guidelines</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-soa-status-row">
|
||||
<span class="preview-soa-tile">
|
||||
@switch (fixedDepositAccountData.accountStatus) {
|
||||
@case ('INDIVIDUAL_MALE') {
|
||||
<i class="fas fa-male"></i> Individual (Male)
|
||||
}
|
||||
@case ('INDIVIDUAL_FEMALE') {
|
||||
<i class="fas fa-female"></i> Individual (Female)
|
||||
}
|
||||
@case ('SINGLE') {
|
||||
<i class="fas fa-user-circle"></i> Single Account
|
||||
}
|
||||
@case ('JOINT') {
|
||||
<i class="fas fa-users"></i> Joint Account
|
||||
}
|
||||
@case ('MINOR') {
|
||||
<i class="fas fa-child"></i> Minor Account
|
||||
}
|
||||
@case ('SENIOR_CITIZEN') {
|
||||
<i class="fas fa-user-clock"></i> Senior Citizen
|
||||
}
|
||||
@default {
|
||||
{{ fixedDepositAccountData.accountStatus }}
|
||||
}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="fixedDepositAccountData.charges">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.name + ', ' + charge.currency.displaySymbol }}
|
||||
</td>
|
||||
</ng-container>
|
||||
@if (fixedDepositAccountData.accountStatus === 'JOINT') {
|
||||
<div class="preview-grid preview-grid--three" style="margin-top: 12px">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Mode of Operation</span>
|
||||
<span class="preview-value">
|
||||
@switch (fixedDepositAccountData.modeOfOperation) {
|
||||
@case ('EITHER_OR_SURVIVOR') {
|
||||
Either or Survivor
|
||||
}
|
||||
@case ('FORMER_OR_SURVIVOR') {
|
||||
Former or Survivor
|
||||
}
|
||||
@case ('JOINTLY') {
|
||||
Jointly
|
||||
}
|
||||
@case ('ANYONE_OR_SURVIVOR') {
|
||||
Anyone or Survivor
|
||||
}
|
||||
@default {
|
||||
{{ fixedDepositAccountData.modeOfOperation || '—' }}
|
||||
}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (fixedDepositAccountData.jointHolders?.length) {
|
||||
<div class="preview-soa-table-wrap">
|
||||
<table class="preview-soa-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="preview-soa-th">Sr.</th>
|
||||
<th class="preview-soa-th">Holder Name</th>
|
||||
<th class="preview-soa-th">CIF No.</th>
|
||||
<th class="preview-soa-th">Ownership %</th>
|
||||
<th class="preview-soa-th">Signing</th>
|
||||
<th class="preview-soa-th">Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (holder of fixedDepositAccountData.jointHolders; track holder.clientId; let i = $index) {
|
||||
<tr [class.preview-soa-tr--primary]="holder.isPrimary">
|
||||
<td class="preview-soa-td">{{ i + 1 }}</td>
|
||||
<td class="preview-soa-td">
|
||||
{{ holder.displayName || holder.clientName || '—' }}
|
||||
@if (holder.isPrimary) {
|
||||
<span class="preview-soa-primary-tag">Primary</span>
|
||||
}
|
||||
</td>
|
||||
<td class="preview-soa-td preview-mono">{{ holder.cifNumber || holder.clientCif || '—' }}</td>
|
||||
<td class="preview-soa-td">{{ holder.sharePercentage ?? '—' }}%</td>
|
||||
<td class="preview-soa-td">{{ holder.isMandatory ? 'Mandatory' : 'Optional' }}</td>
|
||||
<td class="preview-soa-td">{{ holder.isPrimary ? 'Primary Account Holder' : 'Co-Holder' }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<ng-container matColumnDef="chargeCalculationType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.chargeCalculationType.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
@if (fixedDepositAccountData.accountStatus === 'INDIVIDUAL_MALE' && fixedDepositAccountData.specialCategory) {
|
||||
<div class="preview-grid preview-grid--three" style="margin-top: 12px">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Special Category</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData.specialCategory }}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.amount }}
|
||||
</td>
|
||||
</ng-container>
|
||||
@if (fixedDepositAccountData.accountStatus === 'INDIVIDUAL_FEMALE' && fixedDepositAccountData.weakerSection) {
|
||||
<div class="preview-grid preview-grid--three" style="margin-top: 12px">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Weaker Section</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData.weakerSection }}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<ng-container matColumnDef="date">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Date' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
@if (fixedDepositAccountData.accountStatus === 'MINOR') {
|
||||
<div class="preview-grid preview-grid--three" style="margin-top: 12px">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Guardian Name</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData.guardianName || '—' }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Guardian CIF</span>
|
||||
<span class="preview-value preview-mono">{{ fixedDepositAccountData.guardianCifNumber || '—' }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Relationship</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData.guardianRelationship || '—' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 1c. Linked Account ════════════════════════════════════════ -->
|
||||
@if (fixedDepositAccountData?.linkedAccountId) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-link preview-section__icon"></i>
|
||||
<span class="preview-section__title">Linked Deposit Account</span>
|
||||
<span class="preview-section__badge">{{ fixedDepositAccountData.linkedAccountType }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Account No</span>
|
||||
<span class="preview-value preview-mono">{{ fixedDepositAccountData.linkedAccountNo }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Account Type</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData.linkedAccountType }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Account ID</span>
|
||||
<span class="preview-value preview-mono">{{ fixedDepositAccountData.linkedAccountId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 2. Currency & Configuration ══════════════════════════════ -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-coins preview-section__icon"></i>
|
||||
<span class="preview-section__title">{{ 'labels.heading.Currency' | translate }}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Currency' | translate }}</span>
|
||||
<span class="preview-value">{{ currencyName }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Decimal Places' | translate }}</span>
|
||||
<span class="preview-value">{{ decimalPlaces }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Currency In Multiples Of' | translate }}</span>
|
||||
<span class="preview-value">{{ inMultiplesOf }}</span>
|
||||
</div>
|
||||
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Auto Debit Facility</span>
|
||||
<span
|
||||
*ngIf="charge.chargeTimeType.value === 'Specified due date' || charge.chargeTimeType.value === 'Weekly Fee'"
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="fixedDepositAccountData?.autoDebitFacility"
|
||||
>
|
||||
{{ (charge.dueDate | dateFormat) || 'Unassigned' }}
|
||||
</span>
|
||||
<span *ngIf="charge.chargeTimeType.value === 'Monthly Fee' || charge.chargeTimeType.value === 'Annual Fee'">
|
||||
{{ charge.feeOnMonthDay ? ([2000].concat(charge.feeOnMonthDay) | dateFormat: 'dd MMMM') : 'Unassigned' }}
|
||||
{{ fixedDepositAccountData?.autoDebitFacility | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ 2b. Dormancy Monitoring ═══════════════════════════════════ -->
|
||||
@if (fixedDepositsAccountProductTemplate?.dormancyAlert) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-moon preview-section__icon"></i>
|
||||
<span class="preview-section__title">Dormancy Monitoring</span>
|
||||
<span class="preview-section__badge preview-section__badge--active">Monitoring: Active</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-dormancy-timeline">
|
||||
<div class="preview-dorm-step">
|
||||
<div class="preview-dorm-step__dot preview-dorm-step__dot--inactive"></div>
|
||||
<div class="preview-dorm-step__body">
|
||||
<span class="preview-dorm-step__label">Inactive</span>
|
||||
<span class="preview-dorm-step__days">
|
||||
After {{ fixedDepositsAccountProductTemplate?.daysToInactiveFromProduct || 30 }} days of no transaction
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-dorm-connector"></div>
|
||||
<div class="preview-dorm-step">
|
||||
<div class="preview-dorm-step__dot preview-dorm-step__dot--dormant"></div>
|
||||
<div class="preview-dorm-step__body">
|
||||
<span class="preview-dorm-step__label">Dormant</span>
|
||||
<span class="preview-dorm-step__days">
|
||||
{{ fixedDepositsAccountProductTemplate?.daysToDormancyFromProduct || 60 }} days after inactive
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-dorm-connector"></div>
|
||||
<div class="preview-dorm-step">
|
||||
<div class="preview-dorm-step__dot preview-dorm-step__dot--escheat"></div>
|
||||
<div class="preview-dorm-step__body">
|
||||
<span class="preview-dorm-step__label">Escheat</span>
|
||||
<span class="preview-dorm-step__days">
|
||||
{{ fixedDepositsAccountProductTemplate?.daysToEscheatFromProduct || 90 }} days after dormant
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 2c. Nominee Details ══════════════════════════════════════ -->
|
||||
@if (fixedDepositAccountData?.nominee) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-user-shield preview-section__icon"></i>
|
||||
<span class="preview-section__title">Nominee Details</span>
|
||||
<span class="preview-section__badge">{{
|
||||
fixedDepositAccountData.nominee.mode === 'linked' ? 'Linked Client' : 'Manual Entry'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Full Name</span>
|
||||
<span class="preview-value">{{
|
||||
fixedDepositAccountData.nominee.fullName || fixedDepositAccountData.nominee.displayName
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Relationship</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData.nominee.relationship }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Nominee Share (%)</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData.nominee.nomineeShare }}%</span>
|
||||
</div>
|
||||
@if (fixedDepositAccountData.nominee.dateOfBirth) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Date of Birth</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData.nominee.dateOfBirth | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (fixedDepositAccountData.nominee.nationality) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Nationality</span>
|
||||
<span class="preview-value">{{ fixedDepositAccountData.nominee.nationality }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (fixedDepositAccountData.nominee.mobileNo) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Mobile Number</span>
|
||||
<span class="preview-value preview-mono">{{ fixedDepositAccountData.nominee.mobileNo }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 5. Interest Rate Chart ═══════════════════════════════════ -->
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-chart-line preview-section__icon"></i>
|
||||
<span class="preview-section__title">{{ 'labels.heading.Interest Rate Chart' | translate }}</span>
|
||||
@if (selectedTenure) {
|
||||
<span class="preview-section__badge preview-section__badge--tenure">
|
||||
<i class="fas fa-calendar-check"></i> Selected Tenure: {{ selectedTenureLabel }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<!-- Selected Tenure + Rate Banner -->
|
||||
@if (selectedTenure && matchedSlabIndex >= 0) {
|
||||
<div class="preview-tenure-banner">
|
||||
<div class="preview-tenure-banner__left">
|
||||
<i class="fas fa-calendar-check"></i>
|
||||
<div>
|
||||
<span class="preview-tenure-banner__label">Your FD Tenure</span>
|
||||
<span class="preview-tenure-banner__value">{{ selectedTenureLabel }}</span>
|
||||
</div>
|
||||
</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
|
||||
>
|
||||
</div>
|
||||
<div class="preview-tenure-banner__slab">
|
||||
<i class="fas fa-layer-group"></i>
|
||||
Slab: {{ interestRateChartData[matchedSlabIndex]?.fromPeriod }}–{{
|
||||
interestRateChartData[matchedSlabIndex]?.toPeriod ?? '∞'
|
||||
}}
|
||||
{{ interestRateChartData[matchedSlabIndex]?.periodType?.value }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="preview-grid preview-grid--three" style="margin-bottom: 1rem">
|
||||
@if (fixedDepositsAccountProductTemplate?.accountChart?.name) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Name</span>
|
||||
<span class="preview-value">{{ fixedDepositsAccountProductTemplate.accountChart.name }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Valid From</span>
|
||||
<span class="preview-value">{{
|
||||
fixedDepositsAccountProductTemplate?.accountChart?.fromDate | dateFormat
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Primary Grouping by Amount</span>
|
||||
<span
|
||||
*ngIf="
|
||||
!(
|
||||
charge.chargeTimeType.value === 'Monthly Fee' ||
|
||||
charge.chargeTimeType.value === 'Annual Fee' ||
|
||||
charge.chargeTimeType.value === 'Specified due date' ||
|
||||
charge.chargeTimeType.value === 'Weekly Fee'
|
||||
)
|
||||
"
|
||||
class="preview-value preview-badge"
|
||||
[class.preview-badge--yes]="fixedDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount"
|
||||
>
|
||||
{{ 'labels.inputs.N/A' | translate }}
|
||||
{{ fixedDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount | yesNo }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container matColumnDef="repaymentsEvery">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Repayments Every' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.feeInterval || 'Not Provided' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<table
|
||||
class="mat-elevation-z1 irc-table preview-table"
|
||||
mat-table
|
||||
[dataSource]="interestRateChartData"
|
||||
multiTemplateDataRows
|
||||
>
|
||||
<ng-container matColumnDef="period">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
{{ chartSlab.fromPeriod }} - {{ chartSlab.toPeriod }} {{
|
||||
chartSlab.periodType?.value | translateKey: 'catalogs'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="chargeTimeType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.chargeTimeType.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amountRange">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount Range' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
{{ chartSlab.amountRangeFrom | formatNumber }} - {{ chartSlab.amountRangeTo | formatNumber }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="chargesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chargesDisplayedColumns"></tr>
|
||||
</table>
|
||||
<ng-container matColumnDef="interest">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.annualInterestRate | formatNumber }} %</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Description' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.description }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
(click)="expandChartSlabIndex = expandChartSlabIndex === chartSlabIndex ? null : chartSlabIndex"
|
||||
>
|
||||
<fa-icon [icon]="expandChartSlabIndex === chartSlabIndex ? 'eye-slash' : 'eye'" class="m-r-10"></fa-icon>
|
||||
{{
|
||||
(expandChartSlabIndex === chartSlabIndex
|
||||
? 'labels.buttons.Hide Incentives'
|
||||
: 'labels.buttons.View Incentives'
|
||||
) | translate
|
||||
}}
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="incentives">
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
|
||||
[attr.colspan]="chartSlabsDisplayedColumns?.length"
|
||||
>
|
||||
<div
|
||||
class="layout-row-wrap flex-100 incentives"
|
||||
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
||||
>
|
||||
<mat-card class="layout-row-wrap flex-100">
|
||||
<h4 class="m-b-10 flex-13">{{ 'labels.heading.Incentives' | translate }}</h4>
|
||||
<table
|
||||
class="flex-100 mat-elevation-z1"
|
||||
mat-table
|
||||
[dataSource]="chartSlab.incentives"
|
||||
*ngIf="chartSlab.incentives?.length"
|
||||
>
|
||||
<ng-container matColumnDef="entityType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.entityType?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeName">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.attributeName?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="conditionType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.conditionType?.value | titlecase }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeValue">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.attributeValueDesc }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incentiveType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.incentiveType?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.amount | formatNumber }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
||||
</table>
|
||||
</mat-card>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: chartSlabsDisplayedColumns; let i = dataIndex"
|
||||
[class.preview-slab-row--matched]="i === matchedSlabIndex"
|
||||
></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns" class="incentives-row"></tr>
|
||||
</table>
|
||||
@if (selectedTenure && matchedSlabIndex >= 0) {
|
||||
<div class="preview-slab-legend">
|
||||
<span class="preview-slab-legend__dot"></span>
|
||||
Highlighted row is the slab applicable for your selected tenure of <strong>{{ selectedTenureLabel }}</strong>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+477
-16
@@ -6,35 +6,277 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
// ── Wrapper ──────────────────────────────────────────────────────────────────
|
||||
.preview-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.mat-elevation-z1 {
|
||||
margin: 1em 0 1.5em;
|
||||
// ── Section card ─────────────────────────────────────────────────────────────
|
||||
.preview-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 5%);
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
padding: 0.6rem 1rem;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 13px;
|
||||
color: #0ea5e9;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__title {
|
||||
flex: 1;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
&__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
border: 1px solid #bfdbfe;
|
||||
text-transform: uppercase;
|
||||
|
||||
&--active {
|
||||
background: #dcfce7 !important;
|
||||
color: #15803d !important;
|
||||
border-color: #bbf7d0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin: 0;
|
||||
// ── Body ─────────────────────────────────────────────────────────────────────
|
||||
.preview-body {
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
// ── Subsection (inside Settings) ─────────────────────────────────────────────
|
||||
.preview-subsection {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&__title {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #666;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-divider {
|
||||
margin: 0.75rem 0 1rem;
|
||||
}
|
||||
|
||||
// ── Grid ─────────────────────────────────────────────────────────────────────
|
||||
.preview-grid {
|
||||
display: grid;
|
||||
gap: 0.85rem 1.5rem;
|
||||
|
||||
&--three {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
|
||||
@media (width <= 768px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (width <= 480px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Field ─────────────────────────────────────────────────────────────────────
|
||||
.preview-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.preview-label {
|
||||
font-size: 0.68rem;
|
||||
color: #94a3b8;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.preview-value {
|
||||
font-size: 0.9rem;
|
||||
color: #1e293b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
span {
|
||||
margin: 0.5em 0;
|
||||
// ── Yes/No badge ─────────────────────────────────────────────────────────────
|
||||
.preview-badge {
|
||||
display: inline-block;
|
||||
padding: 0.15rem 0.6rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
width: fit-content;
|
||||
|
||||
&--yes {
|
||||
background: #f0fdf4;
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
&--warn {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
}
|
||||
|
||||
mat-divider {
|
||||
margin: 0 0 0.5em;
|
||||
// ── Section badge variant for tenure ─────────────────────────────────────────
|
||||
.preview-section__badge--tenure {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.margin-t {
|
||||
margin-top: 1em;
|
||||
// ── Tenure + Rate banner ──────────────────────────────────────────────────────
|
||||
.preview-tenure-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding: 14px 18px;
|
||||
background: #f0fdf4;
|
||||
border: 1.5px solid #86efac;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
> i {
|
||||
font-size: 20px;
|
||||
color: #16a34a;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
&__value {
|
||||
display: block;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: #0f172a;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
&__divider {
|
||||
width: 1px;
|
||||
height: 40px;
|
||||
background: #86efac;
|
||||
}
|
||||
|
||||
&__right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__rate-label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
&__rate {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: #16a34a;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
&__slab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: #374151;
|
||||
margin-left: auto;
|
||||
|
||||
i {
|
||||
color: #16a34a;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.margin-b {
|
||||
margin-bottom: 0.25em;
|
||||
// ── Matched slab row highlight ────────────────────────────────────────────────
|
||||
.preview-slab-row--matched td {
|
||||
background: #dcfce7 !important;
|
||||
border-left: 3px solid #16a34a;
|
||||
}
|
||||
|
||||
// ── Legend below table ────────────────────────────────────────────────────────
|
||||
.preview-slab-legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #4b5563;
|
||||
|
||||
&__dot {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
background: #dcfce7;
|
||||
border: 2px solid #16a34a;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tables ───────────────────────────────────────────────────────────────────
|
||||
.preview-table {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.irc-table {
|
||||
@@ -56,3 +298,222 @@ mat-divider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.margin-t {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
// ── Preview Tenure Result Card ─────────────────────────────────────────────────
|
||||
.preview-tenure-result {
|
||||
border: 1px solid #e0f2fe;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.65rem 1rem;
|
||||
border-bottom: 1px solid #f0f9ff;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&--highlight {
|
||||
background: #f0f9ff;
|
||||
}
|
||||
}
|
||||
|
||||
&__rate {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
color: #0369a1;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Misc ──────────────────────────────────────────────────────────────────────
|
||||
.preview-muted {
|
||||
color: #94a3b8;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.preview-mono {
|
||||
font-family: monospace;
|
||||
font-size: 0.88rem;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
// ── Status of Account (SOA) ───────────────────────────────────────────────────
|
||||
.preview-soa-status-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.preview-soa-tile {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 999px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
border: 2px solid #bfdbfe;
|
||||
|
||||
i {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-soa-table-wrap {
|
||||
margin-top: 14px;
|
||||
overflow-x: auto;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.preview-soa-tbl {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.preview-soa-th {
|
||||
background: #f8fafc;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.preview-soa-td {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
color: #0f172a;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.preview-soa-tr--primary {
|
||||
background: #f0f9ff;
|
||||
|
||||
.preview-soa-td {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-soa-primary-tag {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
padding: 1px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
border: 1px solid #bfdbfe;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// ── Dormancy Monitoring Timeline ──────────────────────────────────────────────
|
||||
.preview-dormancy-timeline {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin: 1.25rem 0;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.preview-dorm-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 0 0 140px;
|
||||
gap: 10px;
|
||||
|
||||
&__dot {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 3px solid #e2e8f0;
|
||||
background: #f8fafc;
|
||||
position: relative;
|
||||
box-shadow: 0 0 0 4px #fff;
|
||||
z-index: 2;
|
||||
|
||||
&--inactive {
|
||||
border-color: #f59e0b;
|
||||
background: #fffbeb;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: #f59e0b;
|
||||
}
|
||||
}
|
||||
|
||||
&--dormant {
|
||||
border-color: #ef4444;
|
||||
background: #fef2f2;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
&--escheat {
|
||||
border-color: #7c3aed;
|
||||
background: #f5f3ff;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: #7c3aed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
&__days {
|
||||
font-size: 10.5px;
|
||||
color: #64748b;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-dorm-connector {
|
||||
flex: 0 0 40px;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, #e2e8f0, #cbd5e1);
|
||||
margin-top: 16px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
+52
-1
@@ -76,6 +76,8 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
||||
@Input() fixedDepositsAccountProductTemplate: any;
|
||||
/** Fixed Deposits Account Data */
|
||||
@Input() fixedDepositAccountData: any;
|
||||
/** Tenure selected from the interest rate chart step */
|
||||
@Input() selectedTenure: { depositPeriod: number; depositPeriodFrequencyId: number } | null = null;
|
||||
|
||||
/** Charges Displayed Columns */
|
||||
chargesDisplayedColumns: string[] = [
|
||||
@@ -110,6 +112,28 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
||||
/** Expand Chart Slab Index used in the view */
|
||||
expandChartSlabIndex: number;
|
||||
|
||||
readonly fdTypeLabels: Record<string, string> = {
|
||||
SINGLE: 'FD Single (Days < 1 Year)',
|
||||
DOUBLE: 'FD Double (≥ 1 Year)',
|
||||
SPECIAL: 'FD Special (Monthly Interest Payout)'
|
||||
};
|
||||
|
||||
getFdTypeLabel(fdType: string): string {
|
||||
return this.fdTypeLabels[fdType] || fdType;
|
||||
}
|
||||
|
||||
get currencyName(): string {
|
||||
return this.fixedDepositsAccountProductTemplate?.currency?.name || '—';
|
||||
}
|
||||
|
||||
get decimalPlaces(): string | number {
|
||||
return this.fixedDepositsAccountProductTemplate?.currency?.decimalPlaces ?? '—';
|
||||
}
|
||||
|
||||
get inMultiplesOf(): string | number {
|
||||
return this.fixedDepositsAccountProductTemplate?.currency?.inMultiplesOf ?? '—';
|
||||
}
|
||||
|
||||
/** Form submission event */
|
||||
@Output() submitEvent = new EventEmitter();
|
||||
|
||||
@@ -117,7 +141,34 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.fixedDepositsAccountProductTemplate) {
|
||||
this.interestRateChartData = this.fixedDepositsAccountProductTemplate.accountChart.chartSlabs;
|
||||
this.interestRateChartData = this.fixedDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
get matchedSlabIndex(): number {
|
||||
if (!this.selectedTenure || !this.interestRateChartData?.length) return -1;
|
||||
const { depositPeriod, depositPeriodFrequencyId } = this.selectedTenure;
|
||||
// Convert selected tenure to days for comparison
|
||||
const toDays = (v: number, id: number) => {
|
||||
if (id === 2) return v * 30; // Months
|
||||
if (id === 3) return v * 365; // Years
|
||||
return v; // Days (id=0)
|
||||
};
|
||||
const enteredDays = toDays(depositPeriod, depositPeriodFrequencyId);
|
||||
|
||||
return this.interestRateChartData.findIndex((slab: any) => {
|
||||
const periodTypeValue = (slab.periodType?.value || '').toLowerCase();
|
||||
const slabId = periodTypeValue.includes('month') ? 2 : periodTypeValue.includes('year') ? 3 : 0;
|
||||
const fromDays = toDays(slab.fromPeriod ?? 0, slabId);
|
||||
const toDaysVal = slab.toPeriod != null ? toDays(slab.toPeriod, slabId) : Infinity;
|
||||
return enteredDays >= fromDays && enteredDays <= toDaysVal;
|
||||
});
|
||||
}
|
||||
|
||||
get selectedTenureLabel(): string {
|
||||
if (!this.selectedTenure) return '';
|
||||
const { depositPeriod, depositPeriodFrequencyId } = this.selectedTenure;
|
||||
const unitMap: Record<number, string> = { 0: 'Days', 2: 'Months', 3: 'Years' };
|
||||
return `${depositPeriod} ${unitMap[depositPeriodFrequencyId] ?? ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
// IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN!
|
||||
/* tslint:disable */
|
||||
export default {
|
||||
'mifos_x': {
|
||||
'version': '260504',
|
||||
'hash': 'e9d2f4b5'
|
||||
'version': '260507',
|
||||
'hash': 'b0cffd38'
|
||||
},
|
||||
'allow_switching_backend_instance': true
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user