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:
+434
-347
@@ -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 }} - #{{
|
||||
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 }} - #{{
|
||||
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 }} - #{{
|
||||
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>
|
||||
|
||||
+61
-8
@@ -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
|
||||
|
||||
+153
-135
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user