feat: add Current Account module, DIP verification, charges integration, and translations
- Current Account module with full CRUD, transactions, charges, hold/release, block/unblock - Current Products management under Products module - DIP mobile number verification with real API call to /lookup/check - Sanctions verification dialog - Charges support for Current Account (chargeAppliesTo=5) in create/edit - Fixed permissions from SAVINGSACCOUNT to CURRENTACCOUNT across all current account components - Added all missing translation keys for Current Account in en-US.json Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
73aa69ee02
commit
0a32d7135e
@@ -0,0 +1,157 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="tab-container mat-typography">
|
||||
<div class="layout-row align-start">
|
||||
<div class="m-b-10">
|
||||
<h3>{{ 'labels.inputs.Charges' | translate }}</h3>
|
||||
</div>
|
||||
@if (this.chargesData) {
|
||||
<div class="action-button m-b-20">
|
||||
<button mat-raised-button color="primary" (click)="toggleCharges()">
|
||||
{{
|
||||
showInactiveCharges
|
||||
? viewAllChargeButtons('View Active Charges')
|
||||
: viewAllChargeButtons('View Inactive Charges')
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mat-elevation-z1 m-b-25">
|
||||
<table mat-table #chargesTable [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.name }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="feeOrPenalty">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Charge Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.penalty ? 'Penalty' : ('Fee' | translateKey: 'catalogs') }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="paymentDueAt">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Payment Due At' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.chargeTimeType.value | translateKey: 'catalogs' }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="dueAsOf">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Due As Of' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.dueDate | dateFormat }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="repeatsOn">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Repeats On (M/d)' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.feeOnMonthDay ? charge.feeOnMonthDay.join('/') : '' }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="calculationType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Calculation Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.chargeCalculationType.value | translateKey: 'catalogs' }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="due">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Due' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.amount | currency: charge.currency.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="paid">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Paid' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.amountPaid | currency: charge.currency.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="waived">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Waived' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.amountWaived | currency: charge.currency.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="outstanding">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Outstanding' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.amountOutstanding | currency: charge.currency.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
@if (currentAccountData.status.value === 'Submitted and pending approval') {
|
||||
<span>
|
||||
<button
|
||||
class="account-action-button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
matTooltip="{{ 'tooltips.Edit Charge' | translate }}"
|
||||
*mifosxHasPermission="'UPDATE_CURRENTACCOUNTCHARGE'"
|
||||
(click)="routeEdit($event); editCharge(charge)"
|
||||
>
|
||||
<i class="fa fa-pencil"></i>
|
||||
</button>
|
||||
<button
|
||||
class="account-action-button"
|
||||
mat-raised-button
|
||||
color="warn"
|
||||
matTooltip="{{ 'tooltips.Delete Charge' | translate }}"
|
||||
*mifosxHasPermission="'DELETE_CURRENTACCOUNTCHARGE'"
|
||||
(click)="routeEdit($event); deleteCharge(charge.id)"
|
||||
>
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
@if (currentAccountData.status.value === 'Active' && charge.amountOutstanding !== 0) {
|
||||
<span>
|
||||
<button
|
||||
class="account-action-button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
matTooltip="{{ 'tooltips.Pay Charge' | translate }}"
|
||||
*mifosxHasPermission="'PAY_CURRENTACCOUNTCHARGE'"
|
||||
(click)="routeEdit($event); payCharge(charge.id)"
|
||||
>
|
||||
<i class="fa fa-dollar"></i>
|
||||
</button>
|
||||
<button
|
||||
class="account-action-button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
matTooltip="{{ 'tooltips.Waive Charge' | translate }}"
|
||||
*mifosxHasPermission="'WAIVE_CURRENTACCOUNTCHARGE'"
|
||||
(click)="routeEdit($event); waiveCharge(charge.id)"
|
||||
>
|
||||
<i class="fa fa-flag"></i>
|
||||
</button>
|
||||
@if (isRecurringCharge(charge)) {
|
||||
<button
|
||||
class="account-action-button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
matTooltip="{{ 'tooltips.Inactivate Charge' | translate }}"
|
||||
*mifosxHasPermission="'INACTIVATE_CURRENTACCOUNTCHARGE'"
|
||||
(click)="routeEdit($event); inactivateCharge(charge.id)"
|
||||
>
|
||||
<i class="fa fa-ban"></i>
|
||||
</button>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="select-row" [routerLink]="[row.id]"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.tab-container {
|
||||
padding: 1%;
|
||||
margin: 1%;
|
||||
|
||||
h3 {
|
||||
margin: 1% auto;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
.account-action-button {
|
||||
min-width: 26px;
|
||||
padding: 0 6px;
|
||||
margin: 4px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.select-row:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, ViewChild, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import {
|
||||
MatTableDataSource,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
|
||||
/** Custom Services */
|
||||
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
|
||||
/** Custom Dialogs */
|
||||
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
|
||||
import { WaiveChargeDialogComponent } from '../custom-dialogs/waive-charge-dialog/waive-charge-dialog.component';
|
||||
import { InactivateChargeDialogComponent } from '../custom-dialogs/inactivate-charge-dialog/inactivate-charge-dialog.component';
|
||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
||||
|
||||
/** Custom Models */
|
||||
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
|
||||
import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base';
|
||||
import { DatepickerBase } from 'app/shared/form-dialog/formfield/model/datepicker-base';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Charges Tab Component
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-charges-tab',
|
||||
templateUrl: './charges-tab.component.html',
|
||||
styleUrls: ['./charges-tab.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatTooltip,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
CurrencyPipe,
|
||||
DateFormatPipe
|
||||
]
|
||||
})
|
||||
export class ChargesTabComponent implements OnInit {
|
||||
private currentAccountsService = inject(CurrentAccountsService);
|
||||
private route = inject(ActivatedRoute);
|
||||
private dateUtils = inject(Dates);
|
||||
private router = inject(Router);
|
||||
private dialog = inject(MatDialog);
|
||||
private settingsService = inject(SettingsService);
|
||||
private translateService = inject(TranslateService);
|
||||
|
||||
/** Current Account Data */
|
||||
currentAccountData: any;
|
||||
/** Charges Data */
|
||||
chargesData: any[];
|
||||
/** Data source for charges table. */
|
||||
dataSource: MatTableDataSource<any>;
|
||||
/** Toggles Charges Table */
|
||||
showInactiveCharges = false;
|
||||
/** Columns to be displayed in charges table. */
|
||||
displayedColumns: string[] = [
|
||||
'name',
|
||||
'feeOrPenalty',
|
||||
'paymentDueAt',
|
||||
'dueAsOf',
|
||||
'repeatsOn',
|
||||
'calculationType',
|
||||
'due',
|
||||
'paid',
|
||||
'waived',
|
||||
'outstanding',
|
||||
'actions'
|
||||
];
|
||||
|
||||
/** Charges Table Reference */
|
||||
@ViewChild('chargesTable', { static: true }) chargesTableRef: MatTable<Element>;
|
||||
|
||||
/**
|
||||
* Retrieves the Current account data from `resolve`.
|
||||
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
* @param {Router} router Router for navigation.
|
||||
* @param {MatDialog} dialog Dialog reference.
|
||||
* @param {Dates} dateUtils Date Utils.
|
||||
* @param {SettingsService} settingsService Setting service
|
||||
*/
|
||||
constructor() {
|
||||
this.route.parent.data.subscribe((data: { currentAccountData: any }) => {
|
||||
this.currentAccountData = data.currentAccountData;
|
||||
this.chargesData = this.currentAccountData.charges;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const activeCharges = this.chargesData ? this.chargesData.filter((charge) => charge.isActive) : [];
|
||||
this.dataSource = new MatTableDataSource(activeCharges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles datasource for active/inactive charges.
|
||||
*/
|
||||
toggleCharges() {
|
||||
this.showInactiveCharges = !this.showInactiveCharges;
|
||||
if (!this.showInactiveCharges) {
|
||||
const activeCharges = this.chargesData.filter((charge) => charge.isActive);
|
||||
this.dataSource.data = activeCharges;
|
||||
} else {
|
||||
const inActiveCharges = this.chargesData.filter((charge) => !charge.isActive);
|
||||
this.dataSource.data = inActiveCharges;
|
||||
}
|
||||
this.chargesTableRef.renderRows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pays the charge.
|
||||
* @param {any} chargeId Charge Id
|
||||
*/
|
||||
payCharge(chargeId: any) {
|
||||
const formfields: FormfieldBase[] = [
|
||||
new InputBase({
|
||||
controlName: 'amount',
|
||||
label: 'Amount',
|
||||
value: '',
|
||||
type: 'number',
|
||||
required: true
|
||||
}),
|
||||
new DatepickerBase({
|
||||
controlName: 'dueDate',
|
||||
label: 'Payment Date',
|
||||
value: '',
|
||||
type: 'date',
|
||||
required: true
|
||||
})
|
||||
];
|
||||
const data = {
|
||||
title: `Pay Charge ${chargeId}`,
|
||||
layout: { addButtonText: 'Confirm' },
|
||||
formfields: formfields
|
||||
};
|
||||
const payChargeDialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||
payChargeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.data) {
|
||||
const locale = this.settingsService.language.code;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const dataObject = {
|
||||
...response.data.value,
|
||||
dueDate: this.dateUtils.formatDate(response.data.value.dueDate, dateFormat),
|
||||
dateFormat,
|
||||
locale
|
||||
};
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountChargesCommand(this.currentAccountData.id, 'paycharge', dataObject, chargeId)
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waive's the charge
|
||||
* @param {any} chargeId Charge Id
|
||||
*/
|
||||
waiveCharge(chargeId: any) {
|
||||
const waiveChargeDialogRef = this.dialog.open(WaiveChargeDialogComponent, { data: { id: chargeId } });
|
||||
waiveChargeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountChargesCommand(this.currentAccountData.id, 'waive', {}, chargeId)
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivate's the charge
|
||||
* @param {any} chargeId Charge Id
|
||||
*/
|
||||
inactivateCharge(chargeId: any) {
|
||||
const inactivateChargeDialogRef = this.dialog.open(InactivateChargeDialogComponent, { data: { id: chargeId } });
|
||||
inactivateChargeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountChargesCommand(this.currentAccountData.id, 'inactivate', {}, chargeId)
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the charge
|
||||
* @param {any} charge Charge
|
||||
*/
|
||||
editCharge(charge: any) {
|
||||
const formfields: FormfieldBase[] = [
|
||||
new InputBase({
|
||||
controlName: 'amount',
|
||||
label: 'Amount',
|
||||
value: charge.amount || charge.amountOrPercentage,
|
||||
type: 'number',
|
||||
required: true
|
||||
})
|
||||
];
|
||||
const data = {
|
||||
title: `Edit Charge ${charge.id}`,
|
||||
layout: { addButtonText: 'Confirm' },
|
||||
formfields: formfields
|
||||
};
|
||||
const editChargeDialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||
editChargeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.data) {
|
||||
const locale = this.settingsService.language.code;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const dataObject = {
|
||||
...response.data.value,
|
||||
dateFormat,
|
||||
locale
|
||||
};
|
||||
this.currentAccountsService
|
||||
.editCurrentAccountCharge(this.currentAccountData.id, dataObject, charge.id)
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the charge
|
||||
* @param {any} chargeId Charge Id
|
||||
*/
|
||||
deleteCharge(chargeId: any) {
|
||||
const deleteChargeDialogRef = this.dialog.open(DeleteDialogComponent, {
|
||||
data: { deleteContext: `charge id:${chargeId}` }
|
||||
});
|
||||
deleteChargeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.delete) {
|
||||
this.currentAccountsService.deleteCurrentAccountCharge(this.currentAccountData.id, chargeId).subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if charge is recurring.
|
||||
* @param {any} charge Charge
|
||||
*/
|
||||
isRecurringCharge(charge: any) {
|
||||
return (
|
||||
charge.chargeTimeType.value === 'Monthly Fee' ||
|
||||
charge.chargeTimeType.value === 'Annual Fee' ||
|
||||
charge.chargeTimeType.value === 'Weekly Fee'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the propagation to view charge page.
|
||||
* @param $event Mouse Event
|
||||
*/
|
||||
routeEdit($event: MouseEvent) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refetches data fot the component
|
||||
* TODO: Replace by a custom reload component instead of hard-coded back-routing.
|
||||
*/
|
||||
private reload() {
|
||||
const url: string = this.router.url;
|
||||
const refreshUrl: string = this.router.url.slice(
|
||||
0,
|
||||
this.router.url.indexOf('current-accounts') + 'current-accounts'.length
|
||||
);
|
||||
this.router.navigateByUrl(refreshUrl, { skipLocationChange: true }).then(() => this.router.navigate([url]));
|
||||
}
|
||||
|
||||
viewAllChargeButtons(text: string): string {
|
||||
return this.translateService.instant('labels.buttons.' + text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<mat-card class="account-card">
|
||||
<mat-card-header class="header layout-column">
|
||||
<mat-card-title-group class="header-title-group">
|
||||
<div class="profile-image-container">
|
||||
<div>
|
||||
<img
|
||||
mat-card-md-image
|
||||
class="profile-image"
|
||||
matTooltip="{{ 'tooltips.Current Account' | translate }}"
|
||||
[src]="'assets/images/current_account_placeholder.png'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mat-typography account-card-title">
|
||||
<mat-card-title class="layout-row layout-lt-md-column">
|
||||
<div class="flex-60">
|
||||
<h3>
|
||||
@if (!currentAccountData.subStatus.block) {
|
||||
<i
|
||||
class="fa fa-stop"
|
||||
[ngClass]="currentAccountData.status.code | statusLookup"
|
||||
[matTooltip]="currentAccountData.status.value"
|
||||
></i>
|
||||
}
|
||||
@if (currentAccountData.subStatus.block) {
|
||||
<i
|
||||
class="fa fa-stop"
|
||||
[ngClass]="currentAccountData.subStatus.value | statusLookup"
|
||||
[matTooltip]="currentAccountData.subStatus.value"
|
||||
></i>
|
||||
}
|
||||
</h3>
|
||||
<!-- Product Name Row -->
|
||||
<span class="product-row">
|
||||
<span class="m-r-5">{{ 'labels.inputs.Current Account Product' | translate }} :</span>
|
||||
<span class="m-r-5">
|
||||
<mifosx-long-text textValue="{{ currentAccountData.currentAccountProductName }}"></mifosx-long-text>
|
||||
</span>
|
||||
</span>
|
||||
<!-- Account Number Row -->
|
||||
<span class="account-number-row">
|
||||
<span class="m-r-5">{{ 'labels.inputs.Account Number' | translate }} :</span>
|
||||
<span class="m-r-5">
|
||||
<mifosx-account-number
|
||||
accountNo="{{ currentAccountData.accountNo }}"
|
||||
display="left"
|
||||
></mifosx-account-number>
|
||||
</span>
|
||||
</span>
|
||||
<!-- External ID Row -->
|
||||
@if (currentAccountData.externalId) {
|
||||
<span class="external-id-row">
|
||||
<span class="m-r-5">{{ 'labels.inputs.External Id' | translate }} :</span>
|
||||
<span>{{ currentAccountData.externalId }}</span>
|
||||
</span>
|
||||
}
|
||||
<span class="account-overview">
|
||||
{{ 'labels.text.' + entityType | translate }} {{ 'labels.inputs.name' | translate }}:
|
||||
{{ currentAccountData.clientName || currentAccountData.groupName }}
|
||||
@if (currentAccountData.clientAccountNo) {
|
||||
<span class="m-l-10">
|
||||
<mifosx-account-number accountNo="{{ currentAccountData.clientAccountNo }}"></mifosx-account-number>
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if (!currentAccountData.status.rejected && !currentAccountData.status.submittedAndPendingApproval) {
|
||||
<div class="flex-30">
|
||||
<h3>{{ 'labels.heading.Account Overview' | translate }}</h3>
|
||||
<table class="account-overview">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Current Balance' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{
|
||||
currentAccountData.summary.accountBalance | currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Available Balance' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{
|
||||
currentAccountData.summary.availableBalance
|
||||
| currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-5">
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="accountMenu"
|
||||
aria-label="Loan account actions"
|
||||
yPosition="below"
|
||||
>
|
||||
<mat-icon matListIcon class="actions-menu">
|
||||
<fa-icon icon="bars" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-card-title>
|
||||
</div>
|
||||
|
||||
<mat-menu #accountMenu="matMenu">
|
||||
@for (item of buttonConfig.singleButtons; track item) {
|
||||
<button mat-menu-item *mifosxHasPermission="item.taskPermissionName" (click)="doAction(item.name)">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="{{ item.icon }}" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
<span> {{ 'labels.menus.' + item.name | translate }} </span>
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (buttonConfig.options && !currentAccountData.subStatus.block) {
|
||||
<button mat-menu-item [matMenuTriggerFor]="More">{{ 'labels.menus.More' | translate }}</button>
|
||||
<mat-menu #More="matMenu">
|
||||
@for (option of buttonConfig.options; track option) {
|
||||
<span>
|
||||
<button mat-menu-item *mifosxHasPermission="option.taskPermissionName" (click)="doAction(option.name)">
|
||||
{{ 'labels.menus.' + option.name | translate }}
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
</mat-menu>
|
||||
}
|
||||
</mat-menu>
|
||||
</mat-card-title-group>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content class="content">
|
||||
@if (isActive) {
|
||||
<nav mat-tab-nav-bar class="navigation-tabs" [tabPanel]="tabPanel">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./general']"
|
||||
routerLinkActive
|
||||
#general="routerLinkActive"
|
||||
[active]="general.isActive"
|
||||
>
|
||||
{{ 'labels.inputs.General' | translate }}
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./transactions']"
|
||||
routerLinkActive
|
||||
#transactions="routerLinkActive"
|
||||
[active]="transactions.isActive"
|
||||
>
|
||||
{{ 'labels.inputs.Transactions' | translate }}
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./passbook']"
|
||||
routerLinkActive
|
||||
#passbook="routerLinkActive"
|
||||
[active]="passbook.isActive"
|
||||
>
|
||||
{{ 'labels.inputs.Passbook' | translate }}
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./charges']"
|
||||
routerLinkActive
|
||||
#charges="routerLinkActive"
|
||||
[active]="charges.isActive"
|
||||
>
|
||||
{{ 'labels.inputs.Charges' | translate }}
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./documents']"
|
||||
routerLinkActive
|
||||
#currentAccountDocuments="routerLinkActive"
|
||||
[active]="currentAccountDocuments.isActive"
|
||||
>
|
||||
{{ 'labels.inputs.Documents' | translate }}
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
*mifosxHasPermission="'READ_CURRENTACCOUNTNOTE'"
|
||||
[routerLink]="['./notes']"
|
||||
routerLinkActive
|
||||
#notes="routerLinkActive"
|
||||
[active]="notes.isActive"
|
||||
>
|
||||
{{ 'labels.inputs.Notes' | translate }}
|
||||
</a>
|
||||
<ng-container *mifosxHasPermission="'READ_CURRENTACCOUNTCHARGE'">
|
||||
@if (currentAccountData.clientId) {
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./standing-instructions']"
|
||||
routerLinkActive
|
||||
#standingInstructions="routerLinkActive"
|
||||
[active]="standingInstructions.isActive"
|
||||
>
|
||||
{{ 'labels.inputs.Standing Instructions' | translate }}
|
||||
</a>
|
||||
}
|
||||
</ng-container>
|
||||
@for (currentAccountDatatable of currentAccountDatatables; track currentAccountDatatable) {
|
||||
<a
|
||||
mat-tab-link
|
||||
*mifosxHasPermission="'READ_' + currentAccountDatatable.registeredTableName"
|
||||
[routerLink]="['./datatables', currentAccountDatatable.registeredTableName]"
|
||||
routerLinkActive
|
||||
#datatable="routerLinkActive"
|
||||
[active]="datatable.isActive"
|
||||
>
|
||||
{{ currentAccountDatatable.registeredTableName }}
|
||||
</a>
|
||||
}
|
||||
</nav>
|
||||
}
|
||||
|
||||
<mat-tab-nav-panel #tabPanel>
|
||||
<router-outlet></router-outlet>
|
||||
</mat-tab-nav-panel>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
mat-card-title {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.product-row,
|
||||
.account-number-row,
|
||||
.external-id-row {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router, RouterLinkActive, RouterLink, RouterOutlet } from '@angular/router';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
/** Custom Dialogs */
|
||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
||||
import { CalculateInterestDialogComponent } from './custom-dialogs/calculate-interest-dialog/calculate-interest-dialog.component';
|
||||
import { PostInterestDialogComponent } from './custom-dialogs/post-interest-dialog/post-interest-dialog.component';
|
||||
import { ToggleWithholdTaxDialogComponent } from './custom-dialogs/toggle-withhold-tax-dialog/toggle-withhold-tax-dialog.component';
|
||||
|
||||
/** Custom Buttons Configuration */
|
||||
import { CurrentAccountButtonsConfiguration } from './current-accounts-buttons.config';
|
||||
import { CurrentAccountsService } from '../current-accounts.service';
|
||||
import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component';
|
||||
import { Currency } from 'app/shared/models/general.model';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/** Environment Configuration */
|
||||
import { environment } from '../../../environments/environment';
|
||||
import {
|
||||
MatCard,
|
||||
MatCardHeader,
|
||||
MatCardTitleGroup,
|
||||
MatCardMdImage,
|
||||
MatCardTitle,
|
||||
MatCardContent
|
||||
} from '@angular/material/card';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { NgClass, CurrencyPipe } from '@angular/common';
|
||||
import { LongTextComponent } from '../../shared/long-text/long-text.component';
|
||||
import { AccountNumberComponent } from '../../shared/account-number/account-number.component';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatMenuTrigger, MatMenu, MatMenuItem } from '@angular/material/menu';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { MatTabNav, MatTabLink, MatTabNavPanel } from '@angular/material/tabs';
|
||||
import { StatusLookupPipe } from '../../pipes/status-lookup.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Current Account View Component
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-current-account-account-view',
|
||||
templateUrl: './current-account-view.component.html',
|
||||
styleUrls: ['./current-account-view.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatCardHeader,
|
||||
MatCardTitleGroup,
|
||||
MatCardMdImage,
|
||||
MatTooltip,
|
||||
MatCardTitle,
|
||||
NgClass,
|
||||
LongTextComponent,
|
||||
AccountNumberComponent,
|
||||
MatIconButton,
|
||||
MatMenuTrigger,
|
||||
MatIcon,
|
||||
FaIconComponent,
|
||||
MatMenu,
|
||||
MatMenuItem,
|
||||
MatTabNav,
|
||||
MatTabLink,
|
||||
RouterLinkActive,
|
||||
MatTabNavPanel,
|
||||
RouterOutlet,
|
||||
CurrencyPipe,
|
||||
StatusLookupPipe
|
||||
]
|
||||
})
|
||||
export class CurrentAccountViewComponent implements OnInit {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private currentAccountsService = inject(CurrentAccountsService);
|
||||
private translateService = inject(TranslateService);
|
||||
dialog = inject(MatDialog);
|
||||
|
||||
/** Current Account Data */
|
||||
currentAccountData: any;
|
||||
/** Current Account Data Tables */
|
||||
currentAccountDatatables: any;
|
||||
/** Button Configurations */
|
||||
buttonConfig: CurrentAccountButtonsConfiguration;
|
||||
/** Entity Type */
|
||||
entityType: string;
|
||||
|
||||
isActive = false;
|
||||
currency: Currency;
|
||||
|
||||
/**
|
||||
* Fetches current account data from `resolve`
|
||||
* @param {ActivatedRoute} route Activated Route
|
||||
* @param {Router} router Router
|
||||
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||
*/
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { currentAccountData: any; currentAccountDatatables: any }) => {
|
||||
this.currentAccountData = data.currentAccountData;
|
||||
this.currency = this.currentAccountData.currency;
|
||||
this.currentAccountDatatables = data.currentAccountDatatables;
|
||||
});
|
||||
if (this.router.url.includes('clients')) {
|
||||
this.entityType = 'Client';
|
||||
} else if (this.router.url.includes('groups')) {
|
||||
this.entityType = 'Group';
|
||||
} else if (this.router.url.includes('centers')) {
|
||||
this.entityType = 'Center';
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.setConditionalButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds options to button config. conditionaly.
|
||||
*/
|
||||
setConditionalButtons() {
|
||||
const status = this.currentAccountData.status.value;
|
||||
this.isActive = status === 'Active';
|
||||
const subStatus = this.currentAccountData.subStatus;
|
||||
this.buttonConfig = new CurrentAccountButtonsConfiguration(status, subStatus);
|
||||
if (this.currentAccountData.clientId) {
|
||||
this.buttonConfig.addOption({
|
||||
name: 'Transfer Funds',
|
||||
taskPermissionName: 'CREATE_ACCOUNTTRANSFER'
|
||||
});
|
||||
}
|
||||
if (this.currentAccountData.externalId && environment.mifosInterbankTransfersEnabled) {
|
||||
this.buttonConfig.addOption({
|
||||
name: 'Interbank Transfer',
|
||||
taskPermissionName: 'CREATE_ACCOUNTTRANSFER'
|
||||
});
|
||||
}
|
||||
if (!this.currentAccountData.fieldOfficerId) {
|
||||
this.buttonConfig.addOption({
|
||||
name: 'Assign Staff',
|
||||
taskPermissionName: 'UPDATESAVINGSOFFICER_CURRENTACCOUNT'
|
||||
});
|
||||
} else {
|
||||
this.buttonConfig.addOption({
|
||||
name: 'Unassign Staff',
|
||||
taskPermissionName: 'REMOVESAVINGSOFFICER_CURRENTACCOUNT'
|
||||
});
|
||||
}
|
||||
if (this.currentAccountData.charges) {
|
||||
const charges: any[] = this.currentAccountData.charges;
|
||||
charges.forEach((charge: any) => {
|
||||
if (charge.name === 'Annual fee - INR') {
|
||||
this.buttonConfig.addOption({
|
||||
name: 'Apply Annual Fees',
|
||||
taskPermissionName: 'APPLYANNUALFEE_CURRENTACCOUNT'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.currentAccountData.taxGroup) {
|
||||
if (this.currentAccountData.withHoldTax) {
|
||||
this.buttonConfig.addOption({
|
||||
name: 'Disable Withhold Tax',
|
||||
taskPermissionName: 'UPDATEWITHHOLDTAX_CURRENTACCOUNT'
|
||||
});
|
||||
} else {
|
||||
this.buttonConfig.addOption({
|
||||
name: 'Enable Withhold Tax',
|
||||
taskPermissionName: 'UPDATEWITHHOLDTAX_CURRENTACCOUNT'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refetches data foe the component
|
||||
* TODO: Replace by a custom reload component instead of hard-coded back-routing.
|
||||
*/
|
||||
private reload() {
|
||||
const url: string = this.router.url;
|
||||
const refreshUrl: string = this.router.url.slice(
|
||||
0,
|
||||
this.router.url.indexOf('current-accounts') + 'current-accounts'.length
|
||||
);
|
||||
this.router.navigateByUrl(refreshUrl, { skipLocationChange: true }).then(() => this.router.navigate([url]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs action button/option action.
|
||||
* @param {string} name action name.
|
||||
*/
|
||||
doAction(name: string) {
|
||||
switch (name) {
|
||||
case 'Approve':
|
||||
case 'Reject':
|
||||
case 'Deposit':
|
||||
case 'Activate':
|
||||
case 'Close':
|
||||
case 'Undo Approval':
|
||||
case 'Post Interest As On':
|
||||
case 'Assign Staff':
|
||||
case 'Add Charge':
|
||||
case 'Hold Amount':
|
||||
case 'Block Account':
|
||||
case 'Block Deposit':
|
||||
case 'Block Withdrawal':
|
||||
case 'Unassign Staff':
|
||||
case 'Withdrawn by Client':
|
||||
case 'Apply Annual Fees':
|
||||
this.router.navigate([`actions/${name}`], { relativeTo: this.route });
|
||||
break;
|
||||
case 'Withdrawal':
|
||||
this.router.navigate([`actions/Withdrawal`], { relativeTo: this.route });
|
||||
break;
|
||||
case 'Modify Application':
|
||||
this.router.navigate(['edit'], { relativeTo: this.route });
|
||||
break;
|
||||
case 'Delete':
|
||||
this.deleteCurrentAccount();
|
||||
break;
|
||||
case 'Calculate Interest':
|
||||
this.calculateInterest();
|
||||
break;
|
||||
case 'Post Interest':
|
||||
this.postInterest();
|
||||
break;
|
||||
case 'Enable Withhold Tax':
|
||||
this.enableWithHoldTax();
|
||||
break;
|
||||
case 'Disable Withhold Tax':
|
||||
this.disableWithHoldTax();
|
||||
break;
|
||||
case 'Transfer Funds':
|
||||
const queryParams: any = {
|
||||
interbank: false,
|
||||
currentAccountId: this.currentAccountData.id,
|
||||
accountType: 'fromcurrentaccount'
|
||||
};
|
||||
this.router.navigate(['transfer-funds/make-account-transfer'], {
|
||||
relativeTo: this.route,
|
||||
queryParams: queryParams,
|
||||
state: { balance: this.currentAccountData.summary.availableBalance }
|
||||
});
|
||||
break;
|
||||
case 'Interbank Transfer': {
|
||||
const queryParams: any = {
|
||||
interbank: true,
|
||||
currentAccountId: this.currentAccountData.id,
|
||||
accountType: 'interbank'
|
||||
};
|
||||
this.router.navigate(['transfer-funds/make-account-transfer'], {
|
||||
relativeTo: this.route,
|
||||
queryParams: queryParams,
|
||||
state: { balance: this.currentAccountData.summary.availableBalance }
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'Unblock Account':
|
||||
case 'Unblock Deposit':
|
||||
case 'Unblock Withdrawal':
|
||||
this.unblockCurrentAccount(name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes Current Account.
|
||||
*/
|
||||
private deleteCurrentAccount() {
|
||||
const deleteCurrentAccountDialogRef = this.dialog.open(DeleteDialogComponent, {
|
||||
data: { deleteContext: `current account with id: ${this.currentAccountData.id}` }
|
||||
});
|
||||
deleteCurrentAccountDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.delete) {
|
||||
this.currentAccountsService.deleteCurrentAccount(this.currentAccountData.id).subscribe(() => {
|
||||
this.router.navigate(['../../'], { relativeTo: this.route });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates current account's interest
|
||||
*/
|
||||
private calculateInterest() {
|
||||
const calculateInterestAccountDialogRef = this.dialog.open(CalculateInterestDialogComponent);
|
||||
calculateInterestAccountDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountCommand(this.currentAccountData.id, 'calculateInterest', {})
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts current account's interest
|
||||
*/
|
||||
private postInterest() {
|
||||
const postInterestAccountDialogRef = this.dialog.open(PostInterestDialogComponent);
|
||||
postInterestAccountDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountCommand(this.currentAccountData.id, 'postInterest', {})
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables withhold tax for current account.
|
||||
*/
|
||||
private enableWithHoldTax() {
|
||||
const deleteCurrentAccountDialogRef = this.dialog.open(ToggleWithholdTaxDialogComponent, {
|
||||
data: { isEnable: true }
|
||||
});
|
||||
deleteCurrentAccountDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountUpdateCommand(this.currentAccountData.id, 'updateWithHoldTax', { withHoldTax: true })
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables withhold tac for current account
|
||||
*/
|
||||
private disableWithHoldTax() {
|
||||
const disableWithHoldTaxDialogRef = this.dialog.open(ToggleWithholdTaxDialogComponent, {
|
||||
data: { isEnable: false }
|
||||
});
|
||||
disableWithHoldTaxDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountUpdateCommand(this.currentAccountData.id, 'updateWithHoldTax', { withHoldTax: false })
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unblock Current Account.
|
||||
*/
|
||||
private unblockCurrentAccount(action: string) {
|
||||
const unblockCurrentAccountDialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
||||
data: {
|
||||
heading: this.translateService.instant('labels.heading.Current Account'),
|
||||
dialogContext:
|
||||
this.translateService.instant('labels.dialogContext.Are you sure you want') +
|
||||
action +
|
||||
this.translateService.instant('this Current Account')
|
||||
}
|
||||
});
|
||||
let command = 'unblock';
|
||||
if (action === 'Unblock Deposit') {
|
||||
command = 'unblockCredit';
|
||||
}
|
||||
if (action === 'Unblock Withdrawal') {
|
||||
command = 'unblockDebit';
|
||||
}
|
||||
unblockCurrentAccountDialogRef.afterClosed().subscribe((response: { confirm: any }) => {
|
||||
if (response.confirm) {
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountCommand(this.currentAccountData.id, command, {})
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Current Account Buttons Configuration */
|
||||
export class CurrentAccountButtonsConfiguration {
|
||||
optionArray: {
|
||||
name: string;
|
||||
taskPermissionName: string;
|
||||
}[];
|
||||
|
||||
buttonsArray: {
|
||||
name: string;
|
||||
icon: string;
|
||||
taskPermissionName: string;
|
||||
}[];
|
||||
|
||||
constructor(status: string, substatus: any) {
|
||||
this.setOptions(status);
|
||||
this.setButtons(status, substatus);
|
||||
}
|
||||
|
||||
get singleButtons() {
|
||||
return this.buttonsArray;
|
||||
}
|
||||
|
||||
get options() {
|
||||
return this.optionArray;
|
||||
}
|
||||
|
||||
setButtons(status: string, substatus: any) {
|
||||
switch (status) {
|
||||
case 'Active':
|
||||
this.buttonsArray = [];
|
||||
if (!substatus.block) {
|
||||
if (!substatus.blockCredit) {
|
||||
this.buttonsArray.push(
|
||||
{
|
||||
name: 'Deposit',
|
||||
icon: 'arrow-up',
|
||||
taskPermissionName: 'DEPOSIT_CURRENTACCOUNT'
|
||||
},
|
||||
{
|
||||
name: 'Block Deposit',
|
||||
icon: 'lock',
|
||||
taskPermissionName: 'BLOCKDEPOSIT_CURRENTACCOUNT'
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.buttonsArray.push({
|
||||
name: 'Unblock Deposit',
|
||||
icon: 'lock-open',
|
||||
taskPermissionName: 'UNBLOCKDEPOSIT_CURRENTACCOUNT'
|
||||
});
|
||||
}
|
||||
if (!substatus.blockDebit) {
|
||||
this.buttonsArray.push(
|
||||
{
|
||||
name: 'Withdrawal',
|
||||
icon: 'arrow-down',
|
||||
taskPermissionName: 'WITHDRAWAL_CURRENTACCOUNT'
|
||||
},
|
||||
{
|
||||
name: 'Block Withdrawal',
|
||||
icon: 'lock',
|
||||
taskPermissionName: 'BLOCKWITHDRAWAL_CURRENTACCOUNT'
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.buttonsArray.push({
|
||||
name: 'Unblock Withdrawal',
|
||||
icon: 'lock-open',
|
||||
taskPermissionName: 'UNBLOCKWITHDRAWAL_CURRENTACCOUNT'
|
||||
});
|
||||
}
|
||||
this.buttonsArray = this.buttonsArray.concat([
|
||||
{
|
||||
name: 'Block Account',
|
||||
icon: 'lock',
|
||||
taskPermissionName: 'BLOCK_CURRENTACCOUNT'
|
||||
},
|
||||
{
|
||||
name: 'Hold Amount',
|
||||
icon: 'lock',
|
||||
taskPermissionName: 'HOLDAMOUNT_CURRENTACCOUNT'
|
||||
},
|
||||
{
|
||||
name: 'Calculate Interest',
|
||||
icon: 'table',
|
||||
taskPermissionName: 'CALCULATEINTEREST_CURRENTACCOUNT'
|
||||
},
|
||||
{
|
||||
name: 'Post Interest As On',
|
||||
icon: 'arrow-right',
|
||||
taskPermissionName: 'POSTINTEREST_CURRENTACCOUNT'
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
this.buttonsArray = [
|
||||
{
|
||||
name: 'Unblock Account',
|
||||
icon: 'lock-open',
|
||||
taskPermissionName: 'UNBLOCK_CURRENTACCOUNT'
|
||||
}
|
||||
];
|
||||
}
|
||||
break;
|
||||
case 'Submitted and pending approval':
|
||||
this.buttonsArray = [
|
||||
{
|
||||
name: 'Modify Application',
|
||||
icon: 'pen',
|
||||
taskPermissionName: 'UPDATE_CURRENTACCOUNT'
|
||||
},
|
||||
{
|
||||
name: 'Approve',
|
||||
icon: 'check',
|
||||
taskPermissionName: 'APPROVE_CURRENTACCOUNT'
|
||||
}
|
||||
];
|
||||
break;
|
||||
case 'Approved':
|
||||
this.buttonsArray = [
|
||||
{
|
||||
name: 'Undo Approval',
|
||||
icon: 'undo',
|
||||
taskPermissionName: 'APPROVALUNDO_CURRENTACCOUNT'
|
||||
},
|
||||
{
|
||||
name: 'Activate',
|
||||
icon: 'check',
|
||||
taskPermissionName: 'ACTIVATE_CURRENTACCOUNT'
|
||||
},
|
||||
{
|
||||
name: 'Add Charge',
|
||||
icon: 'plus',
|
||||
taskPermissionName: 'CREATE_CURRENTACCOUNTCHARGE'
|
||||
}
|
||||
];
|
||||
break;
|
||||
default:
|
||||
this.buttonsArray = [];
|
||||
}
|
||||
}
|
||||
|
||||
setOptions(status: string) {
|
||||
switch (status) {
|
||||
case 'Active':
|
||||
this.optionArray = [
|
||||
{
|
||||
name: 'Post Interest',
|
||||
taskPermissionName: 'POSTINTEREST_CURRENTACCOUNT'
|
||||
},
|
||||
{
|
||||
name: 'Add Charge',
|
||||
taskPermissionName: 'CREATE_CURRENTACCOUNTCHARGE'
|
||||
},
|
||||
{
|
||||
name: 'Close',
|
||||
taskPermissionName: 'CLOSE_CURRENTACCOUNT'
|
||||
}
|
||||
];
|
||||
break;
|
||||
case 'Submitted and pending approval':
|
||||
this.optionArray = [
|
||||
{
|
||||
name: 'Reject',
|
||||
taskPermissionName: 'REJECT_CURRENTACCOUNT'
|
||||
},
|
||||
{
|
||||
name: 'Withdrawn by Client',
|
||||
taskPermissionName: 'WITHDRAW_CURRENTACCOUNT'
|
||||
},
|
||||
{
|
||||
name: 'Add Charge',
|
||||
taskPermissionName: 'CREATE_CURRENTACCOUNTCHARGE'
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
taskPermissionName: 'DELETE_CURRENTACCOUNT'
|
||||
}
|
||||
];
|
||||
break;
|
||||
case 'Approved':
|
||||
default:
|
||||
this.optionArray = [];
|
||||
}
|
||||
}
|
||||
|
||||
addOption(option: { name: string; taskPermissionName: string }) {
|
||||
this.optionArray.push(option);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<mifosx-entity-documents-tab
|
||||
[entityType]="entityType"
|
||||
[entityId]="entityId"
|
||||
[entityDocuments]="entityDocuments"
|
||||
[callbackUpload]="uploadDocument"
|
||||
[callbackDelete]="deleteDocument"
|
||||
>
|
||||
</mifosx-entity-documents-tab>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { EntityDocumentsTabComponent } from '../../../shared/tabs/entity-documents-tab/entity-documents-tab.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-current-account-documents-tab',
|
||||
templateUrl: './current-accounts-documents-tab.component.html',
|
||||
styleUrls: ['./current-accounts-documents-tab.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
EntityDocumentsTabComponent
|
||||
]
|
||||
})
|
||||
export class CurrentAccountDocumentsTabComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private currentAccountsService = inject(CurrentAccountsService);
|
||||
private settingsService = inject(SettingsService);
|
||||
dialog = inject(MatDialog);
|
||||
|
||||
/** Stores the resolved current account documents data */
|
||||
entityDocuments: any;
|
||||
/** Stores the current account Account Id */
|
||||
entityId: string;
|
||||
entityType = 'currentaccounts';
|
||||
|
||||
/**
|
||||
* Retrieves the current account data from `resolve`.
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
*/
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { currentAccountDocuments: any }) => {
|
||||
this.setCurrentAccountDocumentsData(data.currentAccountDocuments);
|
||||
});
|
||||
this.entityId = this.route.parent.snapshot.paramMap.get('currentAccountId');
|
||||
}
|
||||
|
||||
setCurrentAccountDocumentsData(data: any) {
|
||||
data.forEach((ele: any) => {
|
||||
ele.docUrl =
|
||||
this.settingsService.serverUrl +
|
||||
'/currentaccounts/' +
|
||||
ele.parentEntityId +
|
||||
'/documents/' +
|
||||
ele.id +
|
||||
'/attachment?tenantIdentifier=' +
|
||||
environment.fineractPlatformTenantId;
|
||||
if (ele.fileName) {
|
||||
if (
|
||||
ele.fileName.toLowerCase().indexOf('.jpg') !== -1 ||
|
||||
ele.fileName.toLowerCase().indexOf('.jpeg') !== -1 ||
|
||||
ele.fileName.toLowerCase().indexOf('.png') !== -1
|
||||
) {
|
||||
ele.fileIsImage = true;
|
||||
}
|
||||
}
|
||||
if (ele.type) {
|
||||
if (ele.type.toLowerCase().indexOf('image') !== -1) {
|
||||
ele.fileIsImage = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.entityDocuments = data;
|
||||
}
|
||||
|
||||
uploadDocument(formData: FormData): any {
|
||||
return this.currentAccountsService.loadCurrentAccountDocument(this.entityId, formData);
|
||||
}
|
||||
|
||||
deleteDocument(documentId: any) {
|
||||
this.currentAccountsService.deleteCurrentAccountDocument(this.entityId, documentId).subscribe((res: any) => {});
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<h1 mat-dialog-title>{{ 'labels.heading.Calculate Interest' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>{{ 'labels.text.Are you sure you want to calculate interest' | translate }} ?</p>
|
||||
</div>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
|
||||
<button mat-raised-button color="primary" [mat-dialog-close]="{ confirm: true }">
|
||||
{{ 'labels.buttons.Confirm' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, inject } from '@angular/core';
|
||||
import {
|
||||
MatDialogRef,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
} from '@angular/material/dialog';
|
||||
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Calculate interest dialog component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-calculate-interest-dialog',
|
||||
templateUrl: './calculate-interest-dialog.component.html',
|
||||
styleUrls: ['./calculate-interest-dialog.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
CdkScrollable,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
]
|
||||
})
|
||||
export class CalculateInterestDialogComponent {
|
||||
dialogRef = inject<MatDialogRef<CalculateInterestDialogComponent>>(MatDialogRef);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<h1 mat-dialog-title>{{ 'tooltips.Inactivate Charge' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>{{ 'labels.text.Are you sure you want to inactivate charge with id' | translate }}: {{ data.id }} ?</p>
|
||||
</div>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
|
||||
<button mat-raised-button color="primary" [mat-dialog-close]="{ confirm: true }">
|
||||
{{ 'labels.buttons.Confirm' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, inject } from '@angular/core';
|
||||
import {
|
||||
MatDialogRef,
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
} from '@angular/material/dialog';
|
||||
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Inactivate charge dialog component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-inactivate-charge-dialog',
|
||||
templateUrl: './inactivate-charge-dialog.component.html',
|
||||
styleUrls: ['./inactivate-charge-dialog.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
CdkScrollable,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
]
|
||||
})
|
||||
export class InactivateChargeDialogComponent {
|
||||
dialogRef = inject<MatDialogRef<InactivateChargeDialogComponent>>(MatDialogRef);
|
||||
data = inject(MAT_DIALOG_DATA);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<h1 mat-dialog-title>{{ 'labels.heading.Post Interest' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>{{ 'labels.text.Are you sure you want to post interest' | translate }} ?</p>
|
||||
</div>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
|
||||
<button mat-raised-button color="primary" [mat-dialog-close]="{ confirm: true }">
|
||||
{{ 'labels.buttons.Confirm' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, inject } from '@angular/core';
|
||||
import {
|
||||
MatDialogRef,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
} from '@angular/material/dialog';
|
||||
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Post interest dialog component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-post-interest-dialog',
|
||||
templateUrl: './post-interest-dialog.component.html',
|
||||
styleUrls: ['./post-interest-dialog.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
CdkScrollable,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
]
|
||||
})
|
||||
export class PostInterestDialogComponent {
|
||||
dialogRef = inject<MatDialogRef<PostInterestDialogComponent>>(MatDialogRef);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<h1 mat-dialog-title>{{ 'labels.heading.Release Amount' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>{{ 'labels.text.Are you sure you want to release this amount' | translate }}</p>
|
||||
</div>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
|
||||
<button mat-raised-button color="warn" [mat-dialog-close]="{ confirm: true }">
|
||||
{{ 'labels.buttons.Confirm' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, inject } from '@angular/core';
|
||||
import {
|
||||
MatDialogRef,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
} from '@angular/material/dialog';
|
||||
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-release-amount-dialog',
|
||||
templateUrl: './release-amount-dialog.component.html',
|
||||
styleUrls: ['./release-amount-dialog.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
CdkScrollable,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
]
|
||||
})
|
||||
export class ReleaseAmountDialogComponent {
|
||||
dialogRef = inject<MatDialogRef<ReleaseAmountDialogComponent>>(MatDialogRef);
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<h1 mat-dialog-title>
|
||||
{{ (data.isEnable ? 'labels.heading.Enable' : 'labels.heading.Disable') | translate }}
|
||||
{{ 'labels.heading.Withhold Tax' | translate }}
|
||||
</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>
|
||||
{{ 'labels.text.Are you sure you want to' | translate }}
|
||||
{{ (data.isEnable ? 'labels.heading.Enable' : 'labels.heading.Disable') | translate }}
|
||||
{{ 'labels.text.withhold tax for this account' | translate }} ?
|
||||
</p>
|
||||
</div>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
|
||||
<button mat-raised-button color="{{ data.isEnable ? 'accent' : 'warn' }}" [mat-dialog-close]="{ confirm: true }">
|
||||
{{ 'labels.buttons.Confirm' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, inject } from '@angular/core';
|
||||
import {
|
||||
MatDialogRef,
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
} from '@angular/material/dialog';
|
||||
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Toggle withhold tax dialog dialog component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-toggle-withhold-tax-dialog',
|
||||
templateUrl: './toggle-withhold-tax-dialog.component.html',
|
||||
styleUrls: ['./toggle-withhold-tax-dialog.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
CdkScrollable,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
]
|
||||
})
|
||||
export class ToggleWithholdTaxDialogComponent {
|
||||
dialogRef = inject<MatDialogRef<ToggleWithholdTaxDialogComponent>>(MatDialogRef);
|
||||
data = inject(MAT_DIALOG_DATA);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<h1 mat-dialog-title>{{ 'labels.heading.Undo Transaction' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>{{ 'labels.text.Are you sure you want to undo this transaction' | translate }}</p>
|
||||
</div>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
|
||||
<button mat-raised-button color="warn" [mat-dialog-close]="{ confirm: true }">
|
||||
{{ 'labels.buttons.Confirm' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, inject } from '@angular/core';
|
||||
import {
|
||||
MatDialogRef,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
} from '@angular/material/dialog';
|
||||
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
/**
|
||||
* Undo transaction dialog component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-undo-transaction-dialog',
|
||||
templateUrl: './undo-transaction-dialog.component.html',
|
||||
styleUrls: ['./undo-transaction-dialog.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
CdkScrollable,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
]
|
||||
})
|
||||
export class UndoTransactionDialogComponent {
|
||||
dialogRef = inject<MatDialogRef<UndoTransactionDialogComponent>>(MatDialogRef);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<h1 mat-dialog-title>{{ 'labels.buttons.Waive Charge' | translate }}</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>{{ 'labels.text.Are you sure you want to waive charge with id' | translate }}: {{ data.id }} ?</p>
|
||||
</div>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
|
||||
<button mat-raised-button color="primary" [mat-dialog-close]="{ confirm: true }">
|
||||
{{ 'labels.buttons.Confirm' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, inject } from '@angular/core';
|
||||
import {
|
||||
MatDialogRef,
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
} from '@angular/material/dialog';
|
||||
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Waive charge dialog component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-waive-charge-dialog',
|
||||
templateUrl: './waive-charge-dialog.component.html',
|
||||
styleUrls: ['./waive-charge-dialog.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
CdkScrollable,
|
||||
MatDialogContent,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
]
|
||||
})
|
||||
export class WaiveChargeDialogComponent {
|
||||
dialogRef = inject<MatDialogRef<WaiveChargeDialogComponent>>(MatDialogRef);
|
||||
data = inject(MAT_DIALOG_DATA);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="tab-container mat-typography">
|
||||
<mifosx-entity-datatable-tab
|
||||
entityType="CurrentAccounts"
|
||||
[entityId]="entityId"
|
||||
[multiRowDatatableFlag]="multiRowDatatableFlag"
|
||||
[entityDatatable]="entityDatatable"
|
||||
>
|
||||
</mifosx-entity-datatable-tab>
|
||||
</div>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { EntityDatatableTabComponent } from '../../../shared/tabs/entity-datatable-tab/entity-datatable-tab.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Current Account Datatable Tabs Component
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-datatable-tabs',
|
||||
templateUrl: './datatable-tabs.component.html',
|
||||
styleUrls: ['./datatable-tabs.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
EntityDatatableTabComponent
|
||||
]
|
||||
})
|
||||
export class DatatableTabsComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
|
||||
entityId: string;
|
||||
/** Current Account Datatable */
|
||||
entityDatatable: any;
|
||||
/** Multi Row Datatable Flag */
|
||||
multiRowDatatableFlag: boolean;
|
||||
|
||||
/**
|
||||
* Fetches Current Account and datatables data from `resolve`
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
*/
|
||||
constructor() {
|
||||
this.entityId = this.route.parent.parent.snapshot.paramMap.get('currentAccountId');
|
||||
|
||||
this.route.data.subscribe((data: { currentAccountDatatable: any }) => {
|
||||
this.entityDatatable = data.currentAccountDatatable;
|
||||
this.multiRowDatatableFlag = this.entityDatatable.columnHeaders[0].columnName === 'id' ? true : false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
@if (isLoading) {
|
||||
<div class="loader">
|
||||
<div class="loading"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!isLoading) {
|
||||
<div class="current-account-tables layout-row gap-2percent">
|
||||
<div class="flex-49 layout-column gap-4percent">
|
||||
@if (!currentAccountData.status.rejected && !currentAccountData.status.submittedAndPendingApproval) {
|
||||
<div>
|
||||
<h4 class="table-headers">{{ 'labels.heading.Current Account Details' | translate }}</h4>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.External Id' | translate }}</td>
|
||||
@if (currentAccountData.externalId) {
|
||||
<td>
|
||||
<mifosx-external-identifier
|
||||
externalId="{{ currentAccountData.externalId }}"
|
||||
></mifosx-external-identifier>
|
||||
</td>
|
||||
}
|
||||
@if (!currentAccountData.externalId) {
|
||||
<td>{{ 'labels.inputs.Not Provided' | translate }}</td>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Activated On' | translate }}</td>
|
||||
<td>
|
||||
{{
|
||||
currentAccountData.timeline.activatedOnDate
|
||||
? (currentAccountData.timeline.activatedOnDate | dateFormat)
|
||||
: 'Not Activated'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Field Officer' | translate }}</td>
|
||||
<td>{{ currentAccountData.fieldOfficerName ? currentAccountData.fieldOfficerName : 'Unassigned' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Currency' | translate }}</td>
|
||||
<td>{{ currentAccountData.currency.name }} [{{ currentAccountData.currency.code }}]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Nominal Interest Rate' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
@if (currentAccountData.nominalAnnualInterestRate) {
|
||||
<span>{{ currentAccountData.nominalAnnualInterestRate | formatNumber }} %</span>
|
||||
}
|
||||
@if (!currentAccountData.nominalAnnualInterestRate) {
|
||||
<span>0 %</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
} @else {
|
||||
<div>
|
||||
<h4 class="table-headers">{{ 'labels.heading.Current Account Details' | translate }}</h4>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Field Officer' | translate }}</td>
|
||||
<td>{{ currentAccountData.fieldOfficerName ? currentAccountData.fieldOfficerName : 'Unassigned' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Balance' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{ currentAccountData.summary.accountBalance | currency: currency.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
@if (!currentAccountData.status.rejected && !currentAccountData.status.submittedAndPendingApproval) {
|
||||
<div>
|
||||
<h4 class="table-headers">{{ 'labels.heading.Performance History' | translate }}</h4>
|
||||
<table>
|
||||
<tbody>
|
||||
@if (currentAccountData.summary.totalDeposits) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Total Deposits' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{ currentAccountData.summary.totalDeposits | currency: currency.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.summary.totalInterestEarned >= 0) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Total Interest Earned' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{
|
||||
currentAccountData.summary.totalInterestEarned
|
||||
| currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (isActive) {
|
||||
<div class="flex-49">
|
||||
<h4 class="table-headers">{{ 'labels.heading.Account Summary' | translate }}</h4>
|
||||
<table>
|
||||
<tbody>
|
||||
@if (currentAccountData.summary.totalWithdrawals) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Total Withdrawls' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{
|
||||
currentAccountData.summary.totalWithdrawals | currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.summary.totalWithdrawalFees) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Withdrawals Fees' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{
|
||||
currentAccountData.summary.totalWithdrawalFees | currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.summary.totalAnnualFees) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Annual Fees' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{ currentAccountData.summary.totalAnnualFees | currency: currency.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.summary.totalInterestEarned >= 0) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Interest Earned' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{
|
||||
currentAccountData.summary.totalInterestEarned | currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.summary.totalInterestPosted) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Interest Posted' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{
|
||||
currentAccountData.summary.totalInterestPosted | currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.summary.interestNotPosted >= 0) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Interest Earned Not Posted' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{
|
||||
currentAccountData.summary.interestNotPosted | currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.summary.totalOverdraftInterestDerived) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Interest On Overdraft' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{
|
||||
currentAccountData.summary.totalOverdraftInterestDerived
|
||||
| currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.summary.interestNotPosted < 0) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Overdraft Interest Not Posted' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{
|
||||
currentAccountData.summary.interestNotPosted | currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.nominalAnnualInterestRate) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Nominal Interest Rate' | translate }}</td>
|
||||
<td class="r-amount">{{ currentAccountData.nominalAnnualInterestRate | formatNumber }} %</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Interest Compounding Period' | translate }}</td>
|
||||
<td>{{ currentAccountData.interestCompoundingPeriodType.value | translateKey: 'catalogs' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Interest Posting Period' | translate }}</td>
|
||||
<td>{{ currentAccountData.interestPostingPeriodType.value | translateKey: 'catalogs' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Interest Calculated Using' | translate }}</td>
|
||||
<td>{{ currentAccountData.interestCalculationType.value | translateKey: 'catalogs' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Days in Year' | translate }}</td>
|
||||
<td>{{ currentAccountData.interestCalculationDaysInYearType.value | translateKey: 'catalogs' }}</td>
|
||||
</tr>
|
||||
@if (currentAccountData.withdrawalFee) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Withdrawal Fee' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{ currentAccountData.withdrawalFee.amount | currency: currency.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.lastActiveTransactionDate) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Last Active Transaction Date' | translate }}</td>
|
||||
<td>{{ currentAccountData.lastActiveTransactionDate | dateFormat }}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (!(currentAccountData.subStatus.id === 0)) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Substatus' | translate }}</td>
|
||||
<td>{{ currentAccountData.subStatus.value }}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.daysToInactive) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Days to Inactive' | translate }}</td>
|
||||
<td>{{ currentAccountData.daysToInactive }}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.daysToDormancy) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Days to Dormancy' | translate }}</td>
|
||||
<td>{{ currentAccountData.daysToDormancy }}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.daysToEscheat) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Days to Escheat' | translate }}</td>
|
||||
<td>{{ currentAccountData.daysToEscheat }}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.annualFee) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Annual Fee' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{ currentAccountData.annualFee.amount | currency: currency.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.allowOverdraft) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Overdraft Limit' | translate }}</td>
|
||||
<td class="r-amount">
|
||||
{{ currentAccountData.overdraftLimit | currency: currency.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.allowOverdraft) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate }}</td>
|
||||
<td>
|
||||
{{
|
||||
currentAccountData.minOverdraftForInterestCalculation
|
||||
| currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.minBalanceForInterestCalculation) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Min Balance Required for Interest Calculation' | translate }}</td>
|
||||
<td>{{ currentAccountData.minBalanceForInterestCalculation | formatNumber }}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.minRequiredBalance) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Minimum Required Balance' | translate }}</td>
|
||||
<td>{{ currentAccountData.minRequiredBalance | formatNumber }}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.enforceMinRequiredBalance) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Enforce Minimum Required Balance' | translate }}</td>
|
||||
<td>{{ currentAccountData.enforceMinRequiredBalance | yesNo }}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.summary.lastInterestCalculationDate) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Interest Recalculation Date' | translate }}</td>
|
||||
<td>{{ currentAccountData.summary.lastInterestCalculationDate | dateFormat }}</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.onHoldFunds) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.On Hold Funds' | translate }}</td>
|
||||
<td>
|
||||
<a *mifosxHasPermission="'READ_CURRENTACCOUNT'">{{
|
||||
currentAccountData.onHoldFunds | currency: currency.code : 'symbol-narrow' : '1.2-2'
|
||||
}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (currentAccountData.withHoldTax) {
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Withhold Tax Group' | translate }}</td>
|
||||
<td>{{ currentAccountData.taxGroup.name }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="flex-49">
|
||||
<h4 class="table-headers">{{ 'labels.heading.Account Summary' | translate }}</h4>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.External Id' | translate }}</td>
|
||||
@if (currentAccountData.externalId) {
|
||||
<td>
|
||||
<mifosx-external-identifier
|
||||
externalId="{{ currentAccountData.externalId }}"
|
||||
></mifosx-external-identifier>
|
||||
</td>
|
||||
}
|
||||
@if (!currentAccountData.externalId) {
|
||||
<td>{{ 'labels.inputs.Not Provided' | translate }}</td>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Activated On' | translate }}</td>
|
||||
<td>
|
||||
{{
|
||||
currentAccountData.timeline.activatedOnDate
|
||||
? (currentAccountData.timeline.activatedOnDate | dateFormat)
|
||||
: 'Not Activated'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'labels.inputs.Currency' | translate }}</td>
|
||||
<td>{{ currentAccountData.currency.name }} [{{ currentAccountData.currency.code }}]</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$bar: 160px;
|
||||
$foo: $bar * 8;
|
||||
$color: #e2e4ec;
|
||||
|
||||
.loader {
|
||||
margin-bottom: 39px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.sidenav {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
0% {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
16.7% {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
33.3% {
|
||||
left: calc($foo / 2);
|
||||
}
|
||||
|
||||
50% {
|
||||
left: $foo - $bar;
|
||||
}
|
||||
|
||||
66.7% {
|
||||
left: calc($foo / 2);
|
||||
}
|
||||
|
||||
83.3% {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes grow {
|
||||
0% {
|
||||
width: $bar;
|
||||
}
|
||||
|
||||
16.7% {
|
||||
width: calc($foo / 2);
|
||||
}
|
||||
|
||||
33.3% {
|
||||
width: calc($foo / 2);
|
||||
}
|
||||
|
||||
50% {
|
||||
width: $bar;
|
||||
}
|
||||
|
||||
66.7% {
|
||||
width: calc($foo / 2);
|
||||
}
|
||||
|
||||
83.3% {
|
||||
width: calc($foo / 2);
|
||||
}
|
||||
|
||||
100% {
|
||||
width: $bar;
|
||||
}
|
||||
}
|
||||
|
||||
.general-tab-container {
|
||||
padding: 16px 20px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
background-color: var(--background-color, #f8f9fa);
|
||||
color: var(--text-color, #333);
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
color 0.3s ease;
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
padding: 16px 0;
|
||||
position: relative;
|
||||
border-bottom: 1px solid var(--border-color, #e9ecef);
|
||||
|
||||
.header-title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px;
|
||||
line-height: 1.2;
|
||||
color: var(--primary-color, #007bff);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-subtitle {
|
||||
font-size: 0.95rem;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
color: var(--text-secondary, #6c757d);
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.account-info-card {
|
||||
background: var(--card-background, #fff);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
transition:
|
||||
box-shadow 0.3s ease,
|
||||
background-color 0.3s ease;
|
||||
|
||||
.card-header {
|
||||
padding: 16px 20px;
|
||||
background: var(--card-header-bg, #f8f9fa);
|
||||
border-bottom: 1px solid var(--border-color, #e9ecef);
|
||||
position: relative;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid var(--border-light, #f1f3f4);
|
||||
position: relative;
|
||||
min-height: 24px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary, #6c757d);
|
||||
flex: 0 0 auto;
|
||||
margin-right: 16px;
|
||||
white-space: nowrap;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 600;
|
||||
color: var(--text-color, #333);
|
||||
text-align: right;
|
||||
flex: 1 1 auto;
|
||||
line-height: 1.4;
|
||||
|
||||
&.currency {
|
||||
color: var(--success-color, #28a745);
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dark mode styles
|
||||
[data-theme='dark'] .general-tab-container,
|
||||
.dark-theme .general-tab-container {
|
||||
--background-color: #1a1a1a;
|
||||
--text-color: #e9ecef;
|
||||
--text-secondary: #adb5bd;
|
||||
--primary-color: #4dabf7;
|
||||
--card-background: #2d3748;
|
||||
--card-header-bg: #374151;
|
||||
--border-color: #4a5568;
|
||||
--border-light: #2d3748;
|
||||
--success-color: #68d391;
|
||||
}
|
||||
|
||||
// Responsive adjustments for mobile
|
||||
@media (width <= 768px) {
|
||||
.general-tab-container {
|
||||
padding: 12px 16px;
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
padding: 12px 0;
|
||||
|
||||
.header-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.header-subtitle {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.account-info-card {
|
||||
.card-header {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 16px;
|
||||
|
||||
.info-row {
|
||||
padding: 8px 0;
|
||||
|
||||
.label {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Currency } from 'app/shared/models/general.model';
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
import { ExternalIdentifierComponent } from '../../../shared/external-identifier/external-identifier.component';
|
||||
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
|
||||
import { FormatNumberPipe } from '../../../pipes/format-number.pipe';
|
||||
import { YesnoPipe } from '../../../pipes/yesno.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-general-tab',
|
||||
templateUrl: './general-tab.component.html',
|
||||
styleUrls: ['./general-tab.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
ExternalIdentifierComponent,
|
||||
CurrencyPipe,
|
||||
DateFormatPipe,
|
||||
FormatNumberPipe,
|
||||
YesnoPipe
|
||||
]
|
||||
})
|
||||
export class GeneralTabComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
|
||||
isLoading = true;
|
||||
isActive = false;
|
||||
entityType: string;
|
||||
|
||||
currentAccountData: any;
|
||||
currency: Currency;
|
||||
|
||||
constructor() {
|
||||
this.route.parent.parent.data.subscribe((data: { currentAccountData: any }) => {
|
||||
this.currentAccountData = data.currentAccountData;
|
||||
this.currency = this.currentAccountData.currency;
|
||||
this.isLoading = false;
|
||||
const status = this.currentAccountData.status.value;
|
||||
this.isActive = status === 'Active';
|
||||
});
|
||||
if (this.router.url.includes('clients')) {
|
||||
this.entityType = 'Client';
|
||||
} else if (this.router.url.includes('groups')) {
|
||||
this.entityType = 'Group';
|
||||
} else if (this.router.url.includes('centers')) {
|
||||
this.entityType = 'Center';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<mifosx-entity-notes-tab
|
||||
[entityId]="entityId"
|
||||
[entityNotes]="entityNotes"
|
||||
[callbackAdd]="addNote"
|
||||
[callbackEdit]="editNote"
|
||||
[callbackDelete]="deleteNote"
|
||||
>
|
||||
</mifosx-entity-notes-tab>
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AuthenticationService } from 'app/core/authentication/authentication.service';
|
||||
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
|
||||
import { EntityNotesTabComponent } from '../../../shared/tabs/entity-notes-tab/entity-notes-tab.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-notes-tab',
|
||||
templateUrl: './notes-tab.component.html',
|
||||
styleUrls: ['./notes-tab.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
EntityNotesTabComponent
|
||||
]
|
||||
})
|
||||
export class NotesTabComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private currentAccountsService = inject(CurrentAccountsService);
|
||||
private authenticationService = inject(AuthenticationService);
|
||||
|
||||
entityId: string;
|
||||
username: string;
|
||||
entityNotes: any;
|
||||
|
||||
constructor() {
|
||||
const savedCredentials = this.authenticationService.getCredentials();
|
||||
this.username = savedCredentials.username;
|
||||
this.entityId = this.route.parent.snapshot.params['currentAccountId'];
|
||||
this.route.data.subscribe((data: { currentAccountNotes: any }) => {
|
||||
this.entityNotes = data.currentAccountNotes;
|
||||
});
|
||||
}
|
||||
|
||||
addNote(noteContent: any) {
|
||||
this.currentAccountsService.createCurrentAccountNote(this.entityId, noteContent).subscribe((response: any) => {
|
||||
this.entityNotes.push({
|
||||
id: response.resourceId,
|
||||
createdByUsername: this.username,
|
||||
createdOn: new Date(),
|
||||
note: noteContent.note
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
editNote(noteId: string, noteContent: any, index: number) {
|
||||
this.currentAccountsService.editCurrentAccountNote(this.entityId, noteId, noteContent).subscribe(() => {
|
||||
this.entityNotes[index].note = noteContent.note;
|
||||
});
|
||||
}
|
||||
|
||||
deleteNote(noteId: string, index: number) {
|
||||
this.currentAccountsService.deleteCurrentAccountNote(this.entityId, noteId).subscribe(() => {
|
||||
this.entityNotes.splice(index, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="passbook-container">
|
||||
<!-- Date Range Filter -->
|
||||
<div class="passbook-filter-bar">
|
||||
<form [formGroup]="dateRangeForm" class="passbook-filter-form">
|
||||
<mat-form-field appearance="outline" class="passbook-date-field" (click)="fromDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.From Date' | translate }}</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="fromDatePicker" formControlName="fromDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #fromDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="passbook-date-field" (click)="toDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.To Date' | translate }}</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="toDatePicker" formControlName="toDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #toDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="!dateRangeForm.valid"
|
||||
(click)="filterTransactions()"
|
||||
>
|
||||
<mat-icon>filter_list</mat-icon>
|
||||
{{ 'labels.buttons.Filter' | translate }}
|
||||
</button>
|
||||
|
||||
@if (isFiltered) {
|
||||
<button mat-stroked-button type="button" (click)="clearFilter()">
|
||||
<mat-icon>clear</mat-icon>
|
||||
{{ 'labels.buttons.Clear' | translate }}
|
||||
</button>
|
||||
}
|
||||
|
||||
<span class="passbook-filter-spacer"></span>
|
||||
|
||||
<button mat-raised-button color="accent" type="button" (click)="printPassbook()">
|
||||
<mat-icon>print</mat-icon>
|
||||
{{ 'labels.buttons.Print Passbook' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Passbook Preview (Printable Content) -->
|
||||
<div class="passbook-preview" #passbookContent>
|
||||
<div class="passbook-header">
|
||||
<h2>{{ 'labels.heading.Current Account Passbook' | translate }}</h2>
|
||||
<h3>{{ productName }}</h3>
|
||||
</div>
|
||||
|
||||
<!-- Account Info -->
|
||||
<div class="passbook-info-grid">
|
||||
<div class="passbook-info-item">
|
||||
<div class="label">{{ 'labels.inputs.Account Number' | translate }}</div>
|
||||
<div class="value">{{ accountNo }}</div>
|
||||
</div>
|
||||
<div class="passbook-info-item">
|
||||
<div class="label">{{ 'labels.inputs.Account Holder' | translate }}</div>
|
||||
<div class="value">{{ clientName }}</div>
|
||||
</div>
|
||||
<div class="passbook-info-item">
|
||||
<div class="label">{{ 'labels.inputs.Current Account Product' | translate }}</div>
|
||||
<div class="value">{{ productName }}</div>
|
||||
</div>
|
||||
<div class="passbook-info-item">
|
||||
<div class="label">{{ 'labels.inputs.Opening Date' | translate }}</div>
|
||||
<div class="value">{{ activatedOnDate | dateFormat }}</div>
|
||||
</div>
|
||||
<div class="passbook-info-item">
|
||||
<div class="label">{{ 'labels.inputs.Status' | translate }}</div>
|
||||
<div class="value">{{ status }}</div>
|
||||
</div>
|
||||
<div class="passbook-info-item">
|
||||
<div class="label">{{ 'labels.inputs.Interest Rate' | translate }}</div>
|
||||
<div class="value">{{ interestRate }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Balance Summary -->
|
||||
<div class="passbook-balance-row">
|
||||
<div class="balance-item">
|
||||
<span>{{ 'labels.inputs.Current Balance' | translate }}: </span>
|
||||
<b>{{ accountBalance | currency: currency?.code : 'symbol-narrow' : '1.2-2' }}</b>
|
||||
</div>
|
||||
<div class="balance-item">
|
||||
<span>{{ 'labels.inputs.Available Balance' | translate }}: </span>
|
||||
<b>{{ availableBalance | currency: currency?.code : 'symbol-narrow' : '1.2-2' }}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transactions Table -->
|
||||
@if (filteredTransactions.length > 0) {
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>{{ 'labels.inputs.Date' | translate }}</th>
|
||||
<th>{{ 'labels.inputs.Description' | translate }}</th>
|
||||
<th class="text-right">{{ 'labels.inputs.Debit' | translate }}</th>
|
||||
<th class="text-right">{{ 'labels.inputs.Credit' | translate }}</th>
|
||||
<th class="text-right">{{ 'labels.inputs.Balance' | translate }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (txn of filteredTransactions; track txn.id; let i = $index) {
|
||||
<tr [class.reversed]="txn.reversed">
|
||||
<td>{{ i + 1 }}</td>
|
||||
<td>{{ txn.date | dateFormat }}</td>
|
||||
<td>{{ txn.transactionType.value }}</td>
|
||||
<td class="text-right">
|
||||
@if (isDebit(txn.transactionType)) {
|
||||
{{ txn.amount | currency: currency?.code : 'symbol-narrow' : '1.2-2' }}
|
||||
}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
@if (!isDebit(txn.transactionType)) {
|
||||
{{ txn.amount | currency: currency?.code : 'symbol-narrow' : '1.2-2' }}
|
||||
}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ txn.runningBalance | currency: currency?.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
} @else {
|
||||
<div class="passbook-no-data">
|
||||
<mat-icon>info_outline</mat-icon>
|
||||
<span>{{ 'labels.text.No transactions found for the selected date range.' | translate }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="passbook-footer">
|
||||
{{ 'labels.text.Generated on' | translate }}: {{ maxDate | dateFormat }}
|
||||
|
|
||||
{{ 'labels.text.Total Transactions' | translate }}: {{ filteredTransactions.length }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.passbook-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
// Filter Bar
|
||||
.passbook-filter-bar {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.passbook-filter-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.passbook-date-field {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.passbook-filter-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
// Passbook Preview
|
||||
.passbook-preview {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.passbook-header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #1976d2;
|
||||
padding-bottom: 12px;
|
||||
|
||||
h2 {
|
||||
color: #1976d2;
|
||||
font-size: 20px;
|
||||
margin: 0 0 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.passbook-info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px 20px;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.passbook-info-item {
|
||||
.label {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #888;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.passbook-balance-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
padding: 10px 14px;
|
||||
background: #e3f2fd;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
|
||||
.balance-item {
|
||||
b {
|
||||
color: #1976d2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transactions Table
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
|
||||
th {
|
||||
background: #1976d2;
|
||||
color: #fff;
|
||||
padding: 8px 10px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.reversed {
|
||||
text-decoration: line-through;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.passbook-no-data {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 32px;
|
||||
color: #888;
|
||||
font-size: 14px;
|
||||
|
||||
mat-icon {
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
|
||||
.passbook-footer {
|
||||
margin-top: 16px;
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
// Responsive
|
||||
@media (width <= 768px) {
|
||||
.passbook-info-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.passbook-balance-row {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.passbook-filter-form {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.passbook-date-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.passbook-filter-spacer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 480px) {
|
||||
.passbook-info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
// Print-specific styles
|
||||
@media print {
|
||||
.passbook-filter-bar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.passbook-preview {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, OnDestroy, inject, ElementRef, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { UntypedFormBuilder, Validators } from '@angular/forms';
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
/** Custom Services */
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { Currency } from 'app/shared/models/general.model';
|
||||
import {
|
||||
CurrentAccountTransaction,
|
||||
CurrentAccountTransactionType
|
||||
} from 'app/current-accounts/models/current-account-transaction.model';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-passbook-tab',
|
||||
templateUrl: './passbook-tab.component.html',
|
||||
styleUrls: ['./passbook-tab.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatIcon,
|
||||
FaIconComponent,
|
||||
DateFormatPipe,
|
||||
CurrencyPipe
|
||||
]
|
||||
})
|
||||
export class PassbookTabComponent implements OnInit, OnDestroy {
|
||||
private route = inject(ActivatedRoute);
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private settingsService = inject(SettingsService);
|
||||
private dateUtils = inject(Dates);
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
@ViewChild('passbookContent') passbookContent: ElementRef;
|
||||
|
||||
/** Account data */
|
||||
accountNo: string = '';
|
||||
clientName: string = '';
|
||||
productName: string = '';
|
||||
currency: Currency | null = null;
|
||||
status: string = '';
|
||||
interestRate: number = 0;
|
||||
accountBalance: number = 0;
|
||||
availableBalance: number = 0;
|
||||
activatedOnDate: any = null;
|
||||
|
||||
/** Transactions */
|
||||
allTransactions: CurrentAccountTransaction[] = [];
|
||||
filteredTransactions: CurrentAccountTransaction[] = [];
|
||||
|
||||
/** Date range form */
|
||||
minDate = new Date(2000, 0, 1);
|
||||
maxDate = new Date();
|
||||
dateRangeForm: any;
|
||||
isFiltered = false;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.maxDate = this.settingsService.businessDate;
|
||||
|
||||
this.route.parent.data.pipe(takeUntil(this.destroy$)).subscribe((data: { currentAccountData: any }) => {
|
||||
const account = data.currentAccountData;
|
||||
this.accountNo = account.accountNo || '';
|
||||
this.clientName = account.clientName || account.groupName || '';
|
||||
this.productName = account.currentAccountProductName || '';
|
||||
this.currency = account.currency || null;
|
||||
this.status = account.status?.value || '';
|
||||
this.interestRate = account.nominalAnnualInterestRate || 0;
|
||||
this.accountBalance = account.summary?.accountBalance || 0;
|
||||
this.availableBalance = account.summary?.availableBalance || 0;
|
||||
this.activatedOnDate = account.timeline?.activatedOnDate || null;
|
||||
this.allTransactions = account.transactions || [];
|
||||
this.filteredTransactions = [...this.allTransactions];
|
||||
});
|
||||
|
||||
this.dateRangeForm = this.formBuilder.group({
|
||||
fromDate: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
toDate: [
|
||||
'',
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
isDebit(transactionType: CurrentAccountTransactionType): boolean {
|
||||
return (
|
||||
transactionType.withdrawal === true ||
|
||||
transactionType.feeDeduction === true ||
|
||||
transactionType.overdraftInterest === true ||
|
||||
transactionType.withholdTax === true
|
||||
);
|
||||
}
|
||||
|
||||
filterTransactions(): void {
|
||||
const fromDate = this.dateRangeForm.value.fromDate;
|
||||
const toDate = this.dateRangeForm.value.toDate;
|
||||
|
||||
if (!fromDate || !toDate) return;
|
||||
|
||||
const from = new Date(fromDate).getTime();
|
||||
const to = new Date(toDate).getTime();
|
||||
|
||||
this.filteredTransactions = this.allTransactions.filter((txn: CurrentAccountTransaction) => {
|
||||
const txnDate = this.getTransactionDateAsTime(txn.date);
|
||||
return txnDate >= from && txnDate <= to;
|
||||
});
|
||||
this.isFiltered = true;
|
||||
}
|
||||
|
||||
clearFilter(): void {
|
||||
this.dateRangeForm.reset();
|
||||
this.filteredTransactions = [...this.allTransactions];
|
||||
this.isFiltered = false;
|
||||
}
|
||||
|
||||
private getTransactionDateAsTime(date: any): number {
|
||||
if (Array.isArray(date) && date.length >= 3) {
|
||||
return new Date(date[0], date[1] - 1, date[2]).getTime();
|
||||
}
|
||||
return new Date(date).getTime();
|
||||
}
|
||||
|
||||
printPassbook(): void {
|
||||
const content = this.passbookContent?.nativeElement?.innerHTML;
|
||||
if (!content) return;
|
||||
|
||||
const printWindow = window.open('', 'Passbook', 'height=700,width=900');
|
||||
printWindow.document.write(`
|
||||
<html>
|
||||
<head>
|
||||
<title>Current Account Passbook - ${this.accountNo}</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Segoe UI', Arial, sans-serif; padding: 20px; color: #333; }
|
||||
.passbook-header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #1976d2; padding-bottom: 12px; }
|
||||
.passbook-header h2 { color: #1976d2; font-size: 18px; margin-bottom: 4px; }
|
||||
.passbook-header h3 { font-size: 14px; font-weight: 400; color: #666; }
|
||||
.passbook-info-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px 16px; margin-bottom: 16px; padding: 10px; background: #f5f5f5; border-radius: 4px; }
|
||||
.passbook-info-item { font-size: 12px; }
|
||||
.passbook-info-item .label { font-weight: 600; color: #666; text-transform: uppercase; font-size: 10px; }
|
||||
.passbook-info-item .value { color: #333; }
|
||||
.passbook-balance-row { display: flex; justify-content: space-between; margin-bottom: 16px; padding: 8px 10px; background: #e3f2fd; border-radius: 4px; font-size: 13px; }
|
||||
.passbook-balance-row .balance-item b { color: #1976d2; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 11px; }
|
||||
th { background: #1976d2; color: #fff; padding: 6px 8px; text-align: left; font-weight: 600; }
|
||||
td { padding: 5px 8px; border-bottom: 1px solid #e0e0e0; }
|
||||
tr:nth-child(even) { background: #fafafa; }
|
||||
.text-right { text-align: right; }
|
||||
.passbook-footer { margin-top: 16px; font-size: 10px; color: #999; text-align: center; border-top: 1px solid #e0e0e0; padding-top: 8px; }
|
||||
.reversed { text-decoration: line-through; color: #999; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${content}
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
printWindow.document.close();
|
||||
printWindow.focus();
|
||||
printWindow.print();
|
||||
printWindow.close();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="tab-container mat-typography">
|
||||
<div class="m-b-10">
|
||||
<h3>{{ 'labels.heading.All Standing Instructions' | translate }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="mat-elevation-z1 m-b-25">
|
||||
<table mat-table #instructionsTable [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="client">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Client' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let instruction">
|
||||
{{ instruction.fromClient.displayName }}-{{ instruction.fromClient.id }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="fromAccount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.From Account' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let instruction">
|
||||
{{ instruction.fromAccount.accountNo }} ({{ instruction.fromAccountType.value }})
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="beneficiary">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Beneficiary' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let instruction">{{ instruction.toClient.displayName }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="toAccount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.To Account' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let instruction">
|
||||
{{ instruction.toAccount.accountNo }} ({{ instruction.toAccountType.value }})
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let instruction">{{ instruction.instructionType.value }}/{{ instruction.amount }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="validity">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Validity' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let instruction">
|
||||
{{ instruction.validFrom | dateFormat }} {{ 'labels.inputs.To' | translate }}
|
||||
{{ instruction.validTill | dateFormat }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let instruction">
|
||||
@if (instruction.status.value !== 'Deleted') {
|
||||
<span>
|
||||
<button
|
||||
class="account-action-button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
matTooltip="{{ 'tooltips.Edit Standing Instruction' | translate }}"
|
||||
*mifosxHasPermission="'UPDATE_STANDINGINSTRUCTION'"
|
||||
>
|
||||
<i class="fa fa-edit"></i>
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
@if (instruction.status.value !== 'Deleted') {
|
||||
<span>
|
||||
<button
|
||||
class="account-action-button"
|
||||
mat-raised-button
|
||||
color="warn"
|
||||
matTooltip="{{ 'tooltips.Delete Standing Instruction' | translate }}"
|
||||
*mifosxHasPermission="'DELETE_STANDINGINSTRUCTION'"
|
||||
(click)="deleteStandingInstruction(instruction.id)"
|
||||
>
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
<button
|
||||
class="account-action-button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
matTooltip="{{ 'tooltips.View Standing Instruction' | translate }}"
|
||||
*mifosxHasPermission="'READ_STANDINGINSTRUCTION'"
|
||||
>
|
||||
<i class="fa fa-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.tab-container {
|
||||
padding: 1%;
|
||||
margin: 1%;
|
||||
|
||||
h3 {
|
||||
margin: 1% auto;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
.account-action-button {
|
||||
min-width: 26px;
|
||||
padding: 0 6px;
|
||||
margin: 4px;
|
||||
line-height: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, ViewChild, inject } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import {
|
||||
MatTableDataSource,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
/** Custom Services */
|
||||
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
|
||||
import { AccountTransfersService } from 'app/account-transfers/account-transfers.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
|
||||
/** Dialog Components */
|
||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Current Account Standing Instructions Tab
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-standing-instructions-tab',
|
||||
templateUrl: './standing-instructions-tab.component.html',
|
||||
styleUrls: ['./standing-instructions-tab.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatTooltip,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
DateFormatPipe
|
||||
]
|
||||
})
|
||||
export class StandingInstructionsTabComponent implements OnInit {
|
||||
private route = inject(ActivatedRoute);
|
||||
private currentAccountsService = inject(CurrentAccountsService);
|
||||
private dialog = inject(MatDialog);
|
||||
private accountTransfersService = inject(AccountTransfersService);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
/** Current Account Data */
|
||||
currentAccountData: any;
|
||||
/** Instructions Data */
|
||||
instructionsData: any[];
|
||||
/** Data source for instructions table. */
|
||||
dataSource = new MatTableDataSource();
|
||||
/** Columns to be displayed in instructions table. */
|
||||
displayedColumns: string[] = [
|
||||
'client',
|
||||
'fromAccount',
|
||||
'beneficiary',
|
||||
'toAccount',
|
||||
'amount',
|
||||
'validity',
|
||||
'actions'
|
||||
];
|
||||
|
||||
/** Instruction Table Reference */
|
||||
@ViewChild('instructionsTable', { static: true }) instructionTableRef: MatTable<Element>;
|
||||
|
||||
/**
|
||||
* Retrieves Current Account Data from `resolve`.
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
* @param {SettingsService} settingsService Setting service
|
||||
*/
|
||||
constructor() {
|
||||
this.route.parent.data.subscribe((data: { currentAccountData: any }) => {
|
||||
this.currentAccountData = data.currentAccountData;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.getStandingInstructions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves standing instructions and initializes instructions table.
|
||||
*/
|
||||
getStandingInstructions() {
|
||||
const clientId = this.currentAccountData.clientId;
|
||||
const clientName = this.currentAccountData.clientName;
|
||||
const accountId = this.currentAccountData.id;
|
||||
const locale = this.settingsService.language.code;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
this.currentAccountsService
|
||||
.getStandingInstructions(clientId, clientName, accountId, locale, dateFormat)
|
||||
.subscribe((response: any) => {
|
||||
this.instructionsData = response.pageItems;
|
||||
this.dataSource.data = this.instructionsData;
|
||||
this.instructionTableRef.renderRows();
|
||||
});
|
||||
}
|
||||
|
||||
deleteStandingInstruction(instructionId: any) {
|
||||
const deleteStandingInstructionDialogRef = this.dialog.open(DeleteDialogComponent, {
|
||||
data: { deleteContext: `standing instruction id: ${instructionId}` }
|
||||
});
|
||||
deleteStandingInstructionDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.delete) {
|
||||
this.accountTransfersService.deleteStandingInstrucions(instructionId).subscribe(() => {});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<form class="m-t-20 layout-column" [formGroup]="transactionsReportForm" (ngSubmit)="generate()">
|
||||
<div class="layout-row layout-align-center gap-3percent">
|
||||
<mat-form-field class="flex-30" (click)="fromDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.From Date' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="fromDatePicker"
|
||||
required
|
||||
formControlName="fromDate"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #fromDatePicker></mat-datepicker>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.From Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-30" (click)="toDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.To Date' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="toDatePicker"
|
||||
required
|
||||
formControlName="toDate"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #toDatePicker></mat-datepicker>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.To Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="generate-button layout-row layout-xs-column layout-align-center gap-5percent">
|
||||
<button type="button" mat-raised-button [routerLink]="['../']">{{ 'labels.buttons.Cancel' | translate }}</button>
|
||||
<button mat-raised-button color="primary" [disabled]="!transactionsReportForm.valid">
|
||||
<fa-icon icon="cogs" class="m-r-10"></fa-icon>{{ 'labels.buttons.Generate Report' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@if (!hideOutput) {
|
||||
<iframe [src]="pentahoUrl" frameborder="0" width="100%" height="750px;"></iframe>
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.generate-button {
|
||||
max-height: 2%;
|
||||
padding: 1% 0 2% 6%;
|
||||
align-self: center;
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
|
||||
/** Custom Services */
|
||||
import { ReportsService } from 'app/reports/reports.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Export Client Current Account Transactions Component
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-export-transactions',
|
||||
templateUrl: './export-transactions.component.html',
|
||||
styleUrls: ['./export-transactions.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent
|
||||
]
|
||||
})
|
||||
export class ExportTransactionsComponent implements OnInit {
|
||||
private sanitizer = inject(DomSanitizer);
|
||||
private reportsService = inject(ReportsService);
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dateUtils = inject(Dates);
|
||||
private route = inject(ActivatedRoute);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
/** Minimum date allowed. */
|
||||
minDate = new Date(2000, 0, 1);
|
||||
/** Maximum date allowed. */
|
||||
maxDate = new Date();
|
||||
/** Transactions Report Form */
|
||||
transactionsReportForm: any;
|
||||
/** substitute for resolver */
|
||||
hideOutput = true;
|
||||
/** trusted resource url for pentaho output */
|
||||
pentahoUrl: any;
|
||||
/** Current Account Id */
|
||||
currentAccountId: any;
|
||||
|
||||
/**
|
||||
* Fetches current account data from grandparent's `resolve`
|
||||
* @param {DomSanitizer} sanitizer DOM Sanitizer
|
||||
* @param {ReportsService} reportsService Reports Service
|
||||
* @param {FormBuilder} formBuilder Form Builder
|
||||
* @param {Dates} dateUtils Date Utils
|
||||
* @param {ActivatedRoute} route Activated Route
|
||||
* @param {SettingsService} settingsService Settings Service
|
||||
*/
|
||||
constructor() {
|
||||
this.route.parent.parent.data.subscribe((data: { currentAccountData: any }) => {
|
||||
this.currentAccountId = data.currentAccountData.accountNo;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.maxDate = this.settingsService.businessDate;
|
||||
this.createTransactionsReportForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the transactions report form.
|
||||
*/
|
||||
createTransactionsReportForm() {
|
||||
this.transactionsReportForm = this.formBuilder.group({
|
||||
fromDate: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
toDate: [
|
||||
'',
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates client current account transactions report.
|
||||
*/
|
||||
generate() {
|
||||
const data = {
|
||||
'output-type': 'PDF',
|
||||
R_startDate: this.dateUtils.formatDate(
|
||||
this.transactionsReportForm.value.fromDate,
|
||||
this.settingsService.dateFormat
|
||||
),
|
||||
R_endDate: this.dateUtils.formatDate(this.transactionsReportForm.value.toDate, this.settingsService.dateFormat),
|
||||
R_currentAccountId: this.currentAccountId
|
||||
};
|
||||
this.reportsService
|
||||
.getPentahoRunReportData(
|
||||
'Client Current Account Transactions',
|
||||
data,
|
||||
'default',
|
||||
this.settingsService.language.code,
|
||||
this.settingsService.dateFormat
|
||||
)
|
||||
.subscribe((res: any) => {
|
||||
const contentType = res.headers.get('Content-Type');
|
||||
const file = new Blob([res.body], { type: contentType });
|
||||
const filecontent = URL.createObjectURL(file);
|
||||
this.pentahoUrl = this.sanitizer.bypassSecurityTrustResourceUrl(filecontent);
|
||||
this.hideOutput = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<div class="mat-elevation-z1 m-b-25" [hidden]="!accountWithTransactions">
|
||||
<div class="layout-row align-end">
|
||||
@if (checkStatus()) {
|
||||
<div class="action-button m-b-20 layout-row gap-20px">
|
||||
<mat-checkbox [formControl]="hideReversedParam" (click)="hideReversed()" class="accruals">
|
||||
{{ 'labels.inputs.Hide Reversed' | translate }}</mat-checkbox
|
||||
>
|
||||
<mat-checkbox [formControl]="hideAccrualsParam" (click)="hideAccruals()" class="accruals">
|
||||
{{ 'labels.inputs.Hide Accruals' | translate }}</mat-checkbox
|
||||
>
|
||||
<button mat-raised-button color="primary" [routerLink]="['export']">
|
||||
{{ 'labels.buttons.Export' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" matSort>
|
||||
<ng-container matColumnDef="row">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>#</th>
|
||||
<td mat-cell *matCellDef="let transaction; let idx = index" (click)="showTransactions(transaction)">
|
||||
{{ idx + 1 }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Id' | translate }}</th>
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let transaction"
|
||||
class="td-select"
|
||||
[ngClass]="currentAccountTransactionColor(transaction)"
|
||||
(click)="showTransactions(transaction)"
|
||||
>
|
||||
{{ transaction.id }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="date">
|
||||
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
|
||||
{{ 'labels.inputs.Transaction Date' | translate }}
|
||||
</th>
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let transaction"
|
||||
[ngClass]="currentAccountTransactionColor(transaction)"
|
||||
(click)="showTransactions(transaction)"
|
||||
>
|
||||
{{ transaction.date | dateFormat }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="externalId">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.External Id' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let transaction" [ngClass]="currentAccountTransactionColor(transaction)">
|
||||
<mifosx-external-identifier externalId="{{ transaction.externalId }}"></mifosx-external-identifier>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="transactionType">
|
||||
<th mat-header-cell class="center" *matHeaderCellDef>{{ 'labels.inputs.Transaction Type' | translate }}</th>
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let transaction"
|
||||
[ngClass]="currentAccountTransactionColor(transaction)"
|
||||
(click)="showTransactions(transaction)"
|
||||
>
|
||||
{{ transaction.transactionType.value | translateKey: 'catalogs' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="debit">
|
||||
<th mat-header-cell class="r-amount" *matHeaderCellDef>{{ 'labels.inputs.Debit' | translate }}</th>
|
||||
<td
|
||||
mat-cell
|
||||
class="r-amount"
|
||||
*matCellDef="let transaction"
|
||||
[ngClass]="currentAccountTransactionColor(transaction)"
|
||||
(click)="showTransactions(transaction)"
|
||||
>
|
||||
{{
|
||||
isDebit(transaction.transactionType)
|
||||
? (transaction.amount | currency: currency?.code || 'USD' : 'symbol-narrow' : '1.2-2')
|
||||
: 'N/A'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="credit">
|
||||
<th mat-header-cell class="r-amount" *matHeaderCellDef>{{ 'labels.inputs.Credit' | translate }}</th>
|
||||
<td
|
||||
mat-cell
|
||||
class="r-amount"
|
||||
*matCellDef="let transaction"
|
||||
[ngClass]="currentAccountTransactionColor(transaction)"
|
||||
(click)="showTransactions(transaction)"
|
||||
>
|
||||
{{
|
||||
!isDebit(transaction.transactionType)
|
||||
? (transaction.amount | currency: currency?.code || 'USD' : 'symbol-narrow' : '1.2-2')
|
||||
: 'N/A'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="balance">
|
||||
<th mat-header-cell class="r-amount" *matHeaderCellDef>{{ 'labels.inputs.Balance' | translate }}</th>
|
||||
<td
|
||||
mat-cell
|
||||
class="r-amount"
|
||||
*matCellDef="let transaction"
|
||||
[ngClass]="currentAccountTransactionColor(transaction)"
|
||||
(click)="showTransactions(transaction)"
|
||||
>
|
||||
{{ transaction.runningBalance | currency: currency?.code || 'USD' : 'symbol-narrow' : '1.2-2' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell class="center" *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell class="center" *matCellDef="let transaction">
|
||||
<button mat-icon-button [matMenuTriggerFor]="transactionMenu" aria-label="" class="action-button">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #transactionMenu="matMenu">
|
||||
<button mat-menu-item (click)="showTransactions(transaction)">
|
||||
<mat-icon><fa-icon icon="eye" size="sm"></fa-icon></mat-icon>
|
||||
<span>{{ 'labels.text.View Transaction' | translate }}</span>
|
||||
</button>
|
||||
@if (!transaction.reversed) {
|
||||
<span>
|
||||
<button
|
||||
mat-menu-item
|
||||
*mifosxHasPermission="'ADJUSTTRANSACTION_CURRENTACCOUNT'"
|
||||
(click)="undoTransaction(transaction, $event)"
|
||||
>
|
||||
<mat-icon><fa-icon icon="undo" size="sm"></fa-icon></mat-icon>
|
||||
<span>{{ 'tooltips.Undo Transaction' | translate }}</span>
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
<button
|
||||
mat-menu-item
|
||||
class="account-action-button"
|
||||
(click)="routeEdit($event)"
|
||||
[routerLink]="[transaction.id, 'reciept']"
|
||||
>
|
||||
<mat-icon><fa-icon icon="receipt" size="sm"></fa-icon></mat-icon>
|
||||
<span>{{ 'tooltips.View Receipts' | translate }}</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="routeEdit($event)"
|
||||
[routerLink]="['/', 'journal-entry', 'view', 'S' + transaction.id]"
|
||||
>
|
||||
<mat-icon><fa-icon icon="table-cells" size="sm"></fa-icon></mat-icon>
|
||||
<span>{{ 'tooltips.View Journal Entries' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="select-row"></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator [pageSize]="50" [pageSizeOptions]="[50, 100, 200]" showFirstLastButtons></mat-paginator>
|
||||
</div>
|
||||
|
||||
<div class="alert" [hidden]="accountWithTransactions">
|
||||
<div class="message">
|
||||
<i class="fa fa-exclamation-circle alert-check"></i>
|
||||
{{ 'labels.text.No transaction was found' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
@use 'assets/styles/helper';
|
||||
|
||||
.tab-container {
|
||||
padding: 1%;
|
||||
margin: 1%;
|
||||
|
||||
h3 {
|
||||
margin: 1% auto;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
.account-action-button {
|
||||
min-width: 26px;
|
||||
padding: 0 0 3px;
|
||||
margin: 0 10%;
|
||||
line-height: 25px;
|
||||
|
||||
.accruals {
|
||||
padding-top: 1%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select-row:hover {
|
||||
cursor: pointer;
|
||||
font-weight: 800;
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, ViewChild, inject } from '@angular/core';
|
||||
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort, MatSortHeader } from '@angular/material/sort';
|
||||
import { CurrencyPipe, NgClass } from '@angular/common';
|
||||
import {
|
||||
MatTableDataSource,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import {
|
||||
CurrentAccountTransaction,
|
||||
CurrentAccountTransactionType
|
||||
} from 'app/current-accounts/models/current-account-transaction.model';
|
||||
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { UndoTransactionDialogComponent } from '../custom-dialogs/undo-transaction-dialog/undo-transaction-dialog.component';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Currency } from 'app/shared/models/general.model';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatButton, MatIconButton } from '@angular/material/button';
|
||||
import { ExternalIdentifierComponent } from '../../../shared/external-identifier/external-identifier.component';
|
||||
import { MatMenuTrigger, MatMenu, MatMenuItem } from '@angular/material/menu';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Transactions Tab Component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-transactions-tab',
|
||||
templateUrl: './transactions-tab.component.html',
|
||||
styleUrls: ['./transactions-tab.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatCheckbox,
|
||||
MatTable,
|
||||
MatSort,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatSortHeader,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
NgClass,
|
||||
ExternalIdentifierComponent,
|
||||
MatIconButton,
|
||||
MatMenuTrigger,
|
||||
MatIcon,
|
||||
MatMenu,
|
||||
MatMenuItem,
|
||||
FaIconComponent,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
MatPaginator,
|
||||
DateFormatPipe,
|
||||
CurrencyPipe
|
||||
]
|
||||
})
|
||||
export class TransactionsTabComponent implements OnInit {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private currentAccountsService = inject(CurrentAccountsService);
|
||||
private settingsService = inject(SettingsService);
|
||||
private dialog = inject(MatDialog);
|
||||
private dateUtils = inject(Dates);
|
||||
|
||||
/** Current Account Status */
|
||||
status: any;
|
||||
/** Currency */
|
||||
currency: Currency | null = null;
|
||||
/** Transactions Data */
|
||||
transactionsData: CurrentAccountTransaction[] = [];
|
||||
/** Form control to handle accural parameter */
|
||||
hideAccrualsParam: UntypedFormControl;
|
||||
hideReversedParam: UntypedFormControl;
|
||||
/** Columns to be displayed in transactions table. */
|
||||
displayedColumns: string[] = [
|
||||
'row',
|
||||
'id',
|
||||
'date',
|
||||
'externalId',
|
||||
'transactionType',
|
||||
'debit',
|
||||
'credit',
|
||||
'balance',
|
||||
'actions'
|
||||
];
|
||||
/** Data source for transactions table. */
|
||||
dataSource: MatTableDataSource<any>;
|
||||
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
|
||||
@ViewChild(MatSort, { static: true }) sort: MatSort;
|
||||
|
||||
accountWithTransactions = false;
|
||||
|
||||
accountId: string;
|
||||
|
||||
/**
|
||||
* Retrieves current account data from `resolve`.
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
*/
|
||||
constructor() {
|
||||
this.route.parent.parent.data.subscribe((data: { currentAccountData: any }) => {
|
||||
this.transactionsData = data.currentAccountData.transactions;
|
||||
this.status = data.currentAccountData.status.value;
|
||||
this.currency = data.currentAccountData.currency || null;
|
||||
});
|
||||
this.accountId = this.route.parent.parent.snapshot.params['currentAccountId'];
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.hideAccrualsParam = new UntypedFormControl(false);
|
||||
this.hideReversedParam = new UntypedFormControl(false);
|
||||
this.setTransactions();
|
||||
}
|
||||
|
||||
setTransactions(): void {
|
||||
this.dataSource = new MatTableDataSource(this.transactionsData);
|
||||
this.accountWithTransactions = this.transactionsData && this.transactionsData.length > 0;
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if transaction is debit.
|
||||
* @param {any} transactionType Transaction Type
|
||||
*/
|
||||
isDebit(transactionType: CurrentAccountTransactionType) {
|
||||
return (
|
||||
transactionType.withdrawal === true ||
|
||||
transactionType.feeDeduction === true ||
|
||||
transactionType.overdraftInterest === true ||
|
||||
transactionType.withholdTax === true
|
||||
);
|
||||
}
|
||||
|
||||
isAccrual(transactionType: CurrentAccountTransactionType): boolean {
|
||||
return transactionType.accrual || transactionType.code === 'currentAccountTransactionType.accrual';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks transaction status.
|
||||
*/
|
||||
checkStatus() {
|
||||
if (
|
||||
this.status === 'Active' ||
|
||||
this.status === 'Closed' ||
|
||||
this.status === 'Transfer in progress' ||
|
||||
this.status === 'Transfer on hold' ||
|
||||
this.status === 'Premature Closed' ||
|
||||
this.status === 'Matured'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Transactions Details
|
||||
* @param transactionsData Transactions Data
|
||||
*/
|
||||
showTransactions(transactionsData: CurrentAccountTransaction) {
|
||||
if (transactionsData.transfer) {
|
||||
this.router.navigate([`../transfer-funds/account-transfers/${transactionsData.transfer.id}`], {
|
||||
relativeTo: this.route
|
||||
});
|
||||
} else {
|
||||
this.router.navigate(
|
||||
[
|
||||
transactionsData.id,
|
||||
'general'
|
||||
],
|
||||
{ relativeTo: this.route }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the propagation to view pages.
|
||||
* @param $event Mouse Event
|
||||
*/
|
||||
routeEdit($event: MouseEvent) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
|
||||
hideAccruals() {
|
||||
this.filterTransactions(this.hideReversedParam.value, this.hideAccrualsParam.value);
|
||||
}
|
||||
|
||||
hideReversed() {
|
||||
this.filterTransactions(this.hideReversedParam.value, this.hideAccrualsParam.value);
|
||||
}
|
||||
|
||||
filterTransactions(hideReversed: boolean, hideAccrual: boolean): void {
|
||||
let transactions: CurrentAccountTransaction[] = this.transactionsData;
|
||||
|
||||
if (hideAccrual || hideReversed) {
|
||||
transactions = this.transactionsData.filter((t: CurrentAccountTransaction) => {
|
||||
return !(hideReversed && t.reversed) && !(hideAccrual && t.transactionType.accrual);
|
||||
});
|
||||
}
|
||||
this.dataSource = new MatTableDataSource(transactions);
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
currentAccountTransactionColor(transaction: CurrentAccountTransaction): string {
|
||||
if (transaction.reversed) {
|
||||
return 'strike';
|
||||
} else if (transaction.transfer) {
|
||||
return 'transfer';
|
||||
} else if (transaction.transactionType.accrual) {
|
||||
return 'accrual';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
undoTransaction(transactionData: CurrentAccountTransaction): void {
|
||||
const undoTransactionAccountDialogRef = this.dialog.open(UndoTransactionDialogComponent);
|
||||
undoTransactionAccountDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
const locale = this.settingsService.language.code;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const data = {
|
||||
transactionDate: this.dateUtils.parseDate(transactionData.date),
|
||||
transactionAmount: 0,
|
||||
dateFormat,
|
||||
locale
|
||||
};
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountTransactionsCommand(this.accountId, 'undo', data, transactionData.id)
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private reload() {
|
||||
const clientId = this.route.parent.parent.snapshot.params['clientId'];
|
||||
const url: string = this.router.url;
|
||||
this.router
|
||||
.navigateByUrl(`/clients/${clientId}/current-accounts`, { skipLocationChange: true })
|
||||
.then(() => this.router.navigate([url]));
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<mat-card>
|
||||
<form [formGroup]="editTransactionForm" (ngSubmit)="submit()">
|
||||
<mat-card-content class="layout-column">
|
||||
<mat-form-field (click)="dueDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="dueDatePicker"
|
||||
formControlName="transactionDate"
|
||||
required
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="dueDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #dueDatePicker></mat-datepicker>
|
||||
@if (editTransactionForm.controls.transactionDate.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Transaction Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mifosx-input-amount
|
||||
[currency]="currency"
|
||||
[isRequired]="true"
|
||||
[inputFormControl]="editTransactionForm.controls.transactionAmount"
|
||||
[inputLabel]="'Transaction Amount'"
|
||||
>
|
||||
</mifosx-input-amount>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'labels.inputs.Payment Type' | translate }}</mat-label>
|
||||
<mat-select formControlName="paymentTypeId">
|
||||
@for (paymentType of paymentTypeOptions; track paymentType) {
|
||||
<mat-option [value]="paymentType.id">
|
||||
{{ paymentType.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="flex-fill">
|
||||
<span class="expandcollapsebutton m-l-10 m-t-40 flex-75" (click)="addPaymentDetails()">
|
||||
<mat-slide-toggle>
|
||||
<div [className]="showPaymentDetails ? 'enabled' : 'disabled'">
|
||||
<span class="m-l-10">{{ 'labels.inputs.Show Payment Details' | translate }}</span>
|
||||
</div>
|
||||
</mat-slide-toggle>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if (editTransactionForm.controls['accountNumber']) {
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'labels.inputs.Account Number' | translate }}</mat-label>
|
||||
<input type="number" formControlName="accountNumber" matInput />
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
@if (editTransactionForm.controls['checkNumber']) {
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'labels.inputs.Cheque' | translate }}</mat-label>
|
||||
<input type="number" formControlName="checkNumber" matInput />
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
@if (editTransactionForm.controls['routingCode']) {
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'labels.inputs.Routing Code' | translate }}</mat-label>
|
||||
<input formControlName="routingCode" matInput />
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
@if (editTransactionForm.controls['receiptNumber']) {
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'labels.inputs.Receipt Number' | translate }}</mat-label>
|
||||
<input formControlName="receiptNumber" matInput />
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
@if (editTransactionForm.controls['bankNumber']) {
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'labels.inputs.Bank' | translate }}</mat-label>
|
||||
<input formControlName="bankNumber" matInput />
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
<mat-card-actions class="layout-row layout-xs-column layout-align-center gap-5px">
|
||||
<button type="button" mat-raised-button [routerLink]="['../']">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" [disabled]="!editTransactionForm.valid">
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card-content>
|
||||
</form>
|
||||
</mat-card>
|
||||
</div>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.container {
|
||||
width: 37rem;
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import {
|
||||
UntypedFormGroup,
|
||||
UntypedFormBuilder,
|
||||
Validators,
|
||||
UntypedFormControl,
|
||||
ReactiveFormsModule
|
||||
} from '@angular/forms';
|
||||
import { Router, ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
|
||||
/** Custom Services */
|
||||
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Currency } from 'app/shared/models/general.model';
|
||||
import { InputAmountComponent } from '../../../../shared/input-amount/input-amount.component';
|
||||
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Edit Transaction component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-edit-transaction',
|
||||
templateUrl: './edit-transaction.component.html',
|
||||
styleUrls: ['./edit-transaction.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
InputAmountComponent,
|
||||
MatSlideToggle
|
||||
]
|
||||
})
|
||||
export class EditTransactionComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private dateUtils = inject(Dates);
|
||||
private currentAccountsService = inject(CurrentAccountsService);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
/** Minimum Due Date allowed. */
|
||||
minDate = new Date(2000, 0, 1);
|
||||
/** Maximum Due Date allowed. */
|
||||
maxDate = new Date();
|
||||
/** Current account transaction form. */
|
||||
editTransactionForm: UntypedFormGroup;
|
||||
/** current account transaction payment options. */
|
||||
paymentTypeOptions: {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
isCashPayment: boolean;
|
||||
position: number;
|
||||
}[];
|
||||
/** Flag to enable payment details fields. */
|
||||
showPaymentDetails: Boolean = false;
|
||||
/** current account's Id */
|
||||
currentAccountId: string;
|
||||
/** Transaction Template */
|
||||
transactionTemplateData: any;
|
||||
currency: Currency;
|
||||
|
||||
/**
|
||||
* Retrieves the Current Account transaction template data from `resolve`.
|
||||
* @param {FormBuilder} formBuilder Form Builder.
|
||||
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service.
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
* @param {Dates} dateUtils Date Utils.
|
||||
* @param {Router} router Router for navigation.
|
||||
* @param {SettingsService} settingsService Setting service
|
||||
*/
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { currentAccountTransactionTemplate: any }) => {
|
||||
this.transactionTemplateData = data.currentAccountTransactionTemplate;
|
||||
if (data.currentAccountTransactionTemplate.currency) {
|
||||
this.currency = data.currentAccountTransactionTemplate.currency;
|
||||
}
|
||||
this.paymentTypeOptions = this.transactionTemplateData.paymentTypeOptions;
|
||||
});
|
||||
this.currentAccountId = this.route.snapshot.params['currentAccountId'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Current Account account transaction form when component loads.
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.maxDate = this.settingsService.businessDate;
|
||||
this.createEditTransactionForm();
|
||||
this.editTransactionForm.patchValue({
|
||||
transactionDate: this.transactionTemplateData.date && new Date(this.transactionTemplateData.date),
|
||||
transactionAmount: this.transactionTemplateData.amount,
|
||||
paymentTypeId: this.transactionTemplateData.paymentTypeId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to create the Current Account Transaction Form.
|
||||
*/
|
||||
createEditTransactionForm() {
|
||||
this.editTransactionForm = this.formBuilder.group({
|
||||
transactionDate: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
transactionAmount: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
paymentTypeId: ['']
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to add payment detail fields to the UI.
|
||||
*/
|
||||
addPaymentDetails() {
|
||||
this.showPaymentDetails = !this.showPaymentDetails;
|
||||
if (this.showPaymentDetails) {
|
||||
this.editTransactionForm.addControl('accountNumber', new UntypedFormControl(''));
|
||||
this.editTransactionForm.addControl('checkNumber', new UntypedFormControl(''));
|
||||
this.editTransactionForm.addControl('routingCode', new UntypedFormControl(''));
|
||||
this.editTransactionForm.addControl('receiptNumber', new UntypedFormControl(''));
|
||||
this.editTransactionForm.addControl('bankNumber', new UntypedFormControl(''));
|
||||
} else {
|
||||
this.editTransactionForm.removeControl('accountNumber');
|
||||
this.editTransactionForm.removeControl('checkNumber');
|
||||
this.editTransactionForm.removeControl('routingCode');
|
||||
this.editTransactionForm.removeControl('receiptNumber');
|
||||
this.editTransactionForm.removeControl('bankNumber');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to submit the transaction details.
|
||||
*/
|
||||
submit() {
|
||||
const editTransactionFormData = this.editTransactionForm.value;
|
||||
const locale = this.settingsService.language.code;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const prevTransactionDate: Date = this.editTransactionForm.value.transactionDate;
|
||||
if (editTransactionFormData.transactionDate instanceof Date) {
|
||||
editTransactionFormData.transactionDate = this.dateUtils.formatDate(prevTransactionDate, dateFormat);
|
||||
}
|
||||
const data = {
|
||||
...editTransactionFormData,
|
||||
dateFormat,
|
||||
locale
|
||||
};
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountTransactionsCommand(this.currentAccountId, 'modify', data, this.transactionTemplateData.id)
|
||||
.subscribe((res) => {
|
||||
this.router.navigate(['../'], { relativeTo: this.route });
|
||||
});
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<mat-card class="container">
|
||||
<div class="back-button">
|
||||
<button type="button" color="primary" mat-raised-button [routerLink]="['../../']">
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>Back
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="pentahoUrl; else noData" class="pdf-container">
|
||||
<iframe [src]="pentahoUrl" class="pdf-iframe"></iframe>
|
||||
</div>
|
||||
|
||||
<ng-template #noData>
|
||||
<div class="error-message">
|
||||
<h3>Unable to Load Receipt</h3>
|
||||
<p>The transaction receipt could not be loaded. This may be due to:</p>
|
||||
<ul>
|
||||
<li>Missing or incomplete report configuration</li>
|
||||
<li>Report generation service unavailable</li>
|
||||
<li>Invalid transaction data</li>
|
||||
</ul>
|
||||
<p><strong>Please contact your system administrator for assistance.</strong></p>
|
||||
<p class="console-hint">Technical details may be available in the browser console (F12).</p>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-card>
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.container {
|
||||
max-width: 50rem;
|
||||
margin: 0 auto;
|
||||
|
||||
.back-button {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.pdf-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - 250px);
|
||||
min-height: 600px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #525659;
|
||||
|
||||
.pdf-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 4px;
|
||||
margin: 1rem 0;
|
||||
|
||||
h3 {
|
||||
color: #856404;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.console-hint {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { HttpResponse } from '@angular/common/http';
|
||||
|
||||
/**
|
||||
* View Transaction Reciept Component
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-view-reciept',
|
||||
templateUrl: './view-reciept.component.html',
|
||||
styleUrls: ['./view-reciept.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent
|
||||
]
|
||||
})
|
||||
export class ViewRecieptComponent implements OnInit, OnDestroy {
|
||||
private sanitizer = inject(DomSanitizer);
|
||||
private route = inject(ActivatedRoute);
|
||||
|
||||
/** trusted resource url for pentaho output */
|
||||
pentahoUrl: SafeResourceUrl | null = null;
|
||||
/** Transaction Reciept Data */
|
||||
transactionRecieptData: HttpResponse<Blob>;
|
||||
/** Blob URL for cleanup */
|
||||
private blobUrl: string | null = null;
|
||||
|
||||
/**
|
||||
* Fetches transaction reciept `resolve`
|
||||
* @param {DomSanitizer} sanitizer DOM Sanitizer
|
||||
* @param {ActivatedRoute} route Activated Route
|
||||
*/
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { currentAccountTransactionReciept: HttpResponse<Blob> }) => {
|
||||
this.transactionRecieptData = data.currentAccountTransactionReciept;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.transactionRecieptData || !this.transactionRecieptData.body) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = this.transactionRecieptData.headers.get('Content-Type');
|
||||
const file = new Blob([this.transactionRecieptData.body], { type: contentType });
|
||||
|
||||
if (file.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.blobUrl = URL.createObjectURL(file);
|
||||
this.pentahoUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.blobUrl);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.blobUrl) {
|
||||
URL.revokeObjectURL(this.blobUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<p>{{ 'labels.heading.current-account-transaction-datatable-tab works' | translate }}!</p>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-current-account-transaction-datatable-tab',
|
||||
templateUrl: './current-accounts-transaction-datatable-tab.component.html',
|
||||
styleUrls: ['./current-accounts-transaction-datatable-tab.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS
|
||||
]
|
||||
})
|
||||
export class CurrentAccountTransactionDatatableTabComponent {
|
||||
constructor() {}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container transaction-buttons m-b-20 align-end-center gap-2percent" align="end">
|
||||
@if (
|
||||
!(
|
||||
transactionData.interestedPostedAsOn === false &&
|
||||
(transactionData.transactionType.id === 17 || transactionData.transactionType.id === 3)
|
||||
)
|
||||
) {
|
||||
<span>
|
||||
<span *mifosxHasPermission="'ADJUSTTRANSACTION_CURRENTACCOUNT'">
|
||||
@if (
|
||||
transactionData.transactionType.value === 'Transfer' ||
|
||||
transactionData.reversed === 'true' ||
|
||||
transactionData.transactionType.id === 3 ||
|
||||
transactionData.transactionType.id === 17
|
||||
) {
|
||||
<button mat-raised-button color="primary" [routerLink]="'edit'">
|
||||
<fa-icon icon="edit" class="m-r-10"></fa-icon>{{ 'labels.buttons.Edit' | translate }}
|
||||
</button>
|
||||
}
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
@if (!transactionData.reversed && transactionData.transactionType.amountHold) {
|
||||
<span>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
*mifosxHasPermission="'RELEASEAMOUNT_CURRENTACCOUNT'"
|
||||
(click)="releaseAmount()"
|
||||
>
|
||||
<fa-icon icon="lock-open" class="m-r-10"></fa-icon>{{ 'labels.buttons.Release Amount' | translate }}
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
@if (allowUndo()) {
|
||||
<span>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="warn"
|
||||
*mifosxHasPermission="'UNDOTRANSACTION_CURRENTACCOUNT'"
|
||||
(click)="undoTransaction()"
|
||||
>
|
||||
<fa-icon icon="undo" class="m-r-10"></fa-icon>{{ 'labels.buttons.Undo' | translate }}
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<div class="layout-row-wrap card-content">
|
||||
<div class="flex-100" [ngClass]="transactionColor()"></div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Id' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ transactionData.id }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Transaction Type' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ transactionData.transactionType.value | translateKey: 'catalogs' }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Transaction Date' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ transactionData.date | dateFormat }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Currency' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ transactionData.currency.displayLabel }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Amount' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ transactionData.amount | currency: transactionData.currency.code : 'symbol-narrow' : '1.2-2' }}
|
||||
</div>
|
||||
|
||||
@if (transactionData.note) {
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Note' | translate }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (transactionData.note) {
|
||||
<div class="flex-50">
|
||||
{{ transactionData.note }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (transactionData.paymentDetailData) {
|
||||
<mifosx-transaction-payment-detail [paymentDetailData]="transactionData.paymentDetailData">
|
||||
</mifosx-transaction-payment-detail>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="layout-row layout-align-center gap-2percent column-on-mobile">
|
||||
<button type="button" mat-raised-button color="primary" [routerLink]="['../../']">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
@use 'assets/styles/helper';
|
||||
@use 'assets/styles/colours' as *;
|
||||
|
||||
.container {
|
||||
max-width: 37rem;
|
||||
|
||||
.card-content {
|
||||
div {
|
||||
margin: 1rem 0;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
mifosx-transaction-payment-detail {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.transaction-buttons {
|
||||
margin-top: 3%;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: $status-active;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.linked {
|
||||
background-color: $status-approved;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.undo {
|
||||
background-color: $status-active-overdue;
|
||||
height: 4px;
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { ReleaseAmountDialogComponent } from 'app/current-accounts/current-account-view/custom-dialogs/release-amount-dialog/release-amount-dialog.component';
|
||||
import { UndoTransactionDialogComponent } from 'app/current-accounts/current-account-view/custom-dialogs/undo-transaction-dialog/undo-transaction-dialog.component';
|
||||
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { NgClass, CurrencyPipe } from '@angular/common';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { TransactionPaymentDetailComponent } from '../../../../../shared/transaction-payment-detail/transaction-payment-detail.component';
|
||||
import { DateFormatPipe } from '../../../../../pipes/date-format.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-current-account-transaction-general-tab',
|
||||
templateUrl: './current-accounts-transaction-general-tab.component.html',
|
||||
styleUrls: ['./current-accounts-transaction-general-tab.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent,
|
||||
NgClass,
|
||||
TransactionPaymentDetailComponent,
|
||||
CurrencyPipe,
|
||||
DateFormatPipe
|
||||
]
|
||||
})
|
||||
export class CurrentAccountTransactionGeneralTabComponent {
|
||||
private currentAccountsService = inject(CurrentAccountsService);
|
||||
private route = inject(ActivatedRoute);
|
||||
private dateUtils = inject(Dates);
|
||||
private router = inject(Router);
|
||||
dialog = inject(MatDialog);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
accountId: string;
|
||||
transactionId: string;
|
||||
transactionData: any;
|
||||
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { currentAccountTransaction: any }) => {
|
||||
this.accountId = this.route.parent.snapshot.params['currentAccountId'];
|
||||
this.transactionData = data.currentAccountTransaction;
|
||||
});
|
||||
}
|
||||
|
||||
allowUndo(): boolean {
|
||||
if (this.transactionData.reversed && this.transactionData.transactionType.amountHold) {
|
||||
return false;
|
||||
}
|
||||
return !this.transactionData.reversed;
|
||||
}
|
||||
|
||||
releaseAmount(): void {
|
||||
const releaseAmountDialogRef = this.dialog.open(ReleaseAmountDialogComponent);
|
||||
releaseAmountDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
const data = {};
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountTransactionsCommand(this.accountId, 'releaseAmount', data, this.transactionData.id)
|
||||
.subscribe(() => {
|
||||
this.router.navigate(['../..'], { relativeTo: this.route });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
undoTransaction(): void {
|
||||
const undoTransactionAccountDialogRef = this.dialog.open(UndoTransactionDialogComponent);
|
||||
undoTransactionAccountDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
const locale = this.settingsService.language.code;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const data = {
|
||||
transactionDate: this.dateUtils.formatDate(
|
||||
this.transactionData.date && new Date(this.transactionData.date),
|
||||
dateFormat
|
||||
),
|
||||
transactionAmount: 0,
|
||||
dateFormat,
|
||||
locale
|
||||
};
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountTransactionsCommand(this.accountId, 'undo', data, this.transactionData.id)
|
||||
.subscribe(() => {
|
||||
this.router.navigate(['../..'], { relativeTo: this.route });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
transactionColor(): string {
|
||||
if (this.transactionData.reversed) {
|
||||
return 'undo';
|
||||
}
|
||||
return 'active';
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="tab-container mat-typography">
|
||||
<mifosx-entity-datatable-tab
|
||||
entityType="Current Account Transaction"
|
||||
[entityId]="entityId"
|
||||
[multiRowDatatableFlag]="multiRowDatatableFlag"
|
||||
[entityDatatable]="entityDatatable"
|
||||
>
|
||||
</mifosx-entity-datatable-tab>
|
||||
</div>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { EntityDatatableTabComponent } from '../../../../../shared/tabs/entity-datatable-tab/entity-datatable-tab.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-datatable-transaction-tab',
|
||||
templateUrl: './datatable-transaction-tab.component.html',
|
||||
styleUrls: ['./datatable-transaction-tab.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
EntityDatatableTabComponent
|
||||
]
|
||||
})
|
||||
export class DatatableTransactionTabComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
|
||||
/** Transaction Id */
|
||||
entityId: string;
|
||||
/** Current Account Datatable */
|
||||
entityDatatable: any;
|
||||
/** Multi Row Datatable Flag */
|
||||
multiRowDatatableFlag: boolean;
|
||||
constructor() {
|
||||
this.entityId = this.route.parent.parent.snapshot.paramMap.get('id');
|
||||
this.route.data.subscribe((data: { transactionDatatable: any }) => {
|
||||
this.entityDatatable = data.transactionDatatable;
|
||||
this.multiRowDatatableFlag = this.entityDatatable.columnHeaders[0].columnName === 'id' ? true : false;
|
||||
});
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<nav mat-tab-nav-bar class="navigation-tabs" [tabPanel]="tabPanel">
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./general']"
|
||||
routerLinkActive
|
||||
#general="routerLinkActive"
|
||||
[active]="general.isActive"
|
||||
>
|
||||
{{ 'labels.heading.General' | translate }}
|
||||
</a>
|
||||
@for (entityDatatable of entityDatatables; track entityDatatable) {
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./datatables', entityDatatable.registeredTableName]"
|
||||
routerLinkActive
|
||||
#datatable="routerLinkActive"
|
||||
[active]="datatable.isActive"
|
||||
*mifosxHasPermission="'READ_' + entityDatatable.registeredTableName"
|
||||
>
|
||||
{{ entityDatatable.registeredTableName }}
|
||||
</a>
|
||||
}
|
||||
</nav>
|
||||
|
||||
<mat-tab-nav-panel #tabPanel>
|
||||
<router-outlet></router-outlet>
|
||||
</mat-tab-nav-panel>
|
||||
</div>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.container {
|
||||
max-width: 37rem;
|
||||
|
||||
.content {
|
||||
div {
|
||||
margin: 1rem 0;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLinkActive, RouterLink, RouterOutlet } from '@angular/router';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatTabNav, MatTabLink, MatTabNavPanel } from '@angular/material/tabs';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-view-transaction',
|
||||
templateUrl: './view-transaction.component.html',
|
||||
styleUrls: ['./view-transaction.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatTabNav,
|
||||
MatTabLink,
|
||||
RouterLinkActive,
|
||||
MatTabNavPanel,
|
||||
RouterOutlet
|
||||
]
|
||||
})
|
||||
export class ViewTransactionComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
dialog = inject(MatDialog);
|
||||
|
||||
/** Transaction data. */
|
||||
transactionData: any;
|
||||
|
||||
accountId: any;
|
||||
/** Transaction Data Tables */
|
||||
entityDatatables: any;
|
||||
|
||||
/**
|
||||
* @param {Router} router Router for navigation.
|
||||
* @param {MatDialog} dialog Dialog reference.
|
||||
*/
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { transactionDatatables: any }) => {
|
||||
this.accountId = this.route.snapshot.params['currentAccountId'];
|
||||
this.entityDatatables = data.transactionDatatables;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
@if (currentAccountData.status.value === 'Active' && chargeData.amountOutstanding !== 0) {
|
||||
<div class="layout-row align-end gap-2percent layout-xs-column container m-b-20">
|
||||
<button mat-raised-button color="accent" *mifosxHasPermission="'PAY_CURRENTACCOUNTCHARGE'" (click)="payCharge()">
|
||||
<fa-icon icon="dollar-sign" class="m-r-10"></fa-icon>
|
||||
Pay
|
||||
</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
*mifosxHasPermission="'WAIVE_CURRENTACCOUNTCHARGE'"
|
||||
(click)="waiveCharge()"
|
||||
>
|
||||
<fa-icon icon="flag" class="m-r-10"></fa-icon>
|
||||
Waive
|
||||
</button>
|
||||
@if (isRecurringCharge()) {
|
||||
<span>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="warn"
|
||||
*mifosxHasPermission="'INACTIVATE_CURRENTACCOUNTCHARGE'"
|
||||
(click)="inactivateCharge()"
|
||||
>
|
||||
<fa-icon icon="trash" class="m-r-10"></fa-icon>
|
||||
Inactivate
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentAccountData.status.value === 'Submitted and pending approval') {
|
||||
<div class="layout-row align-end gap-2percent layout-xs-column container m-b-20">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
*mifosxHasPermission="'UPDATE_CURRENTACCOUNTCHARGE'"
|
||||
(click)="editCharge()"
|
||||
>
|
||||
<fa-icon icon="edit" class="m-r-10"></fa-icon>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
color="warn"
|
||||
*mifosxHasPermission="'DELETE_CURRENTACCOUNTCHARGE'"
|
||||
(click)="deleteCharge()"
|
||||
>
|
||||
<fa-icon icon="trash" class="m-r-10"></fa-icon>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="container">
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<div class="layout-row-wrap">
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Name' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ chargeData.name }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Charge Type' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ chargeData.penalty ? 'Penalty' : ('Fee' | translateKey: 'catalogs') }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Currency' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ chargeData.currency.displayLabel }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Payment due at' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ chargeData.chargeTimeType.value | translateKey: 'catalogs' }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Payment due as of' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ chargeData.dueDate | dateFormat }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Calculation Type' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ chargeData.chargeCalculationType.value | translateKey: 'catalogs' }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Due' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ chargeData.amount | formatNumber }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Paid' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ chargeData.amountPaid | formatNumber }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Waived' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ chargeData.amountWaived | formatNumber }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50 mat-body-strong">
|
||||
{{ 'labels.inputs.Outstanding' | translate }}
|
||||
</div>
|
||||
|
||||
<div class="flex-50">
|
||||
{{ chargeData.amountOutstanding | formatNumber }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout-row layout-align-center gap-2percent column-on-mobile">
|
||||
<button type="button" mat-raised-button color="primary" [routerLink]="['../']">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.container {
|
||||
max-width: 37rem;
|
||||
|
||||
.content {
|
||||
div {
|
||||
margin: 1rem 0;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
/** Custom Services */
|
||||
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
|
||||
/** Custom Dialogs */
|
||||
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
|
||||
import { WaiveChargeDialogComponent } from '../custom-dialogs/waive-charge-dialog/waive-charge-dialog.component';
|
||||
import { InactivateChargeDialogComponent } from '../custom-dialogs/inactivate-charge-dialog/inactivate-charge-dialog.component';
|
||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
||||
|
||||
/** Custom Models */
|
||||
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
|
||||
import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base';
|
||||
import { DatepickerBase } from 'app/shared/form-dialog/formfield/model/datepicker-base';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
|
||||
import { FormatNumberPipe } from '../../../pipes/format-number.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* View Charge Component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-view-charge',
|
||||
templateUrl: './view-charge.component.html',
|
||||
styleUrls: ['./view-charge.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent,
|
||||
DateFormatPipe,
|
||||
FormatNumberPipe
|
||||
]
|
||||
})
|
||||
export class ViewChargeComponent {
|
||||
private currentAccountsService = inject(CurrentAccountsService);
|
||||
private route = inject(ActivatedRoute);
|
||||
private dateUtils = inject(Dates);
|
||||
private router = inject(Router);
|
||||
dialog = inject(MatDialog);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
/** Charge data. */
|
||||
chargeData: any;
|
||||
/** Current Account Data */
|
||||
currentAccountData: any;
|
||||
|
||||
/**
|
||||
* Retrieves the Charge data from `resolve`.
|
||||
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
* @param {Router} router Router for navigation.
|
||||
* @param {MatDialog} dialog Dialog reference.
|
||||
* @param {Dates} dateUtils Date Utils.
|
||||
* @param {SettingsService} settingsService Setting service
|
||||
*/
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { currentAccountCharge: any }) => {
|
||||
this.chargeData = data.currentAccountCharge;
|
||||
});
|
||||
this.route.data.subscribe((data: { currentAccountData: any }) => {
|
||||
this.currentAccountData = data.currentAccountData;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pays the charge.
|
||||
*/
|
||||
payCharge() {
|
||||
const formfields: FormfieldBase[] = [
|
||||
new InputBase({
|
||||
controlName: 'amount',
|
||||
label: 'Amount',
|
||||
value: '',
|
||||
type: 'number',
|
||||
required: true
|
||||
}),
|
||||
new DatepickerBase({
|
||||
controlName: 'dueDate',
|
||||
label: 'Payment Date',
|
||||
value: '',
|
||||
type: 'date',
|
||||
required: true
|
||||
})
|
||||
];
|
||||
const data = {
|
||||
title: 'Pay Charge',
|
||||
layout: { addButtonText: 'Confirm' },
|
||||
formfields: formfields
|
||||
};
|
||||
const payChargeDialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||
payChargeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.data) {
|
||||
const locale = this.settingsService.language.code;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const dataObject = {
|
||||
...response.data.value,
|
||||
dueDate: this.dateUtils.formatDate(response.data.value.dueDate, dateFormat),
|
||||
dateFormat,
|
||||
locale
|
||||
};
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountChargesCommand(this.chargeData.accountId, 'pay', dataObject, this.chargeData.id)
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waive's the charge
|
||||
*/
|
||||
waiveCharge() {
|
||||
const waiveChargeDialogRef = this.dialog.open(WaiveChargeDialogComponent, { data: { id: this.chargeData.id } });
|
||||
waiveChargeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountChargesCommand(this.chargeData.accountId, 'waive', {}, this.chargeData.id)
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Inactivate's the charge
|
||||
*/
|
||||
inactivateCharge() {
|
||||
const inactivateChargeDialogRef = this.dialog.open(InactivateChargeDialogComponent, {
|
||||
data: { id: this.chargeData.id }
|
||||
});
|
||||
inactivateChargeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.confirm) {
|
||||
this.currentAccountsService
|
||||
.executeCurrentAccountChargesCommand(this.chargeData.accountId, 'inactivate', {}, this.chargeData.id)
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the charge
|
||||
*/
|
||||
editCharge() {
|
||||
const formfields: FormfieldBase[] = [
|
||||
new InputBase({
|
||||
controlName: 'amount',
|
||||
label: 'Amount',
|
||||
value: this.chargeData.amount || this.chargeData.amountOrPercentage,
|
||||
type: 'number',
|
||||
required: true
|
||||
})
|
||||
];
|
||||
const data = {
|
||||
title: 'Edit Charge',
|
||||
layout: { addButtonText: 'Confirm' },
|
||||
formfields: formfields
|
||||
};
|
||||
const editChargeDialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||
editChargeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.data) {
|
||||
const locale = this.settingsService.language.code;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const dataObject = {
|
||||
...response.data.value,
|
||||
dateFormat,
|
||||
locale
|
||||
};
|
||||
this.currentAccountsService
|
||||
.editCurrentAccountCharge(this.chargeData.accountId, dataObject, this.chargeData.id)
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the charge
|
||||
*/
|
||||
deleteCharge() {
|
||||
const deleteChargeDialogRef = this.dialog.open(DeleteDialogComponent, {
|
||||
data: { deleteContext: `charge id:${this.chargeData.id}` }
|
||||
});
|
||||
deleteChargeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.delete) {
|
||||
this.currentAccountsService
|
||||
.deleteCurrentAccountCharge(this.chargeData.accountId, this.chargeData.id)
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if charge is recurring
|
||||
*/
|
||||
isRecurringCharge() {
|
||||
const chargeTimeType = this.chargeData.chargeTimeType.value;
|
||||
return chargeTimeType === 'Monthly Fee' || chargeTimeType === 'Annual Fee' || chargeTimeType === 'Weekly Fee';
|
||||
}
|
||||
|
||||
/**
|
||||
* Refetches data fot the component
|
||||
* TODO: Replace by a custom reload component instead of hard-coded back-routing.
|
||||
*/
|
||||
private reload() {
|
||||
const url: string = this.router.url.replace(`/${this.chargeData.id}`, '');
|
||||
const refreshUrl: string = this.router.url.slice(
|
||||
0,
|
||||
this.router.url.indexOf('current-accounts') + 'current-accounts'.length
|
||||
);
|
||||
this.router.navigateByUrl(refreshUrl, { skipLocationChange: true }).then(() => this.router.navigate([url]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user