WEB-677: Improve Transfer Funds UX with vertical stepper (#3107)

- Refactored Transfer Funds page into a 3-step vertical stepper:
  Step 1: Beneficiary Selection
  Step 2: Transfer Details
  Step 3: Transfer Status (success/failure)
- Split single form into beneficiaryForm and transferDetailsForm
- Added MatStepper with custom icons and linear progression
- Added transfer status display with success/failure states
- Added missing i18n translation keys across all 13 locale files
- Fixed MatHint import for NG1010 compilation error
This commit is contained in:
Anvay
2026-02-08 13:12:52 -06:00
committed by GitHub
parent f25f82a004
commit 77e95c6146
16 changed files with 754 additions and 490 deletions
@@ -7,384 +7,471 @@
-->
<div class="container">
@if (isLoading) {
<div class="loader-wrapper">
<div class="bottom triangle"></div>
<div class="top triangle"></div>
<div class="left triangle"></div>
<div class="right triangle"></div>
</div>
}
<mat-stepper orientation="vertical" [linear]="true" class="mat-elevation-z8" labelPosition="bottom" #transferStepper>
<!-- Custom Step Icons -->
<ng-template matStepperIcon="number">
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
</ng-template>
<ng-template matStepperIcon="edit">
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
</ng-template>
<ng-template matStepperIcon="done">
<fa-icon icon="check" size="sm"></fa-icon>
</ng-template>
<ng-template matStepperIcon="error">
<fa-icon icon="exclamation-triangle" size="lg"></fa-icon>
</ng-template>
@if (!isLoading) {
<mat-card class="transfer-card">
<mat-card-content>
<!-- Source Account Details Section -->
<div class="section-container">
<h3 class="section-title transfer-heading">{{ 'labels.heading.Transferring From Details' | translate }}</h3>
<mat-divider></mat-divider>
<div class="info-grid">
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Applicant' | translate }}</div>
<div class="info-value">{{ accountTransferTemplateData.fromClient.displayName }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Office' | translate }}</div>
<div class="info-value">{{ accountTransferTemplateData.fromOffice.name }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.From Account' | translate }}</div>
<div class="info-value">
{{ accountTransferTemplateData.fromAccount.productName }}&nbsp;-&nbsp;#{{
accountTransferTemplateData.fromAccount.accountNo
}}
</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.From Account Type' | translate }}</div>
<div class="info-value">{{ accountTransferTemplateData.fromAccountType.value }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Currency' | translate }}</div>
<div class="info-value">{{ accountTransferTemplateData.currency.name }}</div>
<!-- ==================== STEP 1: Beneficiary Selection ==================== -->
<mat-step [stepControl]="beneficiaryForm" [editable]="!transferComplete">
<ng-template matStepLabel>{{ 'labels.heading.Beneficiary Selection' | translate }}</ng-template>
<!-- Source Account Details (Read-Only) -->
<div class="section-container">
<h3 class="section-title transfer-heading">{{ 'labels.heading.Transferring From Details' | translate }}</h3>
<mat-divider></mat-divider>
<div class="info-grid">
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Applicant' | translate }}</div>
<div class="info-value">{{ accountTransferTemplateData.fromClient.displayName }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Office' | translate }}</div>
<div class="info-value">{{ accountTransferTemplateData.fromOffice.name }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.From Account' | translate }}</div>
<div class="info-value">
{{ accountTransferTemplateData.fromAccount.productName }}&nbsp;-&nbsp;#{{
accountTransferTemplateData.fromAccount.accountNo
}}
</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.From Account Type' | translate }}</div>
<div class="info-value">{{ accountTransferTemplateData.fromAccountType.value }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Currency' | translate }}</div>
<div class="info-value">{{ accountTransferTemplateData.currency.name }}</div>
</div>
</div>
<!-- Interbank Section -->
@if (interbank) {
<div class="section-container">
<div class="interbank-section">
<h3 class="section-title transfer-heading">{{ 'labels.heading.Transferred To' | translate }}</h3>
<mat-form-field class="full-width">
</div>
<mat-divider></mat-divider>
<!-- Beneficiary Form -->
@if (interbank) {
<!-- Interbank: Phone Number Search -->
<div class="section-container">
<h3 class="section-title transfer-heading">{{ 'labels.heading.Transferred To' | translate }}</h3>
<form [formGroup]="beneficiaryForm" class="transfer-form">
<div class="form-grid">
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Phone Number' | translate }}</mat-label>
<input
matInput
[readonly]="interbankTransferForm"
type="tel"
#input
[(ngModel)]="phoneAccount"
formControlName="phoneNumber"
maxlength="10"
required
placeholder="{{ 'labels.text.EnterPhoneNumber' | translate }}"
title="Phone number"
/>
<mat-hint align="end">{{ phoneAccount.length || 0 }}/10</mat-hint>
<mat-hint align="end">{{ beneficiaryForm.get('phoneNumber')?.value?.length || 0 }}/10</mat-hint>
</mat-form-field>
</div>
<!--interbank -->
@if (!interbankTransferForm) {
<mat-card-actions class="action-buttons">
<div class="action-buttons step-actions">
<ng-container *mifosxHasPermission="'CREATE_ACCOUNTTRANSFER'">
<button
mat-raised-button
color="primary"
[disabled]="phoneAccount.length !== 10"
[disabled]="beneficiaryForm.get('phoneNumber')?.value?.length !== 10"
(click)="searchAccountByNumber()"
id="search-button"
class="primary-button"
>
{{ 'labels.buttons.Search' | translate }}
</button>
</ng-container>
</mat-card-actions>
</div>
}
<!-- Interbank Beneficiary Preview (Read-Only) -->
@if (interbankTransferForm) {
<div class="info-grid beneficiary-preview">
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Bank' | translate }}</div>
<div class="info-value">{{ beneficiaryForm.get('toBank')?.value }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Client' | translate }}</div>
<div class="info-value">{{ beneficiaryForm.get('toClientId')?.value }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Account Type' | translate }}</div>
<div class="info-value">{{ beneficiaryForm.get('toAccountType')?.value }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Account' | translate }}</div>
<div class="info-value">{{ beneficiaryForm.get('toAccountId')?.value }}</div>
</div>
</div>
}
</form>
</div>
} @else {
<!-- Normal Transfer: Office/Client/Account Selection -->
<form [formGroup]="beneficiaryForm" class="transfer-form">
<h3 class="section-title transfer-heading">{{ 'labels.heading.Transferred To' | translate }}</h3>
<div class="form-grid">
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<mat-select required formControlName="toOfficeId" (selectionChange)="changeEvent()">
@for (toOfficeType of toOfficeTypeData; track toOfficeType) {
<mat-option [value]="toOfficeType.id">
{{ toOfficeType.name }}
</mat-option>
}
</mat-select>
@if (beneficiaryForm.controls['toOfficeId'].hasError('required')) {
<mat-error>
{{ 'labels.inputs.Office' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Client' | translate }}</mat-label>
<input
matInput
formControlName="toClientId"
[matAutocomplete]="clientsAutocomplete"
placeholder="{{ 'labels.text.SelectOrTypeClientName' | translate }}"
title="Client name"
/>
@if (beneficiaryForm.controls['toClientId'].hasError('required')) {
<mat-error>
{{ 'labels.inputs.Client' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-autocomplete
autoActiveFirstOption
#clientsAutocomplete="matAutocomplete"
[displayWith]="displayClient"
>
@for (client of clientsData; track client) {
<mat-option [value]="client"> {{ client.id }} - {{ client.displayName }} </mat-option>
}
</mat-autocomplete>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Account Type' | translate }}</mat-label>
<mat-select required formControlName="toAccountType" (selectionChange)="changeEvent()">
@for (toAccountType of toAccountTypeData; track toAccountType) {
<mat-option [value]="toAccountType.id">
{{ toAccountType.value }}
</mat-option>
}
</mat-select>
@if (beneficiaryForm.controls['toAccountType'].hasError('required')) {
<mat-error>
{{ 'labels.inputs.Account Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Account' | translate }}</mat-label>
<mat-select required formControlName="toAccountId" (selectionChange)="changeEvent()">
@for (toAccount of toAccountData; track toAccount) {
<mat-option [value]="toAccount.id">
{{ toAccount.productName }} - {{ toAccount.accountNo }}
</mat-option>
}
</mat-select>
@if (beneficiaryForm.controls['toAccountId'].hasError('required')) {
<mat-error>
{{ 'labels.inputs.Account' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</form>
}
<!-- Step 1 Navigation -->
<div class="layout-row align-center gap-2percent margin-t responsive-column">
<button mat-raised-button matStepperPrevious disabled>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button
mat-raised-button
matStepperNext
[disabled]="interbank ? !interbankTransferForm : !beneficiaryForm.valid"
>
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
<button mat-raised-button [routerLink]="['../..']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
</div>
</mat-step>
<!-- ==================== STEP 2: Transfer Details ==================== -->
<mat-step [stepControl]="transferDetailsForm" [editable]="!transferComplete">
<ng-template matStepLabel>{{ 'labels.heading.Transfer Details' | translate }}</ng-template>
@if (isLoading) {
<div class="loader-wrapper">
<div class="bottom triangle"></div>
<div class="top triangle"></div>
<div class="left triangle"></div>
<div class="right triangle"></div>
</div>
}
@if (!isLoading) {
<!-- Beneficiary Summary (Read-Only) -->
<div class="section-container beneficiary-summary">
<h3 class="section-title transfer-heading">{{ 'labels.heading.Beneficiary' | translate }}</h3>
<div class="info-grid">
@if (interbank && interbankTransferForm) {
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Bank' | translate }}</div>
<div class="info-value">{{ beneficiaryForm.get('toBank')?.value }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Client' | translate }}</div>
<div class="info-value">{{ beneficiaryForm.get('toClientId')?.value }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Account' | translate }}</div>
<div class="info-value">{{ beneficiaryForm.get('toAccountId')?.value }}</div>
</div>
} @else {
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Office' | translate }}</div>
<div class="info-value">{{ getSelectedOfficeName() }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Client' | translate }}</div>
<div class="info-value">{{ getSelectedClientName() }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Account Type' | translate }}</div>
<div class="info-value">{{ getSelectedAccountTypeName() }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Account' | translate }}</div>
<div class="info-value">{{ getSelectedAccountName() }}</div>
</div>
}
</div>
}
@if (!interbank) {
<mat-divider></mat-divider>
}
<!-- Transfer Form Section (Normal) -->
@if (!interbank && makeAccountTransferForm) {
<form [formGroup]="makeAccountTransferForm" class="transfer-form">
<h3 class="section-title transfer-heading">{{ 'labels.heading.Transfer Details' | translate }}</h3>
<div class="form-grid">
<mat-form-field class="form-field" (click)="transferDatePicker.open()">
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="transferDatePicker"
required
formControlName="transferDate"
placeholder="{{ 'labels.text.SelectDate' | translate }}"
title="Transaction Date"
/>
<mat-datepicker-toggle matSuffix [for]="transferDatePicker"></mat-datepicker-toggle>
<mat-datepicker #transferDatePicker></mat-datepicker>
@if (makeAccountTransferForm.controls.transferDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Transaction Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<mat-select required formControlName="toOfficeId" (selectionChange)="changeEvent()">
@for (toOfficeType of toOfficeTypeData; track toOfficeType) {
<mat-option [value]="toOfficeType.id">
{{ toOfficeType.name }}
</mat-option>
}
</mat-select>
@if (makeAccountTransferForm.controls.toOfficeId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Office' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Client' | translate }}</mat-label>
<input
matInput
formControlName="toClientId"
[matAutocomplete]="clientsAutocomplete"
placeholder="{{ 'labels.text.SelectOrTypeClientName' | translate }}"
title="Client name"
/>
@if (makeAccountTransferForm.controls.toClientId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Client' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-autocomplete
autoActiveFirstOption
#clientsAutocomplete="matAutocomplete"
[displayWith]="displayClient"
>
@for (client of clientsData; track client) {
<mat-option [value]="client"> {{ client.id }} - {{ client.displayName }} </mat-option>
}
</mat-autocomplete>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Account Type' | translate }}</mat-label>
<mat-select required formControlName="toAccountType" (selectionChange)="changeEvent()">
@for (toAccountType of toAccountTypeData; track toAccountType) {
<mat-option [value]="toAccountType.id">
{{ toAccountType.value }}
</mat-option>
}
</mat-select>
@if (makeAccountTransferForm.controls.toAccountType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Account Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Account' | translate }}</mat-label>
<mat-select required formControlName="toAccountId" (selectionChange)="changeEvent()">
@for (toAccount of toAccountData; track toAccount) {
<mat-option [value]="toAccount.id">
{{ toAccount.productName }} - {{ toAccount.accountNo }}
</mat-option>
}
</mat-select>
@if (makeAccountTransferForm.controls.toAccountId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Account' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
<input
type="number"
matInput
required
formControlName="transferAmount"
placeholder="{{ 'labels.text.EnterAmount' | translate }}"
title="Transfer amount"
min="0.01"
step="0.01"
/>
@if (makeAccountTransferForm.controls.transferAmount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (makeAccountTransferForm.get('transferAmount')?.hasError('amountExceedsBalance')) {
<mat-error>
<fa-icon icon="exclamation-triangle" size="md"></fa-icon>
{{ 'errors.validation.msg.savingsproduct.insufficient.balance' | translate: { balance } }}
</mat-error>
}
</mat-form-field>
<mat-form-field class="form-field description-field">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea
matInput
formControlName="transferDescription"
cdkTextareaAutosize
cdkAutosizeMinRows="2"
placeholder="{{ 'labels.text.EnterTransferDescription' | translate }}"
title="Transfer description"
></textarea>
@if (makeAccountTransferForm.controls.transferDescription.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Transfer Description' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</form>
}
<!-- Interbank Transfer Form Section -->
@if (interbank && interbankTransferForm && makeAccountTransferForm) {
<form [formGroup]="makeAccountTransferForm" class="transfer-form">
<h3 class="section-title transfer-heading">{{ 'labels.heading.Transfer Details' | translate }}</h3>
<div class="form-grid">
<mat-form-field class="form-field" (click)="transferDatePicker.open()">
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="transferDatePicker"
required
formControlName="transferDate"
placeholder="{{ 'labels.text.SelectDate' | translate }}"
title="Transaction Date"
/>
<mat-datepicker-toggle matSuffix [for]="transferDatePicker"></mat-datepicker-toggle>
<mat-datepicker #transferDatePicker></mat-datepicker>
@if (makeAccountTransferForm.controls.transferDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Transaction Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Bank' | translate }}</mat-label>
<input
matInput
formControlName="toBank"
[readonly]="true"
placeholder="{{ 'labels.inputs.Bank' | translate }}"
title="Bank"
/>
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Client' | translate }}</mat-label>
<input
matInput
formControlName="toClientId"
[readonly]="true"
placeholder="{{ 'labels.inputs.Client' | translate }}"
title="Client"
/>
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Account Type' | translate }}</mat-label>
<input
matInput
formControlName="toAccountType"
[readonly]="true"
placeholder="{{ 'labels.inputs.Account Type' | translate }}"
title="Account Type"
/>
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Account' | translate }}</mat-label>
<input
matInput
formControlName="toAccountId"
[readonly]="true"
placeholder="{{ 'labels.inputs.Account' | translate }}"
title="Account"
/>
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
<input
type="number"
matInput
required
formControlName="transferAmount"
placeholder="{{ 'labels.text.EnterAmount' | translate }}"
title="Transfer amount"
min="0.01"
step="0.01"
/>
@if (makeAccountTransferForm.controls.transferAmount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (makeAccountTransferForm.controls.transferAmount.hasError('min')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.mustBeAtLeast' | translate }} 0.01
</mat-error>
}
@if (makeAccountTransferForm.get('transferAmount')?.hasError('amountExceedsBalance')) {
<mat-error>
<fa-icon icon="exclamation-triangle" size="md"></fa-icon>
{{ 'errors.validation.msg.savingsproduct.insufficient.balance' | translate: { balance } }}
</mat-error>
}
</mat-form-field>
<mat-form-field class="form-field description-field">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea
matInput
formControlName="transferDescription"
cdkTextareaAutosize
cdkAutosizeMinRows="2"
placeholder="{{ 'labels.text.EnterTransferDescription' | translate }}"
title="Transfer description"
></textarea>
@if (makeAccountTransferForm.controls.transferDescription.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Transfer Description' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</form>
}
</mat-card-content>
@if (!isLoading && makeAccountTransferForm) {
<mat-card-actions class="action-buttons">
<button type="button" mat-raised-button [routerLink]="['../..']" class="cancel-button">
{{ 'labels.buttons.Cancel' | translate }}
</div>
<mat-divider></mat-divider>
<!-- Transfer Details Form -->
<form [formGroup]="transferDetailsForm" class="transfer-form">
<h3 class="section-title transfer-heading">{{ 'labels.heading.Transfer Details' | translate }}</h3>
<div class="form-grid">
<mat-form-field class="form-field" (click)="transferDatePicker.open()">
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="transferDatePicker"
required
formControlName="transferDate"
placeholder="{{ 'labels.text.SelectDate' | translate }}"
title="Transaction Date"
/>
<mat-datepicker-toggle matSuffix [for]="transferDatePicker"></mat-datepicker-toggle>
<mat-datepicker #transferDatePicker></mat-datepicker>
@if (transferDetailsForm.controls['transferDate'].hasError('required')) {
<mat-error>
{{ 'labels.inputs.Transaction Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
<input
type="number"
matInput
required
formControlName="transferAmount"
placeholder="{{ 'labels.text.EnterAmount' | translate }}"
title="Transfer amount"
min="0.01"
step="0.01"
/>
@if (transferDetailsForm.controls['transferAmount'].hasError('required')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (transferDetailsForm.get('transferAmount')?.hasError('amountExceedsBalance')) {
<mat-error>
<fa-icon icon="exclamation-triangle" size="md"></fa-icon>
{{ 'errors.validation.msg.savingsproduct.insufficient.balance' | translate: { balance } }}
</mat-error>
}
</mat-form-field>
<mat-form-field class="form-field description-field">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea
matInput
formControlName="transferDescription"
cdkTextareaAutosize
cdkAutosizeMinRows="2"
placeholder="{{ 'labels.text.EnterTransferDescription' | translate }}"
title="Transfer description"
></textarea>
@if (transferDetailsForm.controls['transferDescription'].hasError('required')) {
<mat-error>
{{ 'labels.inputs.Transfer Description' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</form>
<!-- Step 2 Navigation -->
<div class="layout-row align-center gap-2percent margin-t responsive-column">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<ng-container *mifosxHasPermission="'CREATE_ACCOUNTTRANSFER'">
@if (interbank && interbankTransferForm) {
<button
mat-raised-button
color="primary"
[disabled]="!makeAccountTransferForm.valid"
(click)="submit()"
class="primary-button"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
}
@if (!interbank) {
<button
mat-raised-button
color="primary"
[disabled]="!makeAccountTransferForm.valid"
(click)="submit()"
class="primary-button"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
}
<button mat-raised-button color="primary" [disabled]="!transferDetailsForm.valid" (click)="submit()">
{{ 'labels.buttons.Submit' | translate }}
</button>
</ng-container>
</mat-card-actions>
<button mat-raised-button [routerLink]="['../..']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
</div>
}
</mat-card>
}
</mat-step>
<!-- ==================== STEP 3: Transfer Status ==================== -->
<mat-step [editable]="false">
<ng-template matStepLabel>{{ 'labels.heading.Transfer Status' | translate }}</ng-template>
@if (isLoading) {
<div class="loader-wrapper">
<div class="bottom triangle"></div>
<div class="top triangle"></div>
<div class="left triangle"></div>
<div class="right triangle"></div>
</div>
}
@if (!isLoading && transferComplete) {
<div class="transfer-status-container">
@if (transferSuccess) {
<div class="status-icon success">
<fa-icon icon="check-circle" size="3x"></fa-icon>
</div>
<h3 class="status-title success-text">{{ 'labels.heading.Transfer Successful' | translate }}</h3>
} @else {
<div class="status-icon error">
<fa-icon icon="times-circle" size="3x"></fa-icon>
</div>
<h3 class="status-title error-text">{{ 'labels.heading.Transfer Failed' | translate }}</h3>
@if (transferErrorMessage) {
<p class="status-message error-text">{{ transferErrorMessage }}</p>
}
}
<!-- Transfer Summary -->
<div class="section-container transfer-summary">
<h3 class="section-title transfer-heading">{{ 'labels.heading.Transfer Summary' | translate }}</h3>
<mat-divider></mat-divider>
<div class="info-grid">
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.From Account' | translate }}</div>
<div class="info-value">
{{ accountTransferTemplateData.fromAccount.productName }}&nbsp;-&nbsp;#{{
accountTransferTemplateData.fromAccount.accountNo
}}
</div>
</div>
@if (interbank && interbankTransferForm) {
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Bank' | translate }}</div>
<div class="info-value">{{ beneficiaryForm.get('toBank')?.value }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Client' | translate }}</div>
<div class="info-value">{{ beneficiaryForm.get('toClientId')?.value }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Account' | translate }}</div>
<div class="info-value">{{ beneficiaryForm.get('toAccountId')?.value }}</div>
</div>
} @else {
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Office' | translate }}</div>
<div class="info-value">{{ getSelectedOfficeName() }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Client' | translate }}</div>
<div class="info-value">{{ getSelectedClientName() }}</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Account' | translate }}</div>
<div class="info-value">{{ getSelectedAccountName() }}</div>
</div>
}
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Amount' | translate }}</div>
<div class="info-value">
{{ transferDetailsForm.get('transferAmount')?.value }} {{ accountTransferTemplateData.currency.code }}
</div>
</div>
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Transaction Date' | translate }}</div>
<div class="info-value">{{ transferDetailsForm.get('transferDate')?.value | date }}</div>
</div>
@if (transferDetailsForm.get('transferDescription')?.value) {
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Description' | translate }}</div>
<div class="info-value">{{ transferDetailsForm.get('transferDescription')?.value }}</div>
</div>
}
@if (transferReferenceId) {
<div class="info-row">
<div class="info-label">{{ 'labels.inputs.Transaction ID' | translate }}</div>
<div class="info-value">{{ transferReferenceId }}</div>
</div>
}
</div>
</div>
<!-- Step 3 Navigation -->
<div class="layout-row align-center gap-2percent margin-t responsive-column">
<button mat-raised-button [routerLink]="['../../transactions']">
<fa-icon icon="list" class="m-r-10"></fa-icon>
{{ 'labels.buttons.View Transactions' | translate }}
</button>
<button mat-raised-button [routerLink]="['../..']">
{{ 'labels.buttons.Back to Account' | translate }}
</button>
</div>
</div>
}
</mat-step>
</mat-stepper>
</div>
@@ -12,14 +12,6 @@
padding: 1rem;
}
// Card styling
.transfer-card {
border-radius: 8px;
box-shadow: 0 2px 10px rgb(0 0 0 / 8%);
margin-bottom: 1.5rem;
overflow: hidden;
}
// Section styling
.section-container {
margin-bottom: 2rem;
@@ -82,6 +74,12 @@ mat-divider {
margin-bottom: 1rem;
}
// Beneficiary preview and summary
.beneficiary-preview,
.beneficiary-summary {
margin-top: 1rem;
}
// Form styling
.transfer-form {
margin-top: 1rem;
@@ -178,6 +176,53 @@ mat-divider {
}
}
// Step navigation buttons (matching loans stepper pattern)
.step-actions {
padding: 1rem 0;
}
// Transfer Status (Step 3)
.transfer-status-container {
text-align: center;
padding: 1.5rem 0;
}
.status-icon {
margin-bottom: 1rem;
&.success {
color: #4caf50;
}
&.error {
color: #f44336;
}
}
.status-title {
font-size: 1.5rem;
font-weight: 700;
margin: 0 0 0.5rem;
}
.success-text {
color: #4caf50;
}
.error-text {
color: #f44336;
}
.status-message {
font-size: 0.875rem;
margin-bottom: 1.5rem;
}
.transfer-summary {
text-align: left;
margin-top: 1.5rem;
}
// Loader styling
.loader-wrapper {
display: flex;
@@ -254,6 +299,14 @@ mat-divider {
}
}
}
.status-icon.success {
color: #66bb6a;
}
.status-icon.error {
color: #ef5350;
}
}
// Higher specificity selectors
@@ -7,7 +7,7 @@
*/
/** Angular Imports */
import { Component, OnInit, AfterViewInit, inject } from '@angular/core';
import { Component, OnInit, AfterViewInit, ViewChild, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {
AbstractControl,
@@ -27,8 +27,16 @@ 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 { MatHint } from '@angular/material/form-field';
import { MatAutocompleteTrigger, MatAutocomplete } from '@angular/material/autocomplete';
import {
MatStepper,
MatStepperIcon,
MatStep,
MatStepLabel,
MatStepperNext,
MatStepperPrevious
} from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -47,6 +55,12 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
MatHint,
MatAutocompleteTrigger,
MatAutocomplete,
MatStepper,
MatStepperIcon,
MatStep,
MatStepLabel,
MatStepperNext,
MatStepperPrevious,
FaIconComponent,
CdkTextareaAutosize
]
@@ -60,14 +74,19 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
private settingsService = inject(SettingsService);
private clientsService = inject(ClientsService);
/** Stepper reference */
@ViewChild('transferStepper') transferStepper: MatStepper;
/** 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;
/** Beneficiary selection form (Step 1) */
beneficiaryForm: UntypedFormGroup;
/** Transfer details form (Step 2) */
transferDetailsForm: UntypedFormGroup;
/** To Office Type Data */
toOfficeTypeData: any;
/** To Client Type Data */
@@ -86,22 +105,18 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
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;
/** Transfer status flags (Step 3) */
transferComplete: boolean = false;
transferSuccess: boolean = false;
transferErrorMessage: string = '';
transferReferenceId: string = '';
/**
* 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 }) => {
@@ -111,7 +126,6 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
});
}
/** Sets the value from the URL */
/** Sets the value from the URL */
setParams() {
this.accountType = this.route.snapshot.queryParams['accountType'];
@@ -139,62 +153,23 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
}
/**
* Creates and sets the create standing instructions form.
* Creates and sets the beneficiary and transfer details forms.
*/
ngOnInit() {
this.maxDate = this.settingsService.businessDate;
if (!this.interbank) {
this.createMakeAccountTransferForm();
this.createNormalBeneficiaryForm();
} else {
this.createEmptyInterbankForm();
this.createEmptyInterbankBeneficiaryForm();
}
this.createTransferDetailsForm();
}
/**
* Crea un formulario vacío para interbank mientras se carga
* Creates the beneficiary form for normal (non-interbank) transfers.
*/
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({
createNormalBeneficiaryForm() {
this.beneficiaryForm = this.formBuilder.group({
toOfficeId: [
'',
Validators.required
@@ -210,51 +185,61 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
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) {
/**
* Creates an empty beneficiary form for interbank transfers (phone search).
*/
createEmptyInterbankBeneficiaryForm() {
this.beneficiaryForm = this.formBuilder.group({
phoneNumber: [
'',
[
Validators.required,
Validators.minLength(10),
Validators.maxLength(10)
]
],
toBank: [''],
toClientId: [''],
toAccountType: [''],
toAccountId: ['']
});
}
/**
* Populates the interbank beneficiary form after phone search resolves an account.
*/
populateInterbankBeneficiary(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.beneficiaryForm.patchValue({
toBank: account.destinationFspId || '',
toClientId: (account.firstName || account.firsName || '') + ' ' + (account.lastName || ''),
toAccountType: 'Saving Account',
toAccountId: account.partyId || ''
});
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 || '',
this.interbankTransferForm = true;
this.isLoading = false;
}
/**
* Creates the transfer details form (Step 2).
*/
createTransferDetailsForm() {
const defaultAmount =
this.accountTransferTemplateData?.transferAmount > 0 ? this.accountTransferTemplateData.transferAmount : '';
this.transferDetailsForm = this.formBuilder.group({
transferDate: [
this.settingsService.businessDate,
Validators.required
],
transferAmount: [
@@ -265,16 +250,11 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
this.amountExceedsBalanceValidator.bind(this)
]
],
transferDate: [
this.settingsService.businessDate,
Validators.required
],
transferDescription: [
'Transferencia interbancaria',
'',
Validators.required
]
});
this.isLoading = false;
}
amountExceedsBalanceValidator(control: AbstractControl): ValidationErrors | null {
@@ -291,7 +271,7 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
/** Executes on change of various select options */
changeEvent() {
const formValue = this.refineObject(this.makeAccountTransferForm.value);
const formValue = this.refineObject({ ...this.beneficiaryForm.value });
this.accountTransfersService
.newAccountTranferResource(this.id, this.accountTypeId, formValue)
.subscribe((response: any) => {
@@ -304,10 +284,7 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
/** 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;
refineObject(dataObj: any) {
if (dataObj.toClientId && typeof dataObj.toClientId === 'object') {
dataObj.toClientId = dataObj.toClientId.id;
}
@@ -325,8 +302,8 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
* Subscribes to Clients search filter:
*/
ngAfterViewInit() {
if (!this.interbank && this.makeAccountTransferForm) {
this.makeAccountTransferForm.controls.toClientId.valueChanges.subscribe((value: any) => {
if (!this.interbank && this.beneficiaryForm) {
this.beneficiaryForm.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;
@@ -346,8 +323,35 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
return client ? client.displayName : undefined;
}
/** Helper methods for displaying selected beneficiary info in Step 2 summary */
getSelectedOfficeName(): string {
const officeId = this.beneficiaryForm?.get('toOfficeId')?.value;
const office = this.toOfficeTypeData?.find((o: any) => o.id === officeId);
return office ? office.name : '';
}
getSelectedClientName(): string {
const client = this.beneficiaryForm?.get('toClientId')?.value;
if (typeof client === 'object' && client) {
return client.displayName;
}
return client || '';
}
getSelectedAccountTypeName(): string {
const typeId = this.beneficiaryForm?.get('toAccountType')?.value;
const type = this.toAccountTypeData?.find((t: any) => t.id === typeId);
return type ? type.value : '';
}
getSelectedAccountName(): string {
const accountId = this.beneficiaryForm?.get('toAccountId')?.value;
const account = this.toAccountData?.find((a: any) => a.id === accountId);
return account ? `${account.productName} - ${account.accountNo}` : '';
}
/**
* Submits the standing instructions form
* Submits the transfer
*/
submit() {
this.interbank ? this.makeInterbankTransfer() : this.makeTransfer();
@@ -359,15 +363,16 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
const locale = this.settingsService.language.code;
let toClientIdValue: any;
if (typeof this.makeAccountTransferForm.controls.toClientId.value === 'object') {
toClientIdValue = this.makeAccountTransferForm.controls.toClientId.value.id;
if (typeof this.beneficiaryForm.controls.toClientId.value === 'object') {
toClientIdValue = this.beneficiaryForm.controls.toClientId.value.id;
} else {
toClientIdValue = this.makeAccountTransferForm.controls.toClientId.value;
toClientIdValue = this.beneficiaryForm.controls.toClientId.value;
}
const makeAccountTransferData = {
...this.makeAccountTransferForm.value,
transferDate: this.dateUtils.formatDate(this.makeAccountTransferForm.value.transferDate, dateFormat),
...this.beneficiaryForm.value,
...this.transferDetailsForm.value,
transferDate: this.dateUtils.formatDate(this.transferDetailsForm.value.transferDate, dateFormat),
dateFormat,
locale,
toClientId: toClientIdValue,
@@ -377,21 +382,28 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
fromOfficeId: this.accountTransferTemplateData.fromClient.officeId
};
this.accountTransfersService.createAccountTransfer(makeAccountTransferData).subscribe(() => {
this.isLoading = false;
this.router.navigate(['../../transactions'], { relativeTo: this.route });
});
this.accountTransfersService.createAccountTransfer(makeAccountTransferData).subscribe(
(response: any) => {
this.isLoading = false;
this.transferComplete = true;
this.transferSuccess = true;
this.transferReferenceId = response?.resourceId || response?.transactionId || '';
this.transferStepper.next();
},
(error: any) => {
this.isLoading = false;
this.transferComplete = true;
this.transferSuccess = false;
this.transferErrorMessage =
error?.error?.defaultUserMessage || error?.message || 'An unexpected error occurred.';
this.transferStepper.next();
}
);
}
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: {
@@ -400,14 +412,14 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
idValue: this.accountTransferTemplateData.fromAccount.externalId?.trim() || ''
},
to: {
fspId: this.makeAccountTransferForm.controls.toBank.value,
fspId: this.beneficiaryForm.controls.toBank.value,
idType: 'MSISDN',
idValue: this.makeAccountTransferForm.controls.toAccountId.value
idValue: this.beneficiaryForm.controls.toAccountId.value
},
amountType: 'SEND',
amount: {
currencyCode: this.accountTransferTemplateData.currency.code,
amount: this.makeAccountTransferForm.controls.transferAmount.value
amount: this.transferDetailsForm.controls.transferAmount.value
},
transactionType: {
scenario: 'TRANSFER',
@@ -415,35 +427,41 @@ export class MakeAccountTransfersComponent implements OnInit, AfterViewInit {
initiator: 'PAYER',
initiatorType: 'CUSTOMER'
},
note: this.makeAccountTransferForm.controls.transferDescription.value
note: this.transferDetailsForm.controls.transferDescription.value
};
this.accountTransfersService.sendInterbankTransfer(JSON.stringify(payload)).subscribe(
(trnsfr) => {
if (trnsfr.systemMessage) {
this.isLoading = false;
this.router.navigate(['../../transactions'], { relativeTo: this.route });
}
this.isLoading = false;
this.transferComplete = true;
this.transferSuccess = true;
this.transferReferenceId = trnsfr?.systemMessage || '';
this.transferStepper.next();
},
(error) => {
console.error('Interbank transfer error:', error);
this.isLoading = false;
this.transferComplete = true;
this.transferSuccess = false;
this.transferErrorMessage =
error?.error?.defaultUserMessage || error?.message || 'An unexpected error occurred.';
this.transferStepper.next();
}
);
}
searchAccountByNumber() {
if (!this.phoneAccount || this.phoneAccount.length !== 10) {
const phoneNumber = this.beneficiaryForm.get('phoneNumber')?.value;
if (!phoneNumber || phoneNumber.length !== 10) {
return;
}
this.isLoading = true;
this.accountTransfersService
.getAccountByNumber(this.phoneAccount, this.accountTransferTemplateData.currency.code)
.getAccountByNumber(phoneNumber, this.accountTransferTemplateData.currency.code)
.subscribe(
(acc) => {
this.interbankTransferForm = true;
this.createMakeAccountInterbankTransferForm(acc);
this.populateInterbankBeneficiary(acc);
},
(error) => {
console.error('searching account error:', error);
+8
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "Připojit schůzku",
"Attendance": "Účast",
"Back": "Zadní",
"Back to Account": "Zpět na účet",
"Bar Chart": "Sloupcový graf",
"Browse": "Procházet",
"Bulk JLG Loan Application": "Hromadná žádost o půjčku JLG",
@@ -548,6 +549,7 @@
"View Journal Entries": "Zobrazit záznamy deníku",
"View Standing Instructions": "Prohlédněte si stálé pokyny",
"View Transactions History": "Zobrazit historii transakcí",
"View Transactions": "Zobrazit transakce",
"Waive": "vzdát se",
"Waive Charge": "Zřeknutí se poplatku",
"Week": "Týden",
@@ -926,6 +928,8 @@
"Assets / Liability": "Aktiva / pasiva",
"Audit Trails": "Audit Trails",
"Balance": "Zůstatek",
"Beneficiary": "Příjemce",
"Beneficiary Selection": "Výběr příjemce",
"Block Savings Account": "Blokovat spořicí účet",
"Bulk Import": "Hromadný import",
"Bulk Loan Reassignment": "Hromadné přeřazení půjčky",
@@ -1231,6 +1235,10 @@
"Transaction Reverted": "Transakce vrácena",
"Transactions": "Transakce",
"Transfer Details": "Podrobnosti o převodu",
"Transfer Failed": "Převod se nezdařil",
"Transfer Status": "Stav převodu",
"Transfer Successful": "Převod úspěšný",
"Transfer Summary": "Souhrn převodu",
"Transferred From": "Převedeno z",
"Transferred To": "Převeden k",
"Transferring From Details": "Přenos z podrobností",
+8
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "Besprechung anhängen",
"Attendance": "Teilnahme",
"Back": "Zurück",
"Back to Account": "Zurück zum Konto",
"Bar Chart": "Balkendiagramm",
"Browse": "Durchsuche",
"Bulk JLG Loan Application": "Massenantrag für einen JLG-Kredit",
@@ -548,6 +549,7 @@
"View Journal Entries": "Journaleinträge anzeigen",
"View Standing Instructions": "Ständige Anweisungen anzeigen",
"View Transactions History": "Transaktionsverlauf anzeigen",
"View Transactions": "Transaktionen anzeigen",
"Waive": "Verzichten",
"Waive Charge": "Auf Gebühr verzichten",
"Week": "Woche",
@@ -927,6 +929,8 @@
"Assets / Liability": "Vermögenswerte / Verbindlichkeiten",
"Audit Trails": "Buchungsprotokolle",
"Balance": "Gleichgewicht",
"Beneficiary": "Begünstigter",
"Beneficiary Selection": "Begünstigtenauswahl",
"Block Savings Account": "Sparkonto sperren",
"Bulk Import": "Massenimport",
"Bulk Loan Reassignment": "Neuzuweisung von Massenkrediten",
@@ -1232,6 +1236,10 @@
"Transaction Reverted": "Transaktion rückgängig gemacht",
"Transactions": "Transaktionen",
"Transfer Details": "Transferdetails",
"Transfer Failed": "Überweisung fehlgeschlagen",
"Transfer Status": "Überweisungsstatus",
"Transfer Successful": "Überweisung erfolgreich",
"Transfer Summary": "Überweisungszusammenfassung",
"Transferred From": "Transferiert von",
"Transferred To": "Übertragen auf",
"Transferring From Details": "Von Details übertragen",
+9
View File
@@ -351,6 +351,7 @@
"Attach Meeting": "Attach Meeting",
"Attendance": "Attendance",
"Back": "Back",
"Back to Account": "Back to Account",
"Bar Chart": "Bar Chart",
"Browse": "Browse",
"Bulk JLG Loan Application": "Bulk JLG Loan Application",
@@ -552,6 +553,7 @@
"View Journal Entries": "View Journal Entries",
"View Standing Instructions": "View Standing Instructions",
"View Transactions History": "View Transactions History",
"View Transactions": "View Transactions",
"Waive": "Waive",
"Waive Charge": "Waive Charge",
"Week": "Week",
@@ -932,6 +934,8 @@
"Assets / Liability": "Assets / Liability",
"Audit Trails": "Audit Trails",
"Balance": "Balance",
"Beneficiary": "Beneficiary",
"Beneficiary Selection": "Beneficiary Selection",
"Block Savings Account": "Block Savings Account",
"Bulk Import": "Bulk Import",
"Bulk Loan Reassignment": "Bulk Loan Reassignment",
@@ -1245,6 +1249,10 @@
"Transaction Reverted": "Transaction Reverted",
"Transactions": "Transactions",
"Transfer Details": "Transfer Details",
"Transfer Failed": "Transfer Failed",
"Transfer Status": "Transfer Status",
"Transfer Successful": "Transfer Successful",
"Transfer Summary": "Transfer Summary",
"Transferred From": "Transferred From",
"Transferred To": "Transferred To",
"Transferring From Details": "Transferring From Details",
@@ -1438,6 +1446,7 @@
"Base Price": "Base Price",
"Base Percentage": "Base Percentage",
"Beneficiary": "Beneficiary",
"Beneficiary Selection": "Beneficiary Selection",
"Branch": "Branch",
"Branch Office": "Branch Office",
"Breakdown": "Breakdown",
+8
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "Adjuntar reunión",
"Attendance": "Asistencia",
"Back": "Atrás",
"Back to Account": "Volver a la Cuenta",
"Bar Chart": "Gráfico de barras",
"Browse": "Buscar",
"Bulk JLG Loan Application": "Solicitud de Crédito JLG a granel",
@@ -548,6 +549,7 @@
"View Journal Entries": "Ver entradas de diario",
"View Standing Instructions": "Ver instrucciones permanentes",
"View Transactions History": "Ver historial de transacciones",
"View Transactions": "Ver transacciones",
"Waive": "Renunciar",
"Waive Charge": "Renunciar a Comisión",
"Week": "Semana",
@@ -926,6 +928,8 @@
"Assets / Liability": "Activo / Pasivo",
"Audit Trails": "Pistas de auditoría",
"Balance": "Saldo",
"Beneficiary": "Beneficiario",
"Beneficiary Selection": "Selección de Beneficiario",
"Block Savings Account": "Bloquear Cuenta de Ahorros",
"Bulk Import": "Importación masiva",
"Bulk Loan Reassignment": "Reasignación de Créditos másiva",
@@ -1232,6 +1236,10 @@
"Transaction Reverted": "Transacción revertida",
"Transactions": "Transacciones",
"Transfer Details": "Detalles de la transferencia",
"Transfer Failed": "Transferencia fallida",
"Transfer Status": "Estado de la transferencia",
"Transfer Successful": "Transferencia exitosa",
"Transfer Summary": "Resumen de la transferencia",
"Transferred From": "Transferido desde",
"Transferred To": "Transferido a",
"Transferring From Details": "Transferir desde detalles",
+8
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "Adjuntar reunión",
"Attendance": "Asistencia",
"Back": "Atrás",
"Back to Account": "Volver a la Cuenta",
"Bar Chart": "Gráfico de barras",
"Browse": "Buscar",
"Bulk JLG Loan Application": "Solicitud de Crédito JLG a granel",
@@ -548,6 +549,7 @@
"View Journal Entries": "Ver entradas de diario",
"View Standing Instructions": "Ver instrucciones permanentes",
"View Transactions History": "Ver historial de transacciones",
"View Transactions": "Ver transacciones",
"Waive": "Renunciar",
"Waive Charge": "Renunciar a Comisión",
"Week": "Semana",
@@ -926,6 +928,8 @@
"Assets / Liability": "Activo / Pasivo",
"Audit Trails": "Pistas de auditoría",
"Balance": "Saldo",
"Beneficiary": "Beneficiario",
"Beneficiary Selection": "Selección de Beneficiario",
"Block Savings Account": "Bloquear Cuenta de Ahorros",
"Bulk Import": "Importación masiva",
"Bulk Loan Reassignment": "Reasignación de Créditos másiva",
@@ -1231,6 +1235,10 @@
"Transaction Reverted": "Transacción revertida",
"Transactions": "Transacciones",
"Transfer Details": "Detalles de la transferencia",
"Transfer Failed": "Transferencia fallida",
"Transfer Status": "Estado de la transferencia",
"Transfer Successful": "Transferencia exitosa",
"Transfer Summary": "Resumen de la transferencia",
"Transferred From": "Transferido desde",
"Transferred To": "Transferido a",
"Transferring From Details": "Transferir desde detalles",
+8
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "Joindre une réunion",
"Attendance": "Présence",
"Back": "Dos",
"Back to Account": "Retour au compte",
"Bar Chart": "Diagramme à bandes",
"Browse": "Parcourir",
"Bulk JLG Loan Application": "Demande de prêt JLG en masse",
@@ -548,6 +549,7 @@
"View Journal Entries": "Afficher les entrées de journal",
"View Standing Instructions": "Voir les instructions permanentes",
"View Transactions History": "Afficher l'historique des transactions",
"View Transactions": "Voir les transactions",
"Waive": "Renoncer à",
"Waive Charge": "Renoncer aux frais",
"Week": "Semaine",
@@ -927,6 +929,8 @@
"Assets / Liability": "Actif / Passif",
"Audit Trails": "Des pistes de vérification",
"Balance": "Équilibre",
"Beneficiary": "Bénéficiaire",
"Beneficiary Selection": "Sélection du bénéficiaire",
"Block Savings Account": "Bloquer le compte d'épargne",
"Bulk Import": "Importation en masse",
"Bulk Loan Reassignment": "Réaffectation groupée de prêts",
@@ -1232,6 +1236,10 @@
"Transaction Reverted": "Transaction annulée",
"Transactions": "Transactions",
"Transfer Details": "Détails du transfert",
"Transfer Failed": "Transfert échoué",
"Transfer Status": "Statut du transfert",
"Transfer Successful": "Transfert réussi",
"Transfer Summary": "Résumé du transfert",
"Transferred From": "Transféré de",
"Transferred To": "Transféré à",
"Transferring From Details": "Transfert depuis les détails",
+8
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "Allega riunione",
"Attendance": "Partecipazione",
"Back": "Indietro",
"Back to Account": "Torna al conto",
"Bar Chart": "Grafico a barre",
"Browse": "Navigare",
"Bulk JLG Loan Application": "Richiesta di prestito JLG in blocco",
@@ -548,6 +549,7 @@
"View Journal Entries": "Visualizza le voci del diario",
"View Standing Instructions": "Visualizza le istruzioni per la posizione in piedi",
"View Transactions History": "Visualizza la cronologia delle transazioni",
"View Transactions": "Visualizza transazioni",
"Waive": "Rinuncia",
"Waive Charge": "Rinuncia all'addebito",
"Week": "Settimana",
@@ -926,6 +928,8 @@
"Assets / Liability": "Attività/Passività",
"Audit Trails": "Percorsi di controllo",
"Balance": "Bilancia",
"Beneficiary": "Beneficiario",
"Beneficiary Selection": "Selezione del beneficiario",
"Block Savings Account": "Bloccare il conto di risparmio",
"Bulk Import": "Importazione in blocco",
"Bulk Loan Reassignment": "Riassegnazione del prestito in blocco",
@@ -1231,6 +1235,10 @@
"Transaction Reverted": "Transazione annullata",
"Transactions": "Transazioni",
"Transfer Details": "Dettagli del trasferimento",
"Transfer Failed": "Trasferimento fallito",
"Transfer Status": "Stato del trasferimento",
"Transfer Successful": "Trasferimento riuscito",
"Transfer Summary": "Riepilogo del trasferimento",
"Transferred From": "Trasferito da",
"Transferred To": "Trasferito a",
"Transferring From Details": "Trasferimento dai dettagli",
+8
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "회의 첨부",
"Attendance": "출석",
"Back": "뒤쪽에",
"Back to Account": "계좌로 돌아가기",
"Bar Chart": "막대 차트",
"Browse": "검색",
"Bulk JLG Loan Application": "대량 JLG 대출 신청",
@@ -548,6 +549,7 @@
"View Journal Entries": "저널 항목 보기",
"View Standing Instructions": "기립 지침 보기",
"View Transactions History": "거래 내역 보기",
"View Transactions": "거래 보기",
"Waive": "면제",
"Waive Charge": "요금 면제",
"Week": "주",
@@ -927,6 +929,8 @@
"Assets / Liability": "자산/부채",
"Audit Trails": "감사 추적",
"Balance": "균형",
"Beneficiary": "수익자",
"Beneficiary Selection": "수익자 선택",
"Block Savings Account": "블록저축계좌",
"Bulk Import": "대량 가져오기",
"Bulk Loan Reassignment": "대량대출 재할당",
@@ -1233,6 +1237,10 @@
"Transaction Reverted": "거래가 되돌려졌습니다.",
"Transactions": "업무",
"Transfer Details": "전송 세부 정보",
"Transfer Failed": "이체 실패",
"Transfer Status": "이체 상태",
"Transfer Successful": "이체 성공",
"Transfer Summary": "이체 요약",
"Transferred From": "다음에서 전송됨",
"Transferred To": "로 전송",
"Transferring From Details": "세부정보에서 전송 중",
+8
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "Pridėti susitikimą",
"Attendance": "Lankomumas",
"Back": "Atgal",
"Back to Account": "Grįžti į sąskaitą",
"Bar Chart": "Juostinė diagrama",
"Browse": "Naršyti",
"Bulk JLG Loan Application": "Masinės JLG paskolos paraiška",
@@ -548,6 +549,7 @@
"View Journal Entries": "Peržiūrėkite žurnalo įrašus",
"View Standing Instructions": "Peržiūrėkite nuolatines instrukcijas",
"View Transactions History": "Peržiūrėkite operacijų istoriją",
"View Transactions": "Peržiūrėti operacijas",
"Waive": "Atsisakyti",
"Waive Charge": "Atsisakyti mokesčio",
"Week": "Savaitė",
@@ -925,6 +927,8 @@
"Assets / Liability": "Turtas / įsipareigojimai",
"Audit Trails": "Audito takai",
"Balance": "Balansas",
"Beneficiary": "Gavėjas",
"Beneficiary Selection": "Gavėjo pasirinkimas",
"Block Savings Account": "Blokuoti taupomąją sąskaitą",
"Bulk Import": "Masinis importas",
"Bulk Loan Reassignment": "Masinės paskolos perskirstymas",
@@ -1230,6 +1234,10 @@
"Transaction Reverted": "Sandoris grąžintas",
"Transactions": "Sandoriai",
"Transfer Details": "Pervedimo informacija",
"Transfer Failed": "Pervedimas nepavyko",
"Transfer Status": "Pervedimo būsena",
"Transfer Successful": "Pervedimas sėkmingas",
"Transfer Summary": "Pervedimo santrauka",
"Transferred From": "Perkelta iš",
"Transferred To": "Perkelta į",
"Transferring From Details": "Perkėlimas iš detalių",
+8
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "Pievienot sapulci",
"Attendance": "Apmeklējums",
"Back": "Atpakaļ",
"Back to Account": "Atpakaļ uz kontu",
"Bar Chart": "Joslu diagramma",
"Browse": "Pārlūkot",
"Bulk JLG Loan Application": "Lielapjoma JLG aizdevuma pieteikums",
@@ -548,6 +549,7 @@
"View Journal Entries": "Skatīt žurnāla ierakstus",
"View Standing Instructions": "Skatiet pastāvīgās instrukcijas",
"View Transactions History": "Skatiet darījumu vēsturi",
"View Transactions": "Skatīt darījumus",
"Waive": "Atteikties",
"Waive Charge": "Atteikties no maksas",
"Week": "nedēļa",
@@ -927,6 +929,8 @@
"Assets / Liability": "Aktīvi / Pasīvi",
"Audit Trails": "Revīzijas pēdas",
"Balance": "Līdzsvars",
"Beneficiary": "Saņēmējs",
"Beneficiary Selection": "Saņēmēja izvēle",
"Block Savings Account": "Bloķēt krājkontu",
"Bulk Import": "Lielapjoma importēšana",
"Bulk Loan Reassignment": "Lielapjoma aizdevuma pārcelšana",
@@ -1232,6 +1236,10 @@
"Transaction Reverted": "Darījums ir atsaukts",
"Transactions": "Darījumi",
"Transfer Details": "Pārskaitījuma informācija",
"Transfer Failed": "Pārskaitījums neizdevās",
"Transfer Status": "Pārskaitījuma statuss",
"Transfer Successful": "Pārskaitījums veiksmīgs",
"Transfer Summary": "Pārskaitījuma kopsavilkums",
"Transferred From": "Pārsūtīts no",
"Transferred To": "Pārsūtīts uz",
"Transferring From Details": "Pārsūtīšana no detaļām",
+8
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "बैठक संलग्न गर्नुहोस्",
"Attendance": "उपस्थित",
"Back": "पछाडि",
"Back to Account": "खातामा फर्कनुहोस्",
"Bar Chart": "बार चार्ट",
"Browse": "ब्राउज गर्नुहोस्",
"Bulk JLG Loan Application": "थोक JLG ऋण आवेदन",
@@ -548,6 +549,7 @@
"View Journal Entries": "जर्नल प्रविष्टिहरू हेर्नुहोस्",
"View Standing Instructions": "स्थायी निर्देशनहरू हेर्नुहोस्",
"View Transactions History": "लेनदेन इतिहास हेर्नुहोस्",
"View Transactions": "लेनदेन हेर्नुहोस्",
"Waive": "छुट",
"Waive Charge": "शुल्क छुट",
"Week": "हप्ता",
@@ -925,6 +927,8 @@
"Assets / Liability": "सम्पत्ति / दायित्व",
"Audit Trails": "अडिट ट्रेलहरू",
"Balance": "सन्तुलन",
"Beneficiary": "लाभग्राही",
"Beneficiary Selection": "लाभग्राही छनोट",
"Block Savings Account": "बचत खाता रोक्नुहोस्",
"Bulk Import": "थोक आयात",
"Bulk Loan Reassignment": "बल्क ऋण पुन: असाइनमेन्ट",
@@ -1230,6 +1234,10 @@
"Transaction Reverted": "कारोबार फिर्ता गरियो",
"Transactions": "लेनदेन",
"Transfer Details": "स्थानान्तरण विवरणहरू",
"Transfer Failed": "स्थानान्तरण असफल भयो",
"Transfer Status": "स्थानान्तरण स्थिति",
"Transfer Successful": "स्थानान्तरण सफल भयो",
"Transfer Summary": "स्थानान्तरण सारांश",
"Transferred From": "बाट स्थानान्तरण गरियो",
"Transferred To": "मा हस्तान्तरण गरियो",
"Transferring From Details": "विवरणहरूबाट स्थानान्तरण गर्दै",
+8
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "Anexar reunião",
"Attendance": "Comparecimento",
"Back": "Voltar",
"Back to Account": "Voltar para a Conta",
"Bar Chart": "Gráfico de barras",
"Browse": "Navegar",
"Bulk JLG Loan Application": "Solicitação de empréstimo JLG em massa",
@@ -548,6 +549,7 @@
"View Journal Entries": "Ver lançamentos contábeis manuais",
"View Standing Instructions": "Ver instruções permanentes",
"View Transactions History": "Ver histórico de transações",
"View Transactions": "Ver transações",
"Waive": "Renunciar",
"Waive Charge": "Isenção de cobrança",
"Week": "Semana",
@@ -927,6 +929,8 @@
"Assets / Liability": "Ativos/Passivos",
"Audit Trails": "Trilhas de auditoria",
"Balance": "Equilíbrio",
"Beneficiary": "Beneficiário",
"Beneficiary Selection": "Seleção de Beneficiário",
"Block Savings Account": "Bloquear conta poupança",
"Bulk Import": "Importação em massa",
"Bulk Loan Reassignment": "Reatribuição de empréstimo em massa",
@@ -1231,6 +1235,10 @@
"Transaction Reverted": "Transação revertida",
"Transactions": "Transações",
"Transfer Details": "Detalhes da transferência",
"Transfer Failed": "Transferência falhou",
"Transfer Status": "Status da transferência",
"Transfer Successful": "Transferência bem-sucedida",
"Transfer Summary": "Resumo da transferência",
"Transferred From": "Transferido de",
"Transferred To": "Transferido para",
"Transferring From Details": "Transferindo de detalhes",
+9
View File
@@ -347,6 +347,7 @@
"Attach Meeting": "Ambatanisha Mkutano",
"Attendance": "Mahudhurio",
"Back": "Nyuma",
"Back to Account": "Rudi kwa Akaunti",
"Bar Chart": "Chati ya Baa",
"Browse": "Vinjari",
"Bulk JLG Loan Application": "Maombi ya Mkopo wa Wingi wa JLG",
@@ -548,6 +549,7 @@
"View Journal Entries": "Tazama Maingizo ya Jarida",
"View Standing Instructions": "Tazama Maagizo ya Kudumu",
"View Transactions History": "Tazama Historia ya Shughuli",
"View Transactions": "Tazama Shughuli",
"Waive": "Acha",
"Waive Charge": "Kuondoa Malipo",
"Week": "Wiki",
@@ -925,6 +927,8 @@
"Assets / Liability": "Mali / Dhima",
"Audit Trails": "Njia za Ukaguzi",
"Balance": "Mizani",
"Beneficiary": "Mnufaika",
"Beneficiary Selection": "Uchaguzi wa Mnufaika",
"Block Savings Account": "Zuia Akaunti ya Akiba",
"Bulk Import": "Ingiza Wingi",
"Bulk Loan Reassignment": "Ugawaji upya wa mkopo kwa wingi",
@@ -1228,6 +1232,11 @@
"Transaction Details": "Maelezo ya Muamala",
"Transaction Reverted": "Muamala Umerejeshwa",
"Transactions": "Shughuli",
"Transfer Details": "Maelezo ya Uhamisho",
"Transfer Failed": "Uhamisho Umeshindwa",
"Transfer Status": "Hali ya Uhamisho",
"Transfer Successful": "Uhamisho Umefanikiwa",
"Transfer Summary": "Muhtasari wa Uhamisho",
"Transferred From": "Imehamishwa Kutoka",
"Transferred To": "Imehamishwa Kwa",
"Transferring From Details": "Kuhamisha Kutoka kwa Maelezo",