-
-
-
- | {{ 'labels.inputs.Client ID' | translate }} |
- {{ element.id }} |
-
-
-
- {{ 'labels.inputs.Client Name' | translate }} |
- {{ element.displayName }} |
-
-
-
-
+
+
+
+
+
+ | {{ 'labels.inputs.Client ID' | translate }} |
+ {{ element.id }} |
+
+
+ {{ 'labels.inputs.Client Name' | translate }} |
+ {{ element.displayName }} |
+
+
+
+
+
}
-
{{ 'labels.heading.Terms' | translate }}
-
-
-
- {{ 'labels.inputs.Currency' | translate }}
- {{ savingsAccountTermsForm.get('currencyCode').value }}
-
-
-
- {{ 'labels.inputs.Decimal Places' | translate }}
- {{ savingsAccountTermsForm.get('decimal').value }}
-
-
-
- {{ 'labels.inputs.Nominal Annual Interest' | translate }}:
- {{ savingsAccount.nominalAnnualInterestRate | formatNumber }} %
-
-
-
- {{ 'labels.inputs.Interest Compounding Period' | translate }}:
- {{
- getCatalogTranslation(
- savingsAccount.interestCompoundingPeriodType
- | find: savingsAccountProductTemplate.interestCompoundingPeriodTypeOptions : 'id' : 'value'
- )
- }}
-
-
-
- {{ 'labels.inputs.Interest Posting Period' | translate }}:
- {{
- getCatalogTranslation(
- savingsAccount.interestPostingPeriodType
- | find: savingsAccountProductTemplate.interestPostingPeriodTypeOptions : 'id' : 'value'
- )
- }}
-
-
-
- {{ 'labels.inputs.Interest Calculated using' | translate }}:
- {{
- getCatalogTranslation(
- savingsAccount.interestCalculationType
- | find: savingsAccountProductTemplate.interestCalculationTypeOptions : 'id' : 'value'
- )
- }}
-
-
-
- {{ 'labels.inputs.Days in Year' | translate }}:
- {{
- getCatalogTranslation(
- savingsAccount.interestCalculationDaysInYearType
- | find: savingsAccountProductTemplate.interestCalculationDaysInYearTypeOptions : 'id' : 'value'
- )
- }}
-
-
- @if (savingsAccount.minRequiredOpeningBalance) {
-
- {{ 'labels.inputs.Minimum Opening Balance' | translate }}:
- {{ savingsAccount.minRequiredOpeningBalance | formatNumber }}
-
- }
-
-
- {{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}:
- {{ savingsAccount.withdrawalFeeForTransfers | yesNo }}
-
-
- @if (savingsAccount.lockinPeriodFrequency) {
-
- {{ 'labels.inputs.Lock-in Period' | translate }}:
- {{
- getCatalogTranslation(
- savingsAccount.lockinPeriodFrequency +
- ' ' +
- (savingsAccount.lockinPeriodFrequencyType
- | find: savingsAccountProductTemplate.lockinPeriodFrequencyTypeOptions : 'id' : 'value')
- )
- }}
-
- }
-
-
- {{ 'labels.inputs.Is Overdraft Allowed' | translate }}:
- {{ savingsAccount.allowOverdraft | yesNo }}
-
-
- @if (savingsAccount.minOverdraftForInterestCalculation) {
-
- {{ 'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate }}:
- {{ savingsAccount.minOverdraftForInterestCalculation | formatNumber }}
-
- }
-
- @if (savingsAccount.nominalAnnualInterestRateOverdraft) {
-
- {{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }}:
- {{ savingsAccount.nominalAnnualInterestRateOverdraft | formatNumber }} %
-
- }
-
- @if (savingsAccount.overdraftLimit) {
-
- {{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }}:
- {{ savingsAccount.overdraftLimit | formatNumber }}
-
- }
-
-
- {{ 'labels.inputs.Enforce Minimum Balance' | translate }}:
- {{ savingsAccount.enforceMinRequiredBalance | yesNo }}
-
-
- @if (savingsAccount.minRequiredBalance) {
-
- {{ 'labels.inputs.Minimum Balance' | translate }}:
- {{ savingsAccount.minRequiredBalance }}
-
- }
-
- @if (savingsAccount.charges.length) {
-
-
{{ 'labels.inputs.Charges' | translate }}
-
-
-
- | {{ 'labels.inputs.Name' | translate }} |
-
- {{ charge.name + ', ' + charge.currency.displaySymbol }}
- |
-
-
- {{ 'labels.inputs.Type' | translate }} |
-
- {{ charge.chargeCalculationType.value | translateKey: 'catalogs' }}
- |
-
-
- {{ 'labels.inputs.Amount' | translate }} |
-
- {{ charge.amount | formatNumber }}
- |
-
-
- {{ 'labels.inputs.Date' | translate }} |
-
- @if (charge.chargeTimeType.value === 'Specified due date' || charge.chargeTimeType.value === 'Weekly Fee') {
-
- {{ (charge.dueDate | dateFormat) || 'Unassigned' }}
-
- }
- @if (charge.chargeTimeType.value === 'Monthly Fee' || charge.chargeTimeType.value === 'Annual Fee') {
-
- {{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }}
-
- }
- @if (
- !(
- charge.chargeTimeType.value === 'Monthly Fee' ||
- charge.chargeTimeType.value === 'Annual Fee' ||
- charge.chargeTimeType.value === 'Specified due date' ||
- charge.chargeTimeType.value === 'Weekly Fee'
- )
- ) {
- N/A
- }
- |
-
-
- {{ 'labels.inputs.Repayments Every' | translate }} |
-
- {{ charge.feeInterval || 'Not Provided' }}
- |
-
-
- {{ 'labels.inputs.Collected On' | translate }} |
-
- {{ charge.chargeTimeType.value }}
- |
-
-
-
-
+ @if (savingsAccount?.charges?.length) {
+
+
+
+
+
+ | {{ 'labels.inputs.Name' | translate }} |
+ {{ charge.name + ', ' + charge.currency.displaySymbol }} |
+
+
+ {{ 'labels.inputs.Type' | translate }} |
+
+ {{ charge.chargeCalculationType.value | translateKey: 'catalogs' }}
+ |
+
+
+ {{ 'labels.inputs.Amount' | translate }} |
+ {{ charge.amount | formatNumber }} |
+
+
+ {{ 'labels.inputs.Date' | translate }} |
+
+ @if (
+ charge.chargeTimeType.value === 'Specified due date' || charge.chargeTimeType.value === 'Weekly Fee'
+ ) {
+ {{ (charge.dueDate | dateFormat) || 'Unassigned' }}
+ }
+ @if (charge.chargeTimeType.value === 'Monthly Fee' || charge.chargeTimeType.value === 'Annual Fee') {
+ {{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }}
+ }
+ @if (
+ !(
+ charge.chargeTimeType.value === 'Monthly Fee' ||
+ charge.chargeTimeType.value === 'Annual Fee' ||
+ charge.chargeTimeType.value === 'Specified due date' ||
+ charge.chargeTimeType.value === 'Weekly Fee'
+ )
+ ) {
+ N/A
+ }
+ |
+
+
+ {{ 'labels.inputs.Repayments Every' | translate }} |
+ {{ charge.feeInterval || 'Not Provided' }} |
+
+
+ {{ 'labels.inputs.Collected On' | translate }} |
+ {{ charge.chargeTimeType.value }} |
+
+
+
+
+
}
diff --git a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.scss b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.scss
index 6a4a4b201..bec5645fb 100644
--- a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.scss
+++ b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.scss
@@ -6,27 +6,95 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-table {
- width: 100%;
+.preview-wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+ padding: 0.5rem 0;
}
-.mat-elevation-z1 {
- margin: 1em 0 1.5em;
+.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;
+.preview-body {
+ padding: 1rem 1.25rem;
+}
+
+.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;
+ }
+ }
+}
+
+.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;
+.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;
+.preview-table {
+ width: 100%;
+ margin-top: 1rem;
}
.margin-t {
diff --git a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts
index e903ca566..aae881819 100644
--- a/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts
+++ b/src/app/savings/savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component.ts
@@ -7,8 +7,7 @@
*/
/** Angular Imports */
-import { Component, Input, Output, EventEmitter, OnChanges, inject } from '@angular/core';
-import { TranslateService } from '@ngx-translate/core';
+import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
import {
MatTableDataSource,
MatTable,
@@ -62,7 +61,24 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class SavingsAccountPreviewStepComponent implements OnChanges {
- private translateService = inject(TranslateService);
+ readonly digitalFacilityLabels = [
+ { key: 'atmFacility', label: 'ATM Facility' },
+ { key: 'atmWithdrawalFacility', label: 'ATM Withdrawal' },
+ { key: 'upiFacility', label: 'UPI Facility' },
+ { key: 'posTransactionsFacility', label: 'POS Transactions' },
+ { key: 'chequeBook', label: 'Cheque Book' },
+ { key: 'impsFacility', label: 'IMPS Facility' },
+ { key: 'smsAlerts', label: 'SMS Alerts' },
+ { key: 'bbpsFacility', label: 'BBPS Facility' },
+ { key: 'insuranceCover', label: 'Insurance Cover' },
+ { key: 'bepgFacility', label: 'BEPG Facility' },
+ { key: 'neftFacility', label: 'NEFT Facility' },
+ { key: 'rtgsFacility', label: 'RTGS Facility' },
+ { key: 'interBankTotalCapFacility', label: 'Inter-Bank Total Cap' },
+ { key: 'withdrawalFacility', label: 'Withdrawal Facility' },
+ { key: 'depositFacilityKyc', label: 'Deposit Facility (KYC)' },
+ { key: 'virtualDebitCard', label: 'Virtual Debit Card' }
+ ];
/** Savings Account Product Template */
@Input() savingsAccountProductTemplate: any;
@@ -102,7 +118,15 @@ export class SavingsAccountPreviewStepComponent implements OnChanges {
}
}
- getCatalogTranslation(text: string): string {
- return this.translateService.instant('labels.catalogs.' + text);
+ get digitalPreview(): any {
+ return this.savingsAccountProductTemplate || {};
+ }
+
+ get hasDigitalSection(): boolean {
+ return !!(
+ this.digitalPreview?.accountFacilityConfig ||
+ this.digitalPreview?.accountFacilityLimits?.length ||
+ this.digitalPreview?.depositLimitKyc
+ );
}
}
diff --git a/src/app/savings/savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component.html b/src/app/savings/savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component.html
new file mode 100644
index 000000000..0cfb16fb7
--- /dev/null
+++ b/src/app/savings/savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component.html
@@ -0,0 +1,113 @@
+
+
+
diff --git a/src/app/savings/savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component.scss b/src/app/savings/savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component.scss
new file mode 100644
index 000000000..4356c409f
--- /dev/null
+++ b/src/app/savings/savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component.scss
@@ -0,0 +1,169 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+$accent: #0ea5e9;
+$border: #e2e8f0;
+$gray-50: #f8f9fa;
+$gray-200: #e8eaed;
+$gray-900: #202124;
+
+.sps-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 {
+ font-size: 14px;
+ font-weight: 600;
+ color: $gray-900;
+ }
+ }
+}
+
+.sps-section__badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border-radius: 6px;
+ background: #eff6ff;
+ font-size: 14px;
+}
+
+.sps-body {
+ background: #fff;
+ padding: 18px 24px 24px;
+}
+
+.sps-field {
+ width: 100%;
+
+ &--full {
+ grid-column: 1 / -1;
+ }
+
+ &--compact {
+ width: 100%;
+ }
+}
+
+.sps-layout {
+ display: grid;
+ gap: 16px;
+}
+
+.sps-layout--balance {
+ gap: 22px;
+}
+
+.sps-layout--additional {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ align-items: start;
+
+ @media (width <= 1100px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.sps-row {
+ display: grid;
+ gap: 18px;
+ align-items: start;
+}
+
+.sps-row--three {
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
+ align-items: center;
+
+ .sps-field--compact:first-child {
+ min-width: 0;
+ }
+
+ .sps-toggle--inline {
+ align-self: center;
+ min-height: 56px;
+ }
+}
+
+.sps-row--two {
+ grid-template-columns: minmax(0, 1fr) auto;
+ align-items: center;
+
+ .sps-field--compact {
+ min-width: 0;
+ }
+}
+
+.sps-toggle {
+ display: flex;
+ align-items: center;
+ min-height: 56px;
+
+ mat-checkbox {
+ font-size: 14px;
+ color: $gray-900;
+ }
+
+ &--full {
+ min-height: auto;
+ margin-bottom: 8px;
+ }
+
+ &--inline {
+ padding-top: 0;
+ }
+
+ &--right {
+ justify-content: flex-start;
+ }
+}
+
+.sps-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;
+}
+
+.sps-lockin-row mat-checkbox {
+ margin-right: 2px;
+}
+
+.sps-lockin-fields {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 16px;
+
+ @media (width <= 640px) {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/app/savings/savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component.ts
new file mode 100644
index 000000000..d3683e753
--- /dev/null
+++ b/src/app/savings/savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component.ts
@@ -0,0 +1,152 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+import { Component, Input, OnInit, inject } from '@angular/core';
+import { UntypedFormGroup, UntypedFormBuilder, Validators, UntypedFormControl } from '@angular/forms';
+import { MatCheckbox } from '@angular/material/checkbox';
+import { FormatNumberPipe } from '../../../pipes/format-number.pipe';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+
+@Component({
+ selector: 'mifosx-savings-account-settings-step',
+ templateUrl: './savings-account-settings-step.component.html',
+ styleUrls: ['./savings-account-settings-step.component.scss'],
+ imports: [
+ ...STANDALONE_SHARED_IMPORTS,
+ MatCheckbox,
+ FormatNumberPipe
+ ]
+})
+export class SavingsAccountSettingsStepComponent implements OnInit {
+ private formBuilder = inject(UntypedFormBuilder);
+
+ private _savingsAccountProductTemplate: any;
+
+ @Input()
+ set savingsAccountProductTemplate(value: any) {
+ this._savingsAccountProductTemplate = value;
+ if (value && this.savingsAccountSettingsForm) {
+ this.applyTemplate(value);
+ }
+ }
+ get savingsAccountProductTemplate(): any {
+ return this._savingsAccountProductTemplate;
+ }
+
+ savingsAccountSettingsForm: UntypedFormGroup;
+
+ lockinPeriodFrequencyTypeData: any[] = [];
+
+ periodFrequencyData: any[] = [
+ { value: 'Daily' },
+ { value: 'Weekly' },
+ { value: 'Monthly' },
+ { value: 'Quarterly' },
+ { value: 'Half Yearly' },
+ { value: 'Yearly' }];
+
+ constructor() {
+ this.createForm();
+ this.buildDependencies();
+ }
+
+ createForm() {
+ this.savingsAccountSettingsForm = this.formBuilder.group({
+ minRequiredOpeningBalance: [''],
+ withdrawalFeeForTransfers: [false],
+ enforceMinRequiredBalance: [false],
+ minRequiredBalance: [''],
+ balanceFrequency: ['Monthly'],
+ enableLockinPeriod: [false],
+ lockinPeriodFrequency: [''],
+ lockinPeriodFrequencyType: [''],
+ accountClosedZeroBalanceInDayClosing: [false],
+ allowOverdraft: [false]
+ });
+ }
+
+ ngOnInit() {
+ if (this._savingsAccountProductTemplate) {
+ this.applyTemplate(this._savingsAccountProductTemplate);
+ }
+ }
+
+ private applyTemplate(t: any) {
+ this.lockinPeriodFrequencyTypeData = t.lockinPeriodFrequencyTypeOptions || [];
+ const hasLockin = !!(t.lockinPeriodFrequency && t.lockinPeriodFrequency > 0);
+
+ this.savingsAccountSettingsForm.patchValue({
+ minRequiredOpeningBalance: t.minRequiredOpeningBalance,
+ withdrawalFeeForTransfers: t.withdrawalFeeForTransfers || false,
+ enforceMinRequiredBalance: t.enforceMinRequiredBalance || false,
+ minRequiredBalance: t.minRequiredBalance,
+ balanceFrequency: t.balanceFrequency || 'Monthly',
+ enableLockinPeriod: hasLockin,
+ lockinPeriodFrequency: hasLockin ? t.lockinPeriodFrequency : '',
+ lockinPeriodFrequencyType: hasLockin ? t.lockinPeriodFrequencyType?.id || '' : '',
+ accountClosedZeroBalanceInDayClosing: t.accountClosedZeroBalanceInDayClosing || false,
+ allowOverdraft: t.allowOverdraft || false
+ });
+
+ if (t.allowOverdraft) {
+ if (!this.savingsAccountSettingsForm.contains('minOverdraftForInterestCalculation')) {
+ this.savingsAccountSettingsForm.addControl('minOverdraftForInterestCalculation', new UntypedFormControl(''));
+ this.savingsAccountSettingsForm.addControl('nominalAnnualInterestRateOverdraft', new UntypedFormControl(''));
+ this.savingsAccountSettingsForm.addControl('overdraftLimit', new UntypedFormControl(''));
+ }
+ this.savingsAccountSettingsForm.patchValue({
+ minOverdraftForInterestCalculation: t.minOverdraftForInterestCalculation,
+ nominalAnnualInterestRateOverdraft: t.nominalAnnualInterestRateOverdraft,
+ overdraftLimit: t.overdraftLimit
+ });
+ }
+ }
+
+ buildDependencies() {
+ this.savingsAccountSettingsForm.get('enableLockinPeriod').valueChanges.subscribe((enabled: boolean) => {
+ if (enabled) {
+ this.savingsAccountSettingsForm.get('lockinPeriodFrequency').setValidators([
+ Validators.required,
+ Validators.min(1)
+ ]);
+ this.savingsAccountSettingsForm.get('lockinPeriodFrequencyType').setValidators([Validators.required]);
+ } else {
+ this.savingsAccountSettingsForm.get('lockinPeriodFrequency').setValue('');
+ this.savingsAccountSettingsForm.get('lockinPeriodFrequencyType').setValue('');
+ this.savingsAccountSettingsForm.get('lockinPeriodFrequency').clearValidators();
+ this.savingsAccountSettingsForm.get('lockinPeriodFrequencyType').clearValidators();
+ }
+ this.savingsAccountSettingsForm.get('lockinPeriodFrequency').updateValueAndValidity();
+ this.savingsAccountSettingsForm.get('lockinPeriodFrequencyType').updateValueAndValidity();
+ });
+
+ this.savingsAccountSettingsForm.get('allowOverdraft').valueChanges.subscribe((allowed: boolean) => {
+ if (allowed) {
+ this.savingsAccountSettingsForm.addControl('minOverdraftForInterestCalculation', new UntypedFormControl(''));
+ this.savingsAccountSettingsForm.addControl('nominalAnnualInterestRateOverdraft', new UntypedFormControl(''));
+ this.savingsAccountSettingsForm.addControl('overdraftLimit', new UntypedFormControl(''));
+ } else {
+ this.savingsAccountSettingsForm.removeControl('minOverdraftForInterestCalculation');
+ this.savingsAccountSettingsForm.removeControl('nominalAnnualInterestRateOverdraft');
+ this.savingsAccountSettingsForm.removeControl('overdraftLimit');
+ }
+ });
+ }
+
+ get savingsAccountSettings() {
+ const value = { ...this.savingsAccountSettingsForm.getRawValue() };
+ if (!value.enableLockinPeriod) {
+ value.lockinPeriodFrequency = null;
+ value.lockinPeriodFrequencyType = null;
+ }
+ delete value.enableLockinPeriod;
+ delete value.balanceFrequency;
+ delete value.accountClosedZeroBalanceInDayClosing;
+ return value;
+ }
+}
diff --git a/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.html b/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.html
new file mode 100644
index 000000000..a90dfeba3
--- /dev/null
+++ b/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.html
@@ -0,0 +1,170 @@
+
+
+
diff --git a/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.scss b/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.scss
new file mode 100644
index 000000000..038977265
--- /dev/null
+++ b/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.scss
@@ -0,0 +1,166 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+$accent: #0ea5e9;
+$border: #e2e8f0;
+$gray-50: #f8f9fa;
+$gray-200: #e8eaed;
+$gray-900: #202124;
+
+.sat-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;
+
+ span {
+ font-size: 14px;
+ font-weight: 600;
+ color: $gray-900;
+ }
+ }
+}
+
+.sat-section__badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border-radius: 6px;
+ background: #eff6ff;
+ font-size: 14px;
+}
+
+.sat-body {
+ background: #fff;
+ padding: 18px 24px 24px;
+}
+
+.sat-banner {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 14px;
+ margin-bottom: 18px;
+ border-radius: 8px;
+ background: #eff6ff;
+ border: 1px solid #bfdbfe;
+ font-size: 13px;
+ color: $gray-900;
+
+ &__icon {
+ font-size: 16px;
+ }
+}
+
+.sat-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 4px 16px;
+
+ @media (width <= 900px) {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.sat-field {
+ width: 100%;
+}
+
+.sat-footer {
+ margin-top: 16px;
+ padding-top: 16px;
+ border-top: 1px solid $border;
+
+ &__grid {
+ display: grid;
+ grid-template-columns: auto 1fr 1fr;
+ gap: 4px 16px;
+ align-items: start;
+
+ @media (width <= 900px) {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+ }
+}
+
+.sat-toggle-card {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: 10px 14px;
+ border: 1px solid $border;
+ border-radius: 8px;
+ background: $gray-50;
+ min-height: 56px;
+
+ h4 {
+ margin: 0;
+ font-size: 12px;
+ font-weight: 600;
+ color: #64748b;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ }
+
+ &__choices {
+ display: flex;
+ gap: 16px;
+ align-items: center;
+ }
+}
+
+.sat-radio-label {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 14px;
+ color: $gray-900;
+ cursor: pointer;
+
+ [type='radio'] {
+ accent-color: $accent;
+ width: 16px;
+ height: 16px;
+ cursor: pointer;
+ }
+}
+
+.sat-note {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ margin-top: 12px;
+ padding: 10px 14px;
+ border-radius: 8px;
+ background: #fffbeb;
+ border: 1px solid #fde68a;
+ font-size: 13px;
+ color: #92400e;
+}
diff --git a/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.ts
new file mode 100644
index 000000000..fbf388008
--- /dev/null
+++ b/src/app/savings/savings-account-stepper/savings-account-term-step/savings-account-term-step.component.ts
@@ -0,0 +1,154 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+import { Component, Input, OnInit, inject } from '@angular/core';
+import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
+import { Dates } from 'app/core/utils/dates';
+import { SettingsService } from 'app/settings/settings.service';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+
+@Component({
+ selector: 'mifosx-savings-account-term-step',
+ templateUrl: './savings-account-term-step.component.html',
+ styleUrls: ['./savings-account-term-step.component.scss'],
+ imports: [...STANDALONE_SHARED_IMPORTS]
+})
+export class SavingsAccountTermStepComponent implements OnInit {
+ private formBuilder = inject(UntypedFormBuilder);
+ private dateUtils = inject(Dates);
+ private settingsService = inject(SettingsService);
+
+ private _savingsAccountProductTemplate: any;
+
+ @Input()
+ set savingsAccountProductTemplate(value: any) {
+ this._savingsAccountProductTemplate = value;
+ if (value && this.savingsAccountTermForm) {
+ this.applyTemplate(value);
+ }
+ }
+ get savingsAccountProductTemplate(): any {
+ return this._savingsAccountProductTemplate;
+ }
+
+ savingsAccountTermForm: UntypedFormGroup;
+
+ interestCompoundingPeriodTypeData: any[] = [];
+ interestPostingPeriodTypeData: any[] = [];
+ interestCalculationTypeData: any[] = [];
+ interestCalculationDaysInYearTypeData: any[] = [];
+ interestCalculateSelectorData: any[] = [];
+ periodFrequencyData: any[] = [];
+
+ yesNoOptions = [
+ { value: true, label: 'Yes' },
+ { value: false, label: 'No' }
+ ];
+
+ minDate = new Date(2000, 0, 1);
+ maxDate = new Date();
+
+ constructor() {
+ this.createForm();
+ }
+
+ createForm() {
+ this.savingsAccountTermForm = this.formBuilder.group({
+ currencyCode: [{ value: '', disabled: true }],
+ nominalAnnualInterestRate: [
+ '',
+ [
+ Validators.required,
+ Validators.min(0)
+ ]
+ ],
+ interestCompoundingPeriodType: [
+ '',
+ Validators.required
+ ],
+ interestPostingPeriodType: [
+ '',
+ Validators.required
+ ],
+ interestCalculationType: [
+ '',
+ Validators.required
+ ],
+ interestCalculationDaysInYearType: [
+ '',
+ Validators.required
+ ],
+ effectiveDate: [''],
+ interestCalculateSelector: [1],
+ averagePeriod: ['Monthly'],
+ autoPost: [true],
+ frequencyFixDay: [
+ 30,
+ [Validators.min(0)]
+ ],
+ lastInterestDate: ['']
+ });
+ }
+
+ ngOnInit() {
+ this.maxDate = this.settingsService.businessDate;
+ // Apply template if it was set before the form was ready
+ if (this._savingsAccountProductTemplate) {
+ this.applyTemplate(this._savingsAccountProductTemplate);
+ }
+ }
+
+ private applyTemplate(t: any) {
+ this.maxDate = this.settingsService.businessDate;
+
+ this.interestCompoundingPeriodTypeData = t.interestCompoundingPeriodTypeOptions || [];
+ this.interestPostingPeriodTypeData = t.interestPostingPeriodTypeOptions || [];
+ this.interestCalculationTypeData = t.interestCalculationTypeOptions || [];
+ this.interestCalculateSelectorData = t.interestCalculationTypeOption || [];
+ this.interestCalculationDaysInYearTypeData = t.interestCalculationDaysInYearTypeOptions || [];
+ this.periodFrequencyData = t.periodFrequencyOptions?.length ? t.periodFrequencyOptions : [
+ { value: 'Monthly' },
+ { value: 'Quarterly' },
+ { value: 'Half Yearly' },
+ { value: 'Yearly' }];
+
+ const defaultPeriod = this.periodFrequencyData[0]?.value || 'Monthly';
+ const savedEnumTypeId = t.interestCalculationType?.id || this.interestCalculationTypeData?.[0]?.id || 1;
+ const savedSelectorId = t.interestCalculateSelector ?? this.interestCalculateSelectorData?.[0]?.id ?? null;
+
+ this.savingsAccountTermForm.patchValue({
+ currencyCode: t.currency?.code,
+ nominalAnnualInterestRate: t.nominalAnnualInterestRate,
+ interestCompoundingPeriodType: t.interestCompoundingPeriodType?.id,
+ interestPostingPeriodType: t.interestPostingPeriodType?.id,
+ interestCalculationType: savedEnumTypeId,
+ interestCalculationDaysInYearType: t.interestCalculationDaysInYearType?.id,
+ effectiveDate: t.effectiveDate ? new Date(t.effectiveDate) : '',
+ interestCalculateSelector: savedSelectorId,
+ averagePeriod: t.averagePeriod || defaultPeriod,
+ autoPost: t.autoPost ?? true,
+ frequencyFixDay: t.frequencyFixDay ?? 30,
+ lastInterestDate: t.lastInterestDate ? new Date(t.lastInterestDate) : ''
+ });
+ }
+
+ get savingsAccountTerms() {
+ const value = { ...this.savingsAccountTermForm.getRawValue() };
+ const dateFormat = this.settingsService.dateFormat;
+
+ if (value.effectiveDate instanceof Date) {
+ value.effectiveDate = this.dateUtils.formatDate(value.effectiveDate, dateFormat) || '';
+ }
+ if (value.lastInterestDate instanceof Date) {
+ value.lastInterestDate = this.dateUtils.formatDate(value.lastInterestDate, dateFormat) || '';
+ }
+
+ delete value.currencyCode;
+ return value;
+ }
+}
diff --git a/src/app/savings/savings-account-stepper/savings-account-terms-step/savings-account-terms-step.component.html b/src/app/savings/savings-account-stepper/savings-account-terms-step/savings-account-terms-step.component.html
index 2f6b97985..8d2053bc9 100644
--- a/src/app/savings/savings-account-stepper/savings-account-terms-step/savings-account-terms-step.component.html
+++ b/src/app/savings/savings-account-stepper/savings-account-terms-step/savings-account-terms-step.component.html
@@ -152,13 +152,6 @@
{{ 'labels.inputs.Minimum Balance' | translate }}
-
- @if (savingsAccountTermsForm.controls.minBalanceForInterestCalculation.value) {
-
- {{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}
-
-
- }
diff --git a/src/environments/.env.ts b/src/environments/.env.ts
index c92cec15e..c31d3d90c 100644
--- a/src/environments/.env.ts
+++ b/src/environments/.env.ts
@@ -10,8 +10,8 @@
/* tslint:disable */
export default {
'mifos_x': {
- 'version': '260420',
- 'hash': 'fe61c925'
+ 'version': '260422',
+ 'hash': 'e9a93771'
},
'allow_switching_backend_instance': true
};