saving , current and RD account code update

This commit is contained in:
Nitesh Yadav
2026-04-22 18:28:14 +05:30
parent e9a9377148
commit fc1c5126fc
44 changed files with 5516 additions and 1552 deletions
@@ -93,6 +93,7 @@
[recurringDepositsAccountTemplate]="recurringDepositsAccountTemplate"
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
[recurringDepositAccountData]="recurringDepositAccountData"
[rdInterestRates]="rdInterestRates"
(submitEvent)="submit()"
>
</mifosx-recurring-deposits-account-preview-step>
@@ -94,6 +94,7 @@
[recurringDepositsAccountTemplate]="recurringDepositsAccountAndTemplate"
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
[recurringDepositAccountData]="recurringDepositAccountData"
[rdInterestRates]="rdInterestRates"
(submitEvent)="submit()"
>
</mifosx-recurring-deposits-account-preview-step>
@@ -79,7 +79,7 @@ export class RecurringDepositsAccountChargesStepComponent implements OnInit, OnC
@Input() recurringDepositAccountFormValid: boolean;
/** Charges Data */
chargeData: any;
chargeData: any[] = [];
/** Charges Data Source */
chargesDataSource: {}[] = [];
/** Charges table columns */
@@ -102,8 +102,10 @@ export class RecurringDepositsAccountChargesStepComponent implements OnInit, OnC
ngOnInit() {
this.chargesDataSource = [];
if (this.recurringDepositsAccountTemplate.id && this.recurringDepositsAccountTemplate.charges) {
// Edit mode: load saved charges from existing account
this.chargesDataSource =
this.recurringDepositsAccountTemplate.charges.map((charge: any) => ({ ...charge, id: charge.chargeId })) || [];
this.isChargesPatched = true;
}
}
@@ -120,8 +122,17 @@ export class RecurringDepositsAccountChargesStepComponent implements OnInit, OnC
}
if (this.recurringDepositsAccountProductTemplate) {
this.chargeData = this.recurringDepositsAccountProductTemplate.chargeOptions.filter(
(c: Charge) => c.currency.code === this.currency.code
(c: Charge) => c.currency.code === this.currency?.code
);
// New account: pre-populate charges from product template
if (!this.isChargesPatched && !this.recurringDepositsAccountTemplate?.id) {
const productCharges = this.recurringDepositsAccountProductTemplate.charges;
if (Array.isArray(productCharges) && productCharges.length > 0) {
this.chargesDataSource = productCharges.map((charge: any) => ({ ...charge, id: charge.chargeId }));
}
this.isChargesPatched = true;
}
}
}
@@ -7,80 +7,207 @@
-->
<form [formGroup]="recurringDepositAccountDetailsForm">
<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 ===================== -->
<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>
<input matInput [value]="productTemplate.accountType || ''" readonly />
</mat-form-field>
<mat-form-field appearance="outline" class="rad-field">
<mat-label>Customer Segment</mat-label>
<input matInput [value]="productTemplate.customerSegment || ''" readonly />
</mat-form-field>
<mat-form-field appearance="outline" class="rad-field">
<mat-label>Product Code</mat-label>
<input matInput [value]="productTemplate.productCode || ''" 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>
}
<mat-form-field appearance="outline" class="rad-field">
<mat-label>Introducer Required</mat-label>
<input matInput [value]="productTemplate.introducerRequired ? 'Yes' : 'No'" readonly />
</mat-form-field>
<div class="rad-field rad-info-row">
<span class="rad-info-label">Share Linking Enabled</span>
<span class="rad-badge" [class.rad-badge--yes]="productTemplate.shareLinkingEnabled">
{{ productTemplate.shareLinkingEnabled ? 'Yes' : 'No' }}
</span>
</div>
</div>
</div>
<!-- ===================== Currency & Account Status Configuration ===================== -->
<div class="rad-section rad-section--mt">
<div class="rad-section__header">
<i class="fas fa-coins"></i>
<h3>Currency &amp; Account Status Configuration</h3>
<span class="rad-badge">From Product</span>
</div>
<div class="rad-body">
@if (productTemplate.currency) {
<div class="rad-grid">
<mat-form-field appearance="outline" class="rad-field">
<mat-label>Currency</mat-label>
<input matInput [value]="productTemplate.currency.name" readonly />
<mat-hint>Default: Indian Rupee - auto-set for domestic products</mat-hint>
</mat-form-field>
@if (
productTemplate.currency.decimalPlaces !== undefined && productTemplate.currency.decimalPlaces !== null
) {
<mat-form-field appearance="outline" class="rad-field">
<mat-label>Decimal Places</mat-label>
<input matInput [value]="productTemplate.currency.decimalPlaces" readonly />
</mat-form-field>
}
</div>
}
<div class="rad-alert">
<div class="rad-alert__content">
<h4>Dormancy Alert</h4>
<p>Enable automated notifications when an account becomes inactive based on product rules.</p>
</div>
<div class="rad-alert__choices">
<span class="rad-yn" [class.rad-yn--active]="productTemplate.dormancyAlert === true">yes</span>
<span class="rad-yn" [class.rad-yn--active]="productTemplate.dormancyAlert === false">no</span>
</div>
</div>
<div class="rad-alert">
<div class="rad-alert__content">
<h4>Balance Swap Option</h4>
<p>Enable automated transfer of excess funds to linked deposits or vice-versa to maintain liquidity.</p>
</div>
<div class="rad-alert__choices">
<span class="rad-yn" [class.rad-yn--active]="productTemplate.balanceSwapOption === true">yes</span>
<span class="rad-yn" [class.rad-yn--active]="productTemplate.balanceSwapOption === false">no</span>
</div>
</div>
<div class="rad-alert">
<div class="rad-alert__content">
<h4>MSME</h4>
<p>Micro, Small and Medium Enterprises</p>
</div>
<div class="rad-alert__choices">
<span class="rad-yn" [class.rad-yn--active]="productTemplate.msme === true">yes</span>
<span class="rad-yn" [class.rad-yn--active]="productTemplate.msme === false">no</span>
</div>
</div>
<div class="rad-alert">
<div class="rad-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>
<div class="rad-alert__choices">
<span class="rad-yn" [class.rad-yn--active]="productTemplate.nominationFacility === true">yes</span>
<span class="rad-yn" [class.rad-yn--active]="productTemplate.nominationFacility === false">no</span>
</div>
</div>
<div class="rad-alert">
<div class="rad-alert__content">
<h4>Auto Debit Facility</h4>
<p>Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.</p>
</div>
<div class="rad-alert__choices">
<span class="rad-yn" [class.rad-yn--active]="productTemplate.autoDebitFacility === true">yes</span>
<span class="rad-yn" [class.rad-yn--active]="productTemplate.autoDebitFacility === false">no</span>
</div>
</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>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button
mat-raised-button
matStepperNext
[disabled]="
!recurringDepositAccountDetailsForm.get('productId')?.value ||
!recurringDepositAccountDetailsForm.get('submittedOnDate')?.value
"
>
<button mat-raised-button matStepperNext [disabled]="recurringDepositAccountDetailsForm.invalid">
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
@@ -6,6 +6,180 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.margin-t {
margin-top: 1em;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-900: #202124;
// ── Section card ───────────────────────────────────────────────
.rad-section {
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
background: #fff;
&--mt {
margin-top: 16px;
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
flex: 1;
}
}
}
// ── 2-col grid (Account Details inputs) ───────────────────────
.rad-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (width <= 600px) {
grid-template-columns: 1fr;
}
}
// ── 3-col grid (Product Classification) ───────────────────────
.rad-grid--classification {
grid-template-columns: repeat(3, 1fr);
@media (width <= 900px) {
grid-template-columns: repeat(2, 1fr);
}
@media (width <= 600px) {
grid-template-columns: 1fr;
}
}
.rad-field {
width: 100%;
}
// ── Share Linking badge row ────────────────────────────────────
.rad-info-row {
display: flex;
align-items: center;
gap: 12px;
min-height: 56px;
}
.rad-info-label {
font-size: 14px;
color: $gray-900;
font-weight: 500;
}
.rad-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 3px 12px;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
background: #dbeafe;
color: #1d4ed8;
border: 1px solid #bfdbfe;
&--yes {
background: #dcfce7;
color: #15803d;
border-color: #bbf7d0;
}
}
// ── Currency section body ──────────────────────────────────────
.rad-body {
background: #fff;
}
// ── Yes / No alert rows (same as rdpc-alert) ──────────────────
.rad-alert {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
padding: 18px 24px 24px;
border-top: 1px solid $border;
background: #f8fafc;
@media (width <= 760px) {
flex-direction: column;
align-items: flex-start;
}
&__content {
h4 {
margin: 0 0 6px;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
p {
margin: 0;
font-size: 12px;
line-height: 1.5;
color: #64748b;
max-width: 540px;
}
}
&__choices {
display: flex;
align-items: center;
gap: 18px;
flex-wrap: wrap;
}
}
// ── yes / no indicator pills ───────────────────────────────────
.rad-yn {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
color: #94a3b8;
cursor: default;
&::before {
content: '';
display: inline-block;
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid #cbd5e1;
background: #fff;
flex-shrink: 0;
}
&--active {
color: $gray-900;
font-weight: 500;
&::before {
border-color: #22c55e;
background: radial-gradient(circle, #22c55e 45%, #fff 46%);
}
}
}
@@ -7,9 +7,10 @@
*/
/** 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 } from '@angular/forms';
import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates';
/** Custom Services */
import { RecurringDepositsService } from '../../recurring-deposits.service';
@@ -37,6 +38,8 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
private formBuilder = inject(UntypedFormBuilder);
private recurringDepositsService = inject(RecurringDepositsService);
private settingsService = inject(SettingsService);
private dateUtils = inject(Dates);
private cdr = inject(ChangeDetectorRef);
/** Recurring Deposits Account Template */
@Input() recurringDepositsAccountTemplate: any;
@@ -52,19 +55,16 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
/** For edit recurring deposits form */
isFieldOfficerPatched = false;
/** Recurring Deposits Account Details Form */
recurringDepositAccountDetailsForm: UntypedFormGroup;
recurringDepositAccountDetailsForm!: UntypedFormGroup;
isProductSelected = false;
/** Recurring Deposits Account Template with product data */
@Output() recurringDepositsAccountProductTemplate = new EventEmitter();
/**
* Sets recurring deposits account details form.
* @param {FormBuilder} formBuilder Form Builder.
* @param {RecurringDepositsService} recurringDepositsService Recurring Deposits Service.
* @param {SettingsService} settingsService Settings Service
*/
/** Local copy of product template for displaying classification info */
productTemplate: any = null;
constructor() {
this.createRecurringDepositsAccountDetailsForm();
}
@@ -75,6 +75,7 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
if (this.recurringDepositsAccountTemplate) {
this.productData = this.recurringDepositsAccountTemplate.productOptions;
if (this.recurringDepositsAccountTemplate.depositProductId) {
// Edit mode: patch from saved data
this.recurringDepositAccountDetailsForm.patchValue({
productId: this.recurringDepositsAccountTemplate.depositProductId,
submittedOnDate:
@@ -82,6 +83,11 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
new Date(this.recurringDepositsAccountTemplate.timeline.submittedOnDate),
externalId: this.recurringDepositsAccountTemplate.externalId
});
} else {
// New account: default submittedOnDate to today's business date
this.recurringDepositAccountDetailsForm.patchValue({
submittedOnDate: this.settingsService.businessDate
});
}
}
}
@@ -109,25 +115,72 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
*/
buildDependencies() {
const clientId = this.recurringDepositsAccountTemplate.clientId;
this.recurringDepositAccountDetailsForm.get('productId').valueChanges.subscribe((productId: string) => {
this.recurringDepositAccountDetailsForm.get('productId')!.valueChanges.subscribe((productId: string) => {
this.recurringDepositsService
.getRecurringDepositsAccountTemplate(clientId, productId)
.subscribe((response: any) => {
// Ensure currency is included in the product template
if (!response.currency) {
// Try to get currency from the selected product
const selectedProduct = this.recurringDepositsAccountTemplate.productOptions?.find(
(product: any) => product.id == productId
);
if (selectedProduct?.currency) {
response.currency = selectedProduct.currency;
} else if (this.recurringDepositsAccountTemplate.currency) {
response.currency = this.recurringDepositsAccountTemplate.currency;
}
}
this.productTemplate = response;
this.recurringDepositsAccountProductTemplate.emit(response);
this.fieldOfficerData = response.fieldOfficerOptions;
this.isProductSelected = true;
this.cdr.detectChanges();
if (!this.isFieldOfficerPatched && this.recurringDepositsAccountTemplate.fieldOfficerId) {
this.recurringDepositAccountDetailsForm
.get('fieldOfficerId')
.get('fieldOfficerId')!
.patchValue(this.recurringDepositsAccountTemplate.fieldOfficerId);
this.isFieldOfficerPatched = true;
} else {
this.recurringDepositAccountDetailsForm.get('fieldOfficerId').patchValue('');
this.recurringDepositAccountDetailsForm.get('fieldOfficerId')!.patchValue('');
}
});
});
}
/**
* Returns true if a value is not null, undefined, or empty string.
*/
hasValue(val: any): boolean {
return val !== null && val !== undefined && val !== '';
}
/**
* Formats template dates into the current CBS date format.
* Handles backend date arrays like [2026, 4, 20] as well as strings and Date objects.
*/
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') || '';
}
/**
* Returns recurring deposits account details form value.
*/
@@ -49,30 +49,30 @@
<!-- ── Legacy chart (shown only when no custom rate chart available) ── -->
@if (!_activeRdVersion) {
<div class="flex-fill" *ngIf="recurringDepositsAccountProductTemplate?.accountChart.name">
<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>
<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>
<span class="flex-60">{{ recurringDepositsAccountProductTemplate?.accountChart?.fromDate | dateFormat }}</span>
</div>
<div class="flex-fill" *ngIf="recurringDepositsAccountProductTemplate?.accountChart.endDate">
<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>
<span class="flex-60">{{ recurringDepositsAccountProductTemplate?.accountChart?.endDate | dateFormat }}</span>
</div>
<div class="flex-fill" *ngIf="recurringDepositsAccountProductTemplate?.accountChart.description">
<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>
<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
recurringDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount | yesNo
}}</span>
</div>
}
@@ -120,7 +120,7 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
ngOnChanges() {
if (this.recurringDepositsAccountProductTemplate) {
this.interestRateChartData = this.recurringDepositsAccountProductTemplate.accountChart.chartSlabs;
this.interestRateChartData = this.recurringDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [];
}
}
@@ -6,35 +6,120 @@
* 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 #e0e0e0;
border-radius: 8px;
overflow: hidden;
background: #fff;
box-shadow: 0 1px 3px rgb(0 0 0 / 6%);
&__header {
background: #f5f5f5;
border-bottom: 1px solid #e0e0e0;
padding: 0.6rem 1rem;
}
&__title {
font-size: 0.95rem;
font-weight: 600;
color: #333;
text-transform: uppercase;
letter-spacing: 0.04em;
}
}
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.75rem 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.72rem;
color: #888;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.preview-value {
font-size: 0.92rem;
color: #222;
font-weight: 500;
}
span {
margin: 0.5em 0;
// ── Yes/No badge ─────────────────────────────────────────────────────────────
.preview-badge {
display: inline-block;
padding: 0.1rem 0.55rem;
border-radius: 12px;
font-size: 0.78rem;
font-weight: 600;
background: #ffebee;
color: #c62828;
width: fit-content;
&--yes {
background: #e8f5e9;
color: #2e7d32;
}
}
mat-divider {
margin: 0 0 0.5em;
}
.margin-t {
margin-top: 1em;
}
.margin-b {
margin-bottom: 0.25em;
// ── Tables ───────────────────────────────────────────────────────────────────
.preview-table {
width: 100%;
margin-top: 1rem;
}
.irc-table {
@@ -56,3 +141,78 @@ mat-divider {
}
}
}
.margin-t {
margin-top: 1em;
}
// ── Product feature flag rows (same style as details step) ────────────────────
.preview-feature-list {
margin-top: 1rem;
border-top: 1px solid #e0e0e0;
}
.preview-feature-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 0;
border-bottom: 1px solid #f0f0f0;
gap: 1rem;
}
.preview-feature-info {
display: flex;
flex-direction: column;
gap: 0.15rem;
strong {
font-size: 0.88rem;
font-weight: 600;
color: #222;
}
span {
font-size: 0.78rem;
color: #26a69a;
margin: 0;
}
}
.preview-feature-choices {
display: flex;
gap: 0.5rem;
flex-shrink: 0;
}
.rad-yn {
display: inline-flex;
align-items: center;
gap: 0.3rem;
padding: 0.2rem 0.75rem;
border-radius: 20px;
border: 2px solid #ccc;
font-size: 0.82rem;
color: #999;
cursor: default;
&::before {
content: '';
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid #ccc;
display: inline-block;
}
&--active {
border-color: #4caf50;
color: #2e7d32;
font-weight: 600;
&::before {
border-color: #4caf50;
background: #4caf50;
}
}
}
@@ -73,9 +73,25 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
@Input() recurringDepositsAccountTemplate: any;
@Input() recurringDepositsAccountProductTemplate: any;
@Input() recurringDepositAccountData: any;
@Input() set rdInterestRates(rates: any[]) {
this._rdInterestRates = rates || [];
this._activeRdVersion = this._rdInterestRates.find((v: any) => v.isActive) ?? this._rdInterestRates[0] ?? null;
}
/** Output the submit action */
@Output() submitEvent = new EventEmitter();
_rdInterestRates: any[] = [];
_activeRdVersion: any = null;
readonly rdSlabColumns = [
'srNo',
'description',
'fromPeriod',
'toPeriod',
'periodTypeLabel',
'annualInterestRate'
];
/** Charges Displayed Columns */
chargesDisplayedColumns: string[] = [
'name',
@@ -113,7 +129,7 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
ngOnChanges() {
if (this.recurringDepositsAccountProductTemplate) {
this.interestRateChartData = this.recurringDepositsAccountProductTemplate.accountChart.chartSlabs;
this.interestRateChartData = this.recurringDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [];
}
}
}
@@ -7,219 +7,145 @@
-->
<form [formGroup]="recurringDepositAccountSettingsForm">
<div class="layout-row-wrap gap-2px responsive-column">
<mat-checkbox labelPosition="before" formControlName="isMandatoryDeposit" class="margin-v flex-48">
{{ 'labels.inputs.Is Mandatory Deposit' | translate }}?
</mat-checkbox>
<mat-checkbox class="flex-48 margin-v" labelPosition="before" formControlName="adjustAdvanceTowardsFuturePayments">
{{ 'labels.inputs.Adjust advance payments toward future installments' | translate }}?
</mat-checkbox>
<mat-checkbox labelPosition="before" formControlName="allowWithdrawal" class="margin-v flex-48">
{{ 'labels.inputs.Allow Withdrawals' | translate }}?
</mat-checkbox>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Lock-in Period' | translate }}</h4>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input type="number" matInput formControlName="lockinPeriodFrequency" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
<mat-select formControlName="lockinPeriodFrequencyType">
@for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) {
<mat-option [value]="lockinPeriodFrequencyType.id">
{{ lockinPeriodFrequencyType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Recurring Deposit Details' | translate }}</h4>
@if (currency) {
<mifosx-input-amount
[currency]="currency"
[isRequired]="true"
class="flex-48"
[inputFormControl]="recurringDepositAccountSettingsForm.controls.mandatoryRecommendedDepositAmount"
[inputLabel]="'Recurring Deposit Amount'"
>
</mifosx-input-amount>
}
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Deposit Period' | translate }}</h4>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Deposit Period' | translate }}</mat-label>
<input type="number" matInput formControlName="depositPeriod" required />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
<mat-select formControlName="depositPeriodFrequencyId" required>
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
<mat-option [value]="periodFrequencyType.id">
{{ periodFrequencyType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-checkbox labelPosition="before" formControlName="isCalendarInherited" class="margin-v flex-48">
{{ 'labels.inputs.Deposit Frequency Same as Group/Center meeting' | translate }}
</mat-checkbox>
@if (!recurringDepositAccountSettingsForm.value.isCalendarInherited) {
<mat-form-field class="flex-48" (click)="expectedFirstDepositOnDatePicker.open()">
<mat-label>{{ 'labels.inputs.Deposit Start Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="expectedFirstDepositOnDatePicker"
formControlName="expectedFirstDepositOnDate"
required
/>
<mat-datepicker-toggle matSuffix [for]="expectedFirstDepositOnDatePicker"></mat-datepicker-toggle>
<mat-datepicker #expectedFirstDepositOnDatePicker></mat-datepicker>
</mat-form-field>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Deposit Frequency' | translate }}</h4>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Deposit Frequency' | translate }}</mat-label>
<input type="number" matInput formControlName="recurringFrequency" required />
<mat-error>
{{ 'labels.inputs.Deposit Frequency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
<mat-select formControlName="recurringFrequencyType" required>
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
<mat-option [value]="periodFrequencyType.id">
{{ periodFrequencyType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Deposit Frequency Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
}
<mat-divider class="flex-98"></mat-divider>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Minimum Deposit Term' | translate }}</h4>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input type="number" matInput formControlName="minDepositTerm" required />
<mat-error>
{{ 'labels.inputs.Minimum Deposit Term Frequency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency Type' | translate }}</mat-label>
<mat-select formControlName="minDepositTermTypeId" required>
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
<mat-option [value]="periodFrequencyType.id">
{{ periodFrequencyType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Minimum Deposit Term Frequency Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.And thereafter, in Multiples of' | translate }}</h4>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input type="number" matInput formControlName="inMultiplesOfDepositTerm" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
<mat-select formControlName="inMultiplesOfDepositTermTypeId">
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
<mat-option [value]="periodFrequencyType.id">
{{ periodFrequencyType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Maximum Deposit Term' | translate }}</h4>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input type="number" matInput formControlName="maxDepositTerm" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
<mat-select formControlName="maxDepositTermTypeId">
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
<mat-option [value]="periodFrequencyType.id">
{{ periodFrequencyType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-divider class="flex-98"></mat-divider>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.For Pre-mature closure' | translate }}</h4>
<mat-checkbox class="flex-73 margin-v" labelPosition="before" formControlName="preClosurePenalApplicable">
{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}
</mat-checkbox>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Penal Interest' | translate }} (%)</mat-label>
<input type="number" matInput formControlName="preClosurePenalInterest" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Period' | translate }}</mat-label>
<mat-select formControlName="preClosurePenalInterestOnTypeId">
@for (preClosurePenalInterestOnType of preClosurePenalInterestOnTypeData; track preClosurePenalInterestOnType) {
<mat-option [value]="preClosurePenalInterestOnType.id">
{{ preClosurePenalInterestOnType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<!-- <mat-divider class="flex-98"></mat-divider> -->
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Minimum Balance For Interest Calculation' | translate }}</mat-label>
<input type="number" matInput formControlName="minBalanceForInterestCalculation" />
</mat-form-field>
@if (taxGroup) {
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Tax Group' | translate }}</h4>
<mat-checkbox labelPosition="before" formControlName="withHoldTax" class="margin-v flex-48">
{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}?
</mat-checkbox>
@if (recurringDepositAccountSettingsForm.controls.taxGroupId) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Tax Group' | translate }}</mat-label>
<input type="text" matInput formControlName="taxGroupId" />
</mat-form-field>
}
}
<!-- ===================== Premature Option ===================== -->
<div class="rdps-section">
<div class="rdps-section__header">
<span class="rdps-section__badge"><i class="fas fa-balance-scale"></i></span>
<span>Premature option</span>
</div>
<div class="rdps-body">
<div class="rdps-premature-row">
<mat-checkbox formControlName="loanFacility">Loan Facility</mat-checkbox>
<mat-checkbox formControlName="prematureWithdrawalOption">Premature Withdrawal option</mat-checkbox>
<mat-checkbox formControlName="tenureFlexibility">Tenure Flexibility</mat-checkbox>
<mat-checkbox formControlName="lienMarked">Lien Marked</mat-checkbox>
<mat-checkbox formControlName="withdrawalFeeForTransfers">Apply Withdrawal Fee for Transfers</mat-checkbox>
</div>
</div>
</div>
<!-- ===================== Lock-in Period ===================== -->
<div class="rdps-section">
<div class="rdps-section__header">
<span class="rdps-section__badge"><i class="fas fa-lock"></i></span>
<span>{{ 'labels.inputs.Lock-in Period' | translate }}</span>
</div>
<div class="rdps-body">
<div class="rdps-lockin-row">
<span>Enable Lock-in Period</span>
<mat-checkbox formControlName="enableLockinPeriod"></mat-checkbox>
</div>
@if (recurringDepositAccountSettingsForm.value.enableLockinPeriod) {
<div class="rdps-lockin-fields">
<mat-form-field appearance="outline" class="rdps-field">
<mat-label>Frequency*</mat-label>
<input type="number" matInput formControlName="lockinPeriodFrequency" min="1" step="1" />
</mat-form-field>
<mat-form-field appearance="outline" class="rdps-field">
<mat-label>Type*</mat-label>
<mat-select formControlName="lockinPeriodFrequencyType">
@for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) {
<mat-option [value]="lockinPeriodFrequencyType.id">
{{ lockinPeriodFrequencyType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
}
</div>
</div>
<!-- ===================== Maturity Instructions ===================== -->
<div class="rdps-section">
<div class="rdps-section__header">
<span class="rdps-section__badge"><i class="fas fa-lock"></i></span>
<span>Maturity Instructions</span>
</div>
<div class="rdps-body rdps-body--list">
<div class="rdps-list-row">
<span>Maturity Instructions</span>
<mat-checkbox formControlName="maturityInstructions"></mat-checkbox>
</div>
<div class="rdps-list-row">
<span
>Option to credit maturity proceeds directly to the linked account or reinvest in another product (like
FD).</span
>
<mat-checkbox formControlName="creditMaturityProceeds"></mat-checkbox>
</div>
<div class="rdps-list-row">
<span>Penalty / Grace Period for Missed Installments</span>
<mat-checkbox formControlName="penaltyGracePeriodMissedInstallments"></mat-checkbox>
</div>
<div class="rdps-list-row">
<span>Panel Request Applicable</span>
<mat-checkbox formControlName="panelRequestApplicable"></mat-checkbox>
</div>
<div class="rdps-list-row">
<span>No penalty for nominee / legal heir closure</span>
<mat-checkbox formControlName="noPenaltyNomineeHeirClosure"></mat-checkbox>
</div>
<div class="rdps-list-row">
<span>Deceased Settlement</span>
<mat-checkbox formControlName="deceasedSettlement"></mat-checkbox>
</div>
<div class="rdps-list-row">
<span>Allow Debit Txn after due date</span>
<mat-checkbox formControlName="allowDebitTxnAfterDueDate"></mat-checkbox>
</div>
</div>
</div>
<!-- ===================== Additional Rules ===================== -->
<div class="rdps-section">
<div class="rdps-section__header">
<span class="rdps-section__badge"><i class="fas fa-cog"></i></span>
<span>Additional Rules</span>
</div>
<div class="rdps-body">
<div class="rdps-layout rdps-layout--additional">
<div class="rdps-toggle rdps-toggle--inline">
<mat-checkbox formControlName="accountClosedZeroBalanceInDayClosing">
<span>Account Closed with Zero balance in Day Closing</span>
</mat-checkbox>
</div>
<mat-form-field appearance="outline" class="rdps-field">
<mat-label>Balance Required for Interest Calculation</mat-label>
<input type="number" matInput formControlName="minBalanceForInterestCalculation" min="0" step="0.01" />
</mat-form-field>
</div>
</div>
</div>
<!-- ===================== Withhold Tax (if applicable) ===================== -->
@if (recurringDepositAccountSettingsForm.contains('withHoldTax')) {
<div class="rdps-section">
<div class="rdps-section__header">
<span class="rdps-section__badge"><i class="fas fa-percent"></i></span>
<span>{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}</span>
</div>
<div class="rdps-body">
<div class="rdps-lockin-row">
<span>{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}</span>
<mat-checkbox formControlName="withHoldTax"></mat-checkbox>
</div>
@if (
recurringDepositAccountSettingsForm.value.withHoldTax &&
recurringDepositAccountSettingsForm.contains('taxGroupId')
) {
<mat-form-field appearance="outline" class="rdps-field">
<mat-label>{{ 'labels.inputs.Tax Group' | translate }}</mat-label>
<input matInput formControlName="taxGroupId" readonly />
</mat-form-field>
}
</div>
</div>
}
<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>
@@ -6,27 +6,178 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
h4 {
font-weight: 500;
margin: 1em 0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-900: #202124;
.rdps-section {
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & {
margin-top: 16px;
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
span:not(.rdps-section__badge) {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
}
h3 {
font-weight: 500;
.rdps-section__badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 6px;
background: #eff6ff;
font-size: 14px;
}
mat-divider {
margin: 1em 0 2em;
.rdps-body {
background: #fff;
padding: 18px 24px 24px;
}
.margin-v {
margin: 1em 0;
.rdps-body--list {
padding: 0 24px;
}
.margin-b {
margin: 0 0 1em;
.rdps-field {
width: 100%;
}
.margin-t {
margin-top: 1em;
.rdps-premature-row {
display: flex;
align-items: center;
gap: 24px;
flex-wrap: wrap;
mat-checkbox {
font-size: 14px;
color: $gray-900;
}
}
.rdps-layout {
display: grid;
gap: 8px 16px;
&--two {
grid-template-columns: repeat(2, minmax(0, 1fr));
@media (width <= 640px) {
grid-template-columns: 1fr;
}
}
&--mt {
margin-top: 12px;
}
}
.rdps-toggle {
display: flex;
align-items: center;
min-height: 40px;
mat-checkbox {
font-size: 14px;
color: $gray-900;
}
&--inline {
align-items: center;
}
}
.rdps-layout--additional {
grid-template-columns: repeat(2, minmax(0, 1fr));
@media (width <= 1100px) {
grid-template-columns: 1fr;
}
}
.rdps-list-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 14px 0;
border-bottom: 1px solid $gray-200;
font-size: 14px;
color: $gray-900;
&:last-child {
border-bottom: none;
}
span {
max-width: 85%;
line-height: 1.4;
}
}
.rdps-lockin-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid $gray-200;
font-size: 14px;
color: $gray-900;
}
.rdps-lockin-fields {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
@media (width <= 640px) {
grid-template-columns: 1fr;
}
}
.rdps-sub-header {
font-size: 13px;
font-weight: 600;
color: $gray-900;
margin: 16px 0 8px;
padding-bottom: 6px;
border-bottom: 1px solid $gray-200;
}
.rdps-checkbox-row {
display: flex;
align-items: center;
min-height: 56px;
mat-checkbox {
font-size: 14px;
color: $gray-900;
}
}
@@ -24,8 +24,6 @@ import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/** Custom Services */
/**
* Recurring Deposits Account Settings Step
*/
@@ -58,19 +56,15 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
/** Maximum date allowed. */
maxDate = new Date();
/** Lockin Period Frequency Type Data */
lockinPeriodFrequencyTypeData: any;
lockinPeriodFrequencyTypeData: any[] = [];
/** Period Frequency Type Data */
periodFrequencyTypeData: any;
periodFrequencyTypeData: any[] = [];
/** Preclosure Penal Interest Type on Data */
preClosurePenalInterestOnTypeData: any;
preClosurePenalInterestOnTypeData: any[] = [];
/** Tax Group */
taxGroup: any;
currency: Currency | null = null;
/**
* @param {FormBuilder} formBuilder Form Builder
* @param {SettingsService} settingsService Settings Service
*/
constructor() {
this.createRecurringDepositAccountSettingsForm();
this.buildDependencies();
@@ -82,14 +76,33 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
: this.recurringDepositsAccountTemplate;
if (recurringDepositsAccount) {
this.currency = recurringDepositsAccount.currency;
// Patch CBS settings fields from product template
this.recurringDepositAccountSettingsForm.patchValue({
isMandatoryDeposit: recurringDepositsAccount.isMandatoryDeposit,
adjustAdvanceTowardsFuturePayments: recurringDepositsAccount.adjustAdvanceTowardsFuturePayments,
allowWithdrawal: recurringDepositsAccount.allowWithdrawal,
lockinPeriodFrequency: recurringDepositsAccount.lockinPeriodFrequency,
loanFacility: recurringDepositsAccount.loanFacility ?? false,
prematureWithdrawalOption: recurringDepositsAccount.prematureWithdrawalOption ?? false,
tenureFlexibility: recurringDepositsAccount.tenureFlexibility ?? false,
lienMarked: recurringDepositsAccount.lienMarked ?? false,
withdrawalFeeForTransfers: recurringDepositsAccount.withdrawalFeeForTransfers ?? false,
enableLockinPeriod: recurringDepositsAccount.enableLockinPeriod ?? false,
lockinPeriodFrequency: recurringDepositsAccount.lockinPeriodFrequency ?? '',
lockinPeriodFrequencyType: recurringDepositsAccount.lockinPeriodFrequencyType
? recurringDepositsAccount.lockinPeriodFrequencyType.id
: '',
maturityInstructions: recurringDepositsAccount.maturityInstructions ?? false,
creditMaturityProceeds: recurringDepositsAccount.creditMaturityProceeds ?? false,
penaltyGracePeriodMissedInstallments: recurringDepositsAccount.penaltyGracePeriodMissedInstallments ?? false,
panelRequestApplicable: recurringDepositsAccount.panelRequestApplicable ?? false,
noPenaltyNomineeHeirClosure: recurringDepositsAccount.noPenaltyNomineeHeirClosure ?? false,
deceasedSettlement: recurringDepositsAccount.deceasedSettlement ?? false,
allowDebitTxnAfterDueDate: recurringDepositsAccount.allowDebitTxnAfterDueDate ?? false,
accountClosedZeroBalanceInDayClosing: recurringDepositsAccount.accountClosedZeroBalanceInDayClosing ?? false,
minBalanceForInterestCalculation: recurringDepositsAccount.minBalanceForInterestCalculation ?? '',
// Account-specific fields — auto-filled from product template (hidden from UI)
isMandatoryDeposit: recurringDepositsAccount.isMandatoryDeposit,
adjustAdvanceTowardsFuturePayments: recurringDepositsAccount.adjustAdvanceTowardsFuturePayments,
allowWithdrawal: recurringDepositsAccount.allowWithdrawal,
minDepositTerm: recurringDepositsAccount.minDepositTerm,
minDepositTermTypeId: recurringDepositsAccount.minDepositTermType
? recurringDepositsAccount.minDepositTermType.id
@@ -107,17 +120,16 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
preClosurePenalInterestOnTypeId: recurringDepositsAccount.preClosurePenalInterestOnType
? recurringDepositsAccount.preClosurePenalInterestOnType.id
: '',
minBalanceForInterestCalculation: recurringDepositsAccount.minBalanceForInterestCalculation,
depositPeriod: recurringDepositsAccount.minDepositTerm,
depositPeriodFrequencyId: recurringDepositsAccount.minDepositTermType.id,
expectedFirstDepositOnDate:
recurringDepositsAccount.expectedFirstDepositOnDate &&
new Date(recurringDepositsAccount.expectedFirstDepositOnDate),
depositPeriodFrequencyId: recurringDepositsAccount.minDepositTermType
? recurringDepositsAccount.minDepositTermType.id
: '',
recurringFrequency: recurringDepositsAccount.recurringFrequency,
recurringFrequencyType: recurringDepositsAccount.recurringFrequencyType
? recurringDepositsAccount.recurringFrequencyType.id
: ''
});
if (recurringDepositsAccount.withHoldTax) {
this.recurringDepositAccountSettingsForm.addControl('withHoldTax', new UntypedFormControl(false));
this.recurringDepositAccountSettingsForm.get('withHoldTax').valueChanges.subscribe((value: boolean) => {
@@ -135,7 +147,7 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
});
this.recurringDepositAccountSettingsForm
.get('withHoldTax')
.patchValue(this.recurringDepositsAccountTemplate.withHoldTax);
.patchValue(this.recurringDepositsAccountTemplate?.withHoldTax);
} else {
this.recurringDepositAccountSettingsForm.removeControl('withHoldTax');
}
@@ -157,38 +169,38 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
}
}
/**
* Creates recurring deposits account terms form.
*/
createRecurringDepositAccountSettingsForm() {
this.recurringDepositAccountSettingsForm = this.formBuilder.group({
// CBS Settings (visible — same as product settings page)
loanFacility: [false],
prematureWithdrawalOption: [false],
tenureFlexibility: [false],
lienMarked: [false],
withdrawalFeeForTransfers: [false],
enableLockinPeriod: [false],
lockinPeriodFrequency: [''],
lockinPeriodFrequencyType: [''],
maturityInstructions: [false],
creditMaturityProceeds: [false],
penaltyGracePeriodMissedInstallments: [false],
panelRequestApplicable: [false],
noPenaltyNomineeHeirClosure: [false],
deceasedSettlement: [false],
allowDebitTxnAfterDueDate: [false],
accountClosedZeroBalanceInDayClosing: [false],
minBalanceForInterestCalculation: [''],
// Account-specific fields (hidden from UI — auto-filled from product template)
isMandatoryDeposit: [''],
adjustAdvanceTowardsFuturePayments: [''],
allowWithdrawal: [''],
lockinPeriodFrequency: [''],
lockinPeriodFrequencyType: [''],
mandatoryRecommendedDepositAmount: [
'',
Validators.required
],
depositPeriod: [
'',
Validators.required
],
depositPeriodFrequencyId: [
'',
Validators.required
],
mandatoryRecommendedDepositAmount: [''],
depositPeriod: [''],
depositPeriodFrequencyId: [''],
isCalendarInherited: [''],
expectedFirstDepositOnDate: [''],
recurringFrequency: [
'',
Validators.required
],
recurringFrequencyType: [
'',
Validators.required
],
recurringFrequency: [''],
recurringFrequencyType: [''],
minDepositTerm: [{ value: '', disabled: true }],
minDepositTermTypeId: [{ value: '', disabled: true }],
inMultiplesOfDepositTerm: [{ value: '', disabled: true }],
@@ -197,23 +209,16 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
maxDepositTermTypeId: [{ value: '', disabled: true }],
preClosurePenalApplicable: [{ value: '', disabled: true }],
preClosurePenalInterest: [{ value: '', disabled: true }],
preClosurePenalInterestOnTypeId: [{ value: '', disabled: true }],
minBalanceForInterestCalculation: [{ value: '', disabled: true }]
preClosurePenalInterestOnTypeId: [{ value: '', disabled: true }]
});
}
/**
* Sets all select dropdown options.
*/
setOptions(recurringDepositsAccount: any) {
this.lockinPeriodFrequencyTypeData = recurringDepositsAccount.lockinPeriodFrequencyTypeOptions;
this.periodFrequencyTypeData = recurringDepositsAccount.periodFrequencyTypeOptions;
this.preClosurePenalInterestOnTypeData = recurringDepositsAccount.preClosurePenalInterestOnTypeOptions;
}
/**
* Subscribes to value changes and sets new form controls accordingly.
*/
buildDependencies() {
this.recurringDepositAccountSettingsForm
.get('isCalendarInherited')
@@ -230,9 +235,6 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
});
}
/**
* Returns recurring deposits account settings form value.
*/
get recurringDepositAccountSettings() {
return this.recurringDepositAccountSettingsForm.value;
}
@@ -7,69 +7,171 @@
-->
<form [formGroup]="recurringDepositAccountTermsForm">
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Interest Compounding Period' | translate }}</mat-label>
<mat-select formControlName="interestCompoundingPeriodType" required>
@for (interestCompoundingPeriodType of interestCompoundingPeriodTypeData; track interestCompoundingPeriodType) {
<mat-option [value]="interestCompoundingPeriodType.id">
{{ interestCompoundingPeriodType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Compounding Period' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<!-- ===================== Interest Rate & Posting Terms ===================== -->
<div class="rdpt-section">
<div class="rdpt-section__header">
<span class="rdpt-section__badge"><i class="fas fa-chart-line"></i></span>
<span>Interest Rate &amp; Posting Terms</span>
</div>
<div class="rdpt-body">
@if (recurringDepositsAccountProductTemplate?.nominalAnnualInterestRate != null) {
<div class="rdpt-banner">
<span class="rdpt-banner__icon">ℹ️</span>
<span>
Recurring Deposit base rate:
<b>{{ recurringDepositsAccountProductTemplate?.nominalAnnualInterestRate }}%</b>
</span>
</div>
}
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Interest Posting Period' | translate }}</mat-label>
<mat-select formControlName="interestPostingPeriodType" required>
@for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) {
<mat-option [value]="interestPostingPeriodType.id">
{{ interestPostingPeriodType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<!-- Row 1: Nominal Annual Interest | Effective Date | Interest Calculation Type -->
<div class="rdpt-grid rdpt-grid--three">
<mat-form-field appearance="outline" class="rdpt-field">
<mat-label>Nominal Annual Interest</mat-label>
<input matInput [value]="recurringDepositsAccountProductTemplate?.nominalAnnualInterestRate ?? ''" readonly />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Interest Calculated using' | translate }}</mat-label>
<mat-select formControlName="interestCalculationType" required>
@for (interestCalculationType of interestCalculationTypeData; track interestCalculationType) {
<mat-option [value]="interestCalculationType.id">
{{ interestCalculationType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Calculated using' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="rdpt-field">
<mat-label>Effective Date</mat-label>
<input matInput [value]="formatDate(recurringDepositsAccountProductTemplate?.effectiveDate)" readonly />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Days in Year' | translate }}</mat-label>
<mat-select formControlName="interestCalculationDaysInYearType" required>
@for (
interestCalculationDaysInYearType of interestCalculationDaysInYearTypeData;
track interestCalculationDaysInYearType
) {
<mat-option [value]="interestCalculationDaysInYearType.id">
{{ interestCalculationDaysInYearType.value | translateKey: 'catalogs' }}
</mat-option>
<mat-form-field appearance="outline" class="rdpt-field">
<mat-label>Interest Calculation Type</mat-label>
<input
matInput
[value]="
recurringDepositsAccountProductTemplate?.interestCalculationTypeName ??
(recurringDepositsAccountProductTemplate?.interestCalculationType?.id
| find: recurringDepositsAccountProductTemplate?.interestCalculationTypeOptions : 'id' : 'value') ??
''
"
readonly
/>
</mat-form-field>
</div>
<!-- Row 2: Interest Calculate | Average Period | Interest Posting Period -->
<div class="rdpt-grid rdpt-grid--three">
<mat-form-field appearance="outline" class="rdpt-field">
<mat-label>{{ 'labels.inputs.Interest Calculated using' | translate }}</mat-label>
<mat-select formControlName="interestCalculationType" required>
@for (interestCalculationType of interestCalculationTypeData; track interestCalculationType) {
<mat-option [value]="interestCalculationType.id">
{{ interestCalculationType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Calculated using' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="rdpt-field">
<mat-label>Average Period</mat-label>
<input matInput [value]="recurringDepositsAccountProductTemplate?.averagePeriod ?? ''" readonly />
</mat-form-field>
<mat-form-field appearance="outline" class="rdpt-field">
<mat-label>{{ 'labels.inputs.Interest Posting Period' | translate }}</mat-label>
<mat-select formControlName="interestPostingPeriodType" required>
@for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) {
<mat-option [value]="interestPostingPeriodType.id">
{{ interestPostingPeriodType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
</div>
<!-- Row 3: Interest Compounding Period -->
<div class="rdpt-grid rdpt-grid--three">
<mat-form-field appearance="outline" class="rdpt-field">
<mat-label>{{ 'labels.inputs.Interest Compounding Period' | translate }}</mat-label>
<mat-select formControlName="interestCompoundingPeriodType" required>
@for (
interestCompoundingPeriodType of interestCompoundingPeriodTypeData;
track interestCompoundingPeriodType
) {
<mat-option [value]="interestCompoundingPeriodType.id">
{{ interestCompoundingPeriodType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Compounding Period' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
</div>
<!-- Footer: Auto Interest Post | Frequency Fix Day | Last Interest Date -->
<div class="rdpt-footer">
<div class="rdpt-footer__grid">
<div class="rdpt-toggle-card">
<div>
<h4>Auto Interest Post</h4>
</div>
<div class="rdpt-toggle-card__choices">
<label class="rdpt-radio">
<input type="radio" [checked]="recurringDepositsAccountProductTemplate?.autoPost === true" disabled />
<span>Yes</span>
</label>
<label class="rdpt-radio">
<input type="radio" [checked]="recurringDepositsAccountProductTemplate?.autoPost === false" disabled />
<span>No</span>
</label>
</div>
</div>
<mat-form-field appearance="outline" class="rdpt-field">
<mat-label>Frequency (Fix Day)</mat-label>
<input matInput [value]="recurringDepositsAccountProductTemplate?.frequencyFixDay ?? ''" readonly />
<mat-hint>Days</mat-hint>
</mat-form-field>
<mat-form-field appearance="outline" class="rdpt-field">
<mat-label>Last Interest Date</mat-label>
<input matInput [value]="formatDate(recurringDepositsAccountProductTemplate?.lastInterestDate)" readonly />
</mat-form-field>
</div>
@if (recurringDepositAccountTermsForm.value.interestCalculationType === 1) {
<div class="rdpt-note">
<span>⚠️</span>
<span
>Daily product-based interest accrual enabled - interest calculated EOD on daily closing balance.</span
>
</div>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Days in Year' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<!-- Days in Year (account-level field) -->
<div class="rdpt-footer__grid rdpt-footer__grid--single">
<mat-form-field appearance="outline" class="rdpt-field">
<mat-label>{{ 'labels.inputs.Days in Year' | translate }}</mat-label>
<mat-select formControlName="interestCalculationDaysInYearType" required>
@for (
interestCalculationDaysInYearType of interestCalculationDaysInYearTypeData;
track interestCalculationDaysInYearType
) {
<mat-option [value]="interestCalculationDaysInYearType.id">
{{ interestCalculationDaysInYearType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Days in Year' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
</div>
</div>
</div>
</div>
<div class="layout-row align-center gap-2percent margin-t responsive-column">
@@ -5,3 +5,167 @@
* 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/.
*/
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-900: #202124;
.rdpt-section {
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & {
margin-top: 16px;
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3,
span:not(.rdpt-section__badge) {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
}
.rdpt-section__badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 6px;
background: #eff6ff;
font-size: 14px;
}
.rdpt-body {
background: #fff;
}
.rdpt-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
&--three {
grid-template-columns: repeat(3, 1fr);
}
@media (width <= 600px) {
grid-template-columns: 1fr;
&--three {
grid-template-columns: 1fr;
}
}
}
.rdpt-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
.rdpt-banner {
display: flex;
align-items: center;
gap: 10px;
margin: 16px 24px 0;
padding: 12px 14px;
background: #eff6ff;
border: 1px solid #dbeafe;
border-radius: 8px;
color: #1d4ed8;
font-size: 13px;
&__icon {
font-size: 14px;
}
}
.rdpt-footer {
padding: 0 24px 24px;
border-top: 1px solid #f1f5f9;
}
.rdpt-footer__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
padding-top: 18px;
&--single {
grid-template-columns: repeat(1, 1fr);
padding-top: 12px;
}
@media (width <= 900px) {
grid-template-columns: 1fr;
}
}
.rdpt-toggle-card {
padding: 12px 0;
h4 {
margin: 0 0 8px;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
&__choices {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
}
.rdpt-radio {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
cursor: default;
[type='radio'] {
width: 16px;
height: 16px;
accent-color: $accent;
}
}
.rdpt-note {
display: flex;
align-items: center;
gap: 10px;
margin-top: 18px;
padding: 12px 14px;
border: 1px solid #fde68a;
background: #fffbeb;
color: #b45309;
border-radius: 8px;
font-size: 12px;
}
@@ -13,6 +13,7 @@ import { SettingsService } from 'app/settings/settings.service';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { FindPipe } from '../../../../pipes/find.pipe';
/**
* Recurring Deposits Terms Step
@@ -25,7 +26,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS,
MatStepperPrevious,
FaIconComponent,
MatStepperNext
MatStepperNext,
FindPipe
]
})
export class RecurringDepositsAccountTermsStepComponent implements OnInit, OnChanges {
@@ -42,13 +44,13 @@ export class RecurringDepositsAccountTermsStepComponent implements OnInit, OnCha
/** Recurring Deposits Account Terms Form */
recurringDepositAccountTermsForm: UntypedFormGroup;
/** Interest Compounding Period Type Data */
interestCompoundingPeriodTypeData: any;
interestCompoundingPeriodTypeData: any[] = [];
/** Interest Posting Period Type Data */
interestPostingPeriodTypeData: any;
interestPostingPeriodTypeData: any[] = [];
/** Interest Calculation Type Data */
interestCalculationTypeData: any;
interestCalculationTypeData: any[] = [];
/** Interest Calculation Days in Year Data */
interestCalculationDaysInYearTypeData: any;
interestCalculationDaysInYearTypeData: any[] = [];
/**
* @param {FormBuilder} formBuilder Form Builder
@@ -61,17 +63,18 @@ export class RecurringDepositsAccountTermsStepComponent implements OnInit, OnCha
ngOnChanges() {
if (this.recurringDepositsAccountProductTemplate) {
this.setOptions();
this.initializeTermsValues();
}
}
ngOnInit() {
this.maxDate = this.settingsService.businessDate;
if (this.recurringDepositsAccountTemplate.id) {
if (this.isEditMode()) {
this.recurringDepositAccountTermsForm.patchValue({
interestCompoundingPeriodType: this.recurringDepositsAccountTemplate.interestCompoundingPeriodType.id,
interestPostingPeriodType: this.recurringDepositsAccountTemplate.interestPostingPeriodType.id,
interestCalculationType: this.recurringDepositsAccountTemplate.interestCalculationType.id,
interestCalculationDaysInYearType: this.recurringDepositsAccountTemplate.interestCalculationDaysInYearType.id
interestCompoundingPeriodType: this.recurringDepositsAccountTemplate?.interestCompoundingPeriodType?.id,
interestPostingPeriodType: this.recurringDepositsAccountTemplate?.interestPostingPeriodType?.id,
interestCalculationType: this.recurringDepositsAccountTemplate?.interestCalculationType?.id,
interestCalculationDaysInYearType: this.recurringDepositsAccountTemplate?.interestCalculationDaysInYearType?.id
});
}
}
@@ -110,17 +113,70 @@ export class RecurringDepositsAccountTermsStepComponent implements OnInit, OnCha
this.interestCalculationTypeData = this.recurringDepositsAccountProductTemplate.interestCalculationTypeOptions;
this.interestCalculationDaysInYearTypeData =
this.recurringDepositsAccountProductTemplate.interestCalculationDaysInYearTypeOptions;
if (!this.recurringDepositsAccountTemplate.id) {
if (!this.isEditMode()) {
this.recurringDepositAccountTermsForm.patchValue({
interestCompoundingPeriodType: this.recurringDepositsAccountProductTemplate.interestCompoundingPeriodType.id,
interestPostingPeriodType: this.recurringDepositsAccountProductTemplate.interestPostingPeriodType.id,
interestCalculationType: this.recurringDepositsAccountProductTemplate.interestCalculationType.id,
interestCompoundingPeriodType: this.recurringDepositsAccountProductTemplate?.interestCompoundingPeriodType?.id,
interestPostingPeriodType: this.recurringDepositsAccountProductTemplate?.interestPostingPeriodType?.id,
interestCalculationType: this.recurringDepositsAccountProductTemplate?.interestCalculationType?.id,
interestCalculationDaysInYearType:
this.recurringDepositsAccountProductTemplate.interestCalculationDaysInYearType.id
this.recurringDepositsAccountProductTemplate?.interestCalculationDaysInYearType?.id
});
}
}
/** True when component is used for account edit flow. */
private isEditMode(): boolean {
return !!this.recurringDepositsAccountTemplate?.depositProductId;
}
/**
* Ensures create flow always gets product defaults while edit flow keeps saved values.
*/
private initializeTermsValues(): void {
const isEdit = this.isEditMode();
const fallbackFromProduct = {
interestCompoundingPeriodType: this.recurringDepositsAccountProductTemplate?.interestCompoundingPeriodType?.id,
interestPostingPeriodType: this.recurringDepositsAccountProductTemplate?.interestPostingPeriodType?.id,
interestCalculationType: this.recurringDepositsAccountProductTemplate?.interestCalculationType?.id,
interestCalculationDaysInYearType:
this.recurringDepositsAccountProductTemplate?.interestCalculationDaysInYearType?.id
};
if (!isEdit) {
this.recurringDepositAccountTermsForm.patchValue(fallbackFromProduct);
return;
}
const current = this.recurringDepositAccountTermsForm.value;
this.recurringDepositAccountTermsForm.patchValue({
interestCompoundingPeriodType:
current.interestCompoundingPeriodType ?? fallbackFromProduct.interestCompoundingPeriodType,
interestPostingPeriodType: current.interestPostingPeriodType ?? fallbackFromProduct.interestPostingPeriodType,
interestCalculationType: current.interestCalculationType ?? fallbackFromProduct.interestCalculationType,
interestCalculationDaysInYearType:
current.interestCalculationDaysInYearType ?? fallbackFromProduct.interestCalculationDaysInYearType
});
}
/**
* Formats a date value (Date, [y,m,d] array, or string) to a display string.
*/
formatDate(value: any): string {
if (!value) return '';
if (Array.isArray(value)) {
const [
y,
m,
d
] = value;
return `${String(d).padStart(2, '0')}/${String(m).padStart(2, '0')}/${y}`;
}
if (value instanceof Date) {
return value.toLocaleDateString();
}
return String(value);
}
/**
* Returns recurring deposits account terms form value.
*/