/** * 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, OnInit, AfterViewInit, inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, ValidationErrors, Validators, FormsModule } from '@angular/forms'; /** Custom Services */ import { AccountTransfersService } from '../account-transfers.service'; import { SettingsService } from 'app/settings/settings.service'; import { ClientsService } from 'app/clients/clients.service'; import { Dates } from 'app/core/utils/dates'; /** Environment Configuration */ import { environment } from '../../../environments/environment'; import { MatDivider } from '@angular/material/divider'; import { MatFormField, MatLabel, MatHint, MatError } from '@angular/material/form-field'; import { MatAutocompleteTrigger, MatAutocomplete } from '@angular/material/autocomplete'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { CdkTextareaAutosize } from '@angular/cdk/text-field'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; /** * Create account transfers */ @Component({ selector: 'mifosx-make-account-transfers', templateUrl: './make-account-transfers.component.html', styleUrls: ['./make-account-transfers.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, MatDivider, FormsModule, MatHint, MatAutocompleteTrigger, MatAutocomplete, FaIconComponent, CdkTextareaAutosize ] }) export class MakeAccountTransfersComponent implements OnInit, AfterViewInit { private formBuilder = inject(UntypedFormBuilder); private route = inject(ActivatedRoute); private router = inject(Router); private accountTransfersService = inject(AccountTransfersService); private dateUtils = inject(Dates); private settingsService = inject(SettingsService); private clientsService = inject(ClientsService); /** Standing Instructions Data */ accountTransferTemplateData: any; /** Minimum date allowed. */ minDate = new Date(2000, 0, 1); /** Maximum date allowed. */ maxDate = new Date(2100, 0, 1); /** Edit Standing Instructions form. */ makeAccountTransferForm: UntypedFormGroup; /** To Office Type Data */ toOfficeTypeData: any; /** To Client Type Data */ toClientTypeData: any; /** To Account Type Data */ toAccountTypeData: any; /** To Account Data */ toAccountData: any; /** Account Type Id */ accountTypeId: any; /** Account Type */ accountType: any; /** Savings Id or Loans Id */ id: any; /** Clients Data */ clientsData: any; /** interbank transfer */ interbank: boolean = false; /** Reference of phoneAccount search */ phoneAccount = ''; /** Interbank transfer form flag */ interbankTransferForm: boolean = false; balance: number = 0; isLoading: boolean = false; /** * Retrieves the standing instructions template from `resolve`. * @param {ActivatedRoute} route Activated Route. * @param {FormBuilder} formBuilder Form Builder * @param {Router} router Router * @param {AccountTransfersService} accountTransfersService Account Transfers Service * @param {Dates} dateUtils Date Utils * @param {SettingsService} settingsService Settings Service * @param {ClientsService} clientsService Clients Service */ constructor() { this.route.data.subscribe((data: { accountTransferTemplate: any }) => { this.accountTransferTemplateData = data.accountTransferTemplate; this.setParams(); this.setOptions(); }); } /** Sets the value from the URL */ /** Sets the value from the URL */ setParams() { this.accountType = this.route.snapshot.queryParams['accountType']; switch (this.accountType) { case 'fromloans': this.accountTypeId = '1'; this.id = this.route.snapshot.queryParams['loanId']; break; case 'fromsavings': case 'interbank': this.accountTypeId = '2'; this.id = this.route.snapshot.queryParams['savingsId']; this.interbank = this.route.snapshot.queryParams['interbank'] === 'true'; const navigationBalance = this.router.currentNavigation()?.extras?.state?.balance; const templateBalance = this.accountTransferTemplateData?.fromAccount?.availableBalance ?? this.accountTransferTemplateData?.fromAccount?.summary?.accountBalance ?? this.accountTransferTemplateData?.fromAccount?.balance ?? 0; this.balance = typeof navigationBalance === 'number' ? navigationBalance : templateBalance; break; default: this.accountTypeId = '0'; } } /** * Creates and sets the create standing instructions form. */ ngOnInit() { this.maxDate = this.settingsService.businessDate; if (!this.interbank) { this.createMakeAccountTransferForm(); } else { this.createEmptyInterbankForm(); } } /** * Crea un formulario vacĂ­o para interbank mientras se carga */ createEmptyInterbankForm() { this.makeAccountTransferForm = this.formBuilder.group({ toBank: [ '', Validators.required ], toClientId: [ '', Validators.required ], toAccountType: [ '', Validators.required ], toAccountId: [ '', Validators.required ], transferAmount: [ 0, [ Validators.required, Validators.min(0.01), this.amountExceedsBalanceValidator.bind(this) ] ], transferDate: [ this.settingsService.businessDate, Validators.required ], transferDescription: [ '', Validators.required ] }); } /** * Creates the standing instruction form. */ createMakeAccountTransferForm() { this.makeAccountTransferForm = this.formBuilder.group({ toOfficeId: [ '', Validators.required ], toClientId: [ '', Validators.required ], toAccountType: [ '', Validators.required ], toAccountId: [ '', Validators.required ], transferAmount: [ this.accountTransferTemplateData.transferAmount, [ Validators.required, Validators.min(0.01), this.amountExceedsBalanceValidator.bind(this) ] ], transferDate: [ this.settingsService.businessDate, Validators.required ], transferDescription: [ '', Validators.required ] }); } createMakeAccountInterbankTransferForm(account: any) { if (!account) { console.error('Account data is undefined'); this.isLoading = false; return; } const defaultAmount = this.accountTransferTemplateData?.transferAmount > 0 ? this.accountTransferTemplateData.transferAmount : 1; this.makeAccountTransferForm = this.formBuilder.group({ toBank: [ account.destinationFspId || '', Validators.required ], toClientId: [ (account.firstName || account.firsName || '') + ' ' + (account.lastName || ''), Validators.required ], toAccountType: [ 'Saving Account', Validators.required ], toAccountId: [ account.partyId || '', Validators.required ], transferAmount: [ defaultAmount, [ Validators.required, Validators.min(0.01), this.amountExceedsBalanceValidator.bind(this) ] ], transferDate: [ this.settingsService.businessDate, Validators.required ], transferDescription: [ 'Transferencia interbancaria', Validators.required ] }); this.isLoading = false; } amountExceedsBalanceValidator(control: AbstractControl): ValidationErrors | null { const amount = control.value; return amount > this.balance ? { amountExceedsBalance: true } : null; } /** Sets options value */ setOptions() { this.toOfficeTypeData = this.accountTransferTemplateData.toOfficeOptions; this.toAccountTypeData = this.accountTransferTemplateData.toAccountTypeOptions; this.toAccountData = this.accountTransferTemplateData.toAccountOptions; } /** Executes on change of various select options */ changeEvent() { const formValue = this.refineObject(this.makeAccountTransferForm.value); this.accountTransfersService .newAccountTranferResource(this.id, this.accountTypeId, formValue) .subscribe((response: any) => { this.accountTransferTemplateData = response; this.toClientTypeData = response.toClientOptions; this.setOptions(); }); } /** Refine Object * Removes the object param with null or '' values */ refineObject(dataObj: { [x: string]: any; transferAmount: any; transferDate: any; transferDescription: any }) { delete dataObj.transferAmount; delete dataObj.transferDate; delete dataObj.transferDescription; if (dataObj.toClientId && typeof dataObj.toClientId === 'object') { dataObj.toClientId = dataObj.toClientId.id; } const propNames = Object.getOwnPropertyNames(dataObj); for (let i = 0; i < propNames.length; i++) { const propName = propNames[i]; if (dataObj[propName] === null || dataObj[propName] === undefined || dataObj[propName] === '') { delete dataObj[propName]; } } return dataObj; } /** * Subscribes to Clients search filter: */ ngAfterViewInit() { if (!this.interbank && this.makeAccountTransferForm) { this.makeAccountTransferForm.controls.toClientId.valueChanges.subscribe((value: any) => { if (typeof value === 'string' && value.length >= 2) { this.clientsService.getFilteredClients('displayName', 'ASC', true, value).subscribe((data: any) => { this.clientsData = data.pageItems; }); this.changeEvent(); } }); } } /** * Displays Client name in form control input. * @param {any} client Client data. * @returns {string} Client name if valid otherwise undefined. */ displayClient(client: any): string | undefined { return client ? client.displayName : undefined; } /** * Submits the standing instructions form */ submit() { this.interbank ? this.makeInterbankTransfer() : this.makeTransfer(); } makeTransfer() { this.isLoading = true; const dateFormat = this.settingsService.dateFormat; const locale = this.settingsService.language.code; let toClientIdValue: any; if (typeof this.makeAccountTransferForm.controls.toClientId.value === 'object') { toClientIdValue = this.makeAccountTransferForm.controls.toClientId.value.id; } else { toClientIdValue = this.makeAccountTransferForm.controls.toClientId.value; } const makeAccountTransferData = { ...this.makeAccountTransferForm.value, transferDate: this.dateUtils.formatDate(this.makeAccountTransferForm.value.transferDate, dateFormat), dateFormat, locale, toClientId: toClientIdValue, fromAccountId: this.id, fromAccountType: this.accountTypeId, fromClientId: this.accountTransferTemplateData.fromClient.id, fromOfficeId: this.accountTransferTemplateData.fromClient.officeId }; this.accountTransfersService.createAccountTransfer(makeAccountTransferData).subscribe(() => { this.isLoading = false; this.router.navigate(['../../transactions'], { relativeTo: this.route }); }); } makeInterbankTransfer() { this.isLoading = true; if (!this.makeAccountTransferForm.valid) { console.error('Interbank form is not valid'); this.isLoading = false; return; } const payload = { homeTransactionId: crypto.randomUUID(), from: { fspId: environment.fineractPlatformTenantId, idType: 'MSISDN', idValue: this.accountTransferTemplateData.fromAccount.externalId?.trim() || '' }, to: { fspId: this.makeAccountTransferForm.controls.toBank.value, idType: 'MSISDN', idValue: this.makeAccountTransferForm.controls.toAccountId.value }, amountType: 'SEND', amount: { currencyCode: this.accountTransferTemplateData.currency.code, amount: this.makeAccountTransferForm.controls.transferAmount.value }, transactionType: { scenario: 'TRANSFER', subScenario: 'DOMESTIC', initiator: 'PAYER', initiatorType: 'CUSTOMER' }, note: this.makeAccountTransferForm.controls.transferDescription.value }; this.accountTransfersService.sendInterbankTransfer(JSON.stringify(payload)).subscribe( (trnsfr) => { if (trnsfr.systemMessage) { this.isLoading = false; this.router.navigate(['../../transactions'], { relativeTo: this.route }); } }, (error) => { console.error('Interbank transfer error:', error); this.isLoading = false; } ); } searchAccountByNumber() { if (!this.phoneAccount || this.phoneAccount.length !== 10) { return; } this.isLoading = true; this.accountTransfersService .getAccountByNumber(this.phoneAccount, this.accountTransferTemplateData.currency.code) .subscribe( (acc) => { this.interbankTransferForm = true; this.createMakeAccountInterbankTransferForm(acc); }, (error) => { console.error('searching account error:', error); this.isLoading = false; } ); } }