diff --git a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts
index b15fb3d56..9e8e1acd6 100644
--- a/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts
+++ b/src/app/current-accounts/current-account-stepper/current-account-currency-step/current-account-currency-step.component.ts
@@ -29,6 +29,7 @@ export class CurrentAccountCurrencyStepComponent implements OnChanges {
@Input() currentAccountProductTemplate: any;
@Input() primaryClientId: number | null = null;
@Input() isNonIndividualClient = false;
+ @Input() existingAccount: any = null;
@ViewChild(NomineeFormComponent) nomineeFormComp: NomineeFormComponent;
nominees: any[] = [];
@@ -41,8 +42,29 @@ export class CurrentAccountCurrencyStepComponent implements OnChanges {
msme = false;
autoDebitFacility = false;
+ private existingAccountApplied = false;
+
ngOnChanges(changes: SimpleChanges): void {
- if (changes['currentAccountProductTemplate']?.currentValue) {
+ if (changes['existingAccount']?.currentValue) {
+ const ea = changes['existingAccount'].currentValue;
+ this.existingAccountApplied = true;
+ if (ea.balanceSwapOption !== undefined) {
+ this.balanceSwapOption = !!ea.balanceSwapOption;
+ }
+ if (ea.msme !== undefined) {
+ this.msme = !!ea.msme;
+ }
+ if (ea.autoDebitFacility !== undefined) {
+ this.autoDebitFacility = !!ea.autoDebitFacility;
+ }
+ // nominees: try array first, then parse nomineeData JSON string
+ const nominees =
+ Array.isArray(ea.nominees) && ea.nominees.length > 0 ? ea.nominees : this.parseNomineeData(ea.nomineeData);
+ if (nominees.length > 0) {
+ this.nominees = [...nominees];
+ }
+ }
+ if (changes['currentAccountProductTemplate']?.currentValue && !this.existingAccountApplied) {
const pt = changes['currentAccountProductTemplate'].currentValue;
this.balanceSwapOption = !!pt.balanceSwapOption;
this.msme = !!pt.msme;
@@ -50,6 +72,18 @@ export class CurrentAccountCurrencyStepComponent implements OnChanges {
}
}
+ private parseNomineeData(nomineeData: any): any[] {
+ if (!nomineeData) {
+ return [];
+ }
+ try {
+ const parsed = typeof nomineeData === 'string' ? JSON.parse(nomineeData) : nomineeData;
+ return Array.isArray(parsed) ? parsed : [];
+ } catch {
+ return [];
+ }
+ }
+
get totalShare(): number {
return this.nominees.reduce((sum, n) => sum + (+n.percentageShare || 0), 0);
}
diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html
index 798a94250..435884bcb 100644
--- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html
+++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html
@@ -124,7 +124,7 @@
Account No
@@ -167,6 +167,9 @@
@if (productTemplate?.introducerRequired) {
}
diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts
index 63994c088..6d0bc2920 100644
--- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts
+++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts
@@ -65,6 +65,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
introducerId: number | null = null;
introducerName: string | null = null;
introducerCif: string | null = null;
+ private isEditMode = false;
// ── Non-Individual (existing) ──────────────────────────────────────────
isNonIndividualClient = false;
@@ -201,11 +202,29 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
if (this.currentAccountTemplate) {
this.productData = this.currentAccountTemplate.productOptions;
- if (this.currentAccountTemplate.currentAccountProductId) {
+ const existingProductId =
+ this.currentAccountTemplate.currentAccountProductId ?? this.currentAccountTemplate.productId;
+ // Edit mode: account response always has accountNo; pure template has previewAccountNo only
+ if (this.currentAccountTemplate.accountNo) {
+ this.isEditMode = true;
+ }
+ if ((!this.productData || this.productData.length === 0) && existingProductId) {
+ this.isEditMode = true;
+ this.productData = [
+ {
+ id: existingProductId,
+ name:
+ this.currentAccountTemplate.productName ??
+ this.currentAccountTemplate.currentAccountProductName ??
+ 'Product'
+ }
+ ];
+ }
+ if (existingProductId) {
this.currentAccountDetailsForm.patchValue({
- productId: this.currentAccountTemplate.currentAccountProductId,
+ productId: existingProductId,
submittedOnDate:
- this.currentAccountTemplate.timeline.submittedOnDate &&
+ this.currentAccountTemplate.timeline?.submittedOnDate &&
new Date(this.currentAccountTemplate.timeline.submittedOnDate),
externalId: this.currentAccountTemplate.externalId
});
@@ -273,10 +292,106 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
} else {
this.currentAccountDetailsForm.get('fieldOfficerId').patchValue('');
}
+ // === Restore existing account values when in edit/modify mode ===
+ this.restoreExistingAccountValues();
});
});
}
+ private restoreExistingAccountValues(): void {
+ const ea = this.currentAccountTemplate;
+ // Edit mode: API returns `productId`, create template uses `currentAccountProductId`
+ if (!ea?.currentAccountProductId && !ea?.productId) {
+ return;
+ }
+
+ const strVal = (v: any): string => (typeof v === 'object' && v !== null ? (v.value ?? '') : (v ?? ''));
+
+ const accountStatus = strVal(ea.accountStatus);
+ if (accountStatus) {
+ this.selectedAccountStatus = accountStatus;
+ }
+
+ const customerSegment = strVal(ea.customerSegment);
+ if (customerSegment) {
+ this.selectedCustomerSegment = customerSegment;
+ }
+
+ // cbsAccountType is the CBS descriptive type ("Standard Account", etc.)
+ // ea.accountType is Fineract's enum-based type ({id,value:"Individual"}) — do NOT use as fallback
+ const accountType = ea.cbsAccountType ? strVal(ea.cbsAccountType) : null;
+ if (accountType) {
+ this.selectedAccountType = accountType;
+ }
+
+ if (accountStatus === 'JOINT') {
+ const modeOfOp = strVal(ea.modeOfOperation);
+ if (modeOfOp) {
+ this.modeOfOperation = modeOfOp;
+ }
+ // jointHolders may not be returned by backend — skip if absent
+ if (Array.isArray(ea.jointHolders) && ea.jointHolders.length > 0) {
+ // Support both camelCase and lowercase keys (MySQL queryForList returns lowercase)
+ const isPH = (h: any) => h.isPrimary ?? h.isprimary ?? false;
+ const primary = ea.jointHolders.find((h: any) => isPH(h));
+ const ps = primary?.sharePercentage ?? primary?.sharepercentage;
+ if (ps) {
+ this.primaryHolderShare = +ps;
+ }
+ this.jointHolders = ea.jointHolders
+ .filter((h: any) => !isPH(h))
+ .map((h: any) => ({
+ clientId: h.clientId ?? h.clientid,
+ clientName: h.displayName ?? h.displayname ?? h.clientName ?? '',
+ clientCif: h.cifNumber ?? h.cifnumber ?? h.clientCif ?? '',
+ sharePercentage: +(h.sharePercentage ?? h.sharepercentage ?? 0),
+ isMandatory: !!(h.isMandatory ?? h.ismandatory)
+ }));
+ }
+ }
+ if (accountStatus === 'INDIVIDUAL_MALE' && ea.specialCategory) {
+ this.selectedSpecialCategory = ea.specialCategory;
+ }
+ if (accountStatus === 'INDIVIDUAL_FEMALE' && ea.weakerSection) {
+ this.selectedWeakerSection = ea.weakerSection;
+ }
+ if (accountStatus === 'MINOR') {
+ if (ea.guardianClientId) {
+ this.guardianClientId = ea.guardianClientId;
+ }
+ if (ea.guardianName) {
+ this.guardianName = ea.guardianName;
+ }
+ if (ea.guardianCifNumber) {
+ this.guardianCif = ea.guardianCifNumber;
+ }
+ if (ea.guardianRelationship) {
+ this.guardianRelationship = ea.guardianRelationship;
+ }
+ if (ea.minorDateOfBirth) {
+ const d = new Date(ea.minorDateOfBirth);
+ if (!isNaN(d.getTime())) {
+ this.minorDob = d;
+ }
+ }
+ }
+ // API returns introducerClientId, not introducerId
+ const introId = ea.introducerId ?? ea.introducerClientId;
+ if (introId) {
+ this.introducerId = introId;
+ this.introducerName = ea.introducerName ?? null;
+ this.introducerCif = ea.introducerCifNumber ?? null;
+ }
+ this.currentAccountDetailsForm.patchValue({
+ balanceSwapOption: !!ea.balanceSwapOption,
+ msme: !!ea.msme,
+ autoDebitFacility: !!ea.autoDebitFacility,
+ ...(ea.establishmentDate ? { establishmentDate: new Date(ea.establishmentDate) } : {}),
+ ...(ea.startOperationDate ? { startOperationDate: new Date(ea.startOperationDate) } : {})
+ });
+ this.cdr.detectChanges();
+ }
+
private extractOptions(options: any[], fallback?: string): string[] {
if (Array.isArray(options) && options.length > 0) {
return options.map((o: any) => o.value ?? o.name ?? o);
@@ -317,6 +432,9 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
}
private autoDetectAccountStatus(): void {
+ if (this.isEditMode) {
+ return;
+ }
if (this.clientAge === null) return;
if (this.clientAge < 18) {
this.selectedAccountStatus = 'MINOR';
diff --git a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts
index 321d9984d..49597f57a 100644
--- a/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts
+++ b/src/app/current-accounts/current-account-stepper/current-account-digital-step/current-account-digital-step.component.ts
@@ -40,10 +40,19 @@ const DEFAULT_FACILITY_CONFIG = {
})
export class CurrentAccountDigitalStepComponent {
facilityConfig = { ...DEFAULT_FACILITY_CONFIG };
+ private hasExistingAccount = false;
+
+ @Input()
+ set existingAccount(value: any) {
+ if (value?.accountFacilityConfig) {
+ this.hasExistingAccount = true;
+ this.facilityConfig = { ...DEFAULT_FACILITY_CONFIG, ...value.accountFacilityConfig };
+ }
+ }
@Input()
set currentAccountProductTemplate(value: any) {
- if (value?.accountFacilityConfig) {
+ if (!this.hasExistingAccount && value?.accountFacilityConfig) {
this.facilityConfig = { ...DEFAULT_FACILITY_CONFIG, ...value.accountFacilityConfig };
}
}
diff --git a/src/app/current-accounts/current-account-view/current-accounts-buttons.config.ts b/src/app/current-accounts/current-account-view/current-accounts-buttons.config.ts
index e72cc8c0b..9c33daef1 100644
--- a/src/app/current-accounts/current-account-view/current-accounts-buttons.config.ts
+++ b/src/app/current-accounts/current-account-view/current-accounts-buttons.config.ts
@@ -143,6 +143,16 @@ export class CurrentAccountButtonsConfiguration {
}
];
break;
+ case 'Rejected':
+ case 'rejected':
+ this.buttonsArray = [
+ {
+ name: 'Modify Application',
+ icon: 'pen',
+ taskPermissionName: 'UPDATE_CURRENTACCOUNT'
+ }
+ ];
+ break;
default:
this.buttonsArray = [];
}
diff --git a/src/app/current-accounts/edit-current-account/edit-current-account.component.html b/src/app/current-accounts/edit-current-account/edit-current-account.component.html
index a23533b44..101e37353 100644
--- a/src/app/current-accounts/edit-current-account/edit-current-account.component.html
+++ b/src/app/current-accounts/edit-current-account/edit-current-account.component.html
@@ -6,76 +6,146 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ 'labels.inputs.DETAILS' | translate }}
-
-
-
-
-
-
- {{ 'labels.inputs.TERMS' | translate }}
-
-
-
-
-
- {{ 'labels.inputs.CHARGES' | translate }}
-
-
-
-
-
- @if (currentAccountFormValidAndNotPristine) {
-
- {{ 'labels.inputs.PREVIEW' | translate }}
-
+
+
+
+ Product
+
+
+
-
-
- }
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Currency
+
+
+ @if (currentAccountProductTemplate) {
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Digital
+
+
+ @if (currentAccountProductTemplate) {
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'labels.inputs.PREVIEW' | translate }}
+
+
+ @if (currentAccountProductTemplate) {
+
+
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/current-accounts/edit-current-account/edit-current-account.component.scss b/src/app/current-accounts/edit-current-account/edit-current-account.component.scss
index af9f4c075..c22bb92c9 100644
--- a/src/app/current-accounts/edit-current-account/edit-current-account.component.scss
+++ b/src/app/current-accounts/edit-current-account/edit-current-account.component.scss
@@ -5,3 +5,164 @@
* 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/.
*/
+
+$navy: #0d1b2a;
+$blue: #1e88e5;
+$blue-d: #1e40af;
+$indigo: #4f46e5;
+$border: #e2e8f0;
+
+.cca-container {
+ max-width: 1180px;
+ margin: 0 auto;
+ padding: 20px 24px;
+}
+
+/* ─── Hero header (matches Nexora pages) ────────────────────────────── */
+.cca-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 16px;
+
+ &__icon {
+ width: 44px;
+ height: 44px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 12px;
+ background: linear-gradient(135deg, $blue 0%, $indigo 100%);
+ color: #fff;
+ font-size: 18px;
+ box-shadow: 0 2px 8px rgb(29 78 216 / 25%);
+ flex-shrink: 0;
+ }
+
+ &__title {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 800;
+ color: $navy;
+ line-height: 1.1;
+ letter-spacing: 0.02em;
+ }
+
+ &__sub {
+ margin-top: 2px;
+ font-size: 12.5px;
+ color: #64748b;
+ }
+}
+
+/* ─── Wizard tab strip ──────────────────────────────────────────────── */
+.cca-tabs {
+ background: #fff;
+ border: 1px solid $border;
+ border-radius: 14px;
+ box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
+ overflow: hidden;
+
+ ::ng-deep {
+ .mat-mdc-tab-header {
+ background: #f8fafc;
+ border-bottom: 1px solid $border;
+ }
+
+ .mat-mdc-tab {
+ min-width: 140px;
+ height: 50px;
+ padding: 0 22px;
+ font-size: 13px !important;
+ font-weight: 600 !important;
+ letter-spacing: 0.01em;
+ color: #475569 !important;
+ opacity: 1 !important;
+ }
+
+ .mat-mdc-tab:hover:not(.mdc-tab--active, .mat-mdc-tab-disabled) {
+ background: rgb(30 64 175 / 5%);
+ color: $navy !important;
+ }
+
+ .mat-mdc-tab.mdc-tab--active {
+ .mdc-tab__text-label {
+ color: $blue-d !important;
+ font-weight: 700 !important;
+ }
+ }
+
+ .mdc-tab-indicator__content--underline {
+ border-color: $blue-d !important;
+ border-top-width: 3px !important;
+ }
+
+ .mat-mdc-tab.mat-mdc-tab-disabled {
+ opacity: 0.45 !important;
+ cursor: not-allowed;
+ }
+
+ .mat-mdc-tab-body-wrapper {
+ min-height: 320px;
+ }
+
+ .mat-mdc-tab-body,
+ .mat-mdc-tab-body-content {
+ overflow: visible !important;
+ }
+ }
+}
+
+.cca-tab__icon {
+ margin-right: 8px;
+ font-size: 13px;
+}
+
+/* ─── Tab body + action bar ─────────────────────────────────────────── */
+.cca-tab-body {
+ padding: 24px;
+
+ @media (width <= 768px) {
+ padding: 16px;
+ }
+}
+
+.cca-actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-top: 24px;
+ padding-top: 16px;
+ border-top: 1px solid #f1f5f9;
+
+ &__spacer {
+ flex: 1;
+ }
+
+ button {
+ height: 40px !important;
+ border-radius: 10px !important;
+ font-weight: 600 !important;
+ padding: 0 18px !important;
+ }
+}
+
+.m-l-10 {
+ margin-left: 8px;
+}
+
+.m-r-10 {
+ margin-right: 8px;
+}
+
+button.preview-submit-row__primary,
+.mat-mdc-raised-button.mat-primary.preview-submit-row__primary,
+.preview-submit-row__primary {
+ background: linear-gradient(135deg, #10b981 0%, #047857 100%) !important;
+
+ --mdc-protected-button-container-color: transparent !important;
+ --mdc-filled-button-container-color: transparent !important;
+
+ color: #fff !important;
+ box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
+}
diff --git a/src/app/current-accounts/edit-current-account/edit-current-account.component.ts b/src/app/current-accounts/edit-current-account/edit-current-account.component.ts
index 836824166..60a9561c6 100644
--- a/src/app/current-accounts/edit-current-account/edit-current-account.component.ts
+++ b/src/app/current-accounts/edit-current-account/edit-current-account.component.ts
@@ -9,23 +9,26 @@
/** Angular Imports */
import { Component, ViewChild, inject } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
+import { FormGroup } from '@angular/forms';
+import { ClientsService } from 'app/clients/clients.service';
/** Custom Components */
import { CurrentAccountDetailsStepComponent } from '../current-account-stepper/current-account-details-step/current-account-details-step.component';
-import { CurrentAccountTermsStepComponent } from '../current-account-stepper/current-account-terms-step/current-account-terms-step.component';
-import { CurrentAccountChargesStepComponent } from '../current-account-stepper/current-account-charges-step/current-account-charges-step.component';
+import { CurrentAccountCurrencyStepComponent } from '../current-account-stepper/current-account-currency-step/current-account-currency-step.component';
+import { CurrentAccountDigitalStepComponent } from '../current-account-stepper/current-account-digital-step/current-account-digital-step.component';
/** Custom Services */
import { CurrentAccountsService } from '../current-accounts.service';
import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates';
-import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
+import { AlertService } from 'app/core/alert/alert.service';
+import { MatTabsModule } from '@angular/material/tabs';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { CurrentAccountPreviewStepComponent } from '../current-account-stepper/current-account-preview-step/current-account-preview-step.component';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
- * Edit Current Account Component
+ * Edit Current Account Component — same tab UI as Create, pre-populated with existing data.
*/
@Component({
selector: 'mifosx-edit-current-account-account',
@@ -33,14 +36,11 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./edit-current-account.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
- MatStepper,
- MatStepperIcon,
+ MatTabsModule,
FaIconComponent,
- MatStep,
- MatStepLabel,
CurrentAccountDetailsStepComponent,
- CurrentAccountTermsStepComponent,
- CurrentAccountChargesStepComponent,
+ CurrentAccountCurrencyStepComponent,
+ CurrentAccountDigitalStepComponent,
CurrentAccountPreviewStepComponent
]
})
@@ -50,109 +50,172 @@ export class EditCurrentAccountComponent {
private dateUtils = inject(Dates);
private currentAccountsService = inject(CurrentAccountsService);
private settingsService = inject(SettingsService);
+ private alertService = inject(AlertService);
+ private clientsService = inject(ClientsService);
- /** Current Account Template */
+ /** Full account data + template from resolver */
currentAccountAndTemplate: any;
- /** Current Account Product Template */
+ /** Product template emitted by details step */
currentAccountProductTemplate: any;
+ /** Currently visible wizard tab (0-indexed). */
+ selectedTabIndex = 0;
+ private readonly totalTabs = 4;
- /** Current Account Details Step */
@ViewChild(CurrentAccountDetailsStepComponent, { static: true })
currentAccountDetailsStep: CurrentAccountDetailsStepComponent;
- /** Current Account Terms Step */
- @ViewChild(CurrentAccountTermsStepComponent, { static: true })
- currentAccountTermsStep: CurrentAccountTermsStepComponent;
- /** Current Account Charges Step */
- @ViewChild(CurrentAccountChargesStepComponent, { static: true })
- currentAccountChargesStep: CurrentAccountChargesStepComponent;
- /**
- * Fetches current account template from `resolve`
- * @param {ActivatedRoute} route Activated Route
- * @param {Router} router Router
- * @param {Dates} dateUtils Date Utils
- * @param {CurrentAccountsService} currentAccountsService Current Accounts Service
- * @param {SettingsService} settingsService Settings Service
- */
+ @ViewChild(CurrentAccountCurrencyStepComponent)
+ currentAccountCurrencyStep: CurrentAccountCurrencyStepComponent;
+
+ @ViewChild(CurrentAccountDigitalStepComponent)
+ currentAccountDigitalStep: CurrentAccountDigitalStepComponent;
+
constructor() {
this.route.data.subscribe((data: { currentAccountAndTemplate: any }) => {
- this.currentAccountAndTemplate = data.currentAccountAndTemplate;
+ const account = data.currentAccountAndTemplate;
+ this.currentAccountAndTemplate = account;
+ if (account?.clientId && account?.accountNo) {
+ this.clientsService
+ .getClientNominees(String(account.clientId), account.accountNo, 'CB')
+ .subscribe((nominees: any[]) => {
+ if (Array.isArray(nominees) && nominees.length > 0) {
+ this.currentAccountAndTemplate = { ...this.currentAccountAndTemplate, nominees };
+ }
+ });
+ }
});
}
- /**
- * Sets current account product template.
- * @param {any} $event API response
- */
setTemplate($event: any) {
this.currentAccountProductTemplate = $event;
}
- /**
- * Retrieves current account details form.
- */
+ next(): void {
+ if (this.selectedTabIndex < this.totalTabs - 1) {
+ this.selectedTabIndex += 1;
+ }
+ }
+
+ prev(): void {
+ if (this.selectedTabIndex > 0) {
+ this.selectedTabIndex -= 1;
+ }
+ }
+
get currentAccountDetailsForm() {
return this.currentAccountDetailsStep.currentAccountDetailsForm;
}
- /**
- * Retrieves current account terms form.
- */
- get currentAccountTermsForm() {
- return this.currentAccountTermsStep.currentAccountTermsForm;
+ get currentAccountTermForm(): FormGroup {
+ return new FormGroup({});
}
- /**
- * Checks validity and pristinity of overall current account form .
- */
- get currentAccountFormValidAndNotPristine() {
- return (
- this.currentAccountDetailsForm.valid &&
- this.currentAccountTermsForm.valid &&
- (!this.currentAccountDetailsForm.pristine ||
- !this.currentAccountTermsForm.pristine ||
- !this.currentAccountChargesStep.pristine)
- );
+ get currentAccountSettingsForm(): FormGroup {
+ return new FormGroup({});
+ }
+
+ get currentAccountTermsForm(): FormGroup {
+ return new FormGroup({});
}
- /**
- * Retrieves current account object.
- */
get currentAccount() {
+ return { ...this.currentAccountDetailsStep.currentAccountDetails };
+ }
+
+ get currentAccountDigital() {
+ return this.currentAccountDigitalStep?.currentAccountDigital ?? null;
+ }
+
+ get currentAccountCurrencyFlags() {
return {
- ...this.currentAccountDetailsStep.currentAccountDetails,
- ...this.currentAccountTermsStep.currentAccountTerms,
- ...this.currentAccountChargesStep.currentAccountCharges
+ balanceSwapOption: this.currentAccountCurrencyStep?.balanceSwapOption ?? false,
+ msme: this.currentAccountCurrencyStep?.msme ?? false,
+ autoDebitFacility: this.currentAccountCurrencyStep?.autoDebitFacility ?? false
};
}
- /**
- * Creates a new share account.
- */
submit() {
- // TODO: Update once language and date settings are setup
+ const detailsStep = this.currentAccountDetailsStep;
+ if (detailsStep.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) {
+ this.alertService.alert({
+ type: 'Error',
+ message: `Total ownership is ${detailsStep.totalJointSharePercentage}%. It must equal 100% before submitting.`
+ });
+ return;
+ }
+ if (
+ !this.currentAccountDetailsStep.isNonIndividualClient &&
+ this.currentAccountProductTemplate?.nominationFacility &&
+ (this.currentAccountCurrencyStep?.nominees ?? []).length === 0
+ ) {
+ this.alertService.alert({
+ type: 'Error',
+ message: 'Nomination Facility is enabled for this product. Please add at least one nominee before submitting.'
+ });
+ return;
+ }
+ if (this.currentAccountProductTemplate?.introducerRequired && !this.currentAccountDetailsStep.introducerId) {
+ this.alertService.alert({
+ type: 'Error',
+ message: 'Introducer is required for this product. Please select an introducer before submitting.'
+ });
+ return;
+ }
const locale = this.settingsService.language.code;
const dateFormat = this.settingsService.dateFormat;
const monthDayFormat = 'dd MMMM';
- const currentAccount = {
- ...this.currentAccount,
- charges: this.currentAccount.charges.map((charge: any) => ({
- chargeId: charge.id,
- amount: charge.amount,
- dueDate: this.dateUtils.formatDate(charge.dueDate, dateFormat),
- feeOnMonthDay: this.dateUtils.formatDate(charge.feeOnMonthDay, dateFormat),
- feeInterval: charge.feeInterval
- })),
- submittedOnDate: this.dateUtils.formatDate(this.currentAccount.submittedOnDate, dateFormat),
+ const details = this.currentAccount;
+
+ const currentAccount: any = {
+ ...details,
+ ...this.currentAccountCurrencyStep.currencyStepData,
+ ...(this.currentAccountDigitalStep?.currentAccountDigital ?? {}),
+ charges: [],
+ submittedOnDate: this.dateUtils.formatDate(details.submittedOnDate, dateFormat),
dateFormat,
monthDayFormat,
locale
};
+
+ if (details.establishmentDate) {
+ currentAccount.establishmentDate = this.dateUtils.formatDate(details.establishmentDate, dateFormat);
+ }
+ if (details.startOperationDate) {
+ currentAccount.startOperationDate = this.dateUtils.formatDate(details.startOperationDate, dateFormat);
+ }
+
+ if (this.currentAccountDetailsStep.isNonIndividualClient) {
+ currentAccount.isNonIndividualClient = true;
+ currentAccount.partners = this.currentAccountDetailsStep.relatedClients.map((c: any) => ({
+ clientId: c.clientId,
+ clientName: c.clientName,
+ clientCif: c.clientCif,
+ sharePercentage: c.sharePercentage,
+ isMandatory: c.isMandatory
+ }));
+ } else {
+ delete currentAccount.isNonIndividualClient;
+ }
+
if (this.currentAccountAndTemplate.clientId) {
currentAccount.clientId = this.currentAccountAndTemplate.clientId;
} else {
currentAccount.groupId = this.currentAccountAndTemplate.groupId;
}
+
+ const nominees = this.currentAccountCurrencyStep?.nominees ?? [];
+ if (nominees.length > 0) {
+ currentAccount.nominees = nominees.map((n: any) => ({
+ nomineeClientId: n.nomineeClientId,
+ nominationType: n.nominationType,
+ relationshipId: n.relationshipId,
+ percentageShare: n.percentageShare,
+ nomineeIsMinor: n.nomineeIsMinor,
+ ...(n.guardianClientId ? { guardianClientId: n.guardianClientId } : {}),
+ remarks: n.remarks
+ }));
+ }
+
this.currentAccountsService
.updateCurrentAccount(this.currentAccountAndTemplate.id, currentAccount)
.subscribe((response: any) => {
diff --git a/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.html b/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.html
index 7d09064d1..a0b81c891 100644
--- a/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.html
+++ b/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.html
@@ -87,6 +87,7 @@
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
[cbsBaseRates]="cbsBaseRates"
+ [submittedOnDate]="submittedOnDate"
(tenureSelected)="onTenureSelected($event)"
>
diff --git a/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.ts b/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.ts
index 1d47b3e8d..99e4fcfef 100644
--- a/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.ts
+++ b/src/app/deposits/fixed-deposits/create-fixed-deposit-account/create-fixed-deposit-account.component.ts
@@ -153,6 +153,11 @@ export class CreateFixedDepositAccountComponent {
};
}
+ get submittedOnDate(): Date | null {
+ const val = this.fixedDepositsAccountDetailsStep?.fixedDepositAccountDetails?.submittedOnDate;
+ return val ? new Date(val) : null;
+ }
+
/**
* Submits the fixed deposit form and creates a new fixed deposit account
*/
diff --git a/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.html b/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.html
index b98a181e3..8cbf3f2df 100644
--- a/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.html
+++ b/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.html
@@ -6,96 +6,145 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ 'labels.inputs.DETAILS' | translate }}
-
-
-
-
-
-
- {{ 'labels.inputs.TERMS' | translate }}
-
-
-
-
-
-
- {{ 'labels.inputs.SETTINGS' | translate }}
-
-
-
-
-
-
- {{ 'labels.inputs.INTEREST RATE CHART' | translate }}
-
-
-
-
-
-
- {{ 'labels.inputs.CHARGES' | translate }}
-
-
-
-
-
- @if (fixedDepositAccountFormValidAndNotPristine) {
-
- {{ 'labels.inputs.PREVIEW' | translate }}
-
+
+
+
+ Product
+
+
+
-
-
- }
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Currency
+
+
+ @if (fixedDepositsAccountProductTemplate) {
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Interest Rate Chart
+
+
+ @if (fixedDepositsAccountProductTemplate) {
+
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'labels.inputs.PREVIEW' | translate }}
+
+
+ @if (fixedDepositsAccountProductTemplate) {
+
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.scss b/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.scss
index af9f4c075..03cb2b0c8 100644
--- a/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.scss
+++ b/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.scss
@@ -5,3 +5,164 @@
* 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/.
*/
+
+$navy: #0d1b2a;
+$blue: #1e88e5;
+$blue-d: #1e40af;
+$indigo: #4f46e5;
+$border: #e2e8f0;
+
+.cfa-container {
+ max-width: 1180px;
+ margin: 0 auto;
+ padding: 20px 24px;
+}
+
+/* ─── Hero header (matches Nexora pages) ────────────────────────────── */
+.cfa-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 16px;
+
+ &__icon {
+ width: 44px;
+ height: 44px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 12px;
+ background: linear-gradient(135deg, $blue 0%, $indigo 100%);
+ color: #fff;
+ font-size: 18px;
+ box-shadow: 0 2px 8px rgb(29 78 216 / 25%);
+ flex-shrink: 0;
+ }
+
+ &__title {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 800;
+ color: $navy;
+ line-height: 1.1;
+ letter-spacing: 0.02em;
+ }
+
+ &__sub {
+ margin-top: 2px;
+ font-size: 12.5px;
+ color: #64748b;
+ }
+}
+
+/* ─── Wizard tab strip ──────────────────────────────────────────────── */
+.cfa-tabs {
+ background: #fff;
+ border: 1px solid $border;
+ border-radius: 14px;
+ box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
+ overflow: hidden;
+
+ ::ng-deep {
+ .mat-mdc-tab-header {
+ background: #f8fafc;
+ border-bottom: 1px solid $border;
+ }
+
+ .mat-mdc-tab {
+ min-width: 140px;
+ height: 50px;
+ padding: 0 22px;
+ font-size: 13px !important;
+ font-weight: 600 !important;
+ letter-spacing: 0.01em;
+ color: #475569 !important;
+ opacity: 1 !important;
+ }
+
+ .mat-mdc-tab:hover:not(.mdc-tab--active, .mat-mdc-tab-disabled) {
+ background: rgb(30 64 175 / 5%);
+ color: $navy !important;
+ }
+
+ .mat-mdc-tab.mdc-tab--active {
+ .mdc-tab__text-label {
+ color: $blue-d !important;
+ font-weight: 700 !important;
+ }
+ }
+
+ .mdc-tab-indicator__content--underline {
+ border-color: $blue-d !important;
+ border-top-width: 3px !important;
+ }
+
+ .mat-mdc-tab.mat-mdc-tab-disabled {
+ opacity: 0.45 !important;
+ cursor: not-allowed;
+ }
+
+ .mat-mdc-tab-body-wrapper {
+ min-height: 320px;
+ }
+
+ .mat-mdc-tab-body,
+ .mat-mdc-tab-body-content {
+ overflow: visible !important;
+ }
+ }
+}
+
+.cfa-tab__icon {
+ margin-right: 8px;
+ font-size: 13px;
+}
+
+/* ─── Tab body + action bar ─────────────────────────────────────────── */
+.cfa-tab-body {
+ padding: 24px;
+
+ @media (width <= 768px) {
+ padding: 16px;
+ }
+}
+
+.cfa-actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-top: 24px;
+ padding-top: 16px;
+ border-top: 1px solid #f1f5f9;
+
+ &__spacer {
+ flex: 1;
+ }
+
+ button {
+ height: 40px !important;
+ border-radius: 10px !important;
+ font-weight: 600 !important;
+ padding: 0 18px !important;
+ }
+}
+
+.m-l-10 {
+ margin-left: 8px;
+}
+
+.m-r-10 {
+ margin-right: 8px;
+}
+
+button.preview-submit-row__primary,
+.mat-mdc-raised-button.mat-primary.preview-submit-row__primary,
+.preview-submit-row__primary {
+ background: linear-gradient(135deg, #10b981 0%, #047857 100%) !important;
+
+ --mdc-protected-button-container-color: transparent !important;
+ --mdc-filled-button-container-color: transparent !important;
+
+ color: #fff !important;
+ box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
+}
diff --git a/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.ts b/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.ts
index 8b7135817..413186748 100644
--- a/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.ts
+++ b/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.ts
@@ -12,23 +12,23 @@ import { ActivatedRoute, Router } from '@angular/router';
/** Custom Services */
import { FixedDepositsService } from '../fixed-deposits.service';
+import { ClientsService } from 'app/clients/clients.service';
import { SettingsService } from 'app/settings/settings.service';
+import { AlertService } from 'app/core/alert/alert.service';
+import { ProductsService } from 'app/products/products.service';
/** Custom Components */
import { FixedDepositAccountDetailsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component';
-import { FixedDepositAccountTermsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-terms-step/fixed-deposit-account-terms-step.component';
-import { FixedDepositAccountSettingsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-settings-step/fixed-deposit-account-settings-step.component';
-import { FixedDepositAccountChargesStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-charges-step/fixed-deposit-account-charges-step.component';
+import { FixedDepositAccountCurrencyStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-currency-step/fixed-deposit-account-currency-step.component';
import { Dates } from 'app/core/utils/dates';
-import { Currency } from 'app/shared/models/general.model';
-import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
+import { MatTabsModule } from '@angular/material/tabs';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { FixedDepositAccountInterestRateChartStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-interest-rate-chart-step/fixed-deposit-account-interest-rate-chart-step.component';
import { FixedDepositAccountPreviewStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-preview-step/fixed-deposit-account-preview-step.component';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
- * Edit Fixed Deposit Account Component
+ * Edit Fixed Deposit Account Component — same tab UI as Create, pre-populated with existing data.
*/
@Component({
selector: 'mifosx-edit-fixed-deposit-account',
@@ -36,16 +36,11 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./edit-fixed-deposit-account.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
- MatStepper,
- MatStepperIcon,
+ MatTabsModule,
FaIconComponent,
- MatStep,
- MatStepLabel,
FixedDepositAccountDetailsStepComponent,
- FixedDepositAccountTermsStepComponent,
- FixedDepositAccountSettingsStepComponent,
FixedDepositAccountInterestRateChartStepComponent,
- FixedDepositAccountChargesStepComponent,
+ FixedDepositAccountCurrencyStepComponent,
FixedDepositAccountPreviewStepComponent
]
})
@@ -55,110 +50,201 @@ export class EditFixedDepositAccountComponent {
private dateUtils = inject(Dates);
private fixedDepositsService = inject(FixedDepositsService);
private settingsService = inject(SettingsService);
+ private alertService = inject(AlertService);
+ private productsService = inject(ProductsService);
+ private clientsService = inject(ClientsService);
- /** Fixed Deposits Account Details Step */
@ViewChild(FixedDepositAccountDetailsStepComponent, { static: true })
fixedDepositsAccountDetailsStep: FixedDepositAccountDetailsStepComponent;
- /** Fixed Deposits Account Terms Step */
- @ViewChild(FixedDepositAccountTermsStepComponent, { static: true })
- fixedDepositAccountTermsStep: FixedDepositAccountTermsStepComponent;
- /** Fixed Deposits Account Settings Step */
- @ViewChild(FixedDepositAccountSettingsStepComponent, { static: true })
- fixedDepositAccountSettingsStep: FixedDepositAccountSettingsStepComponent;
- /** Fixed Deposits Account Charges Step */
- @ViewChild(FixedDepositAccountChargesStepComponent, { static: true })
- fixedDepositAccountChargesStep: FixedDepositAccountChargesStepComponent;
+ @ViewChild(FixedDepositAccountCurrencyStepComponent)
+ fixedDepositAccountCurrencyStep: FixedDepositAccountCurrencyStepComponent;
- /** Fixed Deposits Account Template */
+ /** Full account data + template from resolver */
fixedDepositsAccountAndTemplate: any;
- /** Fixed Deposit Account Product Template */
+ /** Product template emitted by details step */
fixedDepositsAccountProductTemplate: any;
+ /** Currently visible wizard tab (0-indexed). */
+ selectedTabIndex = 0;
+ private readonly totalTabs = 4;
+ /** Active CBS base interest rate versions for the selected product's FD type */
+ cbsBaseRates: any[] = [];
+ /** Tenure selected from the interest rate chart step */
+ selectedTenure: {
+ depositPeriod: number;
+ depositPeriodFrequencyId: number;
+ depositAmount?: number;
+ maturityAmount?: number;
+ interestEarned?: number;
+ appliedRate?: number;
+ isSeniorCitizen?: boolean;
+ } | null = null;
- currency: Currency;
-
- /**
- * Fetches FD account template from `resolve`
- * @param {ActivatedRoute} route Activated Route
- * @param {Router} router Router
- * @param {Dates} dateUtils Date Utils
- * @param {FixedDepositsService} fixedDepositsService Fixed Deposits Service
- * @param {SettingsService} settingsService Settings Service
- */
constructor() {
this.route.data.subscribe((data: { fixedDepositsAccountAndTemplate: any }) => {
- this.fixedDepositsAccountAndTemplate = data.fixedDepositsAccountAndTemplate;
+ const account = data.fixedDepositsAccountAndTemplate;
+ this.fixedDepositsAccountAndTemplate = account;
+ // Pre-populate tenure from existing account
+ if (account?.depositPeriod) {
+ this.selectedTenure = {
+ depositPeriod: account.depositPeriod,
+ depositPeriodFrequencyId: account.depositPeriodFrequencyType?.id ?? 2,
+ depositAmount: account.depositAmount,
+ isSeniorCitizen: !!account.isSeniorCitizen,
+ appliedRate: account.seniorCitizenInterestRate ?? undefined
+ };
+ }
+ if (account?.clientId && account?.accountNo) {
+ this.clientsService
+ .getClientNominees(String(account.clientId), account.accountNo, 'FD')
+ .subscribe((nominees: any[]) => {
+ if (Array.isArray(nominees) && nominees.length > 0) {
+ this.fixedDepositsAccountAndTemplate = { ...this.fixedDepositsAccountAndTemplate, nominees };
+ }
+ });
+ }
});
}
- /**
- * Sets fixed deposits account product template.
- * @param {any} $event API response
- */
+ next(): void {
+ if (this.selectedTabIndex < this.totalTabs - 1) {
+ this.selectedTabIndex += 1;
+ }
+ }
+
+ prev(): void {
+ if (this.selectedTabIndex > 0) {
+ this.selectedTabIndex -= 1;
+ }
+ }
+
setTemplate($event: any) {
this.fixedDepositsAccountProductTemplate = $event;
- this.currency = this.fixedDepositsAccountAndTemplate.currency;
+ const fdType = $event?.fdType;
+ this.productsService.getFdBaseInterestRates(fdType).subscribe({
+ next: (rates: any[]) => (this.cbsBaseRates = rates || []),
+ error: () => (this.cbsBaseRates = [])
+ });
+ }
+
+ onTenureSelected($event: {
+ depositPeriod: number;
+ depositPeriodFrequencyId: number;
+ depositAmount?: number;
+ maturityAmount?: number;
+ interestEarned?: number;
+ appliedRate?: number;
+ isSeniorCitizen?: boolean;
+ }) {
+ this.selectedTenure = $event;
}
- /**
- * Retrieves Fixed Deposit Account Details Form Data
- */
get fixedDepositAccountDetailsForm() {
- return this.fixedDepositsAccountDetailsStep.fixedDepositAccountDetailsForm;
+ return this.fixedDepositsAccountDetailsStep?.fixedDepositAccountDetailsForm;
}
- /**
- * Retrieves Fixed Deposit Account Terms Form Data
- */
- get fixedDepositAccountTermsForm() {
- return this.fixedDepositAccountTermsStep.fixedDepositAccountTermsForm;
- }
-
- /**
- * Retrieves Fixed Deposit Account Settings Form Data
- */
- get fixedDepositAccountSettingsForm() {
- return this.fixedDepositAccountSettingsStep.fixedDepositAccountSettingsForm;
- }
-
- /**
- * Checks if stepper is valid and not pristine.
- */
- get fixedDepositAccountFormValidAndNotPristine() {
- return (
- this.fixedDepositAccountDetailsForm.valid &&
- this.fixedDepositAccountTermsForm.valid &&
- this.fixedDepositAccountSettingsForm.valid &&
- (!this.fixedDepositAccountDetailsForm.pristine ||
- !this.fixedDepositAccountTermsForm.pristine ||
- !this.fixedDepositAccountSettingsForm.pristine ||
- !this.fixedDepositAccountChargesStep.pristine)
- );
- }
-
- /**
- * Creates the fixed deposit account object.
- */
get fixedDepositAccount() {
return {
- ...this.fixedDepositsAccountDetailsStep.fixedDepositAccountDetails,
- ...this.fixedDepositAccountTermsStep.fixedDepositAccountTerms,
- ...this.fixedDepositAccountSettingsStep.fixedDepositAccountSettings,
- ...this.fixedDepositAccountChargesStep?.fixedDepositAccountCharges
+ ...this.fixedDepositsAccountDetailsStep?.fixedDepositAccountDetails,
+ ...this.fixedDepositAccountCurrencyStep?.currencyStepData
};
}
- /**
- * Submits the fixed deposit form and creates a new fixed deposit account
- */
+ get submittedOnDate(): Date | null {
+ const val =
+ this.fixedDepositsAccountDetailsStep?.fixedDepositAccountDetails?.submittedOnDate ??
+ this.fixedDepositsAccountAndTemplate?.timeline?.submittedOnDate;
+ if (!val) return null;
+ const d = Array.isArray(val) ? new Date(val[0], val[1] - 1, val[2]) : new Date(val);
+ return isNaN(d.getTime()) ? null : d;
+ }
+
submit() {
+ const detailsStep = this.fixedDepositsAccountDetailsStep;
+ if (detailsStep?.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) {
+ this.alertService.alert({
+ type: 'Error',
+ message: `Total ownership is ${detailsStep.totalJointSharePercentage}%. It must equal 100% before submitting.`
+ });
+ return;
+ }
+ if (
+ this.fixedDepositsAccountProductTemplate?.nominationFacility &&
+ (this.fixedDepositAccountCurrencyStep?.nominees ?? []).length === 0
+ ) {
+ this.alertService.alert({
+ type: 'Error',
+ message: 'Nomination Facility is enabled for this product. Please add at least one nominee before submitting.'
+ });
+ return;
+ }
+ if (!this.selectedTenure || !this.selectedTenure.depositPeriod) {
+ this.alertService.alert({
+ type: 'Error',
+ message: 'Please enter the Deposit Tenure and click "Find Rate" before submitting.'
+ });
+ return;
+ }
+ if (!detailsStep?.selectedCustomerSegment) {
+ this.alertService.alert({ type: 'Error', message: 'Please select a Customer Segment before submitting.' });
+ return;
+ }
const locale = this.settingsService.language.code;
const dateFormat = this.settingsService.dateFormat;
const monthDayFormat = 'dd MMMM';
- const fixedDepositAccount = {
+ const pt = this.fixedDepositsAccountProductTemplate;
+
+ const terms: any = {};
+ if (pt?.nominalAnnualInterestRate != null) terms.nominalAnnualInterestRate = pt.nominalAnnualInterestRate;
+ if (pt?.interestCompoundingPeriodType?.id != null)
+ terms.interestCompoundingPeriodType = pt.interestCompoundingPeriodType.id;
+ if (pt?.interestPostingPeriodType?.id != null) terms.interestPostingPeriodType = pt.interestPostingPeriodType.id;
+ if (pt?.interestCalculationType?.id != null) terms.interestCalculationType = pt.interestCalculationType.id;
+ if (pt?.interestCalculationDaysInYearType?.id != null)
+ terms.interestCalculationDaysInYearType = pt.interestCalculationDaysInYearType.id;
+
+ if (this.selectedTenure?.depositAmount != null) {
+ terms.depositAmount = this.selectedTenure.depositAmount;
+ } else if (pt?.depositAmount != null) {
+ terms.depositAmount = pt.depositAmount;
+ }
+
+ if (pt?.minDepositAmount != null) terms.minDepositAmount = pt.minDepositAmount;
+ if (pt?.maxDepositAmount != null) terms.maxDepositAmount = pt.maxDepositAmount;
+
+ const settings: any = {};
+ if (pt?.lockinPeriodFrequency != null) settings.lockinPeriodFrequency = pt.lockinPeriodFrequency;
+ if (pt?.lockinPeriodFrequencyType?.id != null) settings.lockinPeriodFrequencyType = pt.lockinPeriodFrequencyType.id;
+ if (pt?.minDepositTerm != null) settings.minDepositTerm = pt.minDepositTerm;
+ if (pt?.minDepositTermType?.id != null) settings.minDepositTermTypeId = pt.minDepositTermType.id;
+ if (pt?.inMultiplesOfDepositTerm != null) settings.inMultiplesOfDepositTerm = pt.inMultiplesOfDepositTerm;
+ if (pt?.inMultiplesOfDepositTermType?.id != null)
+ settings.inMultiplesOfDepositTermTypeId = pt.inMultiplesOfDepositTermType.id;
+ if (pt?.maxDepositTerm != null) settings.maxDepositTerm = pt.maxDepositTerm;
+ if (pt?.maxDepositTermType?.id != null) settings.maxDepositTermTypeId = pt.maxDepositTermType.id;
+ if (pt?.preClosurePenalApplicable != null) settings.preClosurePenalApplicable = pt.preClosurePenalApplicable;
+ if (pt?.preClosurePenalInterest != null) settings.preClosurePenalInterest = pt.preClosurePenalInterest;
+ if (pt?.preClosurePenalInterestOnType?.id != null)
+ settings.preClosurePenalInterestOnTypeId = pt.preClosurePenalInterestOnType.id;
+ if (pt?.maturityInstructionId != null) settings.maturityInstructionId = pt.maturityInstructionId;
+ if (pt?.transferInterestToSavings != null) settings.transferInterestToSavings = pt.transferInterestToSavings;
+ if (pt?.withHoldTax != null) settings.withHoldTax = pt.withHoldTax;
+
+ const rawCharges: any[] =
+ (this.fixedDepositAccount.charges?.length ? this.fixedDepositAccount.charges : pt?.charges) ?? [];
+
+ const fixedDepositAccount: any = {
...this.fixedDepositAccount,
+ ...terms,
+ ...settings,
+ ...(this.selectedTenure
+ ? {
+ depositPeriod: this.selectedTenure.depositPeriod,
+ depositPeriodFrequencyId: this.selectedTenure.depositPeriodFrequencyId
+ }
+ : {}),
clientId: this.fixedDepositsAccountAndTemplate.clientId,
- charges: this.fixedDepositAccount.charges.map((charge: any) => ({
- chargeId: charge.id,
+ charges: rawCharges.map((charge: any) => ({
+ chargeId: charge.chargeId ?? charge.id,
amount: charge.amount,
dueDate: charge.dueDate && this.dateUtils.formatDate(charge.dueDate, dateFormat),
feeOnMonthDay:
@@ -166,11 +252,35 @@ export class EditFixedDepositAccountComponent {
feeInterval: charge.feeInterval
})),
submittedOnDate: this.dateUtils.formatDate(this.fixedDepositAccount.submittedOnDate, dateFormat),
- charts: [{ chartSlabs: this.fixedDepositsAccountProductTemplate.accountChart.chartSlabs }],
+ charts: [{ chartSlabs: pt?.accountChart?.chartSlabs ?? [] }],
dateFormat,
monthDayFormat,
locale
};
+
+ const nominees = this.fixedDepositAccountCurrencyStep?.nominees ?? [];
+ if (nominees.length > 0) {
+ fixedDepositAccount.nominees = nominees.map((n: any) => ({
+ nomineeClientId: n.nomineeClientId,
+ nominationType: n.nominationType,
+ relationshipId: n.relationshipId,
+ percentageShare: n.percentageShare,
+ nomineeIsMinor: n.nomineeIsMinor,
+ ...(n.guardianClientId ? { guardianClientId: n.guardianClientId } : {}),
+ remarks: n.remarks
+ }));
+ }
+
+ if (
+ this.selectedTenure?.isSeniorCitizen &&
+ this.selectedTenure.appliedRate != null &&
+ this.selectedTenure.appliedRate > 0
+ ) {
+ fixedDepositAccount.isSeniorCitizen = true;
+ fixedDepositAccount.seniorCitizenInterestRate = this.selectedTenure.appliedRate;
+ fixedDepositAccount.nominalAnnualInterestRate = this.selectedTenure.appliedRate;
+ }
+
this.fixedDepositsService
.updateFixedDepositAccount(this.fixedDepositsAccountAndTemplate.id, fixedDepositAccount)
.subscribe((response: any) => {
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-currency-step/fixed-deposit-account-currency-step.component.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-currency-step/fixed-deposit-account-currency-step.component.ts
index 7bd2ffca1..76e81186b 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-currency-step/fixed-deposit-account-currency-step.component.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-currency-step/fixed-deposit-account-currency-step.component.ts
@@ -28,6 +28,7 @@ import { FormsModule } from '@angular/forms';
export class FixedDepositAccountCurrencyStepComponent implements OnChanges {
@Input() fixedDepositsAccountProductTemplate: any;
@Input() primaryClientId: number | null = null;
+ @Input() existingAccount: any = null;
@ViewChild(NomineeFormComponent) nomineeFormComp: NomineeFormComponent;
nominees: any[] = [];
@@ -38,8 +39,20 @@ export class FixedDepositAccountCurrencyStepComponent implements OnChanges {
autoDebitFacility = false;
nominationFacility = false;
+ private existingAccountApplied = false;
+
ngOnChanges(changes: SimpleChanges): void {
- if (changes['fixedDepositsAccountProductTemplate']?.currentValue) {
+ if (changes['existingAccount']?.currentValue) {
+ const ea = changes['existingAccount'].currentValue;
+ this.existingAccountApplied = true;
+ if (ea.autoDebitFacility !== undefined) {
+ this.autoDebitFacility = !!ea.autoDebitFacility;
+ }
+ if (Array.isArray(ea.nominees) && ea.nominees.length > 0) {
+ this.nominees = [...ea.nominees];
+ }
+ }
+ if (changes['fixedDepositsAccountProductTemplate']?.currentValue && !this.existingAccountApplied) {
const pt = changes['fixedDepositsAccountProductTemplate'].currentValue;
this.autoDebitFacility = !!pt.autoDebitFacility;
this.nominationFacility = !!pt.nominationFacility;
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component.html b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component.html
index dc49e8bd7..b5f865cbd 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component.html
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component.html
@@ -101,7 +101,7 @@
Account No
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component.ts
index 21b6b7435..c4a42c210 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-details-step/fixed-deposit-account-details-step.component.ts
@@ -71,6 +71,7 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
fixedDepositAccountDetailsForm!: UntypedFormGroup;
isProductSelected = false;
+ private isEditMode = false;
/** Fixed Deposits Account Template with product data */
@Output() fixedDepositsAccountProductTemplate = new EventEmitter();
@@ -248,6 +249,20 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
currencyCode: a.currency?.code ?? ''
}));
this.dalLoading = false;
+ // Pre-select linked account in edit mode
+ const linkedId =
+ this.fixedDepositsAccountTemplate?.linkedAccount?.id ?? this.fixedDepositsAccountTemplate?.linkedAccountId;
+ if (linkedId) {
+ const savings = this.dalAllSavings.find((a: any) => a.id === linkedId);
+ const current = this.dalAllCurrent.find((a: any) => a.id === linkedId);
+ if (savings) {
+ this.dalSelectedType = 'SAVINGS';
+ this.selectedLinkedAccount = savings;
+ } else if (current) {
+ this.dalSelectedType = 'CURRENT';
+ this.selectedLinkedAccount = current;
+ }
+ }
this.cdr.detectChanges();
},
error: () => {
@@ -265,9 +280,27 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
this.buildDependencies();
if (this.fixedDepositsAccountTemplate) {
this.productData = this.fixedDepositsAccountTemplate.productOptions;
- if (this.fixedDepositsAccountTemplate.depositProductId) {
+ const existingProductId =
+ this.fixedDepositsAccountTemplate.depositProductId ?? this.fixedDepositsAccountTemplate.productId;
+ // Edit mode: account response has accountNo; pure template has previewAccountNo but no accountNo
+ if (this.fixedDepositsAccountTemplate.accountNo) {
+ this.isEditMode = true;
+ }
+ if ((!this.productData || this.productData.length === 0) && existingProductId) {
+ this.isEditMode = true;
+ this.productData = [
+ {
+ id: existingProductId,
+ name:
+ this.fixedDepositsAccountTemplate.productName ??
+ this.fixedDepositsAccountTemplate.depositProductName ??
+ 'Product'
+ }
+ ];
+ }
+ if (existingProductId) {
this.fixedDepositAccountDetailsForm.patchValue({
- productId: this.fixedDepositsAccountTemplate.depositProductId,
+ productId: existingProductId,
submittedOnDate:
this.fixedDepositsAccountTemplate.timeline?.submittedOnDate &&
new Date(this.fixedDepositsAccountTemplate.timeline.submittedOnDate),
@@ -361,6 +394,9 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
}
private autoDetectAccountStatus(): void {
+ if (this.isEditMode) {
+ return;
+ }
if (this.clientAge === null) return;
if (this.clientAge < 18) {
this.selectedAccountStatus = 'MINOR';
@@ -430,10 +466,90 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
} else {
this.fixedDepositAccountDetailsForm.get('fieldOfficerId')!.patchValue('');
}
+ // === Restore existing account values when in edit/modify mode ===
+ this.restoreExistingAccountValues();
});
});
}
+ private restoreExistingAccountValues(): void {
+ const ea = this.fixedDepositsAccountTemplate;
+ if (!ea?.depositProductId && !ea?.productId) {
+ return;
+ }
+
+ const strVal = (v: any): string => (typeof v === 'object' && v !== null ? (v.value ?? '') : (v ?? ''));
+
+ const accountStatus = strVal(ea.accountStatus);
+ if (accountStatus) {
+ this.selectedAccountStatus = accountStatus;
+ }
+
+ const customerSegment = strVal(ea.customerSegment);
+ if (customerSegment) {
+ this.selectedCustomerSegment = customerSegment;
+ }
+
+ // FD API returns cbs_account_type aliased as "accountType" (string); Current/Savings return it as "cbsAccountType"
+ const rawType = ea.cbsAccountType ?? (typeof ea.accountType === 'string' ? ea.accountType : null);
+ const accountType = rawType ? strVal(rawType) : null;
+ if (accountType) {
+ this.selectedAccountType = accountType;
+ }
+
+ if (accountStatus === 'JOINT') {
+ const modeOfOp = strVal(ea.modeOfOperation);
+ if (modeOfOp) {
+ this.modeOfOperation = modeOfOp;
+ }
+ if (Array.isArray(ea.jointHolders) && ea.jointHolders.length > 0) {
+ // Support both camelCase (fixed) and lowercase (MySQL queryForList) key formats
+ const isPrimary = (h: any) => h.isPrimary ?? h.isprimary ?? false;
+ const primary = ea.jointHolders.find((h: any) => isPrimary(h));
+ const primaryShare = primary?.sharePercentage ?? primary?.sharepercentage;
+ if (primaryShare) {
+ this.primaryHolderShare = +primaryShare;
+ }
+ this.jointHolders = ea.jointHolders
+ .filter((h: any) => !isPrimary(h))
+ .map((h: any) => ({
+ clientId: h.clientId ?? h.clientid,
+ clientName: h.displayName ?? h.displayname ?? h.clientName ?? '',
+ clientCif: h.cifNumber ?? h.cifnumber ?? h.clientCif ?? '',
+ sharePercentage: +(h.sharePercentage ?? h.sharepercentage ?? 0),
+ isMandatory: !!(h.isMandatory ?? h.ismandatory)
+ }));
+ }
+ }
+ if (accountStatus === 'INDIVIDUAL_MALE' && ea.specialCategory) {
+ this.selectedSpecialCategory = ea.specialCategory;
+ }
+ if (accountStatus === 'INDIVIDUAL_FEMALE' && ea.weakerSection) {
+ this.selectedWeakerSection = ea.weakerSection;
+ }
+ if (accountStatus === 'MINOR') {
+ if (ea.guardianClientId) {
+ this.guardianClientId = ea.guardianClientId;
+ }
+ if (ea.guardianName) {
+ this.guardianName = ea.guardianName;
+ }
+ if (ea.guardianCifNumber) {
+ this.guardianCif = ea.guardianCifNumber;
+ }
+ if (ea.guardianRelationship) {
+ this.guardianRelationship = ea.guardianRelationship;
+ }
+ if (ea.minorDateOfBirth) {
+ const d = new Date(ea.minorDateOfBirth);
+ if (!isNaN(d.getTime())) {
+ this.minorDob = d;
+ }
+ }
+ }
+ this.cdr.detectChanges();
+ }
+
hasValue(val: any): boolean {
return val !== null && val !== undefined && val !== '';
}
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-interest-rate-chart-step/fixed-deposit-account-interest-rate-chart-step.component.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-interest-rate-chart-step/fixed-deposit-account-interest-rate-chart-step.component.ts
index 3589f5d2d..a54cf9e1d 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-interest-rate-chart-step/fixed-deposit-account-interest-rate-chart-step.component.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-interest-rate-chart-step/fixed-deposit-account-interest-rate-chart-step.component.ts
@@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-import { Component, Input, OnChanges, Output, EventEmitter, ViewChild } from '@angular/core';
+import { Component, Input, OnChanges, SimpleChanges, Output, EventEmitter, ViewChild, inject } from '@angular/core';
import {
MatTableDataSource,
MatTable,
@@ -25,6 +25,7 @@ import { NgIf, NgSwitch, TitleCasePipe, DecimalPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+import { FixedDepositsService } from '../../fixed-deposits.service';
export type FdPeriodUnit = 'Days' | 'Months' | 'Years';
@@ -69,10 +70,22 @@ export interface SlabMatchResult {
]
})
export class FixedDepositAccountInterestRateChartStepComponent implements OnChanges {
+ private fixedDepositsService = inject(FixedDepositsService);
+
@Input() fixedDepositsAccountTemplate: any;
@Input() fixedDepositsAccountProductTemplate: any;
/** Active CBS base interest rate versions — when provided, overrides the product chart slabs */
@Input() cbsBaseRates: any[] = [];
+ /** Pre-populate tenure from existing account (edit mode) */
+ @Input() initialTenure: {
+ depositPeriod: number;
+ depositPeriodFrequencyId: number;
+ depositAmount?: number;
+ isSeniorCitizen?: boolean;
+ appliedRate?: number;
+ } | null = null;
+ /** Submission date for exact calculation (passed from create/edit form) */
+ @Input() submittedOnDate: Date | null = null;
activeBase: any = null;
@@ -157,8 +170,10 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
return this.fdType === 'SPECIAL';
}
- ngOnChanges() {
- if (this.fixedDepositsAccountProductTemplate) {
+ ngOnChanges(changes: SimpleChanges) {
+ // Only reset/re-apply when TEMPLATE or cbsBaseRates changes, NOT when initialTenure/submittedOnDate changes alone
+ const templateChanged = changes['fixedDepositsAccountProductTemplate'] || changes['cbsBaseRates'];
+ if (templateChanged && this.fixedDepositsAccountProductTemplate) {
const fdType = this.fixedDepositsAccountProductTemplate?.fdType;
this.activeBase = (this.cbsBaseRates || []).find((r) => r.fdType === fdType && r.isActive);
this.interestRateChartData = this.activeBase?.slabs
@@ -169,9 +184,36 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
}
this.resetCalculator();
this.tenurePeriodUnit = this.allowedPeriodUnits[0];
+ // Pre-fill tenure in edit mode
+ this.applyInitialTenure();
}
}
+ private applyInitialTenure(): void {
+ if (!this.initialTenure?.depositPeriod) {
+ return;
+ }
+ // Map frequency ID to period unit: Days=0, Weeks=1, Months=2, Years=3
+ const freqId = this.initialTenure.depositPeriodFrequencyId;
+ const unitMap: Record = { 0: 'Days', 1: 'Days', 2: 'Months', 3: 'Years' };
+ const unit: FdPeriodUnit = unitMap[freqId] ?? 'Months';
+ if (this.allowedPeriodUnits.includes(unit)) {
+ this.tenurePeriodUnit = unit;
+ } else {
+ this.tenurePeriodUnit = this.allowedPeriodUnits[0];
+ }
+ this.tenureValue = this.initialTenure.depositPeriod;
+ if (this.initialTenure.depositAmount) {
+ this.principalAmount = this.initialTenure.depositAmount;
+ }
+ // Pre-check senior citizen if it was set during create
+ if (this.initialTenure.isSeniorCitizen) {
+ this.isSeniorCitizen = true;
+ }
+ // Auto-calculate when tenure is pre-filled (edit mode)
+ setTimeout(() => this.calculateRate(), 0);
+ }
+
private normalizeCbsSlabs(slabs: any[]): any[] {
return (slabs || []).map((s) => ({
...s,
@@ -257,38 +299,116 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
tenureDays: enteredDays
};
- if (this.principalAmount && this.principalAmount > 0) {
- const p = this.principalAmount;
- const r = rate / 100;
- const years = enteredDays / 365;
- const n = 4; // Quarterly compounding
-
- if (this.isSpecial) {
- // Monthly payout: simple interest paid monthly, principal returned at maturity
- result.estimatedMonthlyPayout = (p * rate) / 1200;
- result.interestEarned = result.estimatedMonthlyPayout * (enteredDays / 30);
- result.maturityAmount = p; // principal returned at maturity
- } else {
- // Compound interest (Standard Quarterly)
- result.maturityAmount = p * Math.pow(1 + r / n, n * years);
- result.interestEarned = result.maturityAmount - p;
- }
- }
-
- this.matchResult = result;
-
- // Emit selected tenure and calculated maturity to parent
+ this.matchResult = result; // Show slab match immediately, maturity updates async if principal entered
const frequencyId = this.resolveFrequencyId(this.tenurePeriodUnit);
- if (frequencyId !== null) {
- this.tenureSelected.emit({
- depositPeriod: this.tenureValue,
- depositPeriodFrequencyId: frequencyId,
- depositAmount: this.principalAmount || undefined,
- maturityAmount: result.maturityAmount,
- interestEarned: result.interestEarned,
- appliedRate: result.appliedRate,
- isSeniorCitizen: this.isSeniorCitizen
- });
+
+ if (this.principalAmount && this.principalAmount > 0) {
+ // Convert tenure to months for backend API
+ let tenureInMonths = 0;
+ if (this.tenurePeriodUnit === 'Years') {
+ tenureInMonths = this.tenureValue * 12;
+ } else if (this.tenurePeriodUnit === 'Months') {
+ tenureInMonths = this.tenureValue;
+ } else {
+ tenureInMonths = Math.floor(enteredDays / 30);
+ }
+
+ // Map compounding period type id → months (0=Daily, 1=Daily, 4=Monthly, 5=Quarterly, 6=SemiAnnual, 7=Annual)
+ const compMap: Record = { 1: 0, 4: 1, 5: 3, 6: 6, 7: 12 };
+ const compId = this.fixedDepositsAccountProductTemplate?.interestCompoundingPeriodType?.id ?? 4;
+ const compMonths = compMap[compId] ?? 1;
+
+ // Map posting period type id → months
+ const postId = this.fixedDepositsAccountProductTemplate?.interestPostingPeriodType?.id ?? 4;
+ const postMonths = compMap[postId] ?? 1;
+
+ const emitResult = () => {
+ if (frequencyId !== null) {
+ this.tenureSelected.emit({
+ depositPeriod: this.tenureValue,
+ depositPeriodFrequencyId: frequencyId,
+ depositAmount: this.principalAmount || undefined,
+ maturityAmount: result.maturityAmount,
+ interestEarned: result.interestEarned,
+ appliedRate: result.appliedRate,
+ isSeniorCitizen: this.isSeniorCitizen
+ });
+ }
+ };
+
+ // Get submittedOnDate — Input takes priority over template data
+ let submittedOnDateStr: string;
+ if (this.submittedOnDate) {
+ submittedOnDateStr = this.submittedOnDate.toISOString().split('T')[0];
+ } else {
+ const submittedOnDateRaw =
+ this.fixedDepositsAccountTemplate?.timeline?.submittedOnDate ??
+ this.fixedDepositsAccountProductTemplate?.timeline?.submittedOnDate;
+ if (submittedOnDateRaw) {
+ const d = Array.isArray(submittedOnDateRaw)
+ ? new Date(submittedOnDateRaw[0], submittedOnDateRaw[1] - 1, submittedOnDateRaw[2])
+ : new Date(submittedOnDateRaw);
+ submittedOnDateStr = !isNaN(d.getTime())
+ ? d.toISOString().split('T')[0]
+ : new Date().toISOString().split('T')[0];
+ } else {
+ submittedOnDateStr = new Date().toISOString().split('T')[0];
+ }
+ }
+ const daysInYearVal =
+ this.fixedDepositsAccountProductTemplate?.interestCalculationDaysInYearType?.id === 360 ? 360 : 365;
+
+ // Always use backend API for accurate calculation (including daily compounding with 0)
+ this.fixedDepositsService
+ .calculateFixedDepositInterest({
+ principalAmount: this.principalAmount,
+ annualInterestRate: rate,
+ tenureInMonths,
+ interestCompoundingPeriodInMonths: compMonths, // 0 = daily, now supported by backend
+ interestPostingPeriodInMonths: postMonths,
+ submittedOnDate: submittedOnDateStr,
+ daysInYear: daysInYearVal
+ })
+ .subscribe({
+ next: (apiResult: any) => {
+ result.maturityAmount = apiResult.maturityAmount;
+ result.interestEarned =
+ apiResult.totalInterest ?? Math.round((apiResult.maturityAmount - this.principalAmount) * 100) / 100;
+ this.matchResult = { ...result };
+ emitResult();
+ },
+ error: () => {
+ // Fallback if API fails: frontend formula
+ const r = rate / 100;
+ if (compMonths === 0) {
+ // Daily compounding fallback
+ result.maturityAmount =
+ Math.round(this.principalAmount * Math.pow(1 + r / daysInYearVal, enteredDays) * 100) / 100;
+ } else {
+ const n = 12 / compMonths;
+ const totalCompoundings = Math.floor(tenureInMonths / compMonths);
+ const rPerCompounding = rate / (n * 100);
+ result.maturityAmount =
+ Math.round(this.principalAmount * Math.pow(1 + rPerCompounding, totalCompoundings) * 100) / 100;
+ }
+ result.interestEarned = Math.round((result.maturityAmount - this.principalAmount) * 100) / 100;
+ this.matchResult = { ...result };
+ emitResult();
+ }
+ });
+ } else {
+ this.matchResult = result;
+ if (frequencyId !== null) {
+ this.tenureSelected.emit({
+ depositPeriod: this.tenureValue,
+ depositPeriodFrequencyId: frequencyId,
+ depositAmount: undefined,
+ maturityAmount: undefined,
+ interestEarned: undefined,
+ appliedRate: result.appliedRate,
+ isSeniorCitizen: this.isSeniorCitizen
+ });
+ }
}
}
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts
index ca1b56aee..b4a2ea5e0 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts
@@ -92,6 +92,15 @@ export class FixedDepositsButtonsConfiguration {
}
];
break;
+ case 'Rejected':
+ case 'rejected':
+ this.buttonsArray = [
+ {
+ name: 'Modify Application',
+ icon: 'fa fa-pencil'
+ }
+ ];
+ break;
default:
this.buttonsArray = [];
}
diff --git a/src/app/deposits/fixed-deposits/fixed-deposits.service.ts b/src/app/deposits/fixed-deposits/fixed-deposits.service.ts
index 5e19f5786..6d20ed276 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposits.service.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposits.service.ts
@@ -114,6 +114,34 @@ export class FixedDepositsService {
return this.http.get(`/fixeddepositaccounts/${accountId}`, { params: httpParams });
}
+ /**
+ * Calculate FD maturity using backend formula.
+ * When submittedOnDate + daysInYear are provided, uses exact calendar-based calculation
+ * matching the actual account creation engine.
+ */
+ calculateFixedDepositInterest(params: {
+ principalAmount: number;
+ annualInterestRate: number;
+ tenureInMonths: number;
+ interestCompoundingPeriodInMonths: number;
+ interestPostingPeriodInMonths: number;
+ submittedOnDate?: string; // yyyy-MM-dd — enables exact calendar calculation
+ daysInYear?: number; // 360 or 365
+ }): Observable {
+ let httpParams = new HttpParams();
+ Object.entries(params).forEach(
+ ([
+ key,
+ val
+ ]) => {
+ if (val !== undefined && val !== null) {
+ httpParams = httpParams.set(key, String(val));
+ }
+ }
+ );
+ return this.http.get(`/fixeddepositaccounts/calculate-fd-interest`, { params: httpParams });
+ }
+
/**
* @param fixedDepositAccount Fixed Deposit Account
* @returns {Observable}
diff --git a/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.html b/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.html
index 055268a5f..f3d7d5225 100644
--- a/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.html
+++ b/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.html
@@ -6,103 +6,157 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ 'labels.inputs.DETAILS' | translate }}
-
-
-
-
-
-
- {{ 'labels.inputs.TERMS' | translate }}
-
-
-
-
-
-
- {{ 'labels.inputs.SETTINGS' | translate }}
-
-
-
-
-
-
- {{ 'labels.inputs.INTEREST RATE CHART' | translate }}
-
-
-
-
-
-
- {{ 'labels.inputs.CHARGES' | translate }}
-
-
-
-
-
- @if (recurringDepositAccountFormValidAndNotPristine) {
-
- {{ 'labels.inputs.PREVIEW' | translate }}
-
+
+
+
+ Product
+
+
+
-
-
- }
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Currency
+
+
+ @if (recurringDepositsAccountProductTemplate) {
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Interest Rate Chart
+
+
+ @if (recurringDepositsAccountProductTemplate) {
+
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'labels.inputs.PREVIEW' | translate }}
+
+
+ @if (recurringDepositsAccountProductTemplate) {
+
+
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.scss b/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.scss
index af9f4c075..657546bcc 100644
--- a/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.scss
+++ b/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.scss
@@ -5,3 +5,164 @@
* 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/.
*/
+
+$navy: #0d1b2a;
+$blue: #1e88e5;
+$blue-d: #1e40af;
+$indigo: #4f46e5;
+$border: #e2e8f0;
+
+.cra-container {
+ max-width: 1180px;
+ margin: 0 auto;
+ padding: 20px 24px;
+}
+
+/* ─── Hero header (matches Nexora pages) ────────────────────────────── */
+.cra-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 16px;
+
+ &__icon {
+ width: 44px;
+ height: 44px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 12px;
+ background: linear-gradient(135deg, $blue 0%, $indigo 100%);
+ color: #fff;
+ font-size: 18px;
+ box-shadow: 0 2px 8px rgb(29 78 216 / 25%);
+ flex-shrink: 0;
+ }
+
+ &__title {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 800;
+ color: $navy;
+ line-height: 1.1;
+ letter-spacing: 0.02em;
+ }
+
+ &__sub {
+ margin-top: 2px;
+ font-size: 12.5px;
+ color: #64748b;
+ }
+}
+
+/* ─── Wizard tab strip ──────────────────────────────────────────────── */
+.cra-tabs {
+ background: #fff;
+ border: 1px solid $border;
+ border-radius: 14px;
+ box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
+ overflow: hidden;
+
+ ::ng-deep {
+ .mat-mdc-tab-header {
+ background: #f8fafc;
+ border-bottom: 1px solid $border;
+ }
+
+ .mat-mdc-tab {
+ min-width: 140px;
+ height: 50px;
+ padding: 0 22px;
+ font-size: 13px !important;
+ font-weight: 600 !important;
+ letter-spacing: 0.01em;
+ color: #475569 !important;
+ opacity: 1 !important;
+ }
+
+ .mat-mdc-tab:hover:not(.mdc-tab--active, .mat-mdc-tab-disabled) {
+ background: rgb(30 64 175 / 5%);
+ color: $navy !important;
+ }
+
+ .mat-mdc-tab.mdc-tab--active {
+ .mdc-tab__text-label {
+ color: $blue-d !important;
+ font-weight: 700 !important;
+ }
+ }
+
+ .mdc-tab-indicator__content--underline {
+ border-color: $blue-d !important;
+ border-top-width: 3px !important;
+ }
+
+ .mat-mdc-tab.mat-mdc-tab-disabled {
+ opacity: 0.45 !important;
+ cursor: not-allowed;
+ }
+
+ .mat-mdc-tab-body-wrapper {
+ min-height: 320px;
+ }
+
+ .mat-mdc-tab-body,
+ .mat-mdc-tab-body-content {
+ overflow: visible !important;
+ }
+ }
+}
+
+.cra-tab__icon {
+ margin-right: 8px;
+ font-size: 13px;
+}
+
+/* ─── Tab body + action bar ─────────────────────────────────────────── */
+.cra-tab-body {
+ padding: 24px;
+
+ @media (width <= 768px) {
+ padding: 16px;
+ }
+}
+
+.cra-actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-top: 24px;
+ padding-top: 16px;
+ border-top: 1px solid #f1f5f9;
+
+ &__spacer {
+ flex: 1;
+ }
+
+ button {
+ height: 40px !important;
+ border-radius: 10px !important;
+ font-weight: 600 !important;
+ padding: 0 18px !important;
+ }
+}
+
+.m-l-10 {
+ margin-left: 8px;
+}
+
+.m-r-10 {
+ margin-right: 8px;
+}
+
+button.preview-submit-row__primary,
+.mat-mdc-raised-button.mat-primary.preview-submit-row__primary,
+.preview-submit-row__primary {
+ background: linear-gradient(135deg, #10b981 0%, #047857 100%) !important;
+
+ --mdc-protected-button-container-color: transparent !important;
+ --mdc-filled-button-container-color: transparent !important;
+
+ color: #fff !important;
+ box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
+}
diff --git a/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.ts b/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.ts
index 8df7a977d..702c8228e 100644
--- a/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.ts
+++ b/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.ts
@@ -12,23 +12,23 @@ import { ActivatedRoute, Router } from '@angular/router';
/** Custom Services */
import { RecurringDepositsService } from '../recurring-deposits.service';
+import { ClientsService } from 'app/clients/clients.service';
import { SettingsService } from 'app/settings/settings.service';
import { ProductsService } from 'app/products/products.service';
+import { AlertService } from 'app/core/alert/alert.service';
/** Step Components */
import { RecurringDepositsAccountDetailsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component';
-import { RecurringDepositsAccountTermsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-terms-step/recurring-deposits-account-terms-step.component';
-import { RecurringDepositsAccountSettingsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-settings-step/recurring-deposits-account-settings-step.component';
-import { RecurringDepositsAccountChargesStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-charges-step/recurring-deposits-account-charges-step.component';
import { Dates } from 'app/core/utils/dates';
-import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
+import { MatTabsModule } from '@angular/material/tabs';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+import { RecurringDepositsAccountCurrencyStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component';
import { RecurringDepositsAccountInterestRateChartStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component';
import { RecurringDepositsAccountPreviewStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
- * Edit new recurring deposit account
+ * Edit Recurring Deposit Account Component — same tab UI as Create, pre-populated with existing data.
*/
@Component({
selector: 'mifosx-edit-recurring-deposit-account',
@@ -36,16 +36,11 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./edit-recurring-deposit-account.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
- MatStepper,
- MatStepperIcon,
+ MatTabsModule,
FaIconComponent,
- MatStep,
- MatStepLabel,
RecurringDepositsAccountDetailsStepComponent,
- RecurringDepositsAccountTermsStepComponent,
- RecurringDepositsAccountSettingsStepComponent,
+ RecurringDepositsAccountCurrencyStepComponent,
RecurringDepositsAccountInterestRateChartStepComponent,
- RecurringDepositsAccountChargesStepComponent,
RecurringDepositsAccountPreviewStepComponent
]
})
@@ -56,20 +51,117 @@ export class EditRecurringDepositAccountComponent {
private recurringDepositsService = inject(RecurringDepositsService);
private settingsService = inject(SettingsService);
private productsService = inject(ProductsService);
+ private alertService = inject(AlertService);
+ private clientsService = inject(ClientsService);
- /** Imports all the step component */
@ViewChild(RecurringDepositsAccountDetailsStepComponent, { static: true })
recurringDepositsAccountDetailsStep: RecurringDepositsAccountDetailsStepComponent;
- @ViewChild(RecurringDepositsAccountTermsStepComponent, { static: true })
- recurringDepositAccountTermsStep: RecurringDepositsAccountTermsStepComponent;
- @ViewChild(RecurringDepositsAccountSettingsStepComponent, { static: true })
- recurringDepositAccountSettingsStep: RecurringDepositsAccountSettingsStepComponent;
- @ViewChild(RecurringDepositsAccountChargesStepComponent, { static: true })
- recurringDepositAccountChargesStep: RecurringDepositsAccountChargesStepComponent;
+
+ @ViewChild(RecurringDepositsAccountCurrencyStepComponent)
+ recurringDepositsAccountCurrencyStep: RecurringDepositsAccountCurrencyStepComponent;
@ViewChild(RecurringDepositsAccountInterestRateChartStepComponent)
recurringDepositsAccountChartStep: RecurringDepositsAccountInterestRateChartStepComponent;
+ /** Full account data + template from resolver */
+ recurringDepositsAccountAndTemplate: any;
+ /** Product template emitted by details step */
+ recurringDepositsAccountProductTemplate: any;
+ /** Currently visible wizard tab (0-indexed). */
+ selectedTabIndex = 0;
+ private readonly totalTabs = 4;
+ /** Active CBS base interest rate versions */
+ cbsBaseRates: any[] = [];
+ /** Selected tenure value and frequency from calculator */
+ selectedTenureValue: number | null = null;
+ selectedTenureFrequencyId: number | null = null;
+ selectedDepositAmount: number | null = null;
+ rdAppliedRate: number | null = null;
+ rdIsSeniorCitizen = false;
+
+ constructor() {
+ this.route.data.subscribe((data: { recurringDepositsAccountAndTemplate: any }) => {
+ const account = data.recurringDepositsAccountAndTemplate;
+ this.recurringDepositsAccountAndTemplate = account;
+ // Pre-populate tenure from existing account
+ if (account?.depositPeriod) {
+ this.selectedTenureValue = account.depositPeriod;
+ this.selectedTenureFrequencyId = account.depositPeriodFrequencyType?.id ?? 2;
+ }
+ if (account?.mandatoryRecommendedDepositAmount) {
+ this.selectedDepositAmount = account.mandatoryRecommendedDepositAmount;
+ }
+ if (account?.clientId && account?.accountNo) {
+ this.clientsService
+ .getClientNominees(String(account.clientId), account.accountNo, 'RD')
+ .subscribe((nominees: any[]) => {
+ if (Array.isArray(nominees) && nominees.length > 0) {
+ this.recurringDepositsAccountAndTemplate = { ...this.recurringDepositsAccountAndTemplate, nominees };
+ }
+ });
+ }
+ });
+ }
+
+ get initialTenure(): {
+ depositPeriod: number;
+ depositPeriodFrequencyId: number;
+ depositAmount?: number;
+ isSeniorCitizen?: boolean;
+ appliedRate?: number;
+ } | null {
+ if (!this.selectedTenureValue) {
+ return null;
+ }
+ return {
+ depositPeriod: this.selectedTenureValue,
+ depositPeriodFrequencyId: this.selectedTenureFrequencyId ?? 2,
+ depositAmount: this.selectedDepositAmount ?? undefined,
+ isSeniorCitizen: !!this.recurringDepositsAccountAndTemplate?.isSeniorCitizen,
+ appliedRate: this.recurringDepositsAccountAndTemplate?.seniorCitizenInterestRate ?? undefined
+ };
+ }
+
+ next(): void {
+ if (this.selectedTabIndex < this.totalTabs - 1) {
+ this.selectedTabIndex += 1;
+ }
+ }
+
+ prev(): void {
+ if (this.selectedTabIndex > 0) {
+ this.selectedTabIndex -= 1;
+ }
+ }
+
+ setTemplate($event: any) {
+ this.recurringDepositsAccountProductTemplate = $event;
+ this.productsService.getFdBaseInterestRates().subscribe({
+ next: (rates: any[]) => (this.cbsBaseRates = rates || []),
+ error: () => (this.cbsBaseRates = [])
+ });
+ }
+
+ handleTenureSelected($event: any) {
+ this.selectedTenureValue = $event.depositPeriod;
+ this.selectedTenureFrequencyId = $event.depositPeriodFrequencyId;
+ this.selectedDepositAmount = $event.depositAmount;
+ this.rdAppliedRate = $event.appliedRate ?? null;
+ this.rdIsSeniorCitizen = $event.isSeniorCitizen ?? false;
+ }
+
+ get recurringDepositAccountDetailsForm() {
+ return this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetailsForm;
+ }
+
+ get recurringDepositAccountData() {
+ return { ...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails };
+ }
+
+ get recurringDepositAccount() {
+ return { ...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails };
+ }
+
get rdTenureDays(): number | null {
return this.recurringDepositsAccountChartStep?.matchResult?.tenureDays ?? null;
}
@@ -94,112 +186,153 @@ export class EditRecurringDepositAccountComponent {
return this.recurringDepositAccountDetailsForm?.value?.submittedOnDate ?? null;
}
- /** Recurring Deposits Account And Template */
- recurringDepositsAccountAndTemplate: any;
- /** Recurring Deposit Account Product Template */
- recurringDepositsAccountProductTemplate: any;
-
- constructor() {
- this.route.data.subscribe((data: { recurringDepositsAccountAndTemplate: any }) => {
- this.recurringDepositsAccountAndTemplate = data.recurringDepositsAccountAndTemplate;
- });
- }
-
- /**
- * Sets recurring deposits account product template.
- * @param {any} $event API response
- */
- setTemplate($event: any) {
- this.recurringDepositsAccountProductTemplate = $event;
- }
-
- /** Get Recurring Deposit Account Details Form Data */
- get recurringDepositAccountDetailsForm() {
- return this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetailsForm;
- }
-
- /** Get Recurring Deposit Account Terms Form Data */
- get recurringDepositAccountTermsForm() {
- return this.recurringDepositAccountTermsStep.recurringDepositAccountTermsForm;
- }
-
- /** Get Recurring Deposit Account Settings Form Data */
- get recurringDepositAccountSettingsForm() {
- return this.recurringDepositAccountSettingsStep.recurringDepositAccountSettingsForm;
- }
-
- // Checks if stepper is valid and not pristine.
- get recurringDepositAccountFormValidAndNotPristine() {
- return (
- this.recurringDepositAccountDetailsForm.valid &&
- this.recurringDepositAccountTermsForm.valid &&
- this.recurringDepositAccountSettingsForm.valid &&
- (!this.recurringDepositAccountDetailsForm.pristine ||
- !this.recurringDepositAccountTermsForm.pristine ||
- !this.recurringDepositAccountSettingsForm.pristine ||
- !this.recurringDepositAccountChargesStep.pristine)
- );
- }
-
- /** Retrieves Data of all the forms */
- get recurringDepositAccountData() {
+ get rdCurrencyFlags() {
return {
- ...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails,
- ...this.recurringDepositAccountTermsStep.recurringDepositAccountTerms,
- ...this.recurringDepositAccountSettingsStep.recurringDepositAccountSettings,
- ...this.recurringDepositAccountChargesStep.recurringDepositAccountCharges
+ autoDebitFacility: this.recurringDepositsAccountCurrencyStep?.autoDebitFacility ?? false
};
}
- /** Retrieves Data of all forms except Currency to submit the data */
- get recurringDepositAccount() {
- return {
- ...this.recurringDepositsAccountDetailsStep.recurringDepositAccountDetails,
- ...this.recurringDepositAccountTermsStep.recurringDepositAccountTerms,
- ...this.recurringDepositAccountSettingsStep.recurringDepositAccountSettings,
- ...this.recurringDepositAccountChargesStep.recurringDepositAccountCharges
- };
- }
-
- /**
- * Submits the recurring deposit form to create a new recurring deposit account
- */
submit() {
+ const detailsStep = this.recurringDepositsAccountDetailsStep;
+ if (detailsStep.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) {
+ this.alertService.alert({
+ type: 'Error',
+ message: `Total ownership is ${detailsStep.totalJointSharePercentage}%. It must equal 100% before submitting.`
+ });
+ return;
+ }
+ if (
+ this.recurringDepositsAccountProductTemplate?.nominationFacility &&
+ (this.recurringDepositsAccountCurrencyStep?.nominees ?? []).length === 0
+ ) {
+ this.alertService.alert({
+ type: 'Error',
+ message: 'Nomination Facility is enabled for this product. Please add at least one nominee before submitting.'
+ });
+ return;
+ }
+ if (!this.selectedTenureValue || this.selectedTenureFrequencyId === null) {
+ this.alertService.alert({
+ type: 'Error',
+ message: 'Please enter the Deposit Tenure and click "Find Rate" before submitting.'
+ });
+ return;
+ }
+ if (!this.selectedDepositAmount || this.selectedDepositAmount <= 0) {
+ this.alertService.alert({
+ type: 'Error',
+ message: 'Please enter the Monthly Installment amount and click "Find Rate" before submitting.'
+ });
+ return;
+ }
+ if (!detailsStep?.selectedCustomerSegment) {
+ this.alertService.alert({ type: 'Error', message: 'Please select a Customer Segment before submitting.' });
+ return;
+ }
const locale = this.settingsService.language.code;
const dateFormat = this.settingsService.dateFormat;
const monthDayFormat = 'dd MMMM';
- const recurringDepositAccount = {
- ...this.recurringDepositAccount,
+ const pt = this.recurringDepositsAccountProductTemplate;
+ const details = this.recurringDepositAccount;
+ const currencyStep = this.recurringDepositsAccountCurrencyStep;
+
+ const terms: any = {};
+ if (pt?.nominalAnnualInterestRate != null) terms.nominalAnnualInterestRate = pt.nominalAnnualInterestRate;
+ if (pt?.interestCompoundingPeriodType?.id != null)
+ terms.interestCompoundingPeriodType = pt.interestCompoundingPeriodType.id;
+ if (pt?.interestPostingPeriodType?.id != null) terms.interestPostingPeriodType = pt.interestPostingPeriodType.id;
+ if (pt?.interestCalculationType?.id != null) terms.interestCalculationType = pt.interestCalculationType.id;
+ if (pt?.interestCalculationDaysInYearType?.id != null)
+ terms.interestCalculationDaysInYearType = pt.interestCalculationDaysInYearType.id;
+
+ const settings: any = {};
+ if (pt?.lockinPeriodFrequency != null) settings.lockinPeriodFrequency = pt.lockinPeriodFrequency;
+ if (pt?.lockinPeriodFrequencyType?.id != null) settings.lockinPeriodFrequencyType = pt.lockinPeriodFrequencyType.id;
+ if (pt?.minDepositTerm != null) settings.minDepositTerm = pt.minDepositTerm;
+ if (pt?.minDepositTermType?.id != null) settings.minDepositTermTypeId = pt.minDepositTermType.id;
+ if (pt?.inMultiplesOfDepositTerm != null) settings.inMultiplesOfDepositTerm = pt.inMultiplesOfDepositTerm;
+ if (pt?.inMultiplesOfDepositTermType?.id != null)
+ settings.inMultiplesOfDepositTermTypeId = pt.inMultiplesOfDepositTermType.id;
+ if (pt?.maxDepositTerm != null) settings.maxDepositTerm = pt.maxDepositTerm;
+ if (pt?.maxDepositTermType?.id != null) settings.maxDepositTermTypeId = pt.maxDepositTermType.id;
+ if (pt?.preClosurePenalApplicable != null) settings.preClosurePenalApplicable = pt.preClosurePenalApplicable;
+ if (pt?.preClosurePenalInterest != null) settings.preClosurePenalInterest = pt.preClosurePenalInterest;
+ if (pt?.preClosurePenalInterestOnType?.id != null)
+ settings.preClosurePenalInterestOnTypeId = pt.preClosurePenalInterestOnType.id;
+ if (pt?.maturityInstructionId != null) settings.maturityInstructionId = pt.maturityInstructionId;
+ if (pt?.transferInterestToSavings != null) settings.transferInterestToSavings = pt.transferInterestToSavings;
+ if (pt?.withHoldTax != null) settings.withHoldTax = pt.withHoldTax;
+
+ const productDefaults: any = {
+ isCalendarInherited: pt?.isCalendarInherited ?? false,
+ isMandatoryDeposit: pt?.isMandatoryDeposit ?? false,
+ allowWithdrawal: pt?.allowWithdrawal ?? false,
+ adjustAdvanceTowardsFuturePayments: pt?.adjustAdvanceTowardsFuturePayments ?? false,
+ autoDebitFacility: currencyStep?.autoDebitFacility ?? pt?.autoDebitFacility ?? false
+ };
+
+ const rawCharges: any[] = pt?.charges ?? [];
+
+ const recurringDepositAccount: any = {
+ ...details,
+ ...terms,
+ ...settings,
+ ...productDefaults,
clientId: this.recurringDepositsAccountAndTemplate.clientId,
- charges: this.recurringDepositAccount.charges.map((charge: any) => ({
- chargeId: charge.id,
+ mandatoryRecommendedDepositAmount: pt?.minDepositAmount ?? 0,
+ recurringFrequency: pt?.recurringFrequency ?? 1,
+ recurringFrequencyType: pt?.recurringFrequencyType ?? 2,
+ submittedOnDate: this.dateUtils.formatDate(details.submittedOnDate, dateFormat),
+ expectedFirstDepositOnDate: this.dateUtils.formatDate(details.expectedFirstDepositOnDate, dateFormat),
+ dateFormat,
+ monthDayFormat,
+ locale,
+ charts: [
+ {
+ chartSlabs:
+ pt?.accountChart?.chartSlabs ?? pt?.activeChart?.chartSlabs ?? pt?.interestRateCharts?.[0]?.chartSlabs ?? []
+ }
+ ],
+ charges: rawCharges.map((charge: any) => ({
+ chargeId: charge.chargeId ?? charge.id,
amount: charge.amount,
dueDate: charge.dueDate && this.dateUtils.formatDate(charge.dueDate, dateFormat),
feeOnMonthDay: charge.feeOnMonthDay,
feeInterval: charge.feeInterval
- })),
- isCalendarInherited: this.recurringDepositAccount.recurringDepositAccount
- ? this.recurringDepositAccount.recurringDepositAccount
- : false,
- submittedOnDate: this.dateUtils.formatDate(this.recurringDepositAccount.submittedOnDate, dateFormat),
- expectedFirstDepositOnDate: this.dateUtils.formatDate(
- this.recurringDepositAccount.expectedFirstDepositOnDate,
- dateFormat
- ),
- charts: [
- {
- chartSlabs:
- this.recurringDepositsAccountProductTemplate?.accountChart?.chartSlabs ??
- this.recurringDepositsAccountProductTemplate?.activeChart?.chartSlabs ??
- this.recurringDepositsAccountProductTemplate?.interestRateCharts?.[0]?.chartSlabs ??
- []
- }
- ],
- dateFormat,
- monthDayFormat,
- locale
+ }))
};
+ if (this.selectedTenureValue && this.selectedTenureFrequencyId !== null) {
+ recurringDepositAccount.depositPeriod = this.selectedTenureValue;
+ recurringDepositAccount.depositPeriodFrequencyId = this.selectedTenureFrequencyId;
+ } else if (this.rdTenureDays) {
+ recurringDepositAccount.depositPeriod = this.rdTenureDays;
+ recurringDepositAccount.depositPeriodFrequencyId = 0;
+ }
+
+ if (this.selectedDepositAmount) {
+ recurringDepositAccount.mandatoryRecommendedDepositAmount = this.selectedDepositAmount;
+ }
+
+ if (this.rdIsSeniorCitizen && this.rdAppliedRate != null && this.rdAppliedRate > 0) {
+ recurringDepositAccount.isSeniorCitizen = true;
+ recurringDepositAccount.seniorCitizenInterestRate = this.rdAppliedRate;
+ recurringDepositAccount.nominalAnnualInterestRate = this.rdAppliedRate;
+ }
+
+ const nominees = this.recurringDepositsAccountCurrencyStep?.nominees ?? [];
+ if (nominees.length > 0) {
+ recurringDepositAccount.nominees = nominees.map((n: any) => ({
+ nomineeClientId: n.nomineeClientId,
+ nominationType: n.nominationType,
+ relationshipId: n.relationshipId,
+ percentageShare: n.percentageShare,
+ nomineeIsMinor: n.nomineeIsMinor,
+ ...(n.guardianClientId ? { guardianClientId: n.guardianClientId } : {}),
+ remarks: n.remarks
+ }));
+ }
+
this.recurringDepositsService
.updateRecurringDepositAccount(this.recurringDepositsAccountAndTemplate.id, recurringDepositAccount)
.subscribe((response: any) => {
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts
index 8ca90813f..cb46ab05c 100644
--- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts
+++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component.ts
@@ -28,6 +28,7 @@ import { FormsModule } from '@angular/forms';
export class RecurringDepositsAccountCurrencyStepComponent implements OnChanges {
@Input() recurringDepositsAccountProductTemplate: any;
@Input() primaryClientId: number | null = null;
+ @Input() existingAccount: any = null;
@ViewChild(NomineeFormComponent) nomineeFormComp: NomineeFormComponent;
nominees: any[] = [];
@@ -37,8 +38,20 @@ export class RecurringDepositsAccountCurrencyStepComponent implements OnChanges
autoDebitFacility = false;
+ private existingAccountApplied = false;
+
ngOnChanges(changes: SimpleChanges): void {
- if (changes['recurringDepositsAccountProductTemplate']?.currentValue) {
+ if (changes['existingAccount']?.currentValue) {
+ const ea = changes['existingAccount'].currentValue;
+ this.existingAccountApplied = true;
+ if (ea.autoDebitFacility !== undefined) {
+ this.autoDebitFacility = !!ea.autoDebitFacility;
+ }
+ if (Array.isArray(ea.nominees) && ea.nominees.length > 0) {
+ this.nominees = [...ea.nominees];
+ }
+ }
+ if (changes['recurringDepositsAccountProductTemplate']?.currentValue && !this.existingAccountApplied) {
const pt = changes['recurringDepositsAccountProductTemplate'].currentValue;
this.autoDebitFacility = !!pt.autoDebitFacility;
}
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html
index ee854d2ac..702defd3a 100644
--- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html
+++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.html
@@ -95,7 +95,7 @@
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts
index 18d37b66a..6ba4bea37 100644
--- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts
+++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-details-step/recurring-deposits-account-details-step.component.ts
@@ -70,6 +70,7 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
recurringDepositAccountDetailsForm!: UntypedFormGroup;
isProductSelected = false;
+ private isEditMode = false;
/** Recurring Deposits Account Template with product data */
@Output() recurringDepositsAccountProductTemplate = new EventEmitter();
@@ -224,6 +225,21 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
currencyCode: a.currency?.code ?? ''
}));
this.dalLoading = false;
+ // Pre-select linked account in edit mode
+ const linkedId =
+ this.recurringDepositsAccountTemplate?.linkedAccount?.id ??
+ this.recurringDepositsAccountTemplate?.linkedAccountId;
+ if (linkedId) {
+ const savings = this.dalAllSavings.find((a: any) => a.id === linkedId);
+ const current = this.dalAllCurrent.find((a: any) => a.id === linkedId);
+ if (savings) {
+ this.dalSelectedType = 'SAVINGS';
+ this.selectedLinkedAccount = savings;
+ } else if (current) {
+ this.dalSelectedType = 'CURRENT';
+ this.selectedLinkedAccount = current;
+ }
+ }
this.cdr.detectChanges();
},
error: () => {
@@ -241,11 +257,29 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
this.buildDependencies();
if (this.recurringDepositsAccountTemplate) {
this.productData = this.recurringDepositsAccountTemplate.productOptions;
- if (this.recurringDepositsAccountTemplate.depositProductId) {
+ const existingProductId =
+ this.recurringDepositsAccountTemplate.depositProductId ?? this.recurringDepositsAccountTemplate.productId;
+ // Edit mode: account response has accountNo; pure template has previewAccountNo but no accountNo
+ if (this.recurringDepositsAccountTemplate.accountNo) {
+ this.isEditMode = true;
+ }
+ if ((!this.productData || this.productData.length === 0) && existingProductId) {
+ this.isEditMode = true;
+ this.productData = [
+ {
+ id: existingProductId,
+ name:
+ this.recurringDepositsAccountTemplate.productName ??
+ this.recurringDepositsAccountTemplate.depositProductName ??
+ 'Product'
+ }
+ ];
+ }
+ if (existingProductId) {
this.recurringDepositAccountDetailsForm.patchValue({
- productId: this.recurringDepositsAccountTemplate.depositProductId,
+ productId: existingProductId,
submittedOnDate:
- this.recurringDepositsAccountTemplate.timeline.submittedOnDate &&
+ this.recurringDepositsAccountTemplate.timeline?.submittedOnDate &&
new Date(this.recurringDepositsAccountTemplate.timeline.submittedOnDate),
externalId: this.recurringDepositsAccountTemplate.externalId
});
@@ -317,6 +351,9 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
}
private autoDetectAccountStatus(): void {
+ if (this.isEditMode) {
+ return;
+ }
if (this.clientAge === null) return;
if (this.clientAge < 18) {
this.selectedAccountStatus = 'MINOR';
@@ -391,10 +428,93 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
} else {
this.recurringDepositAccountDetailsForm.get('fieldOfficerId')!.patchValue('');
}
+ // === Restore existing account values when in edit/modify mode ===
+ this.restoreExistingAccountValues();
});
});
}
+ private restoreExistingAccountValues(): void {
+ const ea = this.recurringDepositsAccountTemplate;
+ if (!ea?.depositProductId && !ea?.productId) {
+ return;
+ }
+
+ const strVal = (v: any): string => (typeof v === 'object' && v !== null ? (v.value ?? '') : (v ?? ''));
+
+ const accountStatus = strVal(ea.accountStatus);
+ if (accountStatus) {
+ this.selectedAccountStatus = accountStatus;
+ }
+
+ const customerSegment = strVal(ea.customerSegment);
+ if (customerSegment) {
+ this.selectedCustomerSegment = customerSegment;
+ }
+
+ // RD API returns cbs_account_type aliased as "accountType" (string); Current/Savings return it as "cbsAccountType"
+ const rawType = ea.cbsAccountType ?? (typeof ea.accountType === 'string' ? ea.accountType : null);
+ const accountType = rawType ? strVal(rawType) : null;
+ if (accountType) {
+ this.selectedAccountType = accountType;
+ }
+
+ if (accountStatus === 'JOINT') {
+ const modeOfOp = strVal(ea.modeOfOperation);
+ if (modeOfOp) {
+ this.modeOfOperation = modeOfOp;
+ }
+ if (Array.isArray(ea.jointHolders) && ea.jointHolders.length > 0) {
+ const isPrimaryHolder = (h: any) => h.isPrimary ?? h.isprimary ?? false;
+ const primary = ea.jointHolders.find((h: any) => isPrimaryHolder(h));
+ const primaryShare = primary?.sharePercentage ?? primary?.sharepercentage;
+ if (primaryShare) {
+ this.primaryHolderShare = +primaryShare;
+ }
+ this.jointHolders = ea.jointHolders
+ .filter((h: any) => !isPrimaryHolder(h))
+ .map((h: any) => ({
+ clientId: h.clientId ?? h.clientid,
+ clientName: h.displayName ?? h.displayname ?? h.clientName ?? '',
+ clientCif: h.cifNumber ?? h.cifnumber ?? h.clientCif ?? '',
+ sharePercentage: +(h.sharePercentage ?? h.sharepercentage ?? 0),
+ isMandatory: !!(h.isMandatory ?? h.ismandatory)
+ }));
+ }
+ }
+ if (accountStatus === 'INDIVIDUAL_MALE' && ea.specialCategory) {
+ this.selectedSpecialCategory = ea.specialCategory;
+ }
+ if (accountStatus === 'INDIVIDUAL_FEMALE' && ea.weakerSection) {
+ this.selectedWeakerSection = ea.weakerSection;
+ }
+ if (accountStatus === 'MINOR') {
+ if (ea.guardianClientId) {
+ this.guardianClientId = ea.guardianClientId;
+ }
+ if (ea.guardianName) {
+ this.guardianName = ea.guardianName;
+ }
+ if (ea.guardianCifNumber) {
+ this.guardianCif = ea.guardianCifNumber;
+ }
+ if (ea.guardianRelationship) {
+ this.guardianRelationship = ea.guardianRelationship;
+ }
+ if (ea.minorDateOfBirth) {
+ const d = new Date(ea.minorDateOfBirth);
+ if (!isNaN(d.getTime())) {
+ this.minorDob = d;
+ }
+ }
+ }
+ // autoDebitFacility from existing account (override product default)
+ if (ea.autoDebitFacility !== undefined) {
+ this.recurringDepositAccountDetailsForm.patchValue({ autoDebitFacility: !!ea.autoDebitFacility });
+ }
+ this.cdr.detectChanges();
+ }
+
hasValue(val: any): boolean {
return val !== null && val !== undefined && val !== '';
}
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.ts
index 55678ab38..46cf19aa6 100644
--- a/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.ts
+++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component.ts
@@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-import { Component, Input, OnChanges, ViewChild, Output, EventEmitter, inject } from '@angular/core';
+import { Component, Input, OnChanges, SimpleChanges, ViewChild, Output, EventEmitter, inject } from '@angular/core';
import { RecurringDepositsService } from '../../recurring-deposits.service';
import {
MatTable,
@@ -86,6 +86,14 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
@Input() submittedOnDate: Date | null = null;
/** Active CBS base interest rate versions — when provided, overrides the product chart slabs */
@Input() cbsBaseRates: any[] = [];
+ /** Pre-populate tenure from existing account (edit mode) */
+ @Input() initialTenure: {
+ depositPeriod: number;
+ depositPeriodFrequencyId: number;
+ depositAmount?: number;
+ isSeniorCitizen?: boolean;
+ appliedRate?: number;
+ } | null = null;
@Output() tenureSelected = new EventEmitter<{
depositPeriod: number;
@@ -165,17 +173,42 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
private recurringDepositsService = inject(RecurringDepositsService);
- ngOnChanges() {
+ ngOnChanges(changes: SimpleChanges) {
+ // Only reset/re-apply when the TEMPLATE or cbsBaseRates changes, NOT when initialTenure changes alone
+ const templateChanged =
+ changes['recurringDepositsAccountProductTemplate'] ||
+ changes['recurringDepositsAccountTemplate'] ||
+ changes['cbsBaseRates'];
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
- if (template) {
+ if (templateChanged && template) {
if (this.chartsTableRef) {
this.chartsTableRef.renderRows();
}
this.resetCalculator();
this.tenurePeriodUnit = this.allowedPeriodUnits[1]; // Default to Months
+ this.applyInitialTenure();
}
}
+ private applyInitialTenure(): void {
+ if (!this.initialTenure?.depositPeriod) {
+ return;
+ }
+ const freqId = this.initialTenure.depositPeriodFrequencyId;
+ // RD: Months=2, Years=3
+ this.tenurePeriodUnit = freqId === 3 ? 'Years' : 'Months';
+ this.tenureValue = this.initialTenure.depositPeriod;
+ if (this.initialTenure.depositAmount) {
+ this.principalAmount = this.initialTenure.depositAmount;
+ }
+ // Pre-check senior citizen if it was set during create
+ if (this.initialTenure.isSeniorCitizen) {
+ this.isSeniorCitizen = true;
+ }
+ // Auto-calculate when tenure is pre-filled (edit mode)
+ setTimeout(() => this.calculateRate(), 0);
+ }
+
// Convert entered tenure to Days for slab matching
private toDays(value: number, unit: RdPeriodUnit): number {
if (unit === 'Days') return value;
@@ -240,6 +273,8 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
tenureDays: enteredDays
};
+ this.matchResult = result; // Show slab match immediately, maturity updates async when API responds
+
// Maturity Estimation via Backend API
if (this.principalAmount && this.principalAmount > 0) {
const compoundingType = this.recurringDepositsAccountProductTemplate?.interestCompoundingPeriodType?.id;
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-buttons.config.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-buttons.config.ts
index 0055573fd..5c18484c6 100644
--- a/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-buttons.config.ts
+++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-buttons.config.ts
@@ -111,6 +111,16 @@ export class RecurringDepositsButtonsConfiguration {
}
];
break;
+ case 'Rejected':
+ case 'rejected':
+ this.buttonsArray = [
+ {
+ name: 'Modify Application',
+ icon: 'fa fa-edit',
+ taskPermissionName: 'UPDATE_RECURRINGDEPOSITACCOUNT'
+ }
+ ];
+ break;
default:
this.buttonsArray = [];
}
diff --git a/src/app/savings/edit-savings-account/edit-savings-account.component.html b/src/app/savings/edit-savings-account/edit-savings-account.component.html
index 3e7293cbd..f262b50d0 100644
--- a/src/app/savings/edit-savings-account/edit-savings-account.component.html
+++ b/src/app/savings/edit-savings-account/edit-savings-account.component.html
@@ -6,76 +6,140 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ 'labels.inputs.DETAILS' | translate }}
-
-
-
-
-
-
- {{ 'labels.inputs.TERMS' | translate }}
-
-
-
-
-
- {{ 'labels.inputs.CHARGES' | translate }}
-
-
-
-
-
- @if (savingsAccountFormValidAndNotPristine) {
-
- {{ 'labels.inputs.PREVIEW' | translate }}
-
+
+
+
+ Product
+
+
+
-
-
- }
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Currency
+
+
+ @if (savingsAccountProductTemplate) {
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Digital
+
+
+ @if (savingsAccountProductTemplate) {
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'labels.inputs.PREVIEW' | translate }}
+
+
+ @if (savingsAccountProductTemplate) {
+
+
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/savings/edit-savings-account/edit-savings-account.component.scss b/src/app/savings/edit-savings-account/edit-savings-account.component.scss
index af9f4c075..4115661a8 100644
--- a/src/app/savings/edit-savings-account/edit-savings-account.component.scss
+++ b/src/app/savings/edit-savings-account/edit-savings-account.component.scss
@@ -5,3 +5,164 @@
* 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/.
*/
+
+$navy: #0d1b2a;
+$blue: #1e88e5;
+$blue-d: #1e40af;
+$indigo: #4f46e5;
+$border: #e2e8f0;
+
+.csa-container {
+ max-width: 1180px;
+ margin: 0 auto;
+ padding: 20px 24px;
+}
+
+/* ─── Hero header (matches Nexora pages) ────────────────────────────── */
+.csa-header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 16px;
+
+ &__icon {
+ width: 44px;
+ height: 44px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 12px;
+ background: linear-gradient(135deg, $blue 0%, $indigo 100%);
+ color: #fff;
+ font-size: 18px;
+ box-shadow: 0 2px 8px rgb(29 78 216 / 25%);
+ flex-shrink: 0;
+ }
+
+ &__title {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 800;
+ color: $navy;
+ line-height: 1.1;
+ letter-spacing: 0.02em;
+ }
+
+ &__sub {
+ margin-top: 2px;
+ font-size: 12.5px;
+ color: #64748b;
+ }
+}
+
+/* ─── Wizard tab strip ──────────────────────────────────────────────── */
+.csa-tabs {
+ background: #fff;
+ border: 1px solid $border;
+ border-radius: 14px;
+ box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
+ overflow: hidden;
+
+ ::ng-deep {
+ .mat-mdc-tab-header {
+ background: #f8fafc;
+ border-bottom: 1px solid $border;
+ }
+
+ .mat-mdc-tab {
+ min-width: 140px;
+ height: 50px;
+ padding: 0 22px;
+ font-size: 13px !important;
+ font-weight: 600 !important;
+ letter-spacing: 0.01em;
+ color: #475569 !important;
+ opacity: 1 !important;
+ }
+
+ .mat-mdc-tab:hover:not(.mdc-tab--active, .mat-mdc-tab-disabled) {
+ background: rgb(30 64 175 / 5%);
+ color: $navy !important;
+ }
+
+ .mat-mdc-tab.mdc-tab--active {
+ .mdc-tab__text-label {
+ color: $blue-d !important;
+ font-weight: 700 !important;
+ }
+ }
+
+ .mdc-tab-indicator__content--underline {
+ border-color: $blue-d !important;
+ border-top-width: 3px !important;
+ }
+
+ .mat-mdc-tab.mat-mdc-tab-disabled {
+ opacity: 0.45 !important;
+ cursor: not-allowed;
+ }
+
+ .mat-mdc-tab-body-wrapper {
+ min-height: 320px;
+ }
+
+ .mat-mdc-tab-body,
+ .mat-mdc-tab-body-content {
+ overflow: visible !important;
+ }
+ }
+}
+
+.csa-tab__icon {
+ margin-right: 8px;
+ font-size: 13px;
+}
+
+/* ─── Tab body + action bar ─────────────────────────────────────────── */
+.csa-tab-body {
+ padding: 24px;
+
+ @media (width <= 768px) {
+ padding: 16px;
+ }
+}
+
+.csa-actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-top: 24px;
+ padding-top: 16px;
+ border-top: 1px solid #f1f5f9;
+
+ &__spacer {
+ flex: 1;
+ }
+
+ button {
+ height: 40px !important;
+ border-radius: 10px !important;
+ font-weight: 600 !important;
+ padding: 0 18px !important;
+ }
+}
+
+.m-l-10 {
+ margin-left: 8px;
+}
+
+.m-r-10 {
+ margin-right: 8px;
+}
+
+button.preview-submit-row__primary,
+.mat-mdc-raised-button.mat-primary.preview-submit-row__primary,
+.preview-submit-row__primary {
+ background: linear-gradient(135deg, #10b981 0%, #047857 100%) !important;
+
+ --mdc-protected-button-container-color: transparent !important;
+ --mdc-filled-button-container-color: transparent !important;
+
+ color: #fff !important;
+ box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
+}
diff --git a/src/app/savings/edit-savings-account/edit-savings-account.component.ts b/src/app/savings/edit-savings-account/edit-savings-account.component.ts
index 99c596600..fcc0e0cd6 100644
--- a/src/app/savings/edit-savings-account/edit-savings-account.component.ts
+++ b/src/app/savings/edit-savings-account/edit-savings-account.component.ts
@@ -9,23 +9,26 @@
/** Angular Imports */
import { Component, ViewChild, inject } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
+import { FormGroup } from '@angular/forms';
+import { ClientsService } from 'app/clients/clients.service';
/** Custom Components */
import { SavingsAccountDetailsStepComponent } from '../savings-account-stepper/savings-account-details-step/savings-account-details-step.component';
-import { SavingsAccountTermsStepComponent } from '../savings-account-stepper/savings-account-terms-step/savings-account-terms-step.component';
-import { SavingsAccountChargesStepComponent } from '../savings-account-stepper/savings-account-charges-step/savings-account-charges-step.component';
+import { SavingsAccountCurrencyStepComponent } from '../savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component';
+import { SavingsAccountDigitalStepComponent } from '../savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component';
/** Custom Services */
import { SavingsService } from '../savings.service';
import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates';
-import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
+import { AlertService } from 'app/core/alert/alert.service';
+import { MatTabsModule } from '@angular/material/tabs';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { SavingsAccountPreviewStepComponent } from '../savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
- * Edit Savings Account Component
+ * Edit Savings Account Component — same tab UI as Create, pre-populated with existing data.
*/
@Component({
selector: 'mifosx-edit-savings-account',
@@ -33,14 +36,11 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./edit-savings-account.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
- MatStepper,
- MatStepperIcon,
+ MatTabsModule,
FaIconComponent,
- MatStep,
- MatStepLabel,
SavingsAccountDetailsStepComponent,
- SavingsAccountTermsStepComponent,
- SavingsAccountChargesStepComponent,
+ SavingsAccountCurrencyStepComponent,
+ SavingsAccountDigitalStepComponent,
SavingsAccountPreviewStepComponent
]
})
@@ -50,109 +50,135 @@ export class EditSavingsAccountComponent {
private dateUtils = inject(Dates);
private savingsService = inject(SavingsService);
private settingsService = inject(SettingsService);
+ private alertService = inject(AlertService);
+ private clientsService = inject(ClientsService);
- /** Savings Account Template */
+ /** Full account data + template from resolver */
savingsAccountAndTemplate: any;
- /** Savings Account Product Template */
+ /** Product template emitted by details step */
savingsAccountProductTemplate: any;
+ /** Currently visible wizard tab (0-indexed). */
+ selectedTabIndex = 0;
+ private readonly totalTabs = 4;
- /** Savings Account Details Step */
@ViewChild(SavingsAccountDetailsStepComponent, { static: true })
savingsAccountDetailsStep: SavingsAccountDetailsStepComponent;
- /** Savings Account Terms Step */
- @ViewChild(SavingsAccountTermsStepComponent, { static: true })
- savingsAccountTermsStep: SavingsAccountTermsStepComponent;
- /** Savings Account Charges Step */
- @ViewChild(SavingsAccountChargesStepComponent, { static: true })
- savingsAccountChargesStep: SavingsAccountChargesStepComponent;
- /**
- * Fetches savings account template from `resolve`
- * @param {ActivatedRoute} route Activated Route
- * @param {Router} router Router
- * @param {Dates} dateUtils Date Utils
- * @param {SavingsService} savingsService Savings Service
- * @param {SettingsService} settingsService Settings Service
- */
+ @ViewChild(SavingsAccountCurrencyStepComponent)
+ savingsAccountCurrencyStep: SavingsAccountCurrencyStepComponent;
+
+ @ViewChild(SavingsAccountDigitalStepComponent)
+ savingsAccountDigitalStep: SavingsAccountDigitalStepComponent;
+
constructor() {
this.route.data.subscribe((data: { savingsAccountAndTemplate: any }) => {
- this.savingsAccountAndTemplate = data.savingsAccountAndTemplate;
+ const account = data.savingsAccountAndTemplate;
+ this.savingsAccountAndTemplate = account;
+ if (account?.clientId && account?.accountNo) {
+ this.clientsService
+ .getClientNominees(String(account.clientId), account.accountNo, 'SB')
+ .subscribe((nominees: any[]) => {
+ if (Array.isArray(nominees) && nominees.length > 0) {
+ this.savingsAccountAndTemplate = { ...this.savingsAccountAndTemplate, nominees };
+ }
+ });
+ }
});
}
- /**
- * Sets savings account product template.
- * @param {any} $event API response
- */
setTemplate($event: any) {
this.savingsAccountProductTemplate = $event;
}
- /**
- * Retrieves savings account details form.
- */
+ next(): void {
+ if (this.selectedTabIndex < this.totalTabs - 1) {
+ this.selectedTabIndex += 1;
+ }
+ }
+
+ prev(): void {
+ if (this.selectedTabIndex > 0) {
+ this.selectedTabIndex -= 1;
+ }
+ }
+
get savingsAccountDetailsForm() {
return this.savingsAccountDetailsStep.savingsAccountDetailsForm;
}
- /**
- * Retrieves savings account terms form.
- */
- get savingsAccountTermsForm() {
- return this.savingsAccountTermsStep.savingsAccountTermsForm;
+ get savingsAccountTermsForm(): FormGroup {
+ return new FormGroup({});
}
- /**
- * Checks validity and pristinity of overall savings account form .
- */
- get savingsAccountFormValidAndNotPristine() {
- return (
- this.savingsAccountDetailsForm.valid &&
- this.savingsAccountTermsForm.valid &&
- (!this.savingsAccountDetailsForm.pristine ||
- !this.savingsAccountTermsForm.pristine ||
- !this.savingsAccountChargesStep.pristine)
- );
- }
-
- /**
- * Retrieves savings account object.
- */
get savingsAccount() {
- return {
- ...this.savingsAccountDetailsStep.savingsAccountDetails,
- ...this.savingsAccountTermsStep.savingsAccountTerms,
- ...this.savingsAccountChargesStep.savingsAccountCharges
- };
+ return { ...this.savingsAccountDetailsStep.savingsAccountDetails };
+ }
+
+ get savingsAccountDigital() {
+ return this.savingsAccountDigitalStep?.savingsAccountDigital ?? null;
}
- /**
- * Creates a new share account.
- */
submit() {
- // TODO: Update once language and date settings are setup
+ const detailsStep = this.savingsAccountDetailsStep;
+ if (detailsStep.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) {
+ this.alertService.alert({
+ type: 'Error',
+ message: `Total ownership is ${detailsStep.totalJointSharePercentage}%. It must equal 100% before submitting.`
+ });
+ return;
+ }
+ if (
+ this.savingsAccountProductTemplate?.nominationFacility &&
+ (this.savingsAccountCurrencyStep?.nominees ?? []).length === 0
+ ) {
+ this.alertService.alert({
+ type: 'Error',
+ message: 'Nomination Facility is enabled for this product. Please add at least one nominee before submitting.'
+ });
+ return;
+ }
+ if (this.savingsAccountProductTemplate?.introducerRequired && !this.savingsAccountDetailsStep.introducerId) {
+ this.alertService.alert({
+ type: 'Error',
+ message: 'Introducer is required for this product. Please select an introducer before submitting.'
+ });
+ return;
+ }
const locale = this.settingsService.language.code;
const dateFormat = this.settingsService.dateFormat;
const monthDayFormat = 'dd MMMM';
- const savingsAccount = {
+ const savingsAccount: any = {
...this.savingsAccount,
- charges: this.savingsAccount.charges.map((charge: any) => ({
- chargeId: charge.id,
- amount: charge.amount,
- dueDate: this.dateUtils.formatDate(charge.dueDate, dateFormat),
- feeOnMonthDay: this.dateUtils.formatDate(charge.feeOnMonthDay, dateFormat),
- feeInterval: charge.feeInterval
- })),
+ charges: [],
submittedOnDate: this.dateUtils.formatDate(this.savingsAccount.submittedOnDate, dateFormat),
dateFormat,
monthDayFormat,
locale
};
+
+ if (this.savingsAccountDigital?.accountFacilityConfig) {
+ savingsAccount.accountFacilityConfig = this.savingsAccountDigital.accountFacilityConfig;
+ }
+
if (this.savingsAccountAndTemplate.clientId) {
savingsAccount.clientId = this.savingsAccountAndTemplate.clientId;
} else {
savingsAccount.groupId = this.savingsAccountAndTemplate.groupId;
}
+
+ const nominees = this.savingsAccountCurrencyStep?.nominees ?? [];
+ if (nominees.length > 0) {
+ savingsAccount.nominees = nominees.map((n: any) => ({
+ nomineeClientId: n.nomineeClientId,
+ nominationType: n.nominationType,
+ relationshipId: n.relationshipId,
+ percentageShare: n.percentageShare,
+ nomineeIsMinor: n.nomineeIsMinor,
+ ...(n.guardianClientId ? { guardianClientId: n.guardianClientId } : {}),
+ remarks: n.remarks
+ }));
+ }
+
this.savingsService
.updateSavingsAccount(this.savingsAccountAndTemplate.id, savingsAccount)
.subscribe((response: any) => {
diff --git a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts
index 8ba58f1a6..1a08f026d 100644
--- a/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts
+++ b/src/app/savings/savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component.ts
@@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-import { Component, Input, ViewChild } from '@angular/core';
+import { Component, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { NomineeFormComponent } from 'app/shared/nominee-form/nominee-form.component';
import { DormancyAlertPanelComponent } from 'app/shared/dormancy-alert-panel/dormancy-alert-panel.component';
@@ -21,9 +21,10 @@ import { DormancyAlertPanelComponent } from 'app/shared/dormancy-alert-panel/dor
DormancyAlertPanelComponent
]
})
-export class SavingsAccountCurrencyStepComponent {
+export class SavingsAccountCurrencyStepComponent implements OnChanges {
@Input() savingsAccountProductTemplate: any;
@Input() primaryClientId: number | null = null;
+ @Input() existingAccount: any = null;
@ViewChild(NomineeFormComponent) nomineeFormComp: NomineeFormComponent;
nominees: any[] = [];
@@ -31,6 +32,32 @@ export class SavingsAccountCurrencyStepComponent {
showNomineeForm = false;
nomineeError = '';
+ private existingAccountApplied = false;
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes['existingAccount']?.currentValue) {
+ const ea = changes['existingAccount'].currentValue;
+ this.existingAccountApplied = true;
+ const nominees =
+ Array.isArray(ea.nominees) && ea.nominees.length > 0 ? ea.nominees : this.parseNomineeData(ea.nomineeData);
+ if (nominees.length > 0) {
+ this.nominees = [...nominees];
+ }
+ }
+ }
+
+ private parseNomineeData(nomineeData: any): any[] {
+ if (!nomineeData) {
+ return [];
+ }
+ try {
+ const parsed = typeof nomineeData === 'string' ? JSON.parse(nomineeData) : nomineeData;
+ return Array.isArray(parsed) ? parsed : [];
+ } catch {
+ return [];
+ }
+ }
+
get totalShare(): number {
return this.nominees.reduce((sum, n) => sum + (+n.percentageShare || 0), 0);
}
diff --git a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html
index 89fa66639..0e2834bc4 100644
--- a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html
+++ b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html
@@ -91,7 +91,7 @@
Account No
@@ -127,6 +127,9 @@
@if (productTemplate?.introducerRequired) {
}
diff --git a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts
index 55fb52cce..f9fb23339 100644
--- a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts
+++ b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.ts
@@ -65,6 +65,7 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
introducerId: number | null = null;
introducerName: string | null = null;
introducerCif: string | null = null;
+ private isEditMode = false;
@Output() savingsAccountProductTemplate = new EventEmitter();
@@ -166,11 +167,25 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
this.buildDependencies();
if (this.savingsAccountTemplate) {
this.productData = this.savingsAccountTemplate.productOptions;
- if (this.savingsAccountTemplate.savingsProductId) {
+ const existingProductId = this.savingsAccountTemplate.savingsProductId ?? this.savingsAccountTemplate.productId;
+ // Edit mode: account response always has accountNo
+ if (this.savingsAccountTemplate.accountNo) {
+ this.isEditMode = true;
+ }
+ if ((!this.productData || this.productData.length === 0) && existingProductId) {
+ this.isEditMode = true;
+ this.productData = [
+ {
+ id: existingProductId,
+ name: this.savingsAccountTemplate.productName ?? this.savingsAccountTemplate.savingsProductName ?? 'Product'
+ }
+ ];
+ }
+ if (existingProductId) {
this.savingsAccountDetailsForm.patchValue({
- productId: this.savingsAccountTemplate.savingsProductId,
+ productId: existingProductId,
submittedOnDate:
- this.savingsAccountTemplate.timeline.submittedOnDate &&
+ this.savingsAccountTemplate.timeline?.submittedOnDate &&
new Date(this.savingsAccountTemplate.timeline.submittedOnDate)
});
} else {
@@ -280,6 +295,9 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
/** Auto-set status tile based on age; pre-fill minorDob if minor */
private autoDetectAccountStatus(): void {
+ if (this.isEditMode) {
+ return;
+ }
if (this.clientAge === null) return;
if (this.clientAge < 18) {
@@ -319,8 +337,6 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
this.customerSegmentOptions = this.extractOptions(response.customerSegmentOptions, response.customerSegment);
this.selectedAccountType = response.accountType ?? null;
this.selectedCustomerSegment = response.customerSegment ?? null;
- // Override the product's default segment with the one inferred
- // from the client's entity type when an option matches.
this.applyInferredCustomerSegment();
this.savingsAccountProductTemplate.emit(response);
this.fieldOfficerData = response.fieldOfficerOptions;
@@ -332,10 +348,93 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
} else {
this.savingsAccountDetailsForm.get('fieldOfficerId').patchValue('');
}
+ // === Restore existing account values when in edit/modify mode ===
+ this.restoreExistingAccountValues();
});
});
}
+ private restoreExistingAccountValues(): void {
+ const ea = this.savingsAccountTemplate;
+ if (!ea?.savingsProductId && !ea?.productId) {
+ return;
+ }
+
+ const strVal = (v: any): string => (typeof v === 'object' && v !== null ? (v.value ?? '') : (v ?? ''));
+
+ const accountStatus = strVal(ea.accountStatus);
+ if (accountStatus) {
+ this.selectedAccountStatus = accountStatus;
+ }
+
+ const customerSegment = strVal(ea.customerSegment);
+ if (customerSegment) {
+ this.selectedCustomerSegment = customerSegment;
+ }
+
+ const accountType = ea.cbsAccountType ? strVal(ea.cbsAccountType) : null;
+ if (accountType) {
+ this.selectedAccountType = accountType;
+ }
+
+ if (accountStatus === 'JOINT') {
+ const modeOfOp = strVal(ea.modeOfOperation);
+ if (modeOfOp) {
+ this.modeOfOperation = modeOfOp;
+ }
+ if (Array.isArray(ea.jointHolders) && ea.jointHolders.length > 0) {
+ const isPH = (h: any) => h.isPrimary ?? h.isprimary ?? false;
+ const primary = ea.jointHolders.find((h: any) => isPH(h));
+ const ps = primary?.sharePercentage ?? primary?.sharepercentage;
+ if (ps) {
+ this.primaryHolderShare = +ps;
+ }
+ this.jointHolders = ea.jointHolders
+ .filter((h: any) => !isPH(h))
+ .map((h: any) => ({
+ clientId: h.clientId ?? h.clientid,
+ clientName: h.displayName ?? h.displayname ?? h.clientName ?? '',
+ clientCif: h.cifNumber ?? h.cifnumber ?? h.clientCif ?? '',
+ sharePercentage: +(h.sharePercentage ?? h.sharepercentage ?? 0),
+ isMandatory: !!(h.isMandatory ?? h.ismandatory)
+ }));
+ }
+ }
+ if (accountStatus === 'INDIVIDUAL_MALE' && ea.specialCategory) {
+ this.selectedSpecialCategory = ea.specialCategory;
+ }
+ if (accountStatus === 'INDIVIDUAL_FEMALE' && ea.weakerSection) {
+ this.selectedWeakerSection = ea.weakerSection;
+ }
+ if (accountStatus === 'MINOR') {
+ if (ea.guardianClientId) {
+ this.guardianClientId = ea.guardianClientId;
+ }
+ if (ea.guardianName) {
+ this.guardianName = ea.guardianName;
+ }
+ if (ea.guardianCifNumber) {
+ this.guardianCif = ea.guardianCifNumber;
+ }
+ if (ea.guardianRelationship) {
+ this.guardianRelationship = ea.guardianRelationship;
+ }
+ if (ea.minorDateOfBirth) {
+ const d = new Date(ea.minorDateOfBirth);
+ if (!isNaN(d.getTime())) {
+ this.minorDob = d;
+ }
+ }
+ }
+ const introId = ea.introducerId ?? ea.introducerClientId;
+ if (introId) {
+ this.introducerId = introId;
+ this.introducerName = ea.introducerName ?? null;
+ this.introducerCif = ea.introducerCifNumber ?? null;
+ }
+ this.cdr.detectChanges();
+ }
+
private extractOptions(options: any[], fallback?: string): string[] {
if (Array.isArray(options) && options.length > 0) {
return options.map((o: any) => o.value ?? o.name ?? o);
diff --git a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts
index 5c747b548..30da85e32 100644
--- a/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts
+++ b/src/app/savings/savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component.ts
@@ -40,10 +40,19 @@ const DEFAULT_FACILITY_CONFIG = {
})
export class SavingsAccountDigitalStepComponent {
facilityConfig = { ...DEFAULT_FACILITY_CONFIG };
+ private hasExistingAccount = false;
+
+ @Input()
+ set existingAccount(value: any) {
+ if (value?.accountFacilityConfig) {
+ this.hasExistingAccount = true;
+ this.facilityConfig = { ...DEFAULT_FACILITY_CONFIG, ...value.accountFacilityConfig };
+ }
+ }
@Input()
set savingsAccountProductTemplate(value: any) {
- if (value?.accountFacilityConfig) {
+ if (!this.hasExistingAccount && value?.accountFacilityConfig) {
this.facilityConfig = { ...DEFAULT_FACILITY_CONFIG, ...value.accountFacilityConfig };
}
}
diff --git a/src/app/savings/savings-account-view/savings-buttons.config.ts b/src/app/savings/savings-account-view/savings-buttons.config.ts
index a9be208f1..ffee9dea1 100644
--- a/src/app/savings/savings-account-view/savings-buttons.config.ts
+++ b/src/app/savings/savings-account-view/savings-buttons.config.ts
@@ -142,6 +142,16 @@ export class SavingsButtonsConfiguration {
}
];
break;
+ case 'Rejected':
+ case 'rejected':
+ this.buttonsArray = [
+ {
+ name: 'Modify Application',
+ icon: 'pen',
+ taskPermissionName: 'UPDATE_SAVINGSACCOUNT'
+ }
+ ];
+ break;
default:
this.buttonsArray = [];
}
diff --git a/src/app/shared/introducer-search/introducer-search.component.ts b/src/app/shared/introducer-search/introducer-search.component.ts
index 8f46fa2b0..467502a06 100644
--- a/src/app/shared/introducer-search/introducer-search.component.ts
+++ b/src/app/shared/introducer-search/introducer-search.component.ts
@@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-import { Component, Input, Output, EventEmitter, inject } from '@angular/core';
+import { Component, Input, Output, EventEmitter, inject, OnChanges, SimpleChanges } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { ClientsService } from 'app/clients/clients.service';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -27,11 +27,12 @@ import { FormsModule } from '@angular/forms';
MatIcon
]
})
-export class IntroducerSearchComponent {
+export class IntroducerSearchComponent implements OnChanges {
private clientsService = inject(ClientsService);
private sanitizer = inject(DomSanitizer);
@Input() excludedClientIds: number[] = [];
+ @Input() preselectedIntroducer: { clientId: number; displayName: string; cifNumber?: string } | null = null;
@Output() introducerChange = new EventEmitter<{ clientId: number; displayName: string; cifNumber?: string } | null>();
entryMode: 'linked' = 'linked';
@@ -50,6 +51,14 @@ export class IntroducerSearchComponent {
private searchTimer: any;
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes['preselectedIntroducer']?.currentValue && !this.selectedClient) {
+ const val = changes['preselectedIntroducer'].currentValue;
+ this.selectedClient = { id: val.clientId, displayName: val.displayName, cifNumber: val.cifNumber ?? '' };
+ this.fetchLinkedClientData(String(val.clientId));
+ }
+ }
+
onSearch(text: string): void {
this.searchText = text;
clearTimeout(this.searchTimer);
diff --git a/src/environments/.env.ts b/src/environments/.env.ts
index b69dd8cf8..9d0b77b96 100644
--- a/src/environments/.env.ts
+++ b/src/environments/.env.ts
@@ -9,8 +9,8 @@
/* tslint:disable */
export default {
'mifos_x': {
- 'version': '260527',
- 'hash': '33b01d66'
+ 'version': '260602',
+ 'hash': '3be2f962'
},
'allow_switching_backend_instance': true
};