/** * Copyright since 2025 Mifos Initiative * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Angular Imports */ import { Component, ViewChild, inject } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { FormGroup } from '@angular/forms'; /** Custom Components */ import { SavingsAccountDetailsStepComponent } from '../savings-account-stepper/savings-account-details-step/savings-account-details-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 { 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'; /** * Create Savings Account Component * * Wizard-tab UI: Product → Currency → Digital → Preview. Tabs 2-4 are * disabled until a product is picked on Tab 1 (matches the prior stepper * gate that only rendered later steps once `savingsAccountProductTemplate` * was set). */ @Component({ selector: 'mifosx-create-savings-account', templateUrl: './create-savings-account.component.html', styleUrls: ['./create-savings-account.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, MatTabsModule, FaIconComponent, SavingsAccountDetailsStepComponent, SavingsAccountCurrencyStepComponent, SavingsAccountDigitalStepComponent, SavingsAccountPreviewStepComponent ] }) export class CreateSavingsAccountComponent { private route = inject(ActivatedRoute); private router = inject(Router); private dateUtils = inject(Dates); private savingsService = inject(SavingsService); private settingsService = inject(SettingsService); private alertService = inject(AlertService); /** Savings Account Template */ savingsAccountTemplate: any; /** Savings Account Product Template */ savingsAccountProductTemplate: any; /** Currently visible wizard tab (0-indexed). */ selectedTabIndex = 0; /** Total wizard tabs — Product, Currency, Preview. */ private readonly totalTabs = 3; /** Guards against duplicate submissions from a double-click while the create request is in flight. */ submitting = false; @ViewChild(SavingsAccountDetailsStepComponent, { static: true }) savingsAccountDetailsStep: SavingsAccountDetailsStepComponent; @ViewChild(SavingsAccountDigitalStepComponent) savingsAccountDigitalStep: SavingsAccountDigitalStepComponent; @ViewChild(SavingsAccountCurrencyStepComponent) savingsAccountCurrencyStep: SavingsAccountCurrencyStepComponent; constructor() { this.route.data.subscribe((data: { savingsAccountTemplate: any }) => { this.savingsAccountTemplate = data.savingsAccountTemplate; }); } setTemplate($event: any) { this.savingsAccountProductTemplate = $event; } 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; } /** Stub — no term step in this flow */ get savingsAccountTermsForm(): FormGroup { return new FormGroup({}); } get savingsAccount() { return { ...this.savingsAccountDetailsStep.savingsAccountDetails }; } get savingsAccountDigital() { return this.savingsAccountDigitalStep?.savingsAccountDigital ?? null; } submit() { if (this.submitting) { return; } 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; } if (detailsStep.shareLinkingEnabled && !detailsStep.shareAccountId) { this.alertService.alert({ type: 'Error', message: 'Share Linking is enabled — please select the share account to link before submitting.' }); return; } const locale = this.settingsService.language.code; const dateFormat = this.settingsService.dateFormat; const monthDayFormat = 'dd MMMM'; const savingsAccount: any = { ...this.savingsAccount, charges: [], submittedOnDate: this.dateUtils.formatDate(this.savingsAccount.submittedOnDate, dateFormat), dateFormat, monthDayFormat, locale }; // Digital Facilities if (this.savingsAccountDigital?.accountFacilityConfig) { savingsAccount.accountFacilityConfig = this.savingsAccountDigital.accountFacilityConfig; } if (this.savingsAccountTemplate.clientId) { savingsAccount.clientId = this.savingsAccountTemplate.clientId; // Set by "Open account in this branch" for a customer of another branch: the branch this account is held at. const accountOfficeId = this.route.snapshot.queryParamMap.get('accountOfficeId'); if (accountOfficeId) { savingsAccount.accountOfficeId = Number(accountOfficeId); } } else { savingsAccount.groupId = this.savingsAccountTemplate.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.submitting = true; this.savingsService.createSavingsAccount(savingsAccount).subscribe({ next: (response: any) => { this.router.navigate( [ '../', response.resourceId ], { relativeTo: this.route } ); }, error: (error: any) => { this.submitting = false; const errorCode = error?.error?.errors?.[0]?.userMessageGlobalisationCode ?? error?.error?.userMessageGlobalisationCode; if (errorCode === 'error.msg.savings.account.duplicate.product.for.client') { this.alertService.alert({ type: 'Error', message: 'This client already has an active account with the selected product. Please choose a different product.' }); } else if (errorCode === 'error.msg.savingsaccount.duplicate.externalId') { this.alertService.alert({ type: 'Error', message: 'An account with this External ID already exists. Please use a unique External ID.' }); } else if ( errorCode === 'error.msg.savingsaccount.share.account.required' || errorCode === 'error.msg.savingsaccount.share.account.not.active.for.client' ) { this.alertService.alert({ type: 'Error', message: 'Please select an active share account belonging to this client before submitting.' }); } } }); } }