saving account,current account and rd account modify based on new update

This commit is contained in:
Nitesh Yadav
2026-04-27 10:55:03 +05:30
parent 5fb25edca5
commit e4516934a7
36 changed files with 4302 additions and 663 deletions
@@ -98,6 +98,10 @@
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
[recurringDepositAccountData]="recurringDepositAccountData"
[rdInterestRates]="rdInterestRates"
[rdNominee]="rdNominee"
[rdCurrencyFlags]="rdCurrencyFlags"
[rdTenureDays]="rdTenureDays"
[rdApplicableSlab]="rdApplicableSlab"
(submitEvent)="submit()"
>
</mifosx-recurring-deposits-account-preview-step>
@@ -65,9 +65,12 @@ export class CreateRecurringDepositsAccountComponent {
@ViewChild(RecurringDepositsAccountDetailsStepComponent, { static: true })
recurringDepositsAccountDetailsStep: RecurringDepositsAccountDetailsStepComponent;
@ViewChild(RecurringDepositsAccountCurrencyStepComponent, { static: true })
@ViewChild(RecurringDepositsAccountCurrencyStepComponent)
recurringDepositsAccountCurrencyStep: RecurringDepositsAccountCurrencyStepComponent;
@ViewChild(RecurringDepositsAccountInterestRateChartStepComponent)
recurringDepositsAccountChartStep: RecurringDepositsAccountInterestRateChartStepComponent;
/** Recurring Deposits Account Template */
recurringDepositsAccountTemplate: any;
/** Recurring Deposit Account Product Template */
@@ -115,6 +118,26 @@ export class CreateRecurringDepositsAccountComponent {
};
}
get rdNominee() {
return this.recurringDepositsAccountCurrencyStep?.nomineeData ?? null;
}
get rdTenureDays(): number | null {
return this.recurringDepositsAccountChartStep?.tenureDays ?? null;
}
get rdApplicableSlab(): any | null {
return this.recurringDepositsAccountChartStep?.applicableSlab ?? null;
}
get rdCurrencyFlags() {
return {
balanceSwapOption: this.recurringDepositsAccountCurrencyStep?.balanceSwapOption ?? false,
msme: this.recurringDepositsAccountCurrencyStep?.msme ?? false,
autoDebitFacility: this.recurringDepositsAccountCurrencyStep?.autoDebitFacility ?? false
};
}
/**
* Submits the recurring deposit form to create a new recurring deposit account
*/
@@ -60,10 +60,11 @@
{{ recurringDepositsAccountProductTemplate?.dormancyAlert ? 'Yes' : 'No' }}
</span>
</div>
<mifosx-dormancy-alert-panel
[productTemplate]="recurringDepositsAccountProductTemplate"
></mifosx-dormancy-alert-panel>
@if (recurringDepositsAccountProductTemplate?.dormancyAlert) {
<mifosx-dormancy-alert-panel
[productTemplate]="recurringDepositsAccountProductTemplate"
></mifosx-dormancy-alert-panel>
}
<div class="racc-alert">
<div class="racc-alert__content">
@@ -74,7 +75,6 @@
{{ recurringDepositsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
</span>
</div>
@if (recurringDepositsAccountProductTemplate?.nominationFacility) {
<mifosx-nominee-form (nomineeChange)="onNomineeChange($event)"></mifosx-nominee-form>
}
@@ -61,6 +61,8 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
isProductSelected = false;
introducerId: number | null = null;
introducerName: string | null = null;
introducerCif: string | null = null;
/** Recurring Deposits Account Template with product data */
@Output() recurringDepositsAccountProductTemplate = new EventEmitter();
@@ -196,14 +198,18 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
/**
* Returns recurring deposits account details form value.
*/
onIntroducerChange(event: { clientId: number; displayName: string } | null): void {
onIntroducerChange(event: { clientId: number; displayName: string; cifNumber?: string } | null): void {
this.introducerId = event?.clientId ?? null;
this.introducerName = event?.displayName ?? null;
this.introducerCif = event?.cifNumber ?? null;
}
get recurringDepositAccountDetails() {
const value = this.recurringDepositAccountDetailsForm.value;
if (this.introducerId) {
value.introducerId = this.introducerId;
value.introducerName = this.introducerName;
value.introducerCif = this.introducerCif;
}
return value;
}
@@ -7,203 +7,46 @@
-->
<div class="layout-column gap-2px">
<!-- ── Custom RD Interest Rate Chart (from m_rd_interest_rate_slab_history) ── -->
<!-- ── Tenure Calculator ───────────────────────────────────────── -->
@if (_activeRdVersion) {
<div class="rd-rate-header">
<strong>{{ _activeRdVersion.versionLabel }}</strong>
<span class="rd-rate-active-badge">Active</span>
<span class="rd-rate-from">Effective from: {{ _activeRdVersion.effectiveFromDate }}</span>
</div>
<table class="mat-elevation-z1 rd-rate-table" mat-table [dataSource]="_activeRdVersion.slabs">
<ng-container matColumnDef="srNo">
<th mat-header-cell *matHeaderCellDef>Sr.</th>
<td mat-cell *matCellDef="let slab">{{ slab.srNo }}</td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Particular</th>
<td mat-cell *matCellDef="let slab">{{ slab.description }}</td>
</ng-container>
<ng-container matColumnDef="fromPeriod">
<th mat-header-cell *matHeaderCellDef>From</th>
<td mat-cell *matCellDef="let slab">{{ slab.fromPeriod }}</td>
</ng-container>
<ng-container matColumnDef="toPeriod">
<th mat-header-cell *matHeaderCellDef>To</th>
<td mat-cell *matCellDef="let slab">{{ slab.toPeriod ?? '—' }}</td>
</ng-container>
<ng-container matColumnDef="periodTypeLabel">
<th mat-header-cell *matHeaderCellDef>Period Type</th>
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
</ng-container>
<ng-container matColumnDef="annualInterestRate">
<th mat-header-cell *matHeaderCellDef>Annual Rate (%)</th>
<td mat-cell *matCellDef="let slab">
<strong>{{ slab.annualInterestRate | number: '1.2-2' }}</strong>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="rdSlabColumns"></tr>
<tr mat-row *matRowDef="let row; columns: rdSlabColumns"></tr>
</table>
}
<!-- ── Legacy chart (shown only when no custom rate chart available) ── -->
@if (!_activeRdVersion) {
<div class="flex-fill" *ngIf="recurringDepositsAccountProductTemplate?.accountChart?.name">
<span class="flex-40">{{ 'labels.inputs.name' | translate }}:</span>
<span class="flex-60">{{ recurringDepositsAccountProductTemplate?.accountChart?.name }}</span>
</div>
<div class="flex-fill">
<span class="flex-40">{{ 'labels.inputs.Valid from Date' | translate }}:</span>
<span class="flex-60">{{ recurringDepositsAccountProductTemplate?.accountChart?.fromDate | dateFormat }}</span>
</div>
<div class="flex-fill" *ngIf="recurringDepositsAccountProductTemplate?.accountChart?.endDate">
<span class="flex-40">{{ 'labels.inputs.End Date' | translate }}:</span>
<span class="flex-60">{{ recurringDepositsAccountProductTemplate?.accountChart?.endDate | dateFormat }}</span>
</div>
<div class="flex-fill" *ngIf="recurringDepositsAccountProductTemplate?.accountChart?.description">
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
<span class="flex-60">{{ recurringDepositsAccountProductTemplate?.accountChart?.description }}</span>
</div>
<div class="flex-fill">
<span class="flex-40">{{ 'labels.inputs.Primary Grouping by Amount' | translate }}:</span>
<span class="flex-60">{{
recurringDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount | yesNo
}}</span>
</div>
}
<table
class="mat-elevation-z1"
[class.hidden]="_activeRdVersion"
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 }}&nbsp;{{
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="incentives flex-fill layout-row flex-wrap"
[@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="mat-elevation-z1 flex-fill"
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="tenure-calc">
<div class="tenure-calc__header">
<i class="fas fa-calculator"></i>
<span>Tenure &amp; Interest Calculator</span>
</div>
<div class="tenure-calc__body">
<div class="tenure-calc__input-row">
<mat-form-field appearance="outline" class="tenure-calc__field">
<mat-label>Enter Tenure (Days)</mat-label>
<input matInput type="number" [(ngModel)]="tenureDays" min="1" placeholder="e.g. 365" />
<mat-hint>Enter the number of days for your RD</mat-hint>
</mat-form-field>
</div>
</td>
</ng-container>
<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>
@if (tenureDays && tenureDays > 0) {
@if (applicableSlab) {
<div class="tenure-calc__result">
<div class="tenure-calc__result-row">
<span class="tenure-calc__result-label">Tenure</span>
<span class="tenure-calc__result-value">{{ tenureDays }} Days</span>
</div>
<div class="tenure-calc__result-row">
<span class="tenure-calc__result-label">Applicable Slab</span>
<span class="tenure-calc__result-value">{{ applicableSlab.description }}</span>
</div>
<div class="tenure-calc__result-row tenure-calc__result-row--highlight">
<span class="tenure-calc__result-label">Annual Interest Rate</span>
<span class="tenure-calc__result-rate">{{ applicableSlab.annualInterestRate | number: '1.2-2' }}%</span>
</div>
</div>
} @else {
<div class="tenure-calc__no-match">
<i class="fas fa-exclamation-circle"></i>
No matching slab found for {{ tenureDays }} days
</div>
}
}
</div>
</div>
}
</div>
@@ -6,6 +6,105 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// ── Tenure Calculator ─────────────────────────────────────────────────────────
.tenure-calc {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
box-shadow: 0 1px 4px rgb(0 0 0 / 5%);
margin-bottom: 1.25rem;
&__header {
display: flex;
align-items: center;
gap: 8px;
background: #f0f9ff;
border-bottom: 1px solid #bae6fd;
padding: 0.65rem 1rem;
font-size: 0.85rem;
font-weight: 700;
color: #0369a1;
text-transform: uppercase;
letter-spacing: 0.05em;
i {
font-size: 13px;
}
}
&__body {
padding: 1rem 1.25rem;
}
&__input-row {
display: flex;
align-items: flex-start;
gap: 1rem;
}
&__field {
width: 260px;
}
&__result {
margin-top: 1rem;
border: 1px solid #e0f2fe;
border-radius: 8px;
overflow: hidden;
}
&__result-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.55rem 1rem;
border-bottom: 1px solid #f0f9ff;
font-size: 0.88rem;
&:last-child {
border-bottom: none;
}
&--highlight {
background: #f0f9ff;
}
}
&__result-label {
color: #64748b;
font-weight: 500;
}
&__result-value {
color: #1e293b;
font-weight: 600;
}
&__result-rate {
font-size: 1.25rem;
font-weight: 800;
color: #0369a1;
}
&__no-match {
margin-top: 0.75rem;
padding: 0.6rem 1rem;
border-radius: 8px;
background: #fef2f2;
color: #dc2626;
font-size: 0.85rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 6px;
i {
font-size: 13px;
}
}
}
.margin-t {
margin-top: 2em;
}
@@ -7,7 +7,7 @@
*/
/** Angular Imports */
import { Component, OnInit, Input, OnChanges, ViewChild } from '@angular/core';
import { Component, OnInit, Input, OnChanges, ViewChild, Output, EventEmitter } from '@angular/core';
import {
MatTable,
MatColumnDef,
@@ -28,6 +28,7 @@ import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
import { YesnoPipe } from '../../../../pipes/yesno.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { FormsModule } from '@angular/forms';
/**
* Recurring Deposits Account Interest Rate Chart Step
@@ -45,6 +46,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
],
imports: [
...STANDALONE_SHARED_IMPORTS,
FormsModule,
MatTable,
MatColumnDef,
MatHeaderCellDef,
@@ -114,10 +116,26 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
/** Expand Chart Slab Index used in the view */
expandChartSlabIndex: number;
/** Tenure entered by user (in days) */
tenureDays: number | null = null;
@ViewChild('chartsTable', { static: true }) chartsTableRef: MatTable<Element>;
constructor() {}
/** Find the matching slab for the entered tenure */
get applicableSlab(): any | null {
if (!this.tenureDays || !this._activeRdVersion?.slabs?.length) return null;
const days = this.tenureDays;
return (
this._activeRdVersion.slabs.find((s: any) => {
const from = s.fromPeriod ?? 0;
const to = s.toPeriod ?? Infinity;
return days >= from && days <= to;
}) ?? null
);
}
ngOnChanges() {
if (this.recurringDepositsAccountProductTemplate) {
this.interestRateChartData = this.recurringDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [];
@@ -7,9 +7,10 @@
-->
<div class="preview-wrapper">
<!-- ===================== Details ===================== -->
<!-- ══ 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">{{ 'labels.heading.Details' | translate }}</span>
</div>
<div class="preview-body">
@@ -21,12 +22,10 @@
| find: recurringDepositsAccountTemplate?.productOptions : 'id' : 'name'
}}</span>
</div>
<div class="preview-field">
<span class="preview-label">{{ 'labels.inputs.Submitted On' | translate }}</span>
<span class="preview-value">{{ recurringDepositAccountData?.submittedOnDate | dateFormat }}</span>
</div>
<div class="preview-field">
<span class="preview-label">{{ 'labels.inputs.Field Officer' | translate }}</span>
<span class="preview-value">{{
@@ -34,18 +33,17 @@
| find: recurringDepositsAccountProductTemplate?.fieldOfficerOptions : 'id' : 'displayName'
}}</span>
</div>
@if (recurringDepositAccountData?.externalId) {
<div class="preview-field">
<span class="preview-label">{{ 'labels.inputs.External Id' | translate }}</span>
<span class="preview-value">{{ recurringDepositAccountData?.externalId }}</span>
<span class="preview-value">{{ recurringDepositAccountData.externalId }}</span>
</div>
}
</div>
</div>
</div>
<!-- ===================== Product Classification ===================== -->
<!-- ══ 2. Product Classification ════════════════════════════════ -->
@if (
recurringDepositsAccountProductTemplate?.accountType ||
recurringDepositsAccountProductTemplate?.customerSegment ||
@@ -57,40 +55,42 @@
) {
<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">From Product</span>
</div>
<div class="preview-body">
<div class="preview-grid preview-grid--three">
@if (recurringDepositsAccountProductTemplate?.accountType) {
<div class="preview-field">
<span class="preview-label">Account Type</span>
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.accountType }}</span>
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.accountType }}</span>
</div>
}
@if (recurringDepositsAccountProductTemplate?.customerSegment) {
<div class="preview-field">
<span class="preview-label">Customer Segment</span>
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.customerSegment }}</span>
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.customerSegment }}</span>
</div>
}
@if (recurringDepositsAccountProductTemplate?.productCode) {
<div class="preview-field">
<span class="preview-label">Product Code</span>
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.productCode }}</span>
<span class="preview-value preview-mono">{{ recurringDepositsAccountProductTemplate.productCode }}</span>
</div>
}
@if (recurringDepositsAccountProductTemplate?.effectiveDate) {
<div class="preview-field">
<span class="preview-label">Effective Date</span>
<span class="preview-value">{{
recurringDepositsAccountProductTemplate?.effectiveDate | dateFormat
recurringDepositsAccountProductTemplate.effectiveDate | dateFormat
}}</span>
</div>
}
@if (recurringDepositsAccountProductTemplate?.endDate) {
<div class="preview-field">
<span class="preview-label">End Date</span>
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.endDate | dateFormat }}</span>
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.endDate | dateFormat }}</span>
</div>
}
@if (
@@ -101,9 +101,9 @@
<span class="preview-label">Introducer Required</span>
<span
class="preview-value preview-badge"
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.introducerRequired"
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate.introducerRequired"
>
{{ recurringDepositsAccountProductTemplate?.introducerRequired | yesNo }}
{{ recurringDepositsAccountProductTemplate.introducerRequired | yesNo }}
</span>
</div>
}
@@ -115,9 +115,9 @@
<span class="preview-label">Share Linking Enabled</span>
<span
class="preview-value preview-badge"
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.shareLinkingEnabled"
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate.shareLinkingEnabled"
>
{{ recurringDepositsAccountProductTemplate?.shareLinkingEnabled | yesNo }}
{{ recurringDepositsAccountProductTemplate.shareLinkingEnabled | yesNo }}
</span>
</div>
}
@@ -126,32 +126,54 @@
</div>
}
<!-- ===================== Currency ===================== -->
<!-- ══ 3. Introducer Details (if selected) ══════════════════════ -->
@if (recurringDepositAccountData?.introducerId) {
<div class="preview-section">
<div class="preview-section__header">
<i class="fas fa-user-tie preview-section__icon"></i>
<span class="preview-section__title">Introducer Details</span>
</div>
<div class="preview-body">
<div class="preview-grid preview-grid--three">
<div class="preview-field">
<span class="preview-label">Name</span>
<span class="preview-value">{{ recurringDepositAccountData?.introducerName || '—' }}</span>
</div>
@if (recurringDepositAccountData?.introducerCif) {
<div class="preview-field">
<span class="preview-label">CIF No</span>
<span class="preview-value preview-mono">{{ recurringDepositAccountData.introducerCif }}</span>
</div>
}
<div class="preview-field">
<span class="preview-label">Client ID</span>
<span class="preview-value preview-mono">{{ recurringDepositAccountData.introducerId }}</span>
</div>
</div>
</div>
</div>
}
<!-- ══ 4. Currency & Account Flags ══════════════════════════════ -->
<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">{{
recurringDepositsAccountProductTemplate?.currency?.code ||
recurringDepositsAccountProductTemplate?.currency?.name ||
'—'
}}</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">{{ recurringDepositsAccountProductTemplate?.currency?.decimalPlaces }}</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">{{ recurringDepositsAccountProductTemplate?.currency?.inMultiplesOf }}</span>
<span class="preview-value">{{ inMultiplesOf }}</span>
</div>
@if (recurringDepositsAccountProductTemplate?.withHoldTax) {
<div class="preview-field">
<span class="preview-label">{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}</span>
@@ -163,14 +185,30 @@
</span>
</div>
}
@if (recurringDepositAccountData?.withHoldTax && recurringDepositsAccountProductTemplate?.taxGroup?.name) {
<div class="preview-field">
<span class="preview-label">{{ 'labels.inputs.Tax Group' | translate }}</span>
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.taxGroup?.name }}</span>
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.taxGroup.name }}</span>
</div>
}
<div class="preview-field">
<span class="preview-label">Balance Swap Option</span>
<span class="preview-value preview-badge" [class.preview-badge--yes]="rdCurrencyFlags?.balanceSwapOption">
{{ rdCurrencyFlags?.balanceSwapOption | yesNo }}
</span>
</div>
<div class="preview-field">
<span class="preview-label">MSME</span>
<span class="preview-value preview-badge" [class.preview-badge--yes]="rdCurrencyFlags?.msme">
{{ rdCurrencyFlags?.msme | yesNo }}
</span>
</div>
<div class="preview-field">
<span class="preview-label">Auto Debit Facility</span>
<span class="preview-value preview-badge" [class.preview-badge--yes]="rdCurrencyFlags?.autoDebitFacility">
{{ rdCurrencyFlags?.autoDebitFacility | yesNo }}
</span>
</div>
@if (
recurringDepositsAccountProductTemplate?.dormancyAlert !== undefined &&
recurringDepositsAccountProductTemplate?.dormancyAlert !== null
@@ -179,43 +217,12 @@
<span class="preview-label">Dormancy Alert</span>
<span
class="preview-value preview-badge"
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.dormancyAlert"
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate.dormancyAlert"
>
{{ recurringDepositsAccountProductTemplate?.dormancyAlert | yesNo }}
{{ recurringDepositsAccountProductTemplate.dormancyAlert | yesNo }}
</span>
</div>
}
@if (
recurringDepositsAccountProductTemplate?.balanceSwapOption !== undefined &&
recurringDepositsAccountProductTemplate?.balanceSwapOption !== null
) {
<div class="preview-field">
<span class="preview-label">Balance Swap Option</span>
<span
class="preview-value preview-badge"
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.balanceSwapOption"
>
{{ recurringDepositsAccountProductTemplate?.balanceSwapOption | yesNo }}
</span>
</div>
}
@if (
recurringDepositsAccountProductTemplate?.msme !== undefined &&
recurringDepositsAccountProductTemplate?.msme !== null
) {
<div class="preview-field">
<span class="preview-label">MSME</span>
<span
class="preview-value preview-badge"
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.msme"
>
{{ recurringDepositsAccountProductTemplate?.msme | yesNo }}
</span>
</div>
}
@if (
recurringDepositsAccountProductTemplate?.nominationFacility !== undefined &&
recurringDepositsAccountProductTemplate?.nominationFacility !== null
@@ -224,24 +231,9 @@
<span class="preview-label">Nomination Facility</span>
<span
class="preview-value preview-badge"
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.nominationFacility"
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate.nominationFacility"
>
{{ recurringDepositsAccountProductTemplate?.nominationFacility | yesNo }}
</span>
</div>
}
@if (
recurringDepositsAccountProductTemplate?.autoDebitFacility !== undefined &&
recurringDepositsAccountProductTemplate?.autoDebitFacility !== null
) {
<div class="preview-field">
<span class="preview-label">Auto Debit Facility</span>
<span
class="preview-value preview-badge"
[class.preview-badge--yes]="recurringDepositsAccountProductTemplate?.autoDebitFacility"
>
{{ recurringDepositsAccountProductTemplate?.autoDebitFacility | yesNo }}
{{ recurringDepositsAccountProductTemplate.nominationFacility | yesNo }}
</span>
</div>
}
@@ -249,93 +241,235 @@
</div>
</div>
<!-- ===================== Interest Rate Chart ===================== -->
<!-- ══ 5. Dormancy Monitoring ════════════════════════════════════ -->
@if (
recurringDepositsAccountProductTemplate?.dormancyAlert ||
recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct
) {
<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"
[class.preview-section__badge--active]="
recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct
"
>
{{ recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }}
</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">
@if (recurringDepositsAccountProductTemplate?.daysToInactiveFromProduct != null) {
After {{ recurringDepositsAccountProductTemplate.daysToInactiveFromProduct }} days of no transaction
} @else {
Not configured
}
</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">
@if (recurringDepositsAccountProductTemplate?.daysToDormancyFromProduct != null) {
{{ recurringDepositsAccountProductTemplate.daysToDormancyFromProduct }} days after inactive
} @else {
Not configured
}
</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">
@if (recurringDepositsAccountProductTemplate?.daysToEscheatFromProduct != null) {
{{ recurringDepositsAccountProductTemplate.daysToEscheatFromProduct }} days after dormant
} @else {
Not configured
}
</span>
</div>
</div>
</div>
<div class="preview-dorm-flags">
<span
class="preview-dorm-flag"
[class.preview-dorm-flag--on]="recurringDepositsAccountProductTemplate?.dormancyAlert"
>
<i class="fas fa-bell"></i> Alert
{{ recurringDepositsAccountProductTemplate?.dormancyAlert ? 'Enabled' : 'Disabled' }}
</span>
<span
class="preview-dorm-flag"
[class.preview-dorm-flag--on]="recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct"
>
<i class="fas fa-eye"></i> Tracking
{{ recurringDepositsAccountProductTemplate?.isDormancyTrackingActiveFromProduct ? 'Active' : 'Inactive' }}
</span>
</div>
</div>
</div>
}
<!-- ══ 6. Nominee Details (if filled) ═══════════════════════════ -->
@if (rdNominee) {
<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">
{{ rdNominee?.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">Name</span>
<span class="preview-value">{{ rdNominee?.fullName || rdNominee?.displayName || '—' }}</span>
</div>
@if (rdNominee?.cifNumber) {
<div class="preview-field">
<span class="preview-label">CIF No</span>
<span class="preview-value preview-mono">{{ rdNominee.cifNumber }}</span>
</div>
}
<div class="preview-field">
<span class="preview-label">Relationship</span>
<span class="preview-value">{{ rdNominee?.relationship || '—' }}</span>
</div>
<div class="preview-field">
<span class="preview-label">Share %</span>
<span class="preview-value">{{ rdNominee?.nomineeShare ?? '—' }}%</span>
</div>
@if (rdNominee?.dateOfBirth) {
<div class="preview-field">
<span class="preview-label">Date of Birth</span>
<span class="preview-value">{{ rdNominee?.dateOfBirth | dateFormat }}</span>
</div>
}
@if (rdNominee?.nationality) {
<div class="preview-field">
<span class="preview-label">Nationality</span>
<span class="preview-value">{{ rdNominee.nationality }}</span>
</div>
}
@if (rdNominee?.mobileNo) {
<div class="preview-field">
<span class="preview-label">Mobile No</span>
<span class="preview-value preview-mono">{{ rdNominee.mobileNo }}</span>
</div>
}
@if (rdNominee?.aadhaarNumber) {
<div class="preview-field">
<span class="preview-label">Aadhaar No</span>
<span class="preview-value preview-mono">{{ rdNominee.aadhaarNumber }}</span>
</div>
}
@if (rdNominee?.panNumber) {
<div class="preview-field">
<span class="preview-label">PAN No</span>
<span class="preview-value preview-mono">{{ rdNominee.panNumber }}</span>
</div>
}
@if (rdNominee?.guardianName) {
<div class="preview-field">
<span class="preview-label">Guardian Name</span>
<span class="preview-value">{{ rdNominee.guardianName }}</span>
</div>
}
@if (rdNominee?.guardianRelationship) {
<div class="preview-field">
<span class="preview-label">Guardian Relationship</span>
<span class="preview-value">{{ rdNominee.guardianRelationship }}</span>
</div>
}
@if (rdNominee?.remarks) {
<div class="preview-field">
<span class="preview-label">Remarks</span>
<span class="preview-value">{{ rdNominee.remarks }}</span>
</div>
}
</div>
</div>
</div>
}
<!-- ══ 7. Interest Rate (Tenure-based result) ════════════════════ -->
@if (_activeRdVersion || recurringDepositsAccountProductTemplate?.accountChart) {
<div class="preview-section">
<div class="preview-section__header">
<span class="preview-section__title">{{ 'labels.inputs.Interest Rate Chart' | translate }}</span>
<i class="fas fa-chart-line preview-section__icon"></i>
<span class="preview-section__title">Interest Rate</span>
@if (_activeRdVersion) {
<span class="preview-section__badge">{{ _activeRdVersion.versionLabel }}</span>
}
</div>
<div class="preview-body">
@if (_activeRdVersion) {
<div class="preview-grid preview-grid--three">
<div class="preview-field">
<span class="preview-label">Version</span>
<span class="preview-value">{{ _activeRdVersion.versionLabel }}</span>
@if (rdTenureDays && rdApplicableSlab) {
<div class="preview-tenure-result">
<div class="preview-tenure-result__row">
<span class="preview-label">Tenure</span>
<span class="preview-value">{{ rdTenureDays }} Days</span>
</div>
<div class="preview-tenure-result__row">
<span class="preview-label">Applicable Slab</span>
<span class="preview-value">{{ rdApplicableSlab.description }}</span>
</div>
<div class="preview-tenure-result__row preview-tenure-result__row--highlight">
<span class="preview-label">Annual Interest Rate</span>
<span class="preview-tenure-result__rate"
>{{ rdApplicableSlab.annualInterestRate | formatNumber }}%</span
>
</div>
</div>
<div class="preview-field">
<span class="preview-label">Active</span>
<span class="preview-value preview-badge preview-badge--yes">Yes</span>
</div>
<div class="preview-field">
<span class="preview-label">Effective From</span>
<span class="preview-value">{{ _activeRdVersion.effectiveFromDate }}</span>
</div>
</div>
<table class="mat-elevation-z1 irc-table preview-table" mat-table [dataSource]="_activeRdVersion.slabs">
<ng-container matColumnDef="srNo">
<th mat-header-cell *matHeaderCellDef>Sr.</th>
<td mat-cell *matCellDef="let slab">{{ slab.srNo }}</td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>Particular</th>
<td mat-cell *matCellDef="let slab">{{ slab.description }}</td>
</ng-container>
<ng-container matColumnDef="fromPeriod">
<th mat-header-cell *matHeaderCellDef>From</th>
<td mat-cell *matCellDef="let slab">{{ slab.fromPeriod }}</td>
</ng-container>
<ng-container matColumnDef="toPeriod">
<th mat-header-cell *matHeaderCellDef>To</th>
<td mat-cell *matCellDef="let slab">{{ slab.toPeriod ?? 'Unassigned' }}</td>
</ng-container>
<ng-container matColumnDef="periodTypeLabel">
<th mat-header-cell *matHeaderCellDef>Period Type</th>
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
</ng-container>
<ng-container matColumnDef="annualInterestRate">
<th mat-header-cell *matHeaderCellDef>Annual Rate (%)</th>
<td mat-cell *matCellDef="let slab">{{ slab.annualInterestRate | formatNumber }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="rdSlabColumns"></tr>
<tr mat-row *matRowDef="let row; columns: rdSlabColumns"></tr>
</table>
} @else {
<span class="preview-muted">No tenure entered — interest rate not calculated</span>
}
} @else {
<div class="preview-grid preview-grid--three">
@if (recurringDepositsAccountProductTemplate?.accountChart?.name) {
<div class="preview-field">
<span class="preview-label">{{ 'labels.inputs.name' | translate }}</span>
<span class="preview-value">{{ recurringDepositsAccountProductTemplate?.accountChart?.name }}</span>
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.accountChart.name }}</span>
</div>
}
@if (recurringDepositsAccountProductTemplate?.accountChart?.fromDate) {
<div class="preview-field">
<span class="preview-label">{{ 'labels.inputs.Valid from Date' | translate }}</span>
<span class="preview-value">{{
recurringDepositsAccountProductTemplate?.accountChart?.fromDate | dateFormat
recurringDepositsAccountProductTemplate.accountChart.fromDate | dateFormat
}}</span>
</div>
}
@if (recurringDepositsAccountProductTemplate?.accountChart?.endDate) {
<div class="preview-field">
<span class="preview-label">{{ 'labels.inputs.End Date' | translate }}</span>
<span class="preview-value">{{
recurringDepositsAccountProductTemplate?.accountChart?.endDate | dateFormat
recurringDepositsAccountProductTemplate.accountChart.endDate | dateFormat
}}</span>
</div>
}
@if (recurringDepositsAccountProductTemplate?.accountChart?.description) {
<div class="preview-field">
<span class="preview-label">{{ 'labels.inputs.Description' | translate }}</span>
<span class="preview-value">{{
recurringDepositsAccountProductTemplate?.accountChart?.description
recurringDepositsAccountProductTemplate.accountChart.description
}}</span>
</div>
}
<div class="preview-field">
<span class="preview-label">{{ 'labels.inputs.Primary Grouping by Amount' | translate }}</span>
<span class="preview-value">{{
@@ -359,24 +493,20 @@
}}
</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">
@@ -396,7 +526,6 @@
</button>
</td>
</ng-container>
<ng-container matColumnDef="incentives">
<td
mat-cell
@@ -446,7 +575,6 @@
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: chartSlabsDisplayedColumns"></tr>
<tr
@@ -23,18 +23,48 @@
box-shadow: 0 1px 3px rgb(0 0 0 / 6%);
&__header {
display: flex;
align-items: center;
gap: 8px;
background: #f5f5f5;
border-bottom: 1px solid #e0e0e0;
padding: 0.6rem 1rem;
}
&__icon {
font-size: 13px;
color: #0ea5e9;
width: 16px;
text-align: center;
}
&__title {
flex: 1;
font-size: 0.95rem;
font-weight: 600;
color: #333;
text-transform: uppercase;
letter-spacing: 0.04em;
}
&__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;
}
}
}
// ── Body ─────────────────────────────────────────────────────────────────────
@@ -146,6 +176,169 @@
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;
}
// ── Dormancy timeline ─────────────────────────────────────────────────────────
.preview-dormancy-timeline {
display: flex;
align-items: flex-start;
margin-bottom: 14px;
}
.preview-dorm-step {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
flex: 1;
&__dot {
width: 36px;
height: 36px;
border-radius: 50%;
border: 3px solid;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
&--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;
}
.preview-dorm-flags {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.preview-dorm-flag {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 12px;
border-radius: 20px;
font-size: 11.5px;
font-weight: 600;
background: #f1f5f9;
color: #94a3b8;
border: 1px solid #e2e8f0;
i {
font-size: 11px;
}
&--on {
background: #f0fdf4;
color: #15803d;
border-color: #bbf7d0;
}
}
// ── Product feature flag rows (same style as details step) ────────────────────
.preview-feature-list {
margin-top: 1rem;
@@ -73,6 +73,10 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
@Input() recurringDepositsAccountTemplate: any;
@Input() recurringDepositsAccountProductTemplate: any;
@Input() recurringDepositAccountData: any;
@Input() rdNominee: any = null;
@Input() rdCurrencyFlags: any = null;
@Input() rdTenureDays: number | null = null;
@Input() rdApplicableSlab: any = null;
@Input() set rdInterestRates(rates: any[]) {
this._rdInterestRates = rates || [];
this._activeRdVersion = this._rdInterestRates.find((v: any) => v.isActive) ?? this._rdInterestRates[0] ?? null;
@@ -80,6 +84,18 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
/** Output the submit action */
@Output() submitEvent = new EventEmitter();
get currencyName(): string {
return this.recurringDepositsAccountProductTemplate?.currency?.name || '—';
}
get decimalPlaces(): string | number {
return this.recurringDepositsAccountProductTemplate?.currency?.decimalPlaces ?? '—';
}
get inMultiplesOf(): string | number {
return this.recurringDepositsAccountProductTemplate?.currency?.inMultiplesOf ?? '—';
}
_rdInterestRates: any[] = [];
_activeRdVersion: any = null;