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
+34
-1
@@ -220,6 +220,39 @@
|
|||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<div class="sanctions-verify-section flex-98">
|
||||||
|
@if (!sanctionsVerified && !sanctionsBlocked) {
|
||||||
|
<button
|
||||||
|
mat-raised-button
|
||||||
|
color="primary"
|
||||||
|
type="button"
|
||||||
|
class="verify-client-btn"
|
||||||
|
[disabled]="!createClientForm.get('firstname')?.value && !createClientForm.get('fullname')?.value"
|
||||||
|
(click)="verifySanctions()"
|
||||||
|
>
|
||||||
|
<fa-icon icon="shield-alt" class="mr-05"></fa-icon>
|
||||||
|
Verify Client
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
@if (sanctionsVerified) {
|
||||||
|
<div class="sanctions-result">
|
||||||
|
<fa-icon icon="check-circle" class="text-success"></fa-icon>
|
||||||
|
<span class="text-success">Client verified — No match found in sanctions list.</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (sanctionsBlocked) {
|
||||||
|
<div class="sanctions-blocked">
|
||||||
|
<fa-icon icon="exclamation-triangle" class="text-danger"></fa-icon>
|
||||||
|
<span class="text-danger">
|
||||||
|
<strong>SANCTIONS MATCH FOUND</strong> — Client cannot be onboarded. Account opening is blocked.
|
||||||
|
</span>
|
||||||
|
<button mat-stroked-button color="warn" type="button" class="recheck-btn" (click)="verifySanctions()">
|
||||||
|
Re-check
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-divider class="flex-98"></mat-divider>
|
<mat-divider class="flex-98"></mat-divider>
|
||||||
|
|
||||||
<div class="dip-info flex-98">
|
<div class="dip-info flex-98">
|
||||||
@@ -356,7 +389,7 @@
|
|||||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||||
{{ 'labels.buttons.Previous' | translate }}
|
{{ 'labels.buttons.Previous' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button mat-raised-button matStepperNext>
|
<button mat-raised-button matStepperNext [disabled]="sanctionsBlocked">
|
||||||
{{ 'labels.buttons.Next' | translate }}
|
{{ 'labels.buttons.Next' | translate }}
|
||||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -172,6 +172,64 @@ mat-checkbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sanctions-verify-section {
|
||||||
|
margin: 12px 0;
|
||||||
|
|
||||||
|
.verify-client-btn {
|
||||||
|
height: 40px;
|
||||||
|
font-size: 13px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sanctions-progress {
|
||||||
|
mat-progress-bar {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sanctions-msg {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sanctions-result {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.text-success {
|
||||||
|
color: #388e3c;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sanctions-blocked {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: #ffebee;
|
||||||
|
border: 1px solid #ef9a9a;
|
||||||
|
border-left: 4px solid #d32f2f;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
margin: 12px 0;
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: #d32f2f;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recheck-btn {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.external-id-status {
|
.external-id-status {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
+57
-2
@@ -31,7 +31,10 @@ import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
|||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
import { MatIcon } from '@angular/material/icon';
|
import { MatIcon } from '@angular/material/icon';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
import { SanctionsVerifyDialogComponent } from './sanctions-verify-dialog.component';
|
||||||
|
import { DipVerifyDialogComponent } from './dip-verify-dialog.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create Client Component
|
* Create Client Component
|
||||||
@@ -59,6 +62,7 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy {
|
|||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
private clientService = inject(ClientsService);
|
private clientService = inject(ClientsService);
|
||||||
externalNationalIdService = inject(ExternalNationalIdService);
|
externalNationalIdService = inject(ExternalNationalIdService);
|
||||||
|
private dialog = inject(MatDialog);
|
||||||
|
|
||||||
/** Subject to trigger unsubscription on destroy */
|
/** Subject to trigger unsubscription on destroy */
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
@@ -98,6 +102,10 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy {
|
|||||||
savingProductOptions: any;
|
savingProductOptions: any;
|
||||||
/** Mobile number DIP verification status */
|
/** Mobile number DIP verification status */
|
||||||
mobileVerified = false;
|
mobileVerified = false;
|
||||||
|
/** Sanctions verification state */
|
||||||
|
sanctionsVerified = false;
|
||||||
|
/** Sanctions match was found — blocks account opening */
|
||||||
|
sanctionsBlocked = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {FormBuilder} formBuilder Form Builder
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
@@ -258,10 +266,57 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify mobile number with DIP.
|
* Verify mobile number with DIP via dialog.
|
||||||
*/
|
*/
|
||||||
verifyWithDIP() {
|
verifyWithDIP() {
|
||||||
this.mobileVerified = true;
|
const mobileNo = this.createClientForm.get('mobileNo')?.value || '';
|
||||||
|
const dialogRef = this.dialog.open(DipVerifyDialogComponent, {
|
||||||
|
width: '500px',
|
||||||
|
data: { mobileNo }
|
||||||
|
});
|
||||||
|
dialogRef.afterClosed().subscribe((result: boolean) => {
|
||||||
|
if (result) {
|
||||||
|
this.mobileVerified = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify client name against sanctions lists via dialog.
|
||||||
|
* Passes the client name to the dialog for real API screening.
|
||||||
|
* If a match is found, sanctionsBlocked is set to true blocking further steps.
|
||||||
|
*/
|
||||||
|
verifySanctions() {
|
||||||
|
const firstname = this.createClientForm.get('firstname')?.value || '';
|
||||||
|
const middlename = this.createClientForm.get('middlename')?.value || '';
|
||||||
|
const lastname = this.createClientForm.get('lastname')?.value || '';
|
||||||
|
const fullname = this.createClientForm.get('fullname')?.value || '';
|
||||||
|
|
||||||
|
const name =
|
||||||
|
fullname ||
|
||||||
|
[
|
||||||
|
firstname,
|
||||||
|
middlename,
|
||||||
|
lastname
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
|
|
||||||
|
const dialogRef = this.dialog.open(SanctionsVerifyDialogComponent, {
|
||||||
|
width: '550px',
|
||||||
|
data: { name }
|
||||||
|
});
|
||||||
|
dialogRef.afterClosed().subscribe((verified: boolean) => {
|
||||||
|
if (verified === true) {
|
||||||
|
// CLEAR — no match found
|
||||||
|
this.sanctionsVerified = true;
|
||||||
|
this.sanctionsBlocked = false;
|
||||||
|
} else if (verified === false) {
|
||||||
|
// FLAGGED — match found, block account opening
|
||||||
|
this.sanctionsVerified = false;
|
||||||
|
this.sanctionsBlocked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getDateLabel(legalFormId: number, values: string[]): string {
|
getDateLabel(legalFormId: number, values: string[]): string {
|
||||||
|
|||||||
@@ -0,0 +1,343 @@
|
|||||||
|
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { ClientsService } from 'app/clients/clients.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-dip-verify-dialog',
|
||||||
|
template: `
|
||||||
|
<h2 mat-dialog-title>
|
||||||
|
<fa-icon icon="satellite-dish" class="dialog-title-icon"></fa-icon>
|
||||||
|
DIP Verification
|
||||||
|
</h2>
|
||||||
|
<mat-dialog-content>
|
||||||
|
@if (verifying) {
|
||||||
|
<mat-progress-bar mode="determinate" [value]="progress" color="primary"></mat-progress-bar>
|
||||||
|
<p class="progress-percent">{{ progress }}%</p>
|
||||||
|
<div class="verify-info">
|
||||||
|
<fa-icon icon="mobile-alt" class="info-icon"></fa-icon>
|
||||||
|
<p class="verify-msg">
|
||||||
|
Please wait while the system verifies the entered mobile number against the
|
||||||
|
<strong>Mobile Number Revocation List (MNRL)</strong> available on the
|
||||||
|
<strong>Digital Intelligence Platform (DIP)</strong> of the Department of Telecommunications (DoT),
|
||||||
|
Government of India.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="warning-row">
|
||||||
|
<fa-icon icon="exclamation-triangle" class="warning-icon"></fa-icon>
|
||||||
|
<span class="warning-text">This may take a few seconds. Do not close or refresh this window.</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!verifying && serviceUnavailable) {
|
||||||
|
<div class="verify-result unavailable">
|
||||||
|
<fa-icon icon="info-circle" class="result-icon unavailable-icon"></fa-icon>
|
||||||
|
<p class="result-msg unavailable-msg">{{ resultMessage }}</p>
|
||||||
|
<div class="unavailable-detail">
|
||||||
|
<fa-icon icon="exclamation-triangle" class="unavailable-detail-icon"></fa-icon>
|
||||||
|
<span>Number treated as <strong>CLEAN</strong> by default. Proceed with caution.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!verifying && !suspected && !serviceUnavailable && !error) {
|
||||||
|
<div class="verify-result">
|
||||||
|
<fa-icon icon="check-circle" class="result-icon"></fa-icon>
|
||||||
|
<p class="result-msg">Mobile number verified — Not found in MNRL revocation list.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!verifying && suspected) {
|
||||||
|
<div class="verify-result suspected">
|
||||||
|
<fa-icon icon="exclamation-circle" class="result-icon suspected-icon"></fa-icon>
|
||||||
|
<p class="result-msg suspected-msg">{{ resultMessage }}</p>
|
||||||
|
@if (suspectedReason) {
|
||||||
|
<div class="suspected-detail">
|
||||||
|
<p><strong>Status:</strong> {{ suspectedStatus }}</p>
|
||||||
|
<p><strong>Reason:</strong> {{ suspectedReason }}</p>
|
||||||
|
<p><strong>Reported by:</strong> {{ suspectedReportedBy }}</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!verifying && error) {
|
||||||
|
<div class="verify-result error">
|
||||||
|
<fa-icon icon="times-circle" class="result-icon error-icon"></fa-icon>
|
||||||
|
<p class="result-msg error-msg">{{ error }}</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
@if (!verifying) {
|
||||||
|
<button mat-raised-button color="primary" (click)="close()">OK</button>
|
||||||
|
}
|
||||||
|
</mat-dialog-actions>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
min-width: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2[mat-dialog-title] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px 24px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1074b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-dialog-content {
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-progress-bar {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-percent {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1074b9;
|
||||||
|
margin: 4px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #e3f2fd;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #1074b9;
|
||||||
|
margin-top: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-msg {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #fff3e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #e65100;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #e65100;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-result {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #388e3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-msg {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #388e3c;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suspected-icon {
|
||||||
|
color: #e65100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suspected-msg {
|
||||||
|
color: #e65100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suspected-detail {
|
||||||
|
background: #fff3e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suspected-detail p {
|
||||||
|
margin: 4px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unavailable-icon {
|
||||||
|
color: #1565c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unavailable-msg {
|
||||||
|
color: #1565c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unavailable-detail {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: #e3f2fd;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unavailable-detail-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #1565c0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon {
|
||||||
|
color: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-msg {
|
||||||
|
color: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-dialog-actions {
|
||||||
|
padding: 8px 24px 16px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
MatDialogModule,
|
||||||
|
MatProgressBarModule,
|
||||||
|
MatButtonModule,
|
||||||
|
FaIconComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class DipVerifyDialogComponent implements OnInit, OnDestroy {
|
||||||
|
private dialogRef = inject(MatDialogRef<DipVerifyDialogComponent>);
|
||||||
|
private data: { mobileNo: string } = inject(MAT_DIALOG_DATA);
|
||||||
|
private clientsService = inject(ClientsService);
|
||||||
|
|
||||||
|
verifying = true;
|
||||||
|
progress = 0;
|
||||||
|
suspected = false;
|
||||||
|
serviceUnavailable = false;
|
||||||
|
error: string | null = null;
|
||||||
|
resultMessage = '';
|
||||||
|
suspectedStatus = '';
|
||||||
|
suspectedReason = '';
|
||||||
|
suspectedReportedBy = '';
|
||||||
|
private subscription: any;
|
||||||
|
private intervalId: any;
|
||||||
|
private apiResponse: any = null;
|
||||||
|
private apiError: any = null;
|
||||||
|
private apiDone = false;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.dialogRef.disableClose = true;
|
||||||
|
|
||||||
|
// Animate progress bar over ~3 seconds (0 → 100)
|
||||||
|
this.intervalId = setInterval(() => {
|
||||||
|
this.progress += 2;
|
||||||
|
if (this.progress >= 100) {
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
this.intervalId = null;
|
||||||
|
this.tryShowResult();
|
||||||
|
}
|
||||||
|
}, 60);
|
||||||
|
|
||||||
|
// Fire the API call in parallel
|
||||||
|
this.subscription = this.clientsService.checkDipLookup(this.data.mobileNo).subscribe({
|
||||||
|
next: (res: any) => {
|
||||||
|
this.apiResponse = res;
|
||||||
|
this.apiDone = true;
|
||||||
|
this.tryShowResult();
|
||||||
|
},
|
||||||
|
error: (err: any) => {
|
||||||
|
this.apiError = err;
|
||||||
|
this.apiDone = true;
|
||||||
|
this.tryShowResult();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Show result only after both progress bar completes and API responds */
|
||||||
|
private tryShowResult() {
|
||||||
|
if (!this.apiDone || this.progress < 100) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.verifying = false;
|
||||||
|
this.dialogRef.disableClose = false;
|
||||||
|
|
||||||
|
if (this.apiError) {
|
||||||
|
this.error =
|
||||||
|
this.apiError?.error?.message || this.apiError?.message || 'DIP verification failed. Please try again.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = this.apiResponse;
|
||||||
|
if (res?.data?.status === 'SERVICE_UNAVAILABLE') {
|
||||||
|
this.serviceUnavailable = true;
|
||||||
|
this.resultMessage = res.message || 'DIP/MNRL lookup service is unavailable. Number treated as CLEAN by default.';
|
||||||
|
} else if (res?.data?.suspected) {
|
||||||
|
this.suspected = true;
|
||||||
|
this.resultMessage = res.message || `Mobile number is ${res.data.status} — found in DIP records`;
|
||||||
|
const detail = res.data.suspectedDetail;
|
||||||
|
if (detail) {
|
||||||
|
this.suspectedStatus = detail.dipStatus || res.data.status;
|
||||||
|
this.suspectedReason = detail.reason || '';
|
||||||
|
this.suspectedReportedBy = detail.reportedBy || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.subscription) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.intervalId) {
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
// Return true if clear or service unavailable (treated as clean), false if suspected or error
|
||||||
|
this.dialogRef.close(!this.suspected && !this.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
+271
@@ -0,0 +1,271 @@
|
|||||||
|
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { MatDialogRef, MatDialogModule, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { ClientsService } from 'app/clients/clients.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-sanctions-verify-dialog',
|
||||||
|
template: `
|
||||||
|
<h2 mat-dialog-title>
|
||||||
|
<fa-icon icon="shield-alt" class="dialog-title-icon"></fa-icon>
|
||||||
|
Sanctions Verification
|
||||||
|
</h2>
|
||||||
|
<mat-dialog-content>
|
||||||
|
@if (verifying) {
|
||||||
|
<mat-progress-bar mode="indeterminate" color="primary"></mat-progress-bar>
|
||||||
|
<p class="sanctions-msg">
|
||||||
|
Checking <strong>{{ clientName }}</strong> against sanctions list<br />
|
||||||
|
<strong>UN Security Council</strong> &
|
||||||
|
<strong>Financial Intelligence Unit - India (FIU-IND under UNSC Resolution 2231)</strong>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
@if (!verifying && !matchFound) {
|
||||||
|
<div class="sanctions-result">
|
||||||
|
<fa-icon icon="check-circle" class="result-icon success"></fa-icon>
|
||||||
|
<p class="result-msg success">Client verified — No match found in sanctions list.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!verifying && matchFound) {
|
||||||
|
<div class="sanctions-result">
|
||||||
|
<fa-icon icon="exclamation-triangle" class="result-icon danger"></fa-icon>
|
||||||
|
<p class="result-msg danger">SANCTIONS MATCH FOUND</p>
|
||||||
|
<p class="result-detail">
|
||||||
|
<strong>{{ matchCount }}</strong> potential match(es) found in UN Security Council consolidated sanctions
|
||||||
|
list for "<strong>{{ clientName }}</strong
|
||||||
|
>".
|
||||||
|
</p>
|
||||||
|
<div class="match-list">
|
||||||
|
@for (match of matches; track match.id) {
|
||||||
|
<div class="match-item">
|
||||||
|
<div class="match-name">{{ match.fullName }}</div>
|
||||||
|
<div class="match-meta">
|
||||||
|
<span class="match-type">{{ match.entryType }}</span>
|
||||||
|
<span class="match-ref">{{ match.referenceNumber }}</span>
|
||||||
|
@if (match.unListType) {
|
||||||
|
<span class="match-list-type">{{ match.unListType }}</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (match.aliases && match.aliases.length > 0) {
|
||||||
|
<div class="match-aliases"><em>Also known as:</em> {{ match.aliases.join(', ') }}</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<p class="block-msg">This client cannot be onboarded. Account opening is blocked due to sanctions match.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (!verifying && errorOccurred) {
|
||||||
|
<div class="sanctions-result">
|
||||||
|
<fa-icon icon="exclamation-circle" class="result-icon warning"></fa-icon>
|
||||||
|
<p class="result-msg warning">Verification failed. Please try again.</p>
|
||||||
|
<p class="result-detail">{{ errorMessage }}</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
@if (!verifying && !matchFound && !errorOccurred) {
|
||||||
|
<button mat-raised-button color="primary" (click)="close(true)">OK</button>
|
||||||
|
}
|
||||||
|
@if (!verifying && matchFound) {
|
||||||
|
<button mat-raised-button color="warn" (click)="close(false)">Close</button>
|
||||||
|
}
|
||||||
|
@if (!verifying && errorOccurred) {
|
||||||
|
<button mat-raised-button (click)="close(false)">Close</button>
|
||||||
|
}
|
||||||
|
</mat-dialog-actions>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
min-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2[mat-dialog-title] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px 24px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1074b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-dialog-content {
|
||||||
|
padding: 16px 24px;
|
||||||
|
max-height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sanctions-msg {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 16px 0 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sanctions-result {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-icon.success {
|
||||||
|
color: #388e3c;
|
||||||
|
}
|
||||||
|
.result-icon.danger {
|
||||||
|
color: #d32f2f;
|
||||||
|
}
|
||||||
|
.result-icon.warning {
|
||||||
|
color: #f57c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-msg {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-msg.success {
|
||||||
|
color: #388e3c;
|
||||||
|
}
|
||||||
|
.result-msg.danger {
|
||||||
|
color: #d32f2f;
|
||||||
|
}
|
||||||
|
.result-msg.warning {
|
||||||
|
color: #f57c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-detail {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555;
|
||||||
|
text-align: center;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-list {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-item {
|
||||||
|
background: #fce4ec;
|
||||||
|
border-left: 4px solid #d32f2f;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-type,
|
||||||
|
.match-ref,
|
||||||
|
.match-list-type {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #ffcdd2;
|
||||||
|
color: #b71c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-aliases {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-msg {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #d32f2f;
|
||||||
|
background: #ffebee;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-dialog-actions {
|
||||||
|
padding: 8px 24px 16px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
MatDialogModule,
|
||||||
|
MatProgressBarModule,
|
||||||
|
MatButtonModule,
|
||||||
|
FaIconComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class SanctionsVerifyDialogComponent implements OnInit, OnDestroy {
|
||||||
|
private dialogRef = inject(MatDialogRef<SanctionsVerifyDialogComponent>);
|
||||||
|
private data: { name: string } = inject(MAT_DIALOG_DATA);
|
||||||
|
private clientsService = inject(ClientsService);
|
||||||
|
|
||||||
|
clientName = '';
|
||||||
|
verifying = true;
|
||||||
|
matchFound = false;
|
||||||
|
matchCount = 0;
|
||||||
|
matches: any[] = [];
|
||||||
|
errorOccurred = false;
|
||||||
|
errorMessage = '';
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.clientName = this.data?.name || '';
|
||||||
|
this.dialogRef.disableClose = true;
|
||||||
|
|
||||||
|
this.clientsService.screenSanctions(this.clientName).subscribe({
|
||||||
|
next: (result: any) => {
|
||||||
|
this.verifying = false;
|
||||||
|
this.dialogRef.disableClose = false;
|
||||||
|
if (result.matchFound) {
|
||||||
|
this.matchFound = true;
|
||||||
|
this.matchCount = result.matchCount || 0;
|
||||||
|
this.matches = result.matches || [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (err: any) => {
|
||||||
|
this.verifying = false;
|
||||||
|
this.dialogRef.disableClose = false;
|
||||||
|
this.errorOccurred = true;
|
||||||
|
this.errorMessage = err?.error?.message || err?.message || 'Unable to connect to sanctions screening service.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {}
|
||||||
|
|
||||||
|
close(verified: boolean) {
|
||||||
|
this.dialogRef.close(verified);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -288,6 +288,11 @@ const routes: Routes = [
|
|||||||
path: 'savings-accounts',
|
path: 'savings-accounts',
|
||||||
loadChildren: () => import('../savings/savings.module').then((m) => m.SavingsModule)
|
loadChildren: () => import('../savings/savings.module').then((m) => m.SavingsModule)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'current-accounts',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('../current-accounts/current-accounts.module').then((m) => m.CurrentAccountsModule)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'recurring-deposits-accounts',
|
path: 'recurring-deposits-accounts',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
|
|||||||
@@ -274,6 +274,13 @@
|
|||||||
>
|
>
|
||||||
{{ 'labels.buttons.New Fixed Deposits Account' | translate }}
|
{{ 'labels.buttons.New Fixed Deposits Account' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
mat-menu-item
|
||||||
|
*mifosxHasPermission="'CREATE_CURRENTACCOUNT'"
|
||||||
|
[routerLink]="['current-accounts', 'create']"
|
||||||
|
>
|
||||||
|
{{ 'labels.buttons.New Current Account' | translate }}
|
||||||
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<mat-menu #Actions="matMenu">
|
<mat-menu #Actions="matMenu">
|
||||||
|
|||||||
@@ -490,6 +490,15 @@ export class ClientsService {
|
|||||||
return this.http.post(`/clients/${clientId}/kyc`, kycData);
|
return this.http.post(`/clients/${clientId}/kyc`, kycData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen a client name against the UN Security Council consolidated sanctions list.
|
||||||
|
* @param name The name to screen
|
||||||
|
* @returns Observable with screening result (status: FLAGGED or CLEAR)
|
||||||
|
*/
|
||||||
|
screenSanctions(name: string): Observable<any> {
|
||||||
|
return this.http.post('/sanctions/screen', { name });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates existing KYC details for a client.
|
* Updates existing KYC details for a client.
|
||||||
* @param clientId Client ID
|
* @param clientId Client ID
|
||||||
@@ -550,6 +559,16 @@ export class ClientsService {
|
|||||||
return this.http.post(`/clients/${clientId}/ckyc/documents`, formData);
|
return this.http.post(`/clients/${clientId}/ckyc/documents`, formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check mobile number against the DIP (Digital Intelligence Platform) MNRL.
|
||||||
|
* Calls an external service directly, bypassing Fineract interceptors.
|
||||||
|
* @param mobileNo Mobile number to verify
|
||||||
|
*/
|
||||||
|
checkDipLookup(mobileNo: string): Observable<any> {
|
||||||
|
const httpParams = new HttpParams().set('mobileNumber', mobileNo);
|
||||||
|
return this.http.get('/lookup/check', { params: httpParams });
|
||||||
|
}
|
||||||
|
|
||||||
getClientForm60(clientId: string): Observable<any> {
|
getClientForm60(clientId: string): Observable<any> {
|
||||||
return this.http.get(`/clients/${clientId}/form60`);
|
return this.http.get(`/clients/${clientId}/form60`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ export class CreateClientComponent {
|
|||||||
|
|
||||||
areFormvalids(): boolean {
|
areFormvalids(): boolean {
|
||||||
let areValids = this.clientGeneralForm.valid;
|
let areValids = this.clientGeneralForm.valid;
|
||||||
|
// Block form if sanctions match found
|
||||||
|
if (this.clientGeneralStep.sanctionsBlocked) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (this.clientTemplate.isAddressEnabled) {
|
if (this.clientTemplate.isAddressEnabled) {
|
||||||
areValids = areValids && this.clientAddressStep.address.address.length > 0;
|
areValids = areValids && this.clientAddressStep.address.address.length > 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,57 @@
|
|||||||
<mat-sidenav-content class="sidenav">
|
<mat-sidenav-content class="sidenav">
|
||||||
<!-- Toolbar -->
|
<!-- Toolbar -->
|
||||||
<mifosx-toolbar [sidenav]="sidenav" (collapse)="toggleCollapse($event)"></mifosx-toolbar>
|
<mifosx-toolbar [sidenav]="sidenav" (collapse)="toggleCollapse($event)"></mifosx-toolbar>
|
||||||
|
<!-- Sub Menu Bar -->
|
||||||
|
<div class="sub-menu-bar">
|
||||||
|
<a class="sub-menu-link" tabindex="0">
|
||||||
|
<fa-icon icon="money-bill" class="sub-menu-icon" size="lg"></fa-icon>Cashier
|
||||||
|
</a>
|
||||||
|
<a class="sub-menu-link" tabindex="0">
|
||||||
|
<fa-icon icon="user-tie" class="sub-menu-icon" size="lg"></fa-icon>Officer
|
||||||
|
</a>
|
||||||
|
<a class="sub-menu-link" tabindex="0" [matMenuTriggerFor]="depositsMenu">
|
||||||
|
<fa-icon icon="piggy-bank" class="sub-menu-icon" size="lg"></fa-icon>Deposits
|
||||||
|
<fa-icon icon="caret-down" class="sub-menu-caret"></fa-icon>
|
||||||
|
</a>
|
||||||
|
<a class="sub-menu-link" tabindex="0">
|
||||||
|
<fa-icon icon="exchange" class="sub-menu-icon" size="lg"></fa-icon>Clearing
|
||||||
|
</a>
|
||||||
|
<a class="sub-menu-link" tabindex="0">
|
||||||
|
<fa-icon icon="money-check" class="sub-menu-icon" size="lg"></fa-icon>HRM
|
||||||
|
</a>
|
||||||
|
<a class="sub-menu-link" tabindex="0" [matMenuTriggerFor]="shareMenu">
|
||||||
|
<fa-icon icon="coins" class="sub-menu-icon" size="lg"></fa-icon>Share
|
||||||
|
<fa-icon icon="caret-down" class="sub-menu-caret"></fa-icon>
|
||||||
|
</a>
|
||||||
|
<a class="sub-menu-link" tabindex="0" [matMenuTriggerFor]="othersMenu">
|
||||||
|
<fa-icon icon="folder-open" class="sub-menu-icon" size="lg"></fa-icon>Others
|
||||||
|
<fa-icon icon="caret-down" class="sub-menu-caret"></fa-icon>
|
||||||
|
</a>
|
||||||
|
<a class="sub-menu-link" tabindex="0">
|
||||||
|
<fa-icon icon="exclamation-triangle" class="sub-menu-icon" size="lg"></fa-icon>NPA Module
|
||||||
|
</a>
|
||||||
|
<a class="sub-menu-link" tabindex="0">
|
||||||
|
<fa-icon icon="fingerprint" class="sub-menu-icon" size="lg"></fa-icon>CKYC Management
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sub Menu Dropdowns -->
|
||||||
|
<mat-menu #depositsMenu="matMenu" [overlapTrigger]="false">
|
||||||
|
<button mat-menu-item tabindex="0">Saving</button>
|
||||||
|
<button mat-menu-item tabindex="0">Current</button>
|
||||||
|
<button mat-menu-item tabindex="0">Fixed Deposit</button>
|
||||||
|
<button mat-menu-item tabindex="0">Recurring</button>
|
||||||
|
<button mat-menu-item tabindex="0">DDS</button>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-menu #shareMenu="matMenu" [overlapTrigger]="false">
|
||||||
|
<button mat-menu-item tabindex="0">Nominal</button>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
|
<mat-menu #othersMenu="matMenu" [overlapTrigger]="false">
|
||||||
|
<button mat-menu-item tabindex="0">Locker</button>
|
||||||
|
<button mat-menu-item tabindex="0">Investment</button>
|
||||||
|
</mat-menu>
|
||||||
<!-- Progress Bar -->
|
<!-- Progress Bar -->
|
||||||
@if (progressBarMode !== 'none') {
|
@if (progressBarMode !== 'none') {
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -36,6 +36,51 @@ $color: #e2e4ec;
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sub-menu-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #1074b9;
|
||||||
|
padding: 2px 0.5rem 2px calc(48px + 2rem);
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
font-family: Roboto, 'Helvetica Neue', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: normal;
|
||||||
|
line-height: 1;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-menu-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgb(255 255 255 / 70%);
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-menu-icon {
|
||||||
|
margin-right: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-menu-caret {
|
||||||
|
margin-left: 0.35rem;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes move {
|
@keyframes move {
|
||||||
0% {
|
0% {
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import { ToolbarComponent } from './toolbar/toolbar.component';
|
|||||||
import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component';
|
import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component';
|
||||||
import { ContentComponent } from './content/content.component';
|
import { ContentComponent } from './content/content.component';
|
||||||
import { FooterComponent } from '../../shared/footer/footer.component';
|
import { FooterComponent } from '../../shared/footer/footer.component';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +45,9 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
BreadcrumbComponent,
|
BreadcrumbComponent,
|
||||||
ContentComponent,
|
ContentComponent,
|
||||||
FooterComponent,
|
FooterComponent,
|
||||||
AsyncPipe
|
AsyncPipe,
|
||||||
|
FaIconComponent,
|
||||||
|
MatMenuModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ShellComponent implements OnInit, OnDestroy {
|
export class ShellComponent implements OnInit, OnDestroy {
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
>
|
>
|
||||||
<fa-icon class="mr-05" icon="university" size="lg"></fa-icon>
|
<fa-icon class="mr-05" icon="university" size="lg"></fa-icon>
|
||||||
{{ 'labels.menus.Institution' | translate }}
|
{{ 'labels.menus.Institution' | translate }}
|
||||||
|
<fa-icon icon="caret-down" class="ml-05"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span class="hide-lt-md">
|
<span class="hide-lt-md">
|
||||||
@@ -79,6 +80,7 @@
|
|||||||
>
|
>
|
||||||
<fa-icon class="mr-05" icon="chart-bar" size="lg"></fa-icon>
|
<fa-icon class="mr-05" icon="chart-bar" size="lg"></fa-icon>
|
||||||
{{ 'labels.menus.Reports' | translate }}
|
{{ 'labels.menus.Reports' | translate }}
|
||||||
|
<fa-icon icon="caret-down" class="ml-05"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
#admin
|
#admin
|
||||||
@@ -90,35 +92,22 @@
|
|||||||
>
|
>
|
||||||
<fa-icon class="mr-05" icon="shield-alt" size="lg"></fa-icon>
|
<fa-icon class="mr-05" icon="shield-alt" size="lg"></fa-icon>
|
||||||
{{ 'labels.menus.Admin' | translate }}
|
{{ 'labels.menus.Admin' | translate }}
|
||||||
|
<fa-icon icon="caret-down" class="ml-05"></fa-icon>
|
||||||
</a>
|
</a>
|
||||||
<a #configWizard class="tab-link" (click)="openDialog()" tabindex="0" *mifosxHasPermission="'READ_CONFIG_WIZARD'">
|
<a #configWizard class="tab-link" (click)="openDialog()" tabindex="0" *mifosxHasPermission="'READ_CONFIG_WIZARD'">
|
||||||
<fa-icon class="mr-05" icon="info" size="lg"></fa-icon>
|
<fa-icon class="mr-05" icon="info" size="lg"></fa-icon>
|
||||||
{{ 'labels.menus.Configuration Wizard' | translate }}
|
{{ 'labels.menus.Configuration Wizard' | translate }}
|
||||||
</a>
|
</a>
|
||||||
|
<a class="tab-link" [matMenuTriggerFor]="ledgerMenu" tabindex="0">
|
||||||
|
<fa-icon class="mr-05" icon="book" size="lg"></fa-icon>
|
||||||
|
Ledger
|
||||||
|
<fa-icon icon="caret-down" class="ml-05"></fa-icon>
|
||||||
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="toolbar-spacer"></span>
|
<span class="toolbar-spacer"></span>
|
||||||
|
|
||||||
<div #globalSearch class="white-text-search">
|
|
||||||
<mifosx-search-tool tabindex="0"></mifosx-search-tool>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div #languageSelector class="white-text-language">
|
|
||||||
<mifosx-language-selector class="ml-1 language hide-lt-md" tabindex="0"></mifosx-language-selector>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
[matMenuTriggerFor]="languageMenu"
|
|
||||||
class="show-lt-md"
|
|
||||||
tabindex="0"
|
|
||||||
[attr.aria-label]="'labels.menus.Language Selector' | translate"
|
|
||||||
matTooltip="{{ 'labels.menus.Language Selector' | translate }}"
|
|
||||||
>
|
|
||||||
<mat-icon>language</mat-icon>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div #notifications>
|
<div #notifications>
|
||||||
<mifosx-notifications-tray #notificationsTray class="hide-lt-md" tabindex="0"></mifosx-notifications-tray>
|
<mifosx-notifications-tray #notificationsTray class="hide-lt-md" tabindex="0"></mifosx-notifications-tray>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,11 +123,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
||||||
<!-- Language selector menu for small screen view -->
|
|
||||||
<mat-menu #languageMenu="matMenu">
|
|
||||||
<mifosx-language-selector class="ml-1 language"></mifosx-language-selector>
|
|
||||||
</mat-menu>
|
|
||||||
|
|
||||||
<mat-menu #institutionMenu="matMenu" [overlapTrigger]="false">
|
<mat-menu #institutionMenu="matMenu" [overlapTrigger]="false">
|
||||||
<button mat-menu-item routerLink="/clients" tabindex="0" *mifosxHasPermission="'READ_CLIENT'">
|
<button mat-menu-item routerLink="/clients" tabindex="0" *mifosxHasPermission="'READ_CLIENT'">
|
||||||
{{ 'labels.menus.Clients' | translate }}
|
{{ 'labels.menus.Clients' | translate }}
|
||||||
@@ -160,6 +144,12 @@
|
|||||||
</span>
|
</span>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
|
<!-- Ledger Menu -->
|
||||||
|
<mat-menu #ledgerMenu="matMenu" [overlapTrigger]="false">
|
||||||
|
<button mat-menu-item tabindex="0">Sub Ledger</button>
|
||||||
|
<button mat-menu-item tabindex="0">General Ledger</button>
|
||||||
|
</mat-menu>
|
||||||
|
|
||||||
<!-- Toolbar Menus -->
|
<!-- Toolbar Menus -->
|
||||||
<mat-menu #reportsMenu="matMenu" [overlapTrigger]="false">
|
<mat-menu #reportsMenu="matMenu" [overlapTrigger]="false">
|
||||||
<button mat-menu-item [routerLink]="['/reports']" tabindex="0" *mifosxHasPermission="'READ_REPORT'">
|
<button mat-menu-item [routerLink]="['/reports']" tabindex="0" *mifosxHasPermission="'READ_REPORT'">
|
||||||
@@ -317,54 +307,6 @@
|
|||||||
<button mat-raised-button color="primary" (click)="popover.close(); showPopover(templateSelfService, selfService)">
|
<button mat-raised-button color="primary" (click)="popover.close(); showPopover(templateSelfService, selfService)">
|
||||||
{{ 'labels.buttons.Back' | translate }}
|
{{ 'labels.buttons.Back' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
mat-raised-button
|
|
||||||
color="primary"
|
|
||||||
(click)="popover.close(); showPopover(templateGlobalSearch, globalSearch)"
|
|
||||||
>
|
|
||||||
{{ 'labels.buttons.Next' | translate }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #templateGlobalSearch let-popover="popover">
|
|
||||||
<h2>{{ 'labels.menus.Global Search' | translate }}</h2>
|
|
||||||
<p class="mw300">{{ 'labels.text.Navigate system selecting entity' | translate }}</p>
|
|
||||||
<div class="layout-row align-end gap-2px responsive-column">
|
|
||||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">
|
|
||||||
{{ 'labels.buttons.Close' | translate }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
mat-raised-button
|
|
||||||
color="primary"
|
|
||||||
(click)="popover.close(); showPopover(templateConfigWizard, configWizard)"
|
|
||||||
>
|
|
||||||
{{ 'labels.buttons.Back' | translate }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
mat-raised-button
|
|
||||||
color="primary"
|
|
||||||
(click)="popover.close(); showPopover(templateLanguageSelector, languageSelector)"
|
|
||||||
>
|
|
||||||
{{ 'labels.buttons.Next' | translate }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<ng-template #templateLanguageSelector let-popover="popover">
|
|
||||||
<h2>{{ 'labels.menus.Language Selector' | translate }}</h2>
|
|
||||||
<p class="mw300">{{ 'labels.text.Organization available languages' | translate }}</p>
|
|
||||||
<div class="layout-row align-end gap-2px responsive-column">
|
|
||||||
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">
|
|
||||||
{{ 'labels.buttons.Close' | translate }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
mat-raised-button
|
|
||||||
color="primary"
|
|
||||||
(click)="popover.close(); showPopover(templateGlobalSearch, globalSearch)"
|
|
||||||
>
|
|
||||||
{{ 'labels.buttons.Back' | translate }}
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -385,7 +327,7 @@
|
|||||||
<button
|
<button
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
color="primary"
|
color="primary"
|
||||||
(click)="popover.close(); showPopover(templateLanguageSelector, languageSelector)"
|
(click)="popover.close(); showPopover(templateConfigWizard, configWizard)"
|
||||||
>
|
>
|
||||||
{{ 'labels.buttons.Back' | translate }}
|
{{ 'labels.buttons.Back' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -8,10 +8,7 @@
|
|||||||
|
|
||||||
#mifosx-toolbar {
|
#mifosx-toolbar {
|
||||||
position: relative;
|
position: relative;
|
||||||
box-shadow:
|
box-shadow: none;
|
||||||
0 3px 1px -2px rgb(0 0 0 / 20%),
|
|
||||||
0 2px 2px 0 rgb(0 0 0 / 14%),
|
|
||||||
0 1px 5px 0 rgb(0 0 0 / 12%);
|
|
||||||
|
|
||||||
.tab-nav-bar {
|
.tab-nav-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -21,7 +18,7 @@
|
|||||||
|
|
||||||
.tab-link {
|
.tab-link {
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
padding: 15px 1rem;
|
padding: 15px 0.5rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -34,21 +31,6 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-bar {
|
|
||||||
.search {
|
|
||||||
max-width: 400px;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.language {
|
|
||||||
min-width: 80px;
|
|
||||||
width: 80px;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
margin-top: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-button {
|
.img-button {
|
||||||
img {
|
img {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
@@ -57,6 +39,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ml-05 {
|
||||||
|
margin-left: 0.35rem;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.ml-1 {
|
.ml-1 {
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
@@ -66,40 +53,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show language icon button only on small screens (when full selector is hidden)
|
|
||||||
.show-lt-md {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 768px) {
|
|
||||||
.show-lt-md {
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.toolbar-spacer {
|
.toolbar-spacer {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.white-text-language ::ng-deep mifosx-language-selector {
|
|
||||||
color: white !important;
|
|
||||||
/* stylelint-disable-next-line selector-max-universal */
|
|
||||||
* {
|
|
||||||
background-color: transparent !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.white-text-search ::ng-deep mifosx-search-tool {
|
|
||||||
color: white !important;
|
|
||||||
/* stylelint-disable-next-line selector-max-universal */
|
|
||||||
* {
|
|
||||||
background-color: transparent !important;
|
|
||||||
color: white !important;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.white-text-language {
|
|
||||||
margin-top: 27px;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export class Charges {
|
|||||||
{ id: 1, code: 'chargeAppliesTo.loan', value: 'Loan' },
|
{ id: 1, code: 'chargeAppliesTo.loan', value: 'Loan' },
|
||||||
{ id: 2, code: 'chargeAppliesTo.savings', value: 'Savings' },
|
{ id: 2, code: 'chargeAppliesTo.savings', value: 'Savings' },
|
||||||
{ id: 3, code: 'chargeAppliesTo.client', value: 'Client' },
|
{ id: 3, code: 'chargeAppliesTo.client', value: 'Client' },
|
||||||
{ id: 4, code: 'chargeAppliesTo.shares', value: 'Shares' }
|
{ id: 4, code: 'chargeAppliesTo.shares', value: 'Shares' },
|
||||||
|
{ id: 5, code: 'chargeAppliesTo.currentAccount', value: 'Current Account' }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
|
import { Observable, forkJoin, of } from 'rxjs';
|
||||||
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
import { ClientsService } from 'app/clients/clients.service';
|
||||||
|
import { GroupsService } from 'app/groups/groups.service';
|
||||||
|
import { OrganizationService } from 'app/organization/organization.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Actions data resolver.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CurrentAccountActionsResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private clientsService = inject(ClientsService);
|
||||||
|
private groupsService = inject(GroupsService);
|
||||||
|
private organizationService = inject(OrganizationService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current account actions data.
|
||||||
|
* @param {ActivatedRouteSnapshot} route Route Snapshot
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const actionName = route.paramMap.get('name');
|
||||||
|
const currentAccountId =
|
||||||
|
route.paramMap.get('currentAccountId') || route.parent.parent.paramMap.get('currentAccountId');
|
||||||
|
switch (actionName) {
|
||||||
|
case 'Assign Staff':
|
||||||
|
return this.currentAccountsService.getCurrentAccountData(currentAccountId).pipe(
|
||||||
|
switchMap((account: any) => {
|
||||||
|
if (!account.clientId && !account.groupId) {
|
||||||
|
return of({ ...account, fieldOfficerOptions: [] });
|
||||||
|
}
|
||||||
|
const entityObs = account.clientId
|
||||||
|
? this.clientsService.getClientData(account.clientId)
|
||||||
|
: this.groupsService.getGroupData(account.groupId);
|
||||||
|
return entityObs.pipe(
|
||||||
|
switchMap((entity: any) => this.organizationService.getStaff(entity.officeId, true)),
|
||||||
|
map((staff: any) => ({
|
||||||
|
...account,
|
||||||
|
fieldOfficerOptions: staff
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
case 'Add Charge':
|
||||||
|
return this.currentAccountsService.getCurrentAccountChargeTemplateResource(currentAccountId);
|
||||||
|
case 'Withdrawal':
|
||||||
|
case 'Deposit':
|
||||||
|
case 'Hold Amount':
|
||||||
|
return this.currentAccountsService.getCurrentAccountTransactionTemplateResource(currentAccountId);
|
||||||
|
case 'Close':
|
||||||
|
return forkJoin([
|
||||||
|
this.currentAccountsService.getCurrentAccountTransactionTemplateResource(currentAccountId),
|
||||||
|
this.currentAccountsService.getCurrentAccountData(currentAccountId)
|
||||||
|
]);
|
||||||
|
case 'Apply Annual Fees':
|
||||||
|
return this.currentAccountsService.getCurrentAccountData(currentAccountId);
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Angular Imports */
|
||||||
|
import { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account data and template resolver.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CurrentAccountAndTemplateResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current Account data and template.
|
||||||
|
* @param {ActivatedRouteSnapshot} route Route Snapshot
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const currentAccountId = route.paramMap.get('currentAccountId');
|
||||||
|
return this.currentAccountsService.getCurrentAccountAndTemplate(currentAccountId, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Charge data resolver.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CurrentAccountChargeResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current Account Charge data.
|
||||||
|
* @param {ActivatedRouteSnapshot} route Route Snapshot
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const currentAccountId = route.parent.paramMap.get('currentAccountId');
|
||||||
|
const chargeId = route.paramMap.get('id');
|
||||||
|
return this.currentAccountsService.getCurrentAccountCharge(currentAccountId, chargeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Template resolver.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CurrentAccountTemplateResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Shares Account Template.
|
||||||
|
* @param {ActivatedRouteSnapshot} route Route Snapshot
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const entityId = route.paramMap.get('clientId') || route.paramMap.get('groupId') || route.paramMap.get('centerId');
|
||||||
|
const isGroup = route.paramMap.get('groupId') || route.paramMap.get('centerId') ? true : false;
|
||||||
|
return this.currentAccountsService.getCurrentAccountTemplate(entityId, undefined, isGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Transaction Template data resolver.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CurrentAccountTransactionTemplateResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current Account Transaction Template data.
|
||||||
|
* @param {ActivatedRouteSnapshot} route Route Snapshot
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const currentAccountId = route.parent.paramMap.get('currentAccountId');
|
||||||
|
const transactionId = route.paramMap.get('id');
|
||||||
|
return this.currentAccountsService.getCurrentAccountTransactionTemplate(currentAccountId, transactionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Transaction data resolver.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CurrentAccountTransactionResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current Account Transaction data.
|
||||||
|
* @param {ActivatedRouteSnapshot} route Route Snapshot
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const currentAccountId = route.parent.paramMap.get('currentAccountId');
|
||||||
|
const transactionId = route.parent.paramMap.get('id');
|
||||||
|
return this.currentAccountsService.getCurrentAccountTransaction(currentAccountId, transactionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Angular Imports */
|
||||||
|
import { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account data resolver.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CurrentAccountViewResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current Account data.
|
||||||
|
* @param {ActivatedRouteSnapshot} route Route Snapshot
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const currentAccountId = route.paramMap.get('currentAccountId');
|
||||||
|
return this.currentAccountsService.getCurrentAccountData(currentAccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Datatable data resolver.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CurrentAccountDatatableResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current Account's Datatable data.
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const accountId =
|
||||||
|
route.parent.parent.paramMap.get('currentAccountId') ||
|
||||||
|
route.parent.parent.paramMap.get('fixedDepositAccountId') ||
|
||||||
|
route.parent.parent.paramMap.get('recurringDepositAccountId');
|
||||||
|
const datatableName = route.paramMap.get('datatableName');
|
||||||
|
return this.currentAccountsService.getCurrentAccountDatatable(accountId, datatableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Angular Imports */
|
||||||
|
import { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Accounts Datatables data resolver.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CurrentAccountDatatablesResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current Account's Datatables data.
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(): Observable<any> {
|
||||||
|
return this.currentAccountsService.getCurrentAccountDatatables();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CurrentAccountDocumentsResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current Account data.
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const currentAccountId = route.parent.paramMap.get('currentAccountId');
|
||||||
|
return this.currentAccountsService.getCurrentAccountDocuments(currentAccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CurrentAccountNotesResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current Account data.
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const currentAccountId = route.parent.paramMap.get('currentAccountId');
|
||||||
|
return this.currentAccountsService.getCurrentAccountNotes(currentAccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
/** rxjs Imports */
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { ReportsService } from 'app/reports/reports.service';
|
||||||
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Transaction Reciept resolver.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CurrentAccountTransactionRecieptResolver {
|
||||||
|
private reportsService = inject(ReportsService);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Current Account Transaction Reciept
|
||||||
|
* @param {ActivatedRouteSnapshot} route Route Snapshot
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const transactionId = route.paramMap.get('id');
|
||||||
|
const data = {
|
||||||
|
'output-type': 'PDF',
|
||||||
|
R_transactionId: transactionId
|
||||||
|
};
|
||||||
|
return this.reportsService.getPentahoRunReportData(
|
||||||
|
'Current Account Transaction Receipt',
|
||||||
|
data,
|
||||||
|
'default',
|
||||||
|
this.settingsService.language.code,
|
||||||
|
this.settingsService.dateFormat
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Angular Imports
|
||||||
|
import { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
// rxjs Imports
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
// Custom Service
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionDatatableResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Transactions Account's Datatable data.
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
const accountId = route.parent.parent.paramMap.get('id');
|
||||||
|
const datatableName = route.paramMap.get('datatableName');
|
||||||
|
return this.currentAccountsService.getCurrentAccountTransactionDatatable(accountId, datatableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Angular Imports
|
||||||
|
import { Injectable, inject } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
// rxjs Imports
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
// Custom Service
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionDatatablesResolver {
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param route
|
||||||
|
* @returns {Observable<any>}
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
return this.currentAccountsService.getCurrentAccountTransactionDatatables();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<!--
|
||||||
|
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-stepper
|
||||||
|
orientation="vertical"
|
||||||
|
[linear]="false"
|
||||||
|
class="mat-elevation-z8"
|
||||||
|
labelPosition="bottom"
|
||||||
|
#currentAccountProductStepper
|
||||||
|
>
|
||||||
|
<ng-template matStepperIcon="number">
|
||||||
|
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template matStepperIcon="edit">
|
||||||
|
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template matStepperIcon="done">
|
||||||
|
<fa-icon icon="check" size="sm"></fa-icon>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template matStepperIcon="error">
|
||||||
|
<fa-icon icon="exclamation-triangle" size="lg"></fa-icon>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template matStepperIcon="preview">
|
||||||
|
<fa-icon icon="eye" size="sm"></fa-icon>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<mat-step [stepControl]="currentAccountDetailsForm">
|
||||||
|
<ng-template matStepLabel>{{ 'labels.inputs.DETAILS' | translate }}</ng-template>
|
||||||
|
|
||||||
|
<mifosx-current-account-details-step
|
||||||
|
[currentAccountTemplate]="currentAccountTemplate"
|
||||||
|
(currentAccountProductTemplate)="setTemplate($event)"
|
||||||
|
>
|
||||||
|
</mifosx-current-account-details-step>
|
||||||
|
</mat-step>
|
||||||
|
|
||||||
|
<mat-step [stepControl]="currentAccountTermsForm">
|
||||||
|
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
|
||||||
|
|
||||||
|
<mifosx-current-account-terms-step
|
||||||
|
[currentAccountProductTemplate]="currentAccountProductTemplate"
|
||||||
|
></mifosx-current-account-terms-step>
|
||||||
|
</mat-step>
|
||||||
|
|
||||||
|
<mat-step>
|
||||||
|
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
|
||||||
|
|
||||||
|
<mifosx-current-account-charges-step
|
||||||
|
[currentAccountProductTemplate]="currentAccountProductTemplate"
|
||||||
|
[currentAccountTemplate]="currentAccountTemplate"
|
||||||
|
[currencyCode]="currentAccountTermsForm.get('currencyCode')"
|
||||||
|
>
|
||||||
|
</mifosx-current-account-charges-step>
|
||||||
|
</mat-step>
|
||||||
|
|
||||||
|
@if (currentAccountFormValid) {
|
||||||
|
<mat-step completed>
|
||||||
|
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
|
||||||
|
<mifosx-current-account-preview-step
|
||||||
|
[currentAccountProductTemplate]="currentAccountProductTemplate"
|
||||||
|
[currentAccountTemplate]="currentAccountTemplate"
|
||||||
|
[currentAccountTermsForm]="currentAccountTermsForm"
|
||||||
|
[currentAccount]="currentAccount"
|
||||||
|
(submitEvent)="submit()"
|
||||||
|
>
|
||||||
|
</mifosx-current-account-preview-step>
|
||||||
|
</mat-step>
|
||||||
|
}
|
||||||
|
</mat-stepper>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* 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 styling with proper spacing and margins
|
||||||
|
.container {
|
||||||
|
margin: 1.5rem auto;
|
||||||
|
max-width: 75rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Material Stepper styling with proper spacing
|
||||||
|
:host ::ng-deep mat-stepper {
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
.mat-step-header {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-step-content {
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form field spacing within steps
|
||||||
|
:host ::ng-deep mat-step-content {
|
||||||
|
mat-form-field {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-row-wrap {
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsive design
|
||||||
|
@media (width <= 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 0 1rem;
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep mat-stepper {
|
||||||
|
.mat-step-content {
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-step-header {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 480px) {
|
||||||
|
.container {
|
||||||
|
padding: 0 0.75rem;
|
||||||
|
margin: 0.75rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep mat-stepper {
|
||||||
|
.mat-step-content {
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* Copyright since 2025 Mifos Initiative
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Angular Imports */
|
||||||
|
import { Component, ViewChild, inject } from '@angular/core';
|
||||||
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
/** Custom Components */
|
||||||
|
import { CurrentAccountDetailsStepComponent } from '../current-account-stepper/current-account-details-step/current-account-details-step.component';
|
||||||
|
import { CurrentAccountTermsStepComponent } from '../current-account-stepper/current-account-terms-step/current-account-terms-step.component';
|
||||||
|
import { CurrentAccountChargesStepComponent } from '../current-account-stepper/current-account-charges-step/current-account-charges-step.component';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../current-accounts.service';
|
||||||
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
import { Dates } from 'app/core/utils/dates';
|
||||||
|
import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { CurrentAccountPreviewStepComponent } from '../current-account-stepper/current-account-preview-step/current-account-preview-step.component';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Current Account Component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-create-current-account-account',
|
||||||
|
templateUrl: './create-current-account.component.html',
|
||||||
|
styleUrls: ['./create-current-account.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
MatStepper,
|
||||||
|
MatStepperIcon,
|
||||||
|
FaIconComponent,
|
||||||
|
MatStep,
|
||||||
|
MatStepLabel,
|
||||||
|
CurrentAccountDetailsStepComponent,
|
||||||
|
CurrentAccountTermsStepComponent,
|
||||||
|
CurrentAccountChargesStepComponent,
|
||||||
|
CurrentAccountPreviewStepComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CreateCurrentAccountComponent {
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Current Account Template */
|
||||||
|
currentAccountTemplate: any;
|
||||||
|
/** Current Account Product Template */
|
||||||
|
currentAccountProductTemplate: any;
|
||||||
|
|
||||||
|
/** Current Account Details Step */
|
||||||
|
@ViewChild(CurrentAccountDetailsStepComponent, { static: true })
|
||||||
|
currentAccountDetailsStep: CurrentAccountDetailsStepComponent;
|
||||||
|
/** Current Account Terms Step */
|
||||||
|
@ViewChild(CurrentAccountTermsStepComponent, { static: true })
|
||||||
|
currentAccountTermsStep: CurrentAccountTermsStepComponent;
|
||||||
|
/** Current Account Charges Step */
|
||||||
|
@ViewChild(CurrentAccountChargesStepComponent, { static: true })
|
||||||
|
currentAccountChargesStep: CurrentAccountChargesStepComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches current account template from `resolve`
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {SettingsService} settingsService Settings Service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.route.data.subscribe((data: { currentAccountTemplate: any }) => {
|
||||||
|
this.currentAccountTemplate = data.currentAccountTemplate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets current account product template.
|
||||||
|
* @param {any} $event API response
|
||||||
|
*/
|
||||||
|
setTemplate($event: any) {
|
||||||
|
this.currentAccountProductTemplate = $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves current account details form.
|
||||||
|
*/
|
||||||
|
get currentAccountDetailsForm() {
|
||||||
|
return this.currentAccountDetailsStep.currentAccountDetailsForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves current account terms form.
|
||||||
|
*/
|
||||||
|
get currentAccountTermsForm() {
|
||||||
|
return this.currentAccountTermsStep.currentAccountTermsForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks validity of overall current account form.
|
||||||
|
*/
|
||||||
|
get currentAccountFormValid() {
|
||||||
|
return this.currentAccountDetailsForm.valid && this.currentAccountTermsForm.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves current account object.
|
||||||
|
*/
|
||||||
|
get currentAccount() {
|
||||||
|
return {
|
||||||
|
...this.currentAccountDetailsStep.currentAccountDetails,
|
||||||
|
...this.currentAccountTermsStep.currentAccountTerms,
|
||||||
|
...this.currentAccountChargesStep.currentAccountCharges
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new share account.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
// TODO: Update once language and date settings are setup
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const monthDayFormat = 'dd MMMM';
|
||||||
|
const currentAccount = {
|
||||||
|
...this.currentAccount,
|
||||||
|
charges: this.currentAccount.charges.map((charge: any) => ({
|
||||||
|
chargeId: charge.id,
|
||||||
|
amount: charge.amount,
|
||||||
|
dueDate: charge.dueDate ? this.dateUtils.formatDate(charge.dueDate, dateFormat) : charge.dueDate,
|
||||||
|
feeOnMonthDay: charge.feeOnMonthDay
|
||||||
|
? this.dateUtils.formatDate(charge.feeOnMonthDay, monthDayFormat)
|
||||||
|
: charge.feeOnMonthDay,
|
||||||
|
feeInterval: charge.feeInterval
|
||||||
|
})),
|
||||||
|
submittedOnDate: this.dateUtils.formatDate(this.currentAccount.submittedOnDate, dateFormat),
|
||||||
|
dateFormat,
|
||||||
|
monthDayFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
if (this.currentAccountTemplate.clientId) {
|
||||||
|
currentAccount.clientId = this.currentAccountTemplate.clientId;
|
||||||
|
} else {
|
||||||
|
currentAccount.groupId = this.currentAccountTemplate.groupId;
|
||||||
|
}
|
||||||
|
this.currentAccountsService.createCurrentAccount(currentAccount).subscribe((response: any) => {
|
||||||
|
this.router.navigate(
|
||||||
|
[
|
||||||
|
'../',
|
||||||
|
response.resourceId
|
||||||
|
],
|
||||||
|
{ relativeTo: this.route }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="activateCurrentAccountForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="layout-column">
|
||||||
|
<mat-form-field class="flex-fill" (click)="activatedOnDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Activated On Date' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="activatedOnDatePicker"
|
||||||
|
required
|
||||||
|
formControlName="activatedOnDate"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="activatedOnDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #activatedOnDatePicker></mat-datepicker>
|
||||||
|
@if (activateCurrentAccountForm.controls.activatedOnDate.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Activated On Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../transactions']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary" [disabled]="!activateCurrentAccountForm.valid">
|
||||||
|
{{ 'labels.buttons.Confirm' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</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 {
|
||||||
|
max-width: 37rem;
|
||||||
|
}
|
||||||
+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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Angular Imports */
|
||||||
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
|
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, 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 { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate Current Account Component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-activate-current-account',
|
||||||
|
templateUrl: './activate-current-account.component.html',
|
||||||
|
styleUrls: ['./activate-current-account.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ActivateCurrentAccountComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Activate Current Account form. */
|
||||||
|
activateCurrentAccountForm: UntypedFormGroup;
|
||||||
|
/** Current Account Id */
|
||||||
|
accountId: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.accountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the activate current account form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.createActivateCurrentAccountForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the activate current account form.
|
||||||
|
*/
|
||||||
|
createActivateCurrentAccountForm() {
|
||||||
|
this.activateCurrentAccountForm = this.formBuilder.group({
|
||||||
|
activatedOnDate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form and activates the current account,
|
||||||
|
* if successful redirects to the current account.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const activateCurrentAccountFormData = this.activateCurrentAccountForm.value;
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevActivatedOnDate: Date = this.activateCurrentAccountForm.value.activatedOnDate;
|
||||||
|
if (activateCurrentAccountFormData.activatedOnDate instanceof Date) {
|
||||||
|
activateCurrentAccountFormData.activatedOnDate = this.dateUtils.formatDate(prevActivatedOnDate, dateFormat);
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
...activateCurrentAccountFormData,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.currentAccountsService.executeCurrentAccountCommand(this.accountId, 'activate', data).subscribe(() => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="currentAccountChargeForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="layout-column">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
|
||||||
|
<mat-select required formControlName="chargeId">
|
||||||
|
@for (currentAccountCharge of currentAccountChargeOptions; track currentAccountCharge) {
|
||||||
|
<mat-option [value]="currentAccountCharge.id">
|
||||||
|
{{ currentAccountCharge.name + ' (' + currentAccountCharge.currency.name + ')' }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
@if (currentAccountChargeForm.controls.chargeId.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Charge' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
@if (chargeDetails) {
|
||||||
|
<div class="layout-column">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
|
||||||
|
<input type="number" required matInput formControlName="amount" />
|
||||||
|
@if (currentAccountChargeForm.controls.amount.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Charge Calculation' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="chargeCalculationType">
|
||||||
|
@for (chargeCalculation of chargeDetails.chargeCalculationTypeOptions; track chargeCalculation) {
|
||||||
|
<mat-option [value]="chargeCalculation.id">
|
||||||
|
{{ chargeCalculation.value }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Charge time type' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="chargeTimeType">
|
||||||
|
@for (chargeTime of chargeDetails.chargeTimeTypeOptions; track chargeTime) {
|
||||||
|
<mat-option [value]="chargeTime.id">
|
||||||
|
{{ chargeTime.value }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
@if (currentAccountChargeForm.contains('dueDate')) {
|
||||||
|
<mat-form-field (click)="dueDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Due for collection on' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="dueDatePicker"
|
||||||
|
required
|
||||||
|
formControlName="dueDate"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="dueDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #dueDatePicker></mat-datepicker>
|
||||||
|
@if (currentAccountChargeForm.controls.dueDate.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Due for collection on' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
@if (currentAccountChargeForm.contains('feeOnMonthDay')) {
|
||||||
|
<mat-form-field (click)="feeOnMonthDayPicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Due On' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="feeOnMonthDayPicker"
|
||||||
|
required
|
||||||
|
formControlName="feeOnMonthDay"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="feeOnMonthDayPicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #feeOnMonthDayPicker></mat-datepicker>
|
||||||
|
@if (currentAccountChargeForm.controls.feeOnMonthDay.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Due Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
@if (currentAccountChargeForm.contains('feeInterval')) {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Repeats Every' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="feeInterval" />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row layout-xs-column layout-align-center gap-5px">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../transactions']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary" [disabled]="!currentAccountChargeForm.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;
|
||||||
|
}
|
||||||
+174
@@ -0,0 +1,174 @@
|
|||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../../current-accounts.service';
|
||||||
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
import { Dates } from 'app/core/utils/dates';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Current Account Charge component.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-add-charge-current-account',
|
||||||
|
templateUrl: './add-charge-current-account.component.html',
|
||||||
|
styleUrls: ['./add-charge-current-account.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddChargeCurrentAccountComponent 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();
|
||||||
|
/** Add Current Account Charge form. */
|
||||||
|
currentAccountChargeForm: UntypedFormGroup;
|
||||||
|
/** current account charge options. */
|
||||||
|
currentAccountChargeOptions: any;
|
||||||
|
/** current account Id of the current account. */
|
||||||
|
currentAccountId: string;
|
||||||
|
/** charge details */
|
||||||
|
chargeDetails: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves charge template data from `resolve`
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {SettingsService} settingsService Settings Service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.route.data.subscribe((data: { currentAccountActionData: any }) => {
|
||||||
|
this.currentAccountChargeOptions = data.currentAccountActionData.chargeOptions;
|
||||||
|
});
|
||||||
|
this.currentAccountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Current Account Charge form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.createCurrentAccountChargeForm();
|
||||||
|
this.buildDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDependencies() {
|
||||||
|
this.currentAccountChargeForm.controls.chargeId.valueChanges.subscribe((chargeId) => {
|
||||||
|
this.currentAccountsService.getChargeTemplate(chargeId).subscribe((data: any) => {
|
||||||
|
this.chargeDetails = data;
|
||||||
|
const chargeTimeType = data.chargeTimeType.id;
|
||||||
|
if (
|
||||||
|
data.chargeTimeType.value === 'Withdrawal Fee' ||
|
||||||
|
data.chargeTimeType.value === 'Current Account No Activity Fee'
|
||||||
|
) {
|
||||||
|
this.chargeDetails.dueDateNotRequired = true;
|
||||||
|
}
|
||||||
|
if (data.chargeTimeType.value === 'Annual Fee' || data.chargeTimeType.value === 'Monthly Fee') {
|
||||||
|
this.chargeDetails.chargeTimeTypeAnnualOrMonth = true;
|
||||||
|
}
|
||||||
|
if (!this.chargeDetails.dueDateNotRequired && !this.chargeDetails.chargeTimeTypeAnnualOrMonth) {
|
||||||
|
this.currentAccountChargeForm.addControl('dueDate', new UntypedFormControl('', Validators.required));
|
||||||
|
} else {
|
||||||
|
this.currentAccountChargeForm.removeControl('dueDate');
|
||||||
|
}
|
||||||
|
if (!this.chargeDetails.dueDateNotRequired && this.chargeDetails.chargeTimeTypeAnnualOrMonth) {
|
||||||
|
this.currentAccountChargeForm.addControl('feeOnMonthDay', new UntypedFormControl('', Validators.required));
|
||||||
|
} else {
|
||||||
|
this.currentAccountChargeForm.removeControl('feeOnMonthDay');
|
||||||
|
}
|
||||||
|
if (chargeTimeType.value === 'Monthly Fee') {
|
||||||
|
this.currentAccountChargeForm.addControl(
|
||||||
|
'feeInterval',
|
||||||
|
new UntypedFormControl(data.feeInterval, Validators.required)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.currentAccountChargeForm.removeControl('feeInterval');
|
||||||
|
}
|
||||||
|
this.currentAccountChargeForm.patchValue({
|
||||||
|
amount: data.amount,
|
||||||
|
chargeCalculationType: data.chargeCalculationType.id,
|
||||||
|
chargeTimeType: data.chargeTimeType.id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Current Account Charge form.
|
||||||
|
*/
|
||||||
|
createCurrentAccountChargeForm() {
|
||||||
|
this.currentAccountChargeForm = this.formBuilder.group({
|
||||||
|
chargeId: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
amount: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
chargeCalculationType: [{ value: '', disabled: true }],
|
||||||
|
chargeTimeType: [{ value: '', disabled: true }]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits current account charge.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const currentAccountCharge = this.currentAccountChargeForm.value;
|
||||||
|
currentAccountCharge.locale = this.settingsService.language.code;
|
||||||
|
if (!currentAccountCharge.feeInterval) {
|
||||||
|
currentAccountCharge.feeInterval = this.chargeDetails.feeInterval;
|
||||||
|
}
|
||||||
|
if (this.chargeDetails.dueDateNotRequired !== true) {
|
||||||
|
if (this.chargeDetails.chargeTimeTypeAnnualOrMonth) {
|
||||||
|
const monthDayFormat = 'MMMM-dd'; // TODO: Update once language and date settings are setup
|
||||||
|
currentAccountCharge.monthDayFormat = monthDayFormat;
|
||||||
|
if (currentAccountCharge.feeOnMonthDay) {
|
||||||
|
const prevDate = this.currentAccountChargeForm.value.feeOnMonthDay;
|
||||||
|
currentAccountCharge.feeOnMonthDay = this.dateUtils.formatDate(prevDate, monthDayFormat);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
currentAccountCharge.dateFormat = dateFormat;
|
||||||
|
if (currentAccountCharge.dueDate) {
|
||||||
|
const prevDate = this.currentAccountChargeForm.value.dueDate;
|
||||||
|
currentAccountCharge.dueDate = this.dateUtils.formatDate(prevDate, dateFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.currentAccountsService
|
||||||
|
.createCurrentAccountCharge(this.currentAccountId, 'charges', currentAccountCharge)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="applyAnnualFeesForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="layout-column">
|
||||||
|
<mat-form-field (click)="dueDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Annual Fees Transaction Date' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="dueDatePicker"
|
||||||
|
required
|
||||||
|
formControlName="dueDate"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="dueDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #dueDatePicker></mat-datepicker>
|
||||||
|
@if (applyAnnualFeesForm.controls.dueDate.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Due Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
|
||||||
|
<input type="number" formControlName="amount" required matInput />
|
||||||
|
@if (applyAnnualFeesForm.controls.amount.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary" [disabled]="!applyAnnualFeesForm.valid">
|
||||||
|
{{ 'labels.buttons.Confirm' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</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 {
|
||||||
|
max-width: 37rem;
|
||||||
|
}
|
||||||
+125
@@ -0,0 +1,125 @@
|
|||||||
|
/**
|
||||||
|
* 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, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, 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 { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply Annual Fees Component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-apply-annual-fees-current-account',
|
||||||
|
templateUrl: './apply-annual-fees-current-account.component.html',
|
||||||
|
styleUrls: ['./apply-annual-fees-current-account.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ApplyAnnualFeesCurrentAccountComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Apply annual fees form. */
|
||||||
|
applyAnnualFeesForm: UntypedFormGroup;
|
||||||
|
/** Current Account Id */
|
||||||
|
accountId: any;
|
||||||
|
/** Annual Fees charge Id */
|
||||||
|
chargeId: any;
|
||||||
|
/** Current Account Data */
|
||||||
|
currentAccountData: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.accountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
this.route.data.subscribe((data: { currentAccountActionData: any }) => {
|
||||||
|
this.currentAccountData = data.currentAccountActionData;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the apply annual fees form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.createApplyAnnualFeesForm();
|
||||||
|
this.applyCharge();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the apply annual fees form.
|
||||||
|
*/
|
||||||
|
createApplyAnnualFeesForm() {
|
||||||
|
this.applyAnnualFeesForm = this.formBuilder.group({
|
||||||
|
dueDate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
amount: ['']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retireves apply annual fees charge for ID and amount.
|
||||||
|
*/
|
||||||
|
applyCharge() {
|
||||||
|
const charges: any[] = this.currentAccountData.charges;
|
||||||
|
charges.forEach((charge: any) => {
|
||||||
|
if (charge.name === 'Annual fee - INR') {
|
||||||
|
this.chargeId = charge.id;
|
||||||
|
this.applyAnnualFeesForm.get('amount').patchValue(charge.amount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form and applies the annual fees,
|
||||||
|
* if successful redirects to the current account.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const applyAnnualFeesFormData = this.applyAnnualFeesForm.value;
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevApprovedOnDate: Date = this.applyAnnualFeesForm.value.dueDate;
|
||||||
|
if (applyAnnualFeesFormData.dueDate instanceof Date) {
|
||||||
|
applyAnnualFeesFormData.dueDate = this.dateUtils.formatDate(prevApprovedOnDate, dateFormat);
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
...applyAnnualFeesFormData,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.currentAccountsService
|
||||||
|
.executeCurrentAccountChargesCommand(this.accountId, 'paycharge', data, this.chargeId)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.router.navigate(['../../'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="approveCurrentAccountForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="layout-column">
|
||||||
|
<mat-form-field (click)="approvedOnDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Approved On Date' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="approvedOnDatePicker"
|
||||||
|
required
|
||||||
|
formControlName="approvedOnDate"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="approvedOnDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #approvedOnDatePicker></mat-datepicker>
|
||||||
|
@if (approveCurrentAccountForm.controls.approvedOnDate.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Approved On Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Note' | translate }}</mat-label>
|
||||||
|
<textarea matInput formControlName="note" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary" [disabled]="!approveCurrentAccountForm.valid">
|
||||||
|
{{ 'labels.buttons.Confirm' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</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 {
|
||||||
|
max-width: 37rem;
|
||||||
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* 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, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, 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 { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Approve Current Account Component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-approve-current-account',
|
||||||
|
templateUrl: './approve-current-account.component.html',
|
||||||
|
styleUrls: ['./approve-current-account.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
CdkTextareaAutosize
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ApproveCurrentAccountComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Approve Current Account form. */
|
||||||
|
approveCurrentAccountForm: UntypedFormGroup;
|
||||||
|
/** Current Account Id */
|
||||||
|
accountId: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.accountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the approve current account form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.createApproveCurrentAccountForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the approve current account form.
|
||||||
|
*/
|
||||||
|
createApproveCurrentAccountForm() {
|
||||||
|
this.approveCurrentAccountForm = this.formBuilder.group({
|
||||||
|
approvedOnDate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
note: ['']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form and approves the current account,
|
||||||
|
* if successful redirects to the current account.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const approveCurrentAccountFormData = this.approveCurrentAccountForm.value;
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevApprovedOnDate: Date = this.approveCurrentAccountForm.value.approvedOnDate;
|
||||||
|
if (approveCurrentAccountFormData.approvedOnDate instanceof Date) {
|
||||||
|
approveCurrentAccountFormData.approvedOnDate = this.dateUtils.formatDate(prevApprovedOnDate, dateFormat);
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
...approveCurrentAccountFormData,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.currentAccountsService.executeCurrentAccountCommand(this.accountId, 'approve', data).subscribe(() => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="closeCurrentAccountForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="layout-column">
|
||||||
|
<mat-form-field (click)="closedOnDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Closed On Date' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="closedOnDatePicker"
|
||||||
|
required
|
||||||
|
formControlName="closedOnDate"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="closedOnDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #closedOnDatePicker></mat-datepicker>
|
||||||
|
@if (closeCurrentAccountForm.controls.closedOnDate.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Closed On Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-checkbox class="m-b-25 m-t-15" formControlName="withdrawBalance">
|
||||||
|
{{ 'labels.inputs.Withdraw Balance' | translate }}
|
||||||
|
</mat-checkbox>
|
||||||
|
|
||||||
|
<mat-checkbox formControlName="postInterestValidationOnClosure">
|
||||||
|
{{ 'labels.inputs.Is Interest Posting required on closure date' | translate }}?
|
||||||
|
</mat-checkbox>
|
||||||
|
|
||||||
|
@if (closeCurrentAccountForm.controls['withdrawBalance'].value) {
|
||||||
|
<div class="layout-column">
|
||||||
|
@if (closeCurrentAccountForm.controls['amount']) {
|
||||||
|
<mat-form-field class="m-t-20">
|
||||||
|
<mat-label>{{ 'labels.inputs.Transaction Amount' | translate }}</mat-label>
|
||||||
|
<input type="number" required matInput formControlName="amount" />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
@if (closeCurrentAccountForm.controls['paymentTypeId']) {
|
||||||
|
<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 (closeCurrentAccountForm.controls['accountNumber']) {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Account Number' | translate }}</mat-label>
|
||||||
|
<input type="number" formControlName="accountNumber" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
@if (closeCurrentAccountForm.controls['checkNumber']) {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Cheque' | translate }}</mat-label>
|
||||||
|
<input type="number" formControlName="checkNumber" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
@if (closeCurrentAccountForm.controls['routingCode']) {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Routing Code' | translate }}</mat-label>
|
||||||
|
<input formControlName="routingCode" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
@if (closeCurrentAccountForm.controls['receiptNumber']) {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Receipt Number' | translate }}</mat-label>
|
||||||
|
<input formControlName="receiptNumber" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
@if (closeCurrentAccountForm.controls['bankNumber']) {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Bank' | translate }}</mat-label>
|
||||||
|
<input formControlName="bankNumber" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Note' | translate }}</mat-label>
|
||||||
|
<textarea matInput formControlName="note" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="warn" [disabled]="!closeCurrentAccountForm.valid">
|
||||||
|
{{ 'labels.buttons.Confirm' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</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 {
|
||||||
|
max-width: 37rem;
|
||||||
|
}
|
||||||
+165
@@ -0,0 +1,165 @@
|
|||||||
|
/**
|
||||||
|
* 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 { ActivatedRoute, Router, 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 { MatCheckbox } from '@angular/material/checkbox';
|
||||||
|
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
||||||
|
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close Current Account Component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-close-current-account',
|
||||||
|
templateUrl: './close-current-account.component.html',
|
||||||
|
styleUrls: ['./close-current-account.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
MatCheckbox,
|
||||||
|
MatSlideToggle,
|
||||||
|
CdkTextareaAutosize
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CloseCurrentAccountComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Close Current Account form. */
|
||||||
|
closeCurrentAccountForm: UntypedFormGroup;
|
||||||
|
/** Current Account Id */
|
||||||
|
accountId: any;
|
||||||
|
/** Flag to enable payment details fields. */
|
||||||
|
addPaymentDetailsFlag = false;
|
||||||
|
/** Payment Type Options */
|
||||||
|
paymentTypeOptions: any;
|
||||||
|
/** Transaction Amount */
|
||||||
|
transactionAmount: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.route.data.subscribe((data: { currentAccountActionData: any }) => {
|
||||||
|
this.paymentTypeOptions = data.currentAccountActionData[0].paymentTypeOptions;
|
||||||
|
this.transactionAmount = data.currentAccountActionData[1].summary.accountBalance;
|
||||||
|
});
|
||||||
|
this.accountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the close current account form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.createCloseCurrentAccountForm();
|
||||||
|
this.buildDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the close current account form.
|
||||||
|
*/
|
||||||
|
createCloseCurrentAccountForm() {
|
||||||
|
this.closeCurrentAccountForm = this.formBuilder.group({
|
||||||
|
closedOnDate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
withdrawBalance: [false],
|
||||||
|
postInterestValidationOnClosure: [false],
|
||||||
|
note: ['']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to value changes of withdraw balance checkbox.
|
||||||
|
*/
|
||||||
|
buildDependencies() {
|
||||||
|
this.closeCurrentAccountForm.get('withdrawBalance').valueChanges.subscribe((value: boolean) => {
|
||||||
|
if (value) {
|
||||||
|
this.closeCurrentAccountForm.addControl(
|
||||||
|
'amount',
|
||||||
|
new UntypedFormControl({ value: this.transactionAmount, disabled: true })
|
||||||
|
);
|
||||||
|
this.closeCurrentAccountForm.addControl('paymentTypeId', new UntypedFormControl(''));
|
||||||
|
} else {
|
||||||
|
this.closeCurrentAccountForm.removeControl('amount');
|
||||||
|
this.closeCurrentAccountForm.removeControl('paymentTypeId');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to add payment detail fields to the UI.
|
||||||
|
*/
|
||||||
|
addPaymentDetails() {
|
||||||
|
this.addPaymentDetailsFlag = !this.addPaymentDetailsFlag;
|
||||||
|
if (this.addPaymentDetailsFlag) {
|
||||||
|
this.closeCurrentAccountForm.addControl('accountNumber', new UntypedFormControl(''));
|
||||||
|
this.closeCurrentAccountForm.addControl('checkNumber', new UntypedFormControl(''));
|
||||||
|
this.closeCurrentAccountForm.addControl('routingCode', new UntypedFormControl(''));
|
||||||
|
this.closeCurrentAccountForm.addControl('receiptNumber', new UntypedFormControl(''));
|
||||||
|
this.closeCurrentAccountForm.addControl('bankNumber', new UntypedFormControl(''));
|
||||||
|
} else {
|
||||||
|
this.closeCurrentAccountForm.removeControl('accountNumber');
|
||||||
|
this.closeCurrentAccountForm.removeControl('checkNumber');
|
||||||
|
this.closeCurrentAccountForm.removeControl('routingCode');
|
||||||
|
this.closeCurrentAccountForm.removeControl('receiptNumber');
|
||||||
|
this.closeCurrentAccountForm.removeControl('bankNumber');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form and closes the current account,
|
||||||
|
* if successful redirects to the current account.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const closeCurrentAccountFormData = this.closeCurrentAccountForm.value;
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevClosedOnDate: Date = this.closeCurrentAccountForm.value.closedOnDate;
|
||||||
|
if (closeCurrentAccountFormData.closedOnDate instanceof Date) {
|
||||||
|
closeCurrentAccountFormData.closedOnDate = this.dateUtils.formatDate(prevClosedOnDate, dateFormat);
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
...closeCurrentAccountFormData,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.currentAccountsService.executeCurrentAccountCommand(this.accountId, 'close', data).subscribe(() => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
<!--
|
||||||
|
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 (actions['Approve']) {
|
||||||
|
<mifosx-approve-current-account></mifosx-approve-current-account>
|
||||||
|
}
|
||||||
|
@if (actions['Reject']) {
|
||||||
|
<mifosx-reject-current-account></mifosx-reject-current-account>
|
||||||
|
}
|
||||||
|
@if (actions['Activate']) {
|
||||||
|
<mifosx-activate-current-account></mifosx-activate-current-account>
|
||||||
|
}
|
||||||
|
@if (actions['Undo Approval']) {
|
||||||
|
<mifosx-undo-approval-current-account></mifosx-undo-approval-current-account>
|
||||||
|
}
|
||||||
|
@if (actions['Post Interest As On']) {
|
||||||
|
<mifosx-post-interest-as-on-current-account></mifosx-post-interest-as-on-current-account>
|
||||||
|
}
|
||||||
|
@if (actions['Assign Staff']) {
|
||||||
|
<mifosx-current-account-assign-staff></mifosx-current-account-assign-staff>
|
||||||
|
}
|
||||||
|
@if (actions['Unassign Staff']) {
|
||||||
|
<mifosx-current-account-unassign-staff></mifosx-current-account-unassign-staff>
|
||||||
|
}
|
||||||
|
@if (actions['Withdrawn by Client']) {
|
||||||
|
<mifosx-withdraw-by-client-current-account></mifosx-withdraw-by-client-current-account>
|
||||||
|
}
|
||||||
|
@if (actions['Add Charge']) {
|
||||||
|
<mifosx-add-charge-current-account></mifosx-add-charge-current-account>
|
||||||
|
}
|
||||||
|
@if (actions['Withdrawal'] || actions['Deposit']) {
|
||||||
|
<mifosx-current-account-transactions></mifosx-current-account-transactions>
|
||||||
|
}
|
||||||
|
@if (actions['Close']) {
|
||||||
|
<mifosx-close-current-account></mifosx-close-current-account>
|
||||||
|
}
|
||||||
|
@if (actions['Apply Annual Fees']) {
|
||||||
|
<mifosx-apply-annual-fees-current-account></mifosx-apply-annual-fees-current-account>
|
||||||
|
}
|
||||||
|
@if (actions['Hold Amount'] || actions['Block Account'] || actions['Block Deposit'] || actions['Block Withdrawal']) {
|
||||||
|
<mifosx-manage-current-account [currency]="currency"> </mifosx-manage-current-account>
|
||||||
|
}
|
||||||
+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/.
|
||||||
|
*/
|
||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Currency } from 'app/shared/models/general.model';
|
||||||
|
import { ApproveCurrentAccountComponent } from './approve-current-account/approve-current-account.component';
|
||||||
|
import { RejectCurrentAccountComponent } from './reject-current-account/reject-current-account.component';
|
||||||
|
import { ActivateCurrentAccountComponent } from './activate-current-account/activate-current-account.component';
|
||||||
|
import { UndoApprovalCurrentAccountComponent } from './undo-approval-current-account/undo-approval-current-account.component';
|
||||||
|
import { PostInterestAsOnCurrentAccountComponent } from './post-interest-as-on-current-account/post-interest-as-on-current-account.component';
|
||||||
|
import { CurrentAccountAssignStaffComponent } from './current-account-assign-staff/current-account-assign-staff.component';
|
||||||
|
import { CurrentAccountUnassignStaffComponent } from './current-account-unassign-staff/current-account-unassign-staff.component';
|
||||||
|
import { WithdrawByClientCurrentAccountComponent } from './withdraw-by-client-current-account/withdraw-by-client-current-account.component';
|
||||||
|
import { AddChargeCurrentAccountComponent } from './add-charge-current-account/add-charge-current-account.component';
|
||||||
|
import { CurrentAccountTransactionsComponent } from './current-account-transactions/current-account-transactions.component';
|
||||||
|
import { CloseCurrentAccountComponent } from './close-current-account/close-current-account.component';
|
||||||
|
import { ApplyAnnualFeesCurrentAccountComponent } from './apply-annual-fees-current-account/apply-annual-fees-current-account.component';
|
||||||
|
import { ManageCurrentAccountComponent } from './manage-current-account/manage-current-account.component';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current account actions component.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-current-account-account-actions',
|
||||||
|
templateUrl: './current-account-actions.component.html',
|
||||||
|
styleUrls: ['./current-account-actions.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
ApproveCurrentAccountComponent,
|
||||||
|
RejectCurrentAccountComponent,
|
||||||
|
ActivateCurrentAccountComponent,
|
||||||
|
UndoApprovalCurrentAccountComponent,
|
||||||
|
PostInterestAsOnCurrentAccountComponent,
|
||||||
|
CurrentAccountAssignStaffComponent,
|
||||||
|
CurrentAccountUnassignStaffComponent,
|
||||||
|
WithdrawByClientCurrentAccountComponent,
|
||||||
|
AddChargeCurrentAccountComponent,
|
||||||
|
CurrentAccountTransactionsComponent,
|
||||||
|
CloseCurrentAccountComponent,
|
||||||
|
ApplyAnnualFeesCurrentAccountComponent,
|
||||||
|
ManageCurrentAccountComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CurrentAccountActionsComponent {
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
|
||||||
|
/** Flag object to store possible actions and render appropriate UI to the user */
|
||||||
|
actions: {
|
||||||
|
Approve: boolean;
|
||||||
|
Reject: boolean;
|
||||||
|
Withdrawal: boolean;
|
||||||
|
Deposit: boolean;
|
||||||
|
Activate: boolean;
|
||||||
|
Close: boolean;
|
||||||
|
'Undo Approval': boolean;
|
||||||
|
'Post Interest As On': boolean;
|
||||||
|
'Assign Staff': boolean;
|
||||||
|
'Add Charge': boolean;
|
||||||
|
'Unassign Staff': boolean;
|
||||||
|
'Withdrawn by Client': boolean;
|
||||||
|
'Apply Annual Fees': boolean;
|
||||||
|
'Hold Amount': boolean;
|
||||||
|
'Block Account': boolean;
|
||||||
|
'Unblock Account': boolean;
|
||||||
|
'Block Deposit': boolean;
|
||||||
|
'Unblock Deposit': boolean;
|
||||||
|
'Block Withdrawal': boolean;
|
||||||
|
'Unblock Withdrawal': boolean;
|
||||||
|
} = {
|
||||||
|
Approve: false,
|
||||||
|
Reject: false,
|
||||||
|
Withdrawal: false,
|
||||||
|
Deposit: false,
|
||||||
|
Activate: false,
|
||||||
|
Close: false,
|
||||||
|
'Undo Approval': false,
|
||||||
|
'Post Interest As On': false,
|
||||||
|
'Assign Staff': false,
|
||||||
|
'Add Charge': false,
|
||||||
|
'Unassign Staff': false,
|
||||||
|
'Withdrawn by Client': false,
|
||||||
|
'Apply Annual Fees': false,
|
||||||
|
'Hold Amount': false,
|
||||||
|
'Block Account': false,
|
||||||
|
'Unblock Account': false,
|
||||||
|
'Block Deposit': false,
|
||||||
|
'Unblock Deposit': false,
|
||||||
|
'Block Withdrawal': false,
|
||||||
|
'Unblock Withdrawal': false
|
||||||
|
};
|
||||||
|
|
||||||
|
currency: Currency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.route.data.subscribe((data: { currentAccountActionData: any }) => {
|
||||||
|
if (data.currentAccountActionData) {
|
||||||
|
this.currency = data.currentAccountActionData.currency;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const name = this.route.snapshot.params['name'] as string;
|
||||||
|
if (name in this.actions) {
|
||||||
|
this.actions[name as keyof typeof this.actions] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="currentAccountAssignStaffForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="layout-column">
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.To Current Account Officer' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="toCurrentAccountOfficerId">
|
||||||
|
@for (officer of fieldOfficerData; track officer) {
|
||||||
|
<mat-option [value]="officer.id">
|
||||||
|
{{ officer.displayName }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
@if (currentAccountAssignStaffForm.controls.toCurrentAccountOfficerId.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.To Current Account Officer' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field (click)="assignmentDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Assignment Date' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="assignmentDatePicker"
|
||||||
|
required
|
||||||
|
formControlName="assignmentDate"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="assignmentDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #assignmentDatePicker></mat-datepicker>
|
||||||
|
@if (currentAccountAssignStaffForm.controls.assignmentDate.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Assignment Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary" [disabled]="!currentAccountAssignStaffForm.valid">
|
||||||
|
{{ 'labels.buttons.Confirm' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</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 {
|
||||||
|
max-width: 37rem;
|
||||||
|
}
|
||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* 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 } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router } 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 { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Assign Staff Component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-current-account-account-assign-staff',
|
||||||
|
templateUrl: './current-account-assign-staff.component.html',
|
||||||
|
styleUrls: ['./current-account-assign-staff.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CurrentAccountAssignStaffComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Current Account Assign Staff form. */
|
||||||
|
currentAccountAssignStaffForm: UntypedFormGroup;
|
||||||
|
/** Current Account Id */
|
||||||
|
accountId: any;
|
||||||
|
/** Field Officer Data */
|
||||||
|
fieldOfficerData: any;
|
||||||
|
/** Current Account Data */
|
||||||
|
currentAccountData: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.accountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
this.route.data.subscribe((data: { currentAccountActionData: any }) => {
|
||||||
|
this.currentAccountData = data.currentAccountActionData;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the current account assign staff form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.fieldOfficerData = this.currentAccountData.fieldOfficerOptions;
|
||||||
|
this.createCurrentAccountAssignStaffForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the current account assign staff form.
|
||||||
|
*/
|
||||||
|
createCurrentAccountAssignStaffForm() {
|
||||||
|
this.currentAccountAssignStaffForm = this.formBuilder.group({
|
||||||
|
toCurrentAccountOfficerId: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
assignmentDate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form and assigns staff the current account,
|
||||||
|
* if successful redirects to the current account.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const formValue = this.currentAccountAssignStaffForm.value;
|
||||||
|
const assignmentDate =
|
||||||
|
formValue.assignmentDate instanceof Date
|
||||||
|
? this.dateUtils.formatDate(formValue.assignmentDate, dateFormat)
|
||||||
|
: formValue.assignmentDate;
|
||||||
|
const data = {
|
||||||
|
toCurrentAccountOfficerId: formValue.toCurrentAccountOfficerId,
|
||||||
|
assignmentDate,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.currentAccountsService
|
||||||
|
.executeCurrentAccountCommand(this.accountId, 'assignCurrentAccountOfficer', data)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+132
@@ -0,0 +1,132 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
@if (transactionType.withdrawal) {
|
||||||
|
<mat-card-title class="form-heading">
|
||||||
|
{{ 'labels.heading.Withdraw Money From Current Account' | translate }}
|
||||||
|
</mat-card-title>
|
||||||
|
}
|
||||||
|
@if (transactionType.deposit) {
|
||||||
|
<mat-card-title class="form-heading">
|
||||||
|
{{ 'labels.heading.Deposit Money To Current Account' | translate }}
|
||||||
|
</mat-card-title>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form [formGroup]="currentAccountTransactionForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<div 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 (currentAccountTransactionForm.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]="currentAccountTransactionForm.controls.transactionAmount"
|
||||||
|
[inputLabel]="'Transaction Amount'"
|
||||||
|
>
|
||||||
|
</mifosx-input-amount>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Payment Type' | translate }}</mat-label>
|
||||||
|
<mat-select required formControlName="paymentTypeId">
|
||||||
|
@for (paymentType of paymentTypeOptions; track paymentType) {
|
||||||
|
<mat-option [value]="paymentType.id">
|
||||||
|
{{ paymentType.name }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
@if (currentAccountTransactionForm.controls.paymentTypeId.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Payment Type' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</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]="addPaymentDetailsFlag ? 'enabled' : 'disabled'">
|
||||||
|
<span class="m-l-10">{{ 'labels.inputs.Show Payment Details' | translate }}</span>
|
||||||
|
</div>
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (addPaymentDetailsFlag) {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Account Number' | translate }}</mat-label>
|
||||||
|
<input type="number" formControlName="accountNumber" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (addPaymentDetailsFlag) {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Cheque' | translate }}</mat-label>
|
||||||
|
<input type="number" formControlName="checkNumber" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (addPaymentDetailsFlag) {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Routing Code' | translate }}</mat-label>
|
||||||
|
<input formControlName="routingCode" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (addPaymentDetailsFlag) {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Receipt Number' | translate }}</mat-label>
|
||||||
|
<input formControlName="receiptNumber" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (addPaymentDetailsFlag) {
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Bank' | translate }}</mat-label>
|
||||||
|
<input formControlName="bankNumber" matInput />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Notes' | translate }}</mat-label>
|
||||||
|
<textarea formControlName="note" matInput cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row layout-xs-column layout-align-center gap-5px">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../transactions']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary" [disabled]="!currentAccountTransactionForm.valid">
|
||||||
|
{{ 'labels.buttons.Submit' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</form>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.expandcollapsebutton {
|
||||||
|
margin-top: -7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 37rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-input {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-label {
|
||||||
|
padding-right: 25px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-heading {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
+169
@@ -0,0 +1,169 @@
|
|||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from '../../current-accounts.service';
|
||||||
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
import { Dates } from 'app/core/utils/dates';
|
||||||
|
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 { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create current account transactions component.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-current-account-transactions',
|
||||||
|
templateUrl: './current-account-transactions.component.html',
|
||||||
|
styleUrls: ['./current-account-transactions.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
InputAmountComponent,
|
||||||
|
MatSlideToggle,
|
||||||
|
CdkTextareaAutosize
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CurrentAccountTransactionsComponent 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. */
|
||||||
|
currentAccountTransactionForm: UntypedFormGroup;
|
||||||
|
/** current account transaction payment options. */
|
||||||
|
paymentTypeOptions: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
isCashPayment: boolean;
|
||||||
|
position: number;
|
||||||
|
}[];
|
||||||
|
/** Flag to enable payment details fields. */
|
||||||
|
addPaymentDetailsFlag: Boolean = false;
|
||||||
|
/** transaction type flag to render required UI */
|
||||||
|
transactionType: { deposit: boolean; withdrawal: boolean } = { deposit: false, withdrawal: false };
|
||||||
|
/** transaction command for submit request */
|
||||||
|
transactionCommand: string;
|
||||||
|
/** current account's Id */
|
||||||
|
currentAccountId: string;
|
||||||
|
currency: Currency | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Settings Service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.route.data.subscribe((data: { currentAccountActionData: any }) => {
|
||||||
|
this.paymentTypeOptions = data.currentAccountActionData.paymentTypeOptions;
|
||||||
|
if (data.currentAccountActionData.currency) {
|
||||||
|
this.currency = data.currentAccountActionData.currency;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.transactionCommand = this.route.snapshot.params['name'].toLowerCase();
|
||||||
|
this.transactionType[this.transactionCommand as 'deposit' | 'withdrawal'] = true;
|
||||||
|
this.currentAccountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Current Account account transaction form when component loads.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.createCurrentAccountTransactionForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to create the Current Account Transaction Form.
|
||||||
|
*/
|
||||||
|
createCurrentAccountTransactionForm() {
|
||||||
|
this.currentAccountTransactionForm = this.formBuilder.group({
|
||||||
|
transactionDate: [
|
||||||
|
this.settingsService.businessDate,
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
transactionAmount: [
|
||||||
|
0,
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
paymentTypeId: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
note: ['']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to add payment detail fields to the UI.
|
||||||
|
*/
|
||||||
|
addPaymentDetails() {
|
||||||
|
this.addPaymentDetailsFlag = !this.addPaymentDetailsFlag;
|
||||||
|
if (this.addPaymentDetailsFlag) {
|
||||||
|
this.currentAccountTransactionForm.addControl('accountNumber', new UntypedFormControl(''));
|
||||||
|
this.currentAccountTransactionForm.addControl('checkNumber', new UntypedFormControl(''));
|
||||||
|
this.currentAccountTransactionForm.addControl('routingCode', new UntypedFormControl(''));
|
||||||
|
this.currentAccountTransactionForm.addControl('receiptNumber', new UntypedFormControl(''));
|
||||||
|
this.currentAccountTransactionForm.addControl('bankNumber', new UntypedFormControl(''));
|
||||||
|
} else {
|
||||||
|
this.currentAccountTransactionForm.removeControl('accountNumber');
|
||||||
|
this.currentAccountTransactionForm.removeControl('checkNumber');
|
||||||
|
this.currentAccountTransactionForm.removeControl('routingCode');
|
||||||
|
this.currentAccountTransactionForm.removeControl('receiptNumber');
|
||||||
|
this.currentAccountTransactionForm.removeControl('bankNumber');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to submit the transaction details.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const currentAccountTransactionFormData = this.currentAccountTransactionForm.value;
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevTransactionDate: Date = this.currentAccountTransactionForm.value.transactionDate;
|
||||||
|
if (currentAccountTransactionFormData.transactionDate instanceof Date) {
|
||||||
|
currentAccountTransactionFormData.transactionDate = this.dateUtils.formatDate(prevTransactionDate, dateFormat);
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
...currentAccountTransactionFormData,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
data['transactionAmount'] = data['transactionAmount'] * 1;
|
||||||
|
this.currentAccountsService
|
||||||
|
.executeCurrentAccountTransactionsCommand(this.currentAccountId, this.transactionCommand, data)
|
||||||
|
.subscribe((res) => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="currentAccountUnassignStaffForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-form-field class="flex-fill" (click)="unassignedDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Unassignment Date' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="unassignedDatePicker"
|
||||||
|
required
|
||||||
|
formControlName="unassignedDate"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="unassignedDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #unassignedDatePicker></mat-datepicker>
|
||||||
|
@if (currentAccountUnassignStaffForm.controls.unassignedDate.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Unassignment Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary" [disabled]="!currentAccountUnassignStaffForm.valid">
|
||||||
|
{{ 'labels.buttons.Confirm' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</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 {
|
||||||
|
max-width: 37rem;
|
||||||
|
}
|
||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* 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, Input, inject } from '@angular/core';
|
||||||
|
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, 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 { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Unassign Staff Component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-current-account-account-unassign-staff',
|
||||||
|
templateUrl: './current-account-unassign-staff.component.html',
|
||||||
|
styleUrls: ['./current-account-unassign-staff.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CurrentAccountUnassignStaffComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Current Account Unassign Staff form. */
|
||||||
|
currentAccountUnassignStaffForm: UntypedFormGroup;
|
||||||
|
/** Current Account Id */
|
||||||
|
accountId: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.accountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the current account unassign staff form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.createCurrentAccountUnassignStaffForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the current account unassign staff form.
|
||||||
|
*/
|
||||||
|
createCurrentAccountUnassignStaffForm() {
|
||||||
|
this.currentAccountUnassignStaffForm = this.formBuilder.group({
|
||||||
|
unassignedDate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form and unassigns staff of the current account,
|
||||||
|
* if successful redirects to the current account.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const currentAccountUnassignStaffFormData = this.currentAccountUnassignStaffForm.value;
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevUnassignmentDate: Date = this.currentAccountUnassignStaffForm.value.unassignedDate;
|
||||||
|
if (currentAccountUnassignStaffFormData.unassignedDate instanceof Date) {
|
||||||
|
currentAccountUnassignStaffFormData.unassignedDate = this.dateUtils.formatDate(prevUnassignmentDate, dateFormat);
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
...currentAccountUnassignStaffFormData,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.currentAccountsService
|
||||||
|
.executeCurrentAccountCommand(this.accountId, 'unassignCurrentAccountOfficer', data)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
@if (transactionType.holdamount) {
|
||||||
|
<mat-card-title>{{ 'labels.heading.Hold Amount' | translate }}</mat-card-title>
|
||||||
|
}
|
||||||
|
@if (transactionType.blockaccount) {
|
||||||
|
<mat-card-title>{{ 'labels.heading.Block Current Account' | translate }}</mat-card-title>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form [formGroup]="manageCurrentAccountForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content class="layout-row">
|
||||||
|
<mat-form-field class="flex-100">
|
||||||
|
<mat-label>{{ 'labels.inputs.Reason' | translate }}</mat-label>
|
||||||
|
<mat-select required formControlName="reasonForBlock">
|
||||||
|
@for (codeValue of reasonOptions; track codeValue) {
|
||||||
|
<mat-option [value]="codeValue.id">
|
||||||
|
{{ codeValue.name }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
@if (manageCurrentAccountForm.controls.reasonForBlock.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Reason' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
@if (transactionType.holdamount) {
|
||||||
|
<mat-form-field class="flex-100" (click)="transactionDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="transactionDatePicker"
|
||||||
|
required
|
||||||
|
formControlName="transactionDate"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="transactionDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #transactionDatePicker></mat-datepicker>
|
||||||
|
@if (manageCurrentAccountForm.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>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (transactionType.holdamount) {
|
||||||
|
<mifosx-input-amount
|
||||||
|
class="flex-100"
|
||||||
|
[currency]="currency"
|
||||||
|
[isRequired]="true"
|
||||||
|
[inputFormControl]="manageCurrentAccountForm.controls.transactionAmount"
|
||||||
|
[inputLabel]="'Transaction Amount'"
|
||||||
|
>
|
||||||
|
</mifosx-input-amount>
|
||||||
|
}
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../transactions']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary" [disabled]="!manageCurrentAccountForm.valid">
|
||||||
|
{{ 'labels.buttons.Confirm' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</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;
|
||||||
|
}
|
||||||
+194
@@ -0,0 +1,194 @@
|
|||||||
|
/**
|
||||||
|
* 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, Input, OnInit, inject } from '@angular/core';
|
||||||
|
import { UntypedFormBuilder, UntypedFormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||||
|
import { Dates } from 'app/core/utils/dates';
|
||||||
|
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 { SystemService } from 'app/system/system.service';
|
||||||
|
import { MatCard, MatCardTitle, MatCardContent, MatCardActions } from '@angular/material/card';
|
||||||
|
import { InputAmountComponent } from '../../../shared/input-amount/input-amount.component';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
type TransactionCommandType = 'holdamount' | 'blockaccount' | 'blockdeposit' | 'blockwithdrawal';
|
||||||
|
|
||||||
|
interface TransactionType {
|
||||||
|
holdamount: boolean;
|
||||||
|
blockaccount: boolean;
|
||||||
|
blockdeposit: boolean;
|
||||||
|
blockwithdrawal: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-manage-current-account',
|
||||||
|
templateUrl: './manage-current-account.component.html',
|
||||||
|
styleUrls: ['./manage-current-account.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
MatCardTitle,
|
||||||
|
InputAmountComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ManageCurrentAccountComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private systemService = inject(SystemService);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
@Input() currency: Currency;
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Manage Current Account form. */
|
||||||
|
manageCurrentAccountForm: UntypedFormGroup;
|
||||||
|
/** Current Account Id */
|
||||||
|
currentAccountId: string;
|
||||||
|
transactionCommand: TransactionCommandType;
|
||||||
|
|
||||||
|
reasonOptions: any = [];
|
||||||
|
|
||||||
|
transactionType: TransactionType = {
|
||||||
|
holdamount: false,
|
||||||
|
blockaccount: false,
|
||||||
|
blockdeposit: false,
|
||||||
|
blockwithdrawal: false
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.transactionCommand = this.route.snapshot.params['name'].toLowerCase().replaceAll(' ', '');
|
||||||
|
this.transactionType[this.transactionCommand] = true;
|
||||||
|
this.currentAccountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the post interest current account form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.createManageCurrentAccountForm();
|
||||||
|
if (
|
||||||
|
this.transactionType.holdamount ||
|
||||||
|
this.transactionType.blockaccount ||
|
||||||
|
this.transactionType.blockdeposit ||
|
||||||
|
this.transactionType.blockwithdrawal
|
||||||
|
) {
|
||||||
|
this.getCodeValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCodeValues() {
|
||||||
|
let codeName = 'CurrentAccountTransactionFreezeReasons'; // Default Hold Amount
|
||||||
|
if (this.transactionType.blockaccount) {
|
||||||
|
codeName = 'CurrentAccountBlockReasons';
|
||||||
|
} else if (this.transactionType.blockdeposit) {
|
||||||
|
codeName = 'CreditTransactionFreezeReasons';
|
||||||
|
} else if (this.transactionType.blockwithdrawal) {
|
||||||
|
codeName = 'DebitTransactionFreezeReasons';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.systemService.getCodes().subscribe((codes: any) => {
|
||||||
|
codes.some((code: any) => {
|
||||||
|
if (code.name === codeName) {
|
||||||
|
this.systemService.getCodeValues(code.id).subscribe((codeValues: any) => {
|
||||||
|
this.reasonOptions = codeValues;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the manage current account form.
|
||||||
|
*/
|
||||||
|
createManageCurrentAccountForm() {
|
||||||
|
if (this.transactionType.holdamount) {
|
||||||
|
this.manageCurrentAccountForm = this.formBuilder.group({
|
||||||
|
reasonForBlock: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
transactionDate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
transactionAmount: [
|
||||||
|
0.0,
|
||||||
|
Validators.required
|
||||||
|
]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.manageCurrentAccountForm = this.formBuilder.group({
|
||||||
|
reasonForBlock: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
let command = '';
|
||||||
|
let payload: { transactionAmount?: number; [key: string]: any } = {};
|
||||||
|
|
||||||
|
if (this.transactionType.holdamount) {
|
||||||
|
const manageCurrentAccountFormData = this.manageCurrentAccountForm.value;
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevTransactionDate: Date = this.manageCurrentAccountForm.value.transactionDate;
|
||||||
|
if (manageCurrentAccountFormData.transactionDate instanceof Date) {
|
||||||
|
manageCurrentAccountFormData.transactionDate = this.dateUtils.formatDate(prevTransactionDate, dateFormat);
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
...manageCurrentAccountFormData,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
command = 'holdAmount';
|
||||||
|
payload['transactionAmount'] = payload['transactionAmount'] * 1;
|
||||||
|
|
||||||
|
this.currentAccountsService
|
||||||
|
.executeCurrentAccountTransactionsCommand(this.currentAccountId, command, payload)
|
||||||
|
.subscribe((response: any) => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
payload = {
|
||||||
|
...this.manageCurrentAccountForm.value
|
||||||
|
};
|
||||||
|
command = 'block';
|
||||||
|
if (this.transactionType.blockdeposit) {
|
||||||
|
command = 'blockCredit';
|
||||||
|
} else if (this.transactionType.blockwithdrawal) {
|
||||||
|
command = 'blockDebit';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentAccountsService
|
||||||
|
.executeCurrentAccountCommand(this.currentAccountId, command, payload)
|
||||||
|
.subscribe((response: any) => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="postInterestCurrentAccountForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-form-field class="flex-100" (click)="transactionDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="transactionDatePicker"
|
||||||
|
required
|
||||||
|
formControlName="transactionDate"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="transactionDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #transactionDatePicker></mat-datepicker>
|
||||||
|
@if (postInterestCurrentAccountForm.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>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary" [disabled]="!postInterestCurrentAccountForm.valid">
|
||||||
|
{{ 'labels.buttons.Confirm' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</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 {
|
||||||
|
max-width: 37rem;
|
||||||
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* 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, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, 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 { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post Interest Current Account Component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-post-interest-as-on-current-account',
|
||||||
|
templateUrl: './post-interest-as-on-current-account.component.html',
|
||||||
|
styleUrls: ['./post-interest-as-on-current-account.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class PostInterestAsOnCurrentAccountComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Post Interest Current Account form. */
|
||||||
|
postInterestCurrentAccountForm: UntypedFormGroup;
|
||||||
|
/** Current Account Id */
|
||||||
|
accountId: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.accountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the post interest current account form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.createPostInterestCurrentAccountForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the post interest current account form.
|
||||||
|
*/
|
||||||
|
createPostInterestCurrentAccountForm() {
|
||||||
|
this.postInterestCurrentAccountForm = this.formBuilder.group({
|
||||||
|
transactionDate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form and post interests of the current account,
|
||||||
|
* if successful redirects to the current account.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const postInterestCurrentAccountFormData = this.postInterestCurrentAccountForm.value;
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevTransactionDate: Date = this.postInterestCurrentAccountForm.value.transactionDate;
|
||||||
|
if (postInterestCurrentAccountFormData.transactionDate instanceof Date) {
|
||||||
|
postInterestCurrentAccountFormData.transactionDate = this.dateUtils.formatDate(prevTransactionDate, dateFormat);
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
...postInterestCurrentAccountFormData,
|
||||||
|
IsPostInterestAsOn: true,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.currentAccountsService
|
||||||
|
.executeCurrentAccountTransactionsCommand(this.accountId, 'postInterestAsOn', data)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="rejectCurrentAccountForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="layout-column">
|
||||||
|
<mat-form-field (click)="rejectedOnDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Rejected On Date' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="rejectedOnDatePicker"
|
||||||
|
required
|
||||||
|
formControlName="rejectedOnDate"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="rejectedOnDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #rejectedOnDatePicker></mat-datepicker>
|
||||||
|
@if (rejectCurrentAccountForm.controls.rejectedOnDate.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Rejected On Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Note' | translate }}</mat-label>
|
||||||
|
<textarea matInput formControlName="note" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="warn" [disabled]="!rejectCurrentAccountForm.valid">
|
||||||
|
{{ 'labels.buttons.Confirm' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</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 {
|
||||||
|
max-width: 37rem;
|
||||||
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* 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, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, 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 { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reject Current Account Component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-reject-current-account',
|
||||||
|
templateUrl: './reject-current-account.component.html',
|
||||||
|
styleUrls: ['./reject-current-account.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
CdkTextareaAutosize
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class RejectCurrentAccountComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Reject Current Account form. */
|
||||||
|
rejectCurrentAccountForm: UntypedFormGroup;
|
||||||
|
/** Current Account Id */
|
||||||
|
accountId: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.accountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the reject current account form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.createRejectCurrentAccountForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the reject current account form.
|
||||||
|
*/
|
||||||
|
createRejectCurrentAccountForm() {
|
||||||
|
this.rejectCurrentAccountForm = this.formBuilder.group({
|
||||||
|
rejectedOnDate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
note: ['']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form and rejects the current account,
|
||||||
|
* if successful redirects to the current account.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const rejectCurrentAccountFormData = this.rejectCurrentAccountForm.value;
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevRejectedOnDate: Date = this.rejectCurrentAccountForm.value.rejectedOnDate;
|
||||||
|
if (rejectCurrentAccountFormData.rejectedOnDate instanceof Date) {
|
||||||
|
rejectCurrentAccountFormData.rejectedOnDate = this.dateUtils.formatDate(prevRejectedOnDate, dateFormat);
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
...rejectCurrentAccountFormData,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.currentAccountsService.executeCurrentAccountCommand(this.accountId, 'reject', data).subscribe(() => {
|
||||||
|
this.router.navigate(['../../'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="undoApprovalCurrentAccountForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-form-field class="flex-fill">
|
||||||
|
<mat-label>{{ 'labels.inputs.Note' | translate }}</mat-label>
|
||||||
|
<textarea matInput formControlName="note" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="warn" [disabled]="!undoApprovalCurrentAccountForm.valid">
|
||||||
|
{{ 'labels.buttons.Confirm' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</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 {
|
||||||
|
max-width: 37rem;
|
||||||
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* 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, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
|
||||||
|
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo Approval Current Account Component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-undo-approval-current-account',
|
||||||
|
templateUrl: './undo-approval-current-account.component.html',
|
||||||
|
styleUrls: ['./undo-approval-current-account.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
CdkTextareaAutosize
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class UndoApprovalCurrentAccountComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
|
||||||
|
/** Undo Approval Current Account form. */
|
||||||
|
undoApprovalCurrentAccountForm: UntypedFormGroup;
|
||||||
|
/** Current Account Id */
|
||||||
|
accountId: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.accountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the undo-approval current account form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.createUndoApprovalCurrentAccountForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the undo-approval current account form.
|
||||||
|
*/
|
||||||
|
createUndoApprovalCurrentAccountForm() {
|
||||||
|
this.undoApprovalCurrentAccountForm = this.formBuilder.group({
|
||||||
|
note: ['']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form and undo the approval of share account,
|
||||||
|
* if successful redirects to the share account.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const data = {
|
||||||
|
...this.undoApprovalCurrentAccountForm.value
|
||||||
|
};
|
||||||
|
this.currentAccountsService.executeCurrentAccountCommand(this.accountId, 'undoapproval', data).subscribe(() => {
|
||||||
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
<!--
|
||||||
|
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-elevation-z8">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="withdrawCurrentAccountForm" (ngSubmit)="submit()">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="layout-column">
|
||||||
|
<mat-form-field (click)="withdrawnOnDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Withdrawn On Date' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="withdrawnOnDatePicker"
|
||||||
|
required
|
||||||
|
formControlName="withdrawnOnDate"
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="withdrawnOnDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #withdrawnOnDatePicker></mat-datepicker>
|
||||||
|
@if (withdrawCurrentAccountForm.controls.withdrawnOnDate.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Withdrawn On Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>{{ 'labels.inputs.Note' | translate }}</mat-label>
|
||||||
|
<textarea matInput formControlName="note" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||||
|
<button type="button" mat-raised-button [routerLink]="['../../']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary" [disabled]="!withdrawCurrentAccountForm.valid">
|
||||||
|
{{ 'labels.buttons.Confirm' | translate }}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</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 {
|
||||||
|
max-width: 37rem;
|
||||||
|
}
|
||||||
+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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Angular Imports */
|
||||||
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
|
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router, 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 { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Withdrawn by Client Current Account Component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-withdraw-by-client-current-account',
|
||||||
|
templateUrl: './withdraw-by-client-current-account.component.html',
|
||||||
|
styleUrls: ['./withdraw-by-client-current-account.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
CdkTextareaAutosize
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class WithdrawByClientCurrentAccountComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private router = inject(Router);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Withdraw Current Account form. */
|
||||||
|
withdrawCurrentAccountForm: UntypedFormGroup;
|
||||||
|
/** Current Account Id */
|
||||||
|
accountId: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service
|
||||||
|
* @param {Dates} dateUtils Date Utils
|
||||||
|
* @param {ActivatedRoute} route Activated Route
|
||||||
|
* @param {Router} router Router
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.accountId = this.route.snapshot.params['currentAccountId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the withdraw current account form.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.createWithdrawCurrentAccountForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the withdraw current account form.
|
||||||
|
*/
|
||||||
|
createWithdrawCurrentAccountForm() {
|
||||||
|
this.withdrawCurrentAccountForm = this.formBuilder.group({
|
||||||
|
withdrawnOnDate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
note: ['']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submits the form and withdraws the current account by client,
|
||||||
|
* if successful redirects to the current account.
|
||||||
|
*/
|
||||||
|
submit() {
|
||||||
|
const withdrawCurrentAccountFormData = this.withdrawCurrentAccountForm.value;
|
||||||
|
const locale = this.settingsService.language.code;
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevWithdrawnOnDate: Date = this.withdrawCurrentAccountForm.value.withdrawnOnDate;
|
||||||
|
if (withdrawCurrentAccountFormData.withdrawnOnDate instanceof Date) {
|
||||||
|
withdrawCurrentAccountFormData.withdrawnOnDate = this.dateUtils.formatDate(prevWithdrawnOnDate, dateFormat);
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
...withdrawCurrentAccountFormData,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.currentAccountsService
|
||||||
|
.executeCurrentAccountCommand(this.accountId, 'withdrawnByApplicant', data)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.router.navigate(['../../'], { relativeTo: this.route });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+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/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div class="layout-row-wrap gap-2px responsive-column">
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
|
||||||
|
<mat-select #charge>
|
||||||
|
@for (charge of chargeData | chargesFilter: chargesDataSource : currencyCode.value; track charge) {
|
||||||
|
<mat-option [value]="charge">
|
||||||
|
{{ charge.name }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div class="flex-48 align-center">
|
||||||
|
<button type="button" mat-raised-button color="primary" (click)="addCharge(charge)" [disabled]="!charge.value">
|
||||||
|
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||||
|
{{ 'labels.buttons.Add' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (chargesDataSource.length > 0) {
|
||||||
|
<table class="flex-98 mat-elevation-z1" mat-table [dataSource]="chargesDataSource">
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.name + ', ' + charge.currency.displaySymbol }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="chargeCalculationType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.chargeCalculationType.value }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="amount">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.amount | formatNumber }}
|
||||||
|
<button mat-icon-button color="primary" (click)="editChargeAmount(charge)">
|
||||||
|
<fa-icon icon="pen"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="chargeTimeType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.chargeTimeType.value | translateKey: 'catalogs' }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="date">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Date' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
@if (charge.chargeTimeType.value === 'Specified due date' || charge.chargeTimeType.value === 'Weekly Fee') {
|
||||||
|
<span>
|
||||||
|
{{ (charge.dueDate | dateFormat) || 'Unassigned' }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
@if (charge.chargeTimeType.value === 'Monthly Fee' || charge.chargeTimeType.value === 'Annual Fee') {
|
||||||
|
<span>
|
||||||
|
{{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
@if (
|
||||||
|
!(
|
||||||
|
charge.chargeTimeType.value === 'Monthly Fee' ||
|
||||||
|
charge.chargeTimeType.value === 'Annual Fee' ||
|
||||||
|
charge.chargeTimeType.value === 'Specified due date' ||
|
||||||
|
charge.chargeTimeType.value === 'Weekly Fee'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
<span> N/A </span>
|
||||||
|
}
|
||||||
|
@if (
|
||||||
|
charge.chargeTimeType.value === 'Weekly Fee' ||
|
||||||
|
charge.chargeTimeType.value === 'Monthly Fee' ||
|
||||||
|
charge.chargeTimeType.value === 'Annual Fee' ||
|
||||||
|
charge.chargeTimeType.value === 'Specified due date'
|
||||||
|
) {
|
||||||
|
<button mat-icon-button color="primary" (click)="editChargeDate(charge)">
|
||||||
|
<fa-icon icon="pen"></fa-icon>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="repaymentsEvery">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Repayments Every' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
@if (charge.feeInterval) {
|
||||||
|
<span>{{ charge.feeInterval }}</span>
|
||||||
|
}
|
||||||
|
@if (!charge.feeInterval) {
|
||||||
|
<span>{{ 'labels.inputs.Not Provided' | translate }}</span>
|
||||||
|
}
|
||||||
|
@if (charge.chargeTimeType.value === 'Weekly Fee' || charge.chargeTimeType.value === 'Monthly Fee') {
|
||||||
|
<button mat-icon-button color="primary" (click)="editChargeFeeInterval(charge)">
|
||||||
|
<fa-icon icon="pen"></fa-icon>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="action">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
<button mat-icon-button color="warn" (click)="deleteCharge(charge)">
|
||||||
|
<fa-icon icon="trash"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-98 layout-row align-center gap-2percent margin-t responsive-column">
|
||||||
|
<button mat-raised-button matStepperPrevious>
|
||||||
|
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||||
|
{{ 'labels.buttons.Previous' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button matStepperNext>
|
||||||
|
{{ 'labels.buttons.Next' | translate }}
|
||||||
|
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-elevation-z1 {
|
||||||
|
margin: 1em 0 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-t {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
+265
@@ -0,0 +1,265 @@
|
|||||||
|
/**
|
||||||
|
* 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, OnChanges, Input, inject } from '@angular/core';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { UntypedFormControl } from '@angular/forms';
|
||||||
|
|
||||||
|
/** Custom Dialogs */
|
||||||
|
import { FormDialogComponent } from 'app/shared/form-dialog/form-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 {
|
||||||
|
MatTableDataSource,
|
||||||
|
MatTable,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCellDef,
|
||||||
|
MatHeaderCell,
|
||||||
|
MatCellDef,
|
||||||
|
MatCell,
|
||||||
|
MatHeaderRowDef,
|
||||||
|
MatHeaderRow,
|
||||||
|
MatRowDef,
|
||||||
|
MatRow
|
||||||
|
} from '@angular/material/table';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { MatButton, MatIconButton } from '@angular/material/button';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
||||||
|
import { ChargesFilterPipe } from '../../../pipes/charges-filter.pipe';
|
||||||
|
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
|
||||||
|
import { FormatNumberPipe } from '../../../pipes/format-number.pipe';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Charges Step
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-current-account-charges-step',
|
||||||
|
templateUrl: './current-account-charges-step.component.html',
|
||||||
|
styleUrls: ['./current-account-charges-step.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
FaIconComponent,
|
||||||
|
MatTable,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCellDef,
|
||||||
|
MatHeaderCell,
|
||||||
|
MatCellDef,
|
||||||
|
MatCell,
|
||||||
|
MatIconButton,
|
||||||
|
MatHeaderRowDef,
|
||||||
|
MatHeaderRow,
|
||||||
|
MatRowDef,
|
||||||
|
MatRow,
|
||||||
|
MatStepperPrevious,
|
||||||
|
MatStepperNext,
|
||||||
|
ChargesFilterPipe,
|
||||||
|
DateFormatPipe,
|
||||||
|
FormatNumberPipe
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CurrentAccountChargesStepComponent implements OnInit, OnChanges {
|
||||||
|
private dialog = inject(MatDialog);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private translateService = inject(TranslateService);
|
||||||
|
|
||||||
|
/** Current Account Product Template */
|
||||||
|
@Input() currentAccountProductTemplate: any;
|
||||||
|
/** Current Account Template */
|
||||||
|
@Input() currentAccountTemplate: any;
|
||||||
|
/** Currency Code */
|
||||||
|
@Input() currencyCode: UntypedFormControl;
|
||||||
|
@Input() activeClientMembers?: any;
|
||||||
|
|
||||||
|
/** Charge Data */
|
||||||
|
chargeData: any = [];
|
||||||
|
/** Charges Data Source */
|
||||||
|
chargesDataSource: {}[] = [];
|
||||||
|
/** Component is pristine if there has been no changes by user interaction */
|
||||||
|
pristine = true;
|
||||||
|
/** For Edit Current Account Form */
|
||||||
|
isChargesPatched = false;
|
||||||
|
/** Display columns for charges table */
|
||||||
|
displayedColumns: string[] = [
|
||||||
|
'name',
|
||||||
|
'chargeCalculationType',
|
||||||
|
'amount',
|
||||||
|
'chargeTimeType',
|
||||||
|
'date',
|
||||||
|
'repaymentsEvery',
|
||||||
|
'action'
|
||||||
|
];
|
||||||
|
/** Table Data Source */
|
||||||
|
dataSource: any;
|
||||||
|
/** Check for select all the Clients List */
|
||||||
|
selectAllItems = false;
|
||||||
|
/** Loan Purpose Options */
|
||||||
|
loanPurposeOptions: string[] = [];
|
||||||
|
/** Table Displayed Columns */
|
||||||
|
displayedColumn: string[] = [
|
||||||
|
'check',
|
||||||
|
'id',
|
||||||
|
'name'
|
||||||
|
];
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (this.currentAccountTemplate) {
|
||||||
|
if (!this.isChargesPatched && this.currentAccountTemplate.charges) {
|
||||||
|
this.chargesDataSource =
|
||||||
|
this.currentAccountProductTemplate.charges.map((charge: any) => ({ ...charge, id: charge.chargeId })) || [];
|
||||||
|
this.isChargesPatched = true;
|
||||||
|
} else {
|
||||||
|
this.chargesDataSource = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
if (this.currentAccountProductTemplate) {
|
||||||
|
this.chargeData = this.currentAccountProductTemplate.chargeOptions;
|
||||||
|
this.chargesDataSource =
|
||||||
|
this.currentAccountProductTemplate.charges.map((charge: any) => ({ ...charge, id: charge.chargeId })) || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the charge to charges table
|
||||||
|
* @param {any} charge Charge
|
||||||
|
*/
|
||||||
|
addCharge(charge: any) {
|
||||||
|
this.chargesDataSource = this.chargesDataSource.concat([charge.value]);
|
||||||
|
charge.value = '';
|
||||||
|
this.pristine = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the Charge Amount
|
||||||
|
* @param {any} charge Charge
|
||||||
|
*/
|
||||||
|
editChargeAmount(charge: any) {
|
||||||
|
const formfields: FormfieldBase[] = [
|
||||||
|
new InputBase({
|
||||||
|
controlName: 'amount',
|
||||||
|
label: this.translateService.instant('labels.inputs.Amount'),
|
||||||
|
value: charge.amount,
|
||||||
|
type: 'number',
|
||||||
|
required: false
|
||||||
|
})
|
||||||
|
];
|
||||||
|
const data = {
|
||||||
|
title: this.translateService.instant('labels.heading.Edit Charge Amount'),
|
||||||
|
layout: { addButtonText: 'Confirm' },
|
||||||
|
formfields: formfields
|
||||||
|
};
|
||||||
|
const editNoteDialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||||
|
editNoteDialogRef.afterClosed().subscribe((response: any) => {
|
||||||
|
if (response.data) {
|
||||||
|
const newCharge = { ...charge, amount: response.data.value.amount };
|
||||||
|
this.chargesDataSource.splice(this.chargesDataSource.indexOf(charge), 1, newCharge);
|
||||||
|
this.chargesDataSource = this.chargesDataSource.concat([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.pristine = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the Charge Date
|
||||||
|
* @param {any} charge Charge
|
||||||
|
*/
|
||||||
|
editChargeDate(charge: any) {
|
||||||
|
const formfields: FormfieldBase[] = [
|
||||||
|
new DatepickerBase({
|
||||||
|
controlName: 'date',
|
||||||
|
label: this.translateService.instant('labels.inputs.Date'),
|
||||||
|
value: charge.dueDate || charge.feeOnMonthDay || '',
|
||||||
|
type: 'datetime-local',
|
||||||
|
required: false
|
||||||
|
})
|
||||||
|
];
|
||||||
|
const data = {
|
||||||
|
title: this.translateService.instant('labels.heading.Edit Charge Date'),
|
||||||
|
layout: { addButtonText: 'Confirm' },
|
||||||
|
formfields: formfields
|
||||||
|
};
|
||||||
|
const editNoteDialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||||
|
editNoteDialogRef.afterClosed().subscribe((response: any) => {
|
||||||
|
if (response.data) {
|
||||||
|
let newCharge: any;
|
||||||
|
const dateFormat = 'dd MMMM yyyy';
|
||||||
|
const date = this.dateUtils.formatDate(response.data.value.date, dateFormat);
|
||||||
|
switch (charge.chargeTimeType.value) {
|
||||||
|
case 'Specified due date':
|
||||||
|
case 'Weekly Fee':
|
||||||
|
newCharge = { ...charge, dueDate: date };
|
||||||
|
break;
|
||||||
|
case 'Annual Fee':
|
||||||
|
case 'Monthly Fee':
|
||||||
|
const dateMonthDay = this.dateUtils.formatDate(response.data.value.date, 'dd MMMM');
|
||||||
|
newCharge = { ...charge, feeOnMonthDay: dateMonthDay };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.chargesDataSource.splice(this.chargesDataSource.indexOf(charge), 1, newCharge);
|
||||||
|
this.chargesDataSource = this.chargesDataSource.concat([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.pristine = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the Charge Fee Interval
|
||||||
|
* @param {any} charge Charge
|
||||||
|
*/
|
||||||
|
editChargeFeeInterval(charge: any) {
|
||||||
|
const formfields: FormfieldBase[] = [
|
||||||
|
new InputBase({
|
||||||
|
controlName: 'feeInterval',
|
||||||
|
label: 'Fee Interval',
|
||||||
|
value: charge.feeInterval,
|
||||||
|
type: 'text',
|
||||||
|
required: false
|
||||||
|
})
|
||||||
|
];
|
||||||
|
const data = {
|
||||||
|
title: 'Edit Charge Fee Interval',
|
||||||
|
layout: { addButtonText: 'Confirm' },
|
||||||
|
formfields: formfields
|
||||||
|
};
|
||||||
|
const editNoteDialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||||
|
editNoteDialogRef.afterClosed().subscribe((response: any) => {
|
||||||
|
if (response.data) {
|
||||||
|
const newCharge = { ...charge, feeInterval: response.data.value.feeInterval };
|
||||||
|
this.chargesDataSource.splice(this.chargesDataSource.indexOf(charge), 1, newCharge);
|
||||||
|
this.chargesDataSource = this.chargesDataSource.concat([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.pristine = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the charge from charges table
|
||||||
|
* @param {any} charge Charge
|
||||||
|
*/
|
||||||
|
deleteCharge(charge: any) {
|
||||||
|
this.chargesDataSource.splice(this.chargesDataSource.indexOf(charge), 1);
|
||||||
|
this.chargesDataSource = this.chargesDataSource.concat([]);
|
||||||
|
this.pristine = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current account charges
|
||||||
|
*/
|
||||||
|
get currentAccountCharges() {
|
||||||
|
return { charges: this.chargesDataSource };
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
<!--
|
||||||
|
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 [formGroup]="currentAccountDetailsForm">
|
||||||
|
<div class="layout-row-wrap gap-2px responsive-column">
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Product Name' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="productId" required>
|
||||||
|
@for (product of productData; track product) {
|
||||||
|
<mat-option [value]="product.id">
|
||||||
|
{{ product.name }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
@if (currentAccountProductSelected) {
|
||||||
|
<mat-form-field class="flex-48" (click)="submittedOnDatePicker.open()">
|
||||||
|
<mat-label>{{ 'labels.inputs.Submitted On' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[min]="minDate"
|
||||||
|
[max]="maxDate"
|
||||||
|
[matDatepicker]="submittedOnDatePicker"
|
||||||
|
formControlName="submittedOnDate"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="submittedOnDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #submittedOnDatePicker></mat-datepicker>
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Submitted On' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (currentAccountProductSelected) {
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Field Officer' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="fieldOfficerId">
|
||||||
|
@for (officer of fieldOfficerData; track officer) {
|
||||||
|
<mat-option [value]="officer.id">
|
||||||
|
{{ officer.displayName }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (currentAccountProductSelected) {
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.External ID' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="externalId" />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||||
|
<button mat-raised-button matStepperPrevious disabled>
|
||||||
|
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||||
|
{{ 'labels.buttons.Previous' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button matStepperNext [disabled]="currentAccountDetailsForm.invalid">
|
||||||
|
{{ 'labels.buttons.Next' | translate }}
|
||||||
|
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
+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/.
|
||||||
|
*/
|
||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* 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, Input, Output, EventEmitter, inject } from '@angular/core';
|
||||||
|
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
/** Custom Services */
|
||||||
|
import { CurrentAccountsService } from 'app/current-accounts/current-accounts.service';
|
||||||
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Details Step
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-current-account-details-step',
|
||||||
|
templateUrl: './current-account-details-step.component.html',
|
||||||
|
styleUrls: ['./current-account-details-step.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
MatStepperPrevious,
|
||||||
|
FaIconComponent,
|
||||||
|
MatStepperNext
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CurrentAccountDetailsStepComponent implements OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Current Account Template */
|
||||||
|
@Input() currentAccountTemplate: any;
|
||||||
|
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Product Data */
|
||||||
|
productData: any;
|
||||||
|
/** Field Officer Data */
|
||||||
|
fieldOfficerData: any;
|
||||||
|
/** For edit current account form */
|
||||||
|
isFieldOfficerPatched = false;
|
||||||
|
/** Current Account Details Form */
|
||||||
|
currentAccountDetailsForm: UntypedFormGroup;
|
||||||
|
|
||||||
|
currentAccountProductSelected = false;
|
||||||
|
|
||||||
|
/** Current Account Template with product data */
|
||||||
|
@Output() currentAccountProductTemplate = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets share account details form.
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder.
|
||||||
|
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service.
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.createCurrentAccountDetailsForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
this.buildDependencies();
|
||||||
|
if (this.currentAccountTemplate) {
|
||||||
|
this.productData = this.currentAccountTemplate.productOptions;
|
||||||
|
if (this.currentAccountTemplate.currentAccountProductId) {
|
||||||
|
this.currentAccountDetailsForm.patchValue({
|
||||||
|
productId: this.currentAccountTemplate.currentAccountProductId,
|
||||||
|
submittedOnDate:
|
||||||
|
this.currentAccountTemplate.timeline.submittedOnDate &&
|
||||||
|
new Date(this.currentAccountTemplate.timeline.submittedOnDate),
|
||||||
|
externalId: this.currentAccountTemplate.externalId
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.currentAccountDetailsForm.patchValue({
|
||||||
|
submittedOnDate: new Date()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates current account details form.
|
||||||
|
*/
|
||||||
|
createCurrentAccountDetailsForm() {
|
||||||
|
this.currentAccountDetailsForm = this.formBuilder.group({
|
||||||
|
productId: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
submittedOnDate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
fieldOfficerId: [''],
|
||||||
|
externalId: ['']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches current account product template on productId value changes
|
||||||
|
*/
|
||||||
|
buildDependencies() {
|
||||||
|
const entityId = this.currentAccountTemplate.groupId || this.currentAccountTemplate.clientId;
|
||||||
|
this.currentAccountDetailsForm.get('productId').valueChanges.subscribe((productId: string) => {
|
||||||
|
this.currentAccountsService
|
||||||
|
.getCurrentAccountTemplate(entityId, productId, this.currentAccountTemplate.groupId ? true : false)
|
||||||
|
.subscribe((response: any) => {
|
||||||
|
this.currentAccountProductTemplate.emit(response);
|
||||||
|
this.fieldOfficerData = response.fieldOfficerOptions;
|
||||||
|
this.currentAccountProductSelected = true;
|
||||||
|
if (!this.isFieldOfficerPatched && this.currentAccountTemplate.fieldOfficerId) {
|
||||||
|
this.currentAccountDetailsForm.get('fieldOfficerId').patchValue(this.currentAccountTemplate.fieldOfficerId);
|
||||||
|
this.isFieldOfficerPatched = true;
|
||||||
|
} else {
|
||||||
|
this.currentAccountDetailsForm.get('fieldOfficerId').patchValue('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current account form value.
|
||||||
|
*/
|
||||||
|
get currentAccountDetails() {
|
||||||
|
return this.currentAccountDetailsForm.getRawValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
+269
@@ -0,0 +1,269 @@
|
|||||||
|
<!--
|
||||||
|
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="responsive-column">
|
||||||
|
<h3 class="mat-h3 flex-fill">{{ 'labels.heading.Details' | translate }}</h3>
|
||||||
|
|
||||||
|
<mat-divider class="flex-fill"></mat-divider>
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Product' | translate }}</span>
|
||||||
|
<span class="flex-60">{{
|
||||||
|
currentAccount.productId | find: currentAccountTemplate.productOptions : 'id' : 'name'
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Submitted On' | translate }}</span>
|
||||||
|
<span class="flex-60">{{ currentAccount.submittedOnDate | dateFormat }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.External Id' | translate }}</span>
|
||||||
|
@if (currentAccount.externalId) {
|
||||||
|
<span class="flex-60">
|
||||||
|
<mifosx-external-identifier externalId="{{ currentAccount.externalId }}"></mifosx-external-identifier>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
@if (!currentAccount.externalId) {
|
||||||
|
<span class="flex-60">
|
||||||
|
{{ 'labels.inputs.Unassigned' | translate }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Field Officer' | translate }}</span>
|
||||||
|
<span class="flex-60">{{
|
||||||
|
currentAccount.fieldOfficerId | find: currentAccountProductTemplate.fieldOfficerOptions : 'id' : 'displayName'
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (activeClientMembers) {
|
||||||
|
<h3 class="mat-h3 margin-t flex-fill">{{ 'labels.heading.Client Members' | translate }}</h3>
|
||||||
|
<mat-divider class="flex-fill"></mat-divider>
|
||||||
|
<div class="flex-60">
|
||||||
|
<table mat-table #membersTable [dataSource]="dataSource">
|
||||||
|
<!-- ID Column -->
|
||||||
|
<ng-container matColumnDef="id">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Client ID' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let element">{{ element.id }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<!-- Name Column -->
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Client Name' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let element">{{ element.displayName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="membersDisplayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: membersDisplayedColumns"></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3 class="mat-h3 margin-t flex-fill">{{ 'labels.heading.Terms' | translate }}</h3>
|
||||||
|
|
||||||
|
<mat-divider class="flex-fill"></mat-divider>
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Currency' | translate }}</span>
|
||||||
|
<span class="flex-60">{{ currentAccountTermsForm.get('currencyCode').value }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Decimal Places' | translate }}</span>
|
||||||
|
<span class="flex-60">{{ currentAccountTermsForm.get('decimal').value }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Nominal Annual Interest' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ currentAccount.nominalAnnualInterestRate | formatNumber }} %</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Interest Compounding Period' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{
|
||||||
|
getCatalogTranslation(
|
||||||
|
currentAccount.interestCompoundingPeriodType
|
||||||
|
| find: currentAccountProductTemplate.interestCompoundingPeriodTypeOptions : 'id' : 'value'
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Interest Posting Period' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{
|
||||||
|
getCatalogTranslation(
|
||||||
|
currentAccount.interestPostingPeriodType
|
||||||
|
| find: currentAccountProductTemplate.interestPostingPeriodTypeOptions : 'id' : 'value'
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Interest Calculated using' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{
|
||||||
|
getCatalogTranslation(
|
||||||
|
currentAccount.interestCalculationType
|
||||||
|
| find: currentAccountProductTemplate.interestCalculationTypeOptions : 'id' : 'value'
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Days in Year' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{
|
||||||
|
getCatalogTranslation(
|
||||||
|
currentAccount.interestCalculationDaysInYearType
|
||||||
|
| find: currentAccountProductTemplate.interestCalculationDaysInYearTypeOptions : 'id' : 'value'
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (currentAccount.minRequiredOpeningBalance) {
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Minimum Opening Balance' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ currentAccount.minRequiredOpeningBalance | formatNumber }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ currentAccount.withdrawalFeeForTransfers | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (currentAccount.lockinPeriodFrequency) {
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Lock-in Period' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{
|
||||||
|
getCatalogTranslation(
|
||||||
|
currentAccount.lockinPeriodFrequency +
|
||||||
|
' ' +
|
||||||
|
(currentAccount.lockinPeriodFrequencyType
|
||||||
|
| find: currentAccountProductTemplate.lockinPeriodFrequencyTypeOptions : 'id' : 'value')
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Is Overdraft Allowed' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ currentAccount.allowOverdraft | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (currentAccount.minOverdraftForInterestCalculation) {
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40"
|
||||||
|
>{{ 'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate }}:</span
|
||||||
|
>
|
||||||
|
<span class="flex-60">{{ currentAccount.minOverdraftForInterestCalculation | formatNumber }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (currentAccount.nominalAnnualInterestRateOverdraft) {
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ currentAccount.nominalAnnualInterestRateOverdraft | formatNumber }} %</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (currentAccount.overdraftLimit) {
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ currentAccount.overdraftLimit | formatNumber }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Enforce Minimum Balance' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ currentAccount.enforceMinRequiredBalance | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (currentAccount.minRequiredBalance) {
|
||||||
|
<div class="flex-fill layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Minimum Balance' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ currentAccount.minRequiredBalance }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (currentAccount.charges.length) {
|
||||||
|
<div class="layout-row-wrap responsive-column flex-fill">
|
||||||
|
<h3 class="mat-h3 flex-fill">{{ 'labels.inputs.Charges' | translate }}</h3>
|
||||||
|
<mat-divider class="flex-fill"></mat-divider>
|
||||||
|
<table class="flex-fill mat-elevation-z1" mat-table [dataSource]="currentAccount.charges">
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.name + ', ' + charge.currency.displaySymbol }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="chargeCalculationType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.chargeCalculationType.value | translateKey: 'catalogs' }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="amount">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.amount | formatNumber }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="date">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Date' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
@if (charge.chargeTimeType.value === 'Specified due date' || charge.chargeTimeType.value === 'Weekly Fee') {
|
||||||
|
<span>
|
||||||
|
{{ (charge.dueDate | dateFormat) || 'Unassigned' }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
@if (charge.chargeTimeType.value === 'Monthly Fee' || charge.chargeTimeType.value === 'Annual Fee') {
|
||||||
|
<span>
|
||||||
|
{{ (charge.feeOnMonthDay | dateFormat) || 'Unassigned' }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
@if (
|
||||||
|
!(
|
||||||
|
charge.chargeTimeType.value === 'Monthly Fee' ||
|
||||||
|
charge.chargeTimeType.value === 'Annual Fee' ||
|
||||||
|
charge.chargeTimeType.value === 'Specified due date' ||
|
||||||
|
charge.chargeTimeType.value === 'Weekly Fee'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
<span> N/A </span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="repaymentsEvery">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Repayments Every' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.feeInterval || 'Not Provided' }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="chargeTimeType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.chargeTimeType.value }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="chargesDisplayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: chargesDisplayedColumns"></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-row responsive-column align-center gap-2px margin-t">
|
||||||
|
<button mat-raised-button matStepperPrevious>
|
||||||
|
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||||
|
{{ 'labels.buttons.Previous' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button [routerLink]="['../..']">
|
||||||
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="primary" (click)="submitEvent.emit()">
|
||||||
|
{{ 'labels.buttons.Submit' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-elevation-z1 {
|
||||||
|
margin: 1em 0 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-divider {
|
||||||
|
margin: 0 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-t {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* 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, Input, Output, EventEmitter, OnChanges, inject } from '@angular/core';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import {
|
||||||
|
MatTableDataSource,
|
||||||
|
MatTable,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCellDef,
|
||||||
|
MatHeaderCell,
|
||||||
|
MatCellDef,
|
||||||
|
MatCell,
|
||||||
|
MatHeaderRowDef,
|
||||||
|
MatHeaderRow,
|
||||||
|
MatRowDef,
|
||||||
|
MatRow
|
||||||
|
} from '@angular/material/table';
|
||||||
|
import { MatDivider } from '@angular/material/divider';
|
||||||
|
import { ExternalIdentifierComponent } from '../../../shared/external-identifier/external-identifier.component';
|
||||||
|
import { MatStepperPrevious } from '@angular/material/stepper';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { FindPipe } from '../../../pipes/find.pipe';
|
||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current account preview step
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-current-account-account-preview-step',
|
||||||
|
templateUrl: './current-account-preview-step.component.html',
|
||||||
|
styleUrls: ['./current-account-preview-step.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
MatDivider,
|
||||||
|
ExternalIdentifierComponent,
|
||||||
|
MatTable,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCellDef,
|
||||||
|
MatHeaderCell,
|
||||||
|
MatCellDef,
|
||||||
|
MatCell,
|
||||||
|
MatHeaderRowDef,
|
||||||
|
MatHeaderRow,
|
||||||
|
MatRowDef,
|
||||||
|
MatRow,
|
||||||
|
MatStepperPrevious,
|
||||||
|
FaIconComponent,
|
||||||
|
FindPipe,
|
||||||
|
DateFormatPipe,
|
||||||
|
FormatNumberPipe,
|
||||||
|
YesnoPipe
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CurrentAccountPreviewStepComponent implements OnChanges {
|
||||||
|
private translateService = inject(TranslateService);
|
||||||
|
|
||||||
|
/** Current Account Product Template */
|
||||||
|
@Input() currentAccountProductTemplate: any;
|
||||||
|
/** Current Account Template */
|
||||||
|
@Input() currentAccountTemplate: any;
|
||||||
|
/** Current Account Terms Form */
|
||||||
|
@Input() currentAccountTermsForm: any;
|
||||||
|
/** Current Account */
|
||||||
|
@Input() currentAccount: any;
|
||||||
|
@Input() activeClientMembers?: any;
|
||||||
|
|
||||||
|
/** Table Data Source */
|
||||||
|
dataSource: any;
|
||||||
|
|
||||||
|
/** Display columns for charges table */
|
||||||
|
chargesDisplayedColumns: string[] = [
|
||||||
|
'name',
|
||||||
|
'chargeCalculationType',
|
||||||
|
'amount',
|
||||||
|
'chargeTimeType',
|
||||||
|
'date',
|
||||||
|
'repaymentsEvery'
|
||||||
|
];
|
||||||
|
/** Columns to be displayed in active members table. */
|
||||||
|
membersDisplayedColumns: string[] = [
|
||||||
|
'id',
|
||||||
|
'name'
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Form submission event */
|
||||||
|
@Output() submitEvent = new EventEmitter();
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
if (this.activeClientMembers?.length > 0) {
|
||||||
|
this.dataSource = new MatTableDataSource<any>(this.activeClientMembers.filter((member: any) => member.selected));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCatalogTranslation(text: string): string {
|
||||||
|
return this.translateService.instant('labels.catalogs.' + text);
|
||||||
|
}
|
||||||
|
}
|
||||||
+174
@@ -0,0 +1,174 @@
|
|||||||
|
<!--
|
||||||
|
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 [formGroup]="currentAccountTermsForm">
|
||||||
|
<div class="layout-row-wrap gap-2percent responsive-column align-start-center">
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
|
||||||
|
<input matInput formControlName="currencyCode" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
|
||||||
|
<input type="number" matInput formControlName="decimal" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Nominal Annual Interest' | translate }}</mat-label>
|
||||||
|
<input type="number" matInput formControlName="nominalAnnualInterestRate" required />
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Nominal Annual Interest' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Interest Compounding Period' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="interestCompoundingPeriodType" required>
|
||||||
|
@for (interestCompoundingPeriodType of interestCompoundingPeriodTypeData; track interestCompoundingPeriodType) {
|
||||||
|
<mat-option [value]="interestCompoundingPeriodType.id">
|
||||||
|
{{ interestCompoundingPeriodType.value | translateKey: 'catalogs' }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Interest Compounding Period' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Interest Posting Period' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="interestPostingPeriodType" required>
|
||||||
|
@for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) {
|
||||||
|
<mat-option [value]="interestPostingPeriodType.id">
|
||||||
|
{{ interestPostingPeriodType.value | translateKey: 'catalogs' }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Interest Calculated using' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="interestCalculationType" required>
|
||||||
|
@for (interestCalculationType of interestCalculationTypeData; track interestCalculationType) {
|
||||||
|
<mat-option [value]="interestCalculationType.id">
|
||||||
|
{{ interestCalculationType.value | translateKey: 'catalogs' }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Interest Calculated using' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Days in Year' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="interestCalculationDaysInYearType" required>
|
||||||
|
@for (
|
||||||
|
interestCalculationDaysInYearType of interestCalculationDaysInYearTypeData;
|
||||||
|
track interestCalculationDaysInYearType
|
||||||
|
) {
|
||||||
|
<mat-option [value]="interestCalculationDaysInYearType.id">
|
||||||
|
{{ interestCalculationDaysInYearType.value | translateKey: 'catalogs' }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Days in Year' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Minimum Opening Balance' | translate }}</mat-label>
|
||||||
|
<input type="number" matInput formControlName="minRequiredOpeningBalance" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-checkbox labelPosition="before" formControlName="withdrawalFeeForTransfers" class="margin-v flex-48">
|
||||||
|
{{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}
|
||||||
|
</mat-checkbox>
|
||||||
|
|
||||||
|
<h4 class="mat-h4 flex-98">{{ 'labels.inputs.Lock-in Period' | translate }}</h4>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
|
||||||
|
<input type="number" matInput formControlName="lockinPeriodFrequency" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
|
||||||
|
<mat-select formControlName="lockinPeriodFrequencyType">
|
||||||
|
@for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) {
|
||||||
|
<mat-option [value]="lockinPeriodFrequencyType.id">
|
||||||
|
{{ lockinPeriodFrequencyType.value | translateKey: 'catalogs' }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-divider class="flex-98"></mat-divider>
|
||||||
|
|
||||||
|
<h3 class="mat-h3 flex-23">{{ 'labels.inputs.Overdraft' | translate }}</h3>
|
||||||
|
|
||||||
|
<mat-checkbox class="flex-73 margin-b" labelPosition="before" formControlName="allowOverdraft">
|
||||||
|
{{ 'labels.inputs.Is Overdraft Allowed' | translate }}
|
||||||
|
</mat-checkbox>
|
||||||
|
|
||||||
|
@if (currentAccountTermsForm.value.allowOverdraft) {
|
||||||
|
<div class="flex-fill layout-row-wrap gap-2percent responsive-column">
|
||||||
|
<mat-form-field class="flex-31">
|
||||||
|
<mat-label>{{ 'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate }}</mat-label>
|
||||||
|
<input type="number" matInput formControlName="minOverdraftForInterestCalculation" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field class="flex-31">
|
||||||
|
<mat-label>{{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }}</mat-label>
|
||||||
|
<input type="number" matInput formControlName="nominalAnnualInterestRateOverdraft" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field class="flex-31">
|
||||||
|
<mat-label>{{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }}</mat-label>
|
||||||
|
<input type="number" matInput formControlName="overdraftLimit" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<mat-divider class="flex-98"></mat-divider>
|
||||||
|
|
||||||
|
<mat-checkbox labelPosition="before" formControlName="enforceMinRequiredBalance" class="margin-v flex-48">
|
||||||
|
{{ 'labels.inputs.Enforce Minimum Balance' | translate }}
|
||||||
|
</mat-checkbox>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Minimum Balance' | translate }}</mat-label>
|
||||||
|
<input type="number" matInput formControlName="minRequiredBalance" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
@if (currentAccountTermsForm.controls.minBalanceForInterestCalculation.value) {
|
||||||
|
<mat-form-field class="flex-48">
|
||||||
|
<mat-label>{{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}</mat-label>
|
||||||
|
<input type="number" matInput formControlName="minBalanceForInterestCalculation" />
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||||
|
<button mat-raised-button matStepperPrevious>
|
||||||
|
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||||
|
{{ 'labels.buttons.Previous' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button matStepperNext>
|
||||||
|
{{ 'labels.buttons.Next' | translate }}
|
||||||
|
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-divider {
|
||||||
|
margin: 1em 0 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-v {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-b {
|
||||||
|
margin: 0 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-t {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
+199
@@ -0,0 +1,199 @@
|
|||||||
|
/**
|
||||||
|
* 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, OnChanges, OnInit, Input, inject } from '@angular/core';
|
||||||
|
import {
|
||||||
|
UntypedFormGroup,
|
||||||
|
UntypedFormBuilder,
|
||||||
|
Validators,
|
||||||
|
UntypedFormControl,
|
||||||
|
ReactiveFormsModule
|
||||||
|
} from '@angular/forms';
|
||||||
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
|
import { MatCheckbox } from '@angular/material/checkbox';
|
||||||
|
import { MatDivider } from '@angular/material/divider';
|
||||||
|
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current Account Terms Step
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-current-account-terms-step',
|
||||||
|
templateUrl: './current-account-terms-step.component.html',
|
||||||
|
styleUrls: ['./current-account-terms-step.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
MatCheckbox,
|
||||||
|
MatDivider,
|
||||||
|
MatStepperPrevious,
|
||||||
|
FaIconComponent,
|
||||||
|
MatStepperNext
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CurrentAccountTermsStepComponent implements OnChanges, OnInit {
|
||||||
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
|
/** Current Account and Product Template */
|
||||||
|
@Input() currentAccountProductTemplate: any;
|
||||||
|
/** Current Account Template */
|
||||||
|
@Input() currentAccountTemplate: any;
|
||||||
|
|
||||||
|
/** Minimum date allowed. */
|
||||||
|
minDate = new Date(2000, 0, 1);
|
||||||
|
/** Maximum date allowed. */
|
||||||
|
maxDate = new Date();
|
||||||
|
/** Current Account Terms Form */
|
||||||
|
currentAccountTermsForm: UntypedFormGroup;
|
||||||
|
/** Lockin Period Frequency Type Data */
|
||||||
|
lockinPeriodFrequencyTypeData: any;
|
||||||
|
/** Interest Compounding Period Type Data */
|
||||||
|
interestCompoundingPeriodTypeData: any;
|
||||||
|
/** Interest Posting Period Type Data */
|
||||||
|
interestPostingPeriodTypeData: any;
|
||||||
|
/** Interest Calculation Type Data */
|
||||||
|
interestCalculationTypeData: any;
|
||||||
|
/** Interest Calculation Days in Year Data */
|
||||||
|
interestCalculationDaysInYearTypeData: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FormBuilder} formBuilder Form Builder
|
||||||
|
* @param {SettingsService} settingsService Setting service
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.createCurrentAccountTermsForm();
|
||||||
|
this.buildDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
if (this.currentAccountProductTemplate) {
|
||||||
|
this.currentAccountTermsForm.patchValue({
|
||||||
|
currencyCode: this.currentAccountProductTemplate.currency.code,
|
||||||
|
decimal: this.currentAccountProductTemplate.currency.decimalPlaces,
|
||||||
|
minBalanceForInterestCalculation: this.currentAccountProductTemplate.minBalanceForInterestCalculation,
|
||||||
|
nominalAnnualInterestRate: this.currentAccountProductTemplate.nominalAnnualInterestRate,
|
||||||
|
interestCompoundingPeriodType: this.currentAccountProductTemplate.interestCompoundingPeriodType.id,
|
||||||
|
interestPostingPeriodType: this.currentAccountProductTemplate.interestPostingPeriodType.id,
|
||||||
|
interestCalculationType: this.currentAccountProductTemplate.interestCalculationType.id,
|
||||||
|
interestCalculationDaysInYearType: this.currentAccountProductTemplate.interestCalculationDaysInYearType.id,
|
||||||
|
minRequiredOpeningBalance: this.currentAccountProductTemplate.minRequiredOpeningBalance,
|
||||||
|
allowOverdraft: this.currentAccountProductTemplate.allowOverdraft,
|
||||||
|
overdraftLimit: this.currentAccountProductTemplate.overdraftLimit,
|
||||||
|
enforceMinRequiredBalance: this.currentAccountProductTemplate.enforceMinRequiredBalance,
|
||||||
|
minOverdraftForInterestCalculation: this.currentAccountProductTemplate.minOverdraftForInterestCalculation,
|
||||||
|
nominalAnnualInterestRateOverdraft: this.currentAccountProductTemplate.nominalAnnualInterestRateOverdraft,
|
||||||
|
minRequiredBalance: this.currentAccountProductTemplate.minRequiredBalance,
|
||||||
|
withdrawalFeeForTransfers: this.currentAccountProductTemplate.withdrawalFeeForTransfers
|
||||||
|
});
|
||||||
|
this.setOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.maxDate = this.settingsService.businessDate;
|
||||||
|
if (this.currentAccountTemplate) {
|
||||||
|
this.currentAccountTermsForm.patchValue({
|
||||||
|
nominalAnnualInterestRate: this.currentAccountTemplate.nominalAnnualInterestRate,
|
||||||
|
interestCompoundingPeriodType: this.currentAccountTemplate.interestCompoundingPeriodType.id,
|
||||||
|
interestPostingPeriodType: this.currentAccountTemplate.interestPostingPeriodType.id,
|
||||||
|
interestCalculationType: this.currentAccountTemplate.interestCalculationType.id,
|
||||||
|
interestCalculationDaysInYearType: this.currentAccountTemplate.interestCalculationDaysInYearType.id,
|
||||||
|
minRequiredOpeningBalance: this.currentAccountTemplate.minRequiredOpeningBalance,
|
||||||
|
withdrawalFeeForTransfers: this.currentAccountTemplate.withdrawalFeeForTransfers,
|
||||||
|
lockinPeriodFrequency: this.currentAccountTemplate.lockinPeriodFrequency,
|
||||||
|
lockinPeriodFrequencyType:
|
||||||
|
this.currentAccountTemplate.lockinPeriodFrequencyType &&
|
||||||
|
this.currentAccountTemplate.lockinPeriodFrequencyType.id,
|
||||||
|
allowOverdraft: this.currentAccountTemplate.allowOverdraft,
|
||||||
|
enforceMinRequiredBalance: this.currentAccountTemplate.enforceMinRequiredBalance,
|
||||||
|
minRequiredBalance: this.currentAccountTemplate.minRequiredBalance
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates current account terms form.
|
||||||
|
*/
|
||||||
|
createCurrentAccountTermsForm() {
|
||||||
|
this.currentAccountTermsForm = this.formBuilder.group({
|
||||||
|
currencyCode: [{ value: '', disabled: true }],
|
||||||
|
decimal: [{ value: '', disabled: true }],
|
||||||
|
nominalAnnualInterestRate: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
interestCompoundingPeriodType: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
interestPostingPeriodType: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
interestCalculationType: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
interestCalculationDaysInYearType: [
|
||||||
|
'',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
minRequiredOpeningBalance: [''],
|
||||||
|
withdrawalFeeForTransfers: [false],
|
||||||
|
lockinPeriodFrequency: [''],
|
||||||
|
lockinPeriodFrequencyType: [''],
|
||||||
|
allowOverdraft: [false],
|
||||||
|
enforceMinRequiredBalance: [false],
|
||||||
|
minRequiredBalance: [''],
|
||||||
|
minBalanceForInterestCalculation: [{ value: '', disabled: true }]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets all select dropdown options.
|
||||||
|
*/
|
||||||
|
setOptions() {
|
||||||
|
this.lockinPeriodFrequencyTypeData = this.currentAccountProductTemplate.lockinPeriodFrequencyTypeOptions;
|
||||||
|
this.interestCompoundingPeriodTypeData = this.currentAccountProductTemplate.interestCompoundingPeriodTypeOptions;
|
||||||
|
this.interestPostingPeriodTypeData = this.currentAccountProductTemplate.interestPostingPeriodTypeOptions;
|
||||||
|
this.interestCalculationTypeData = this.currentAccountProductTemplate.interestCalculationTypeOptions;
|
||||||
|
this.interestCalculationDaysInYearTypeData =
|
||||||
|
this.currentAccountProductTemplate.interestCalculationDaysInYearTypeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to value changes and sets new form controls accordingly.
|
||||||
|
*/
|
||||||
|
buildDependencies() {
|
||||||
|
this.currentAccountTermsForm.get('allowOverdraft').valueChanges.subscribe((allowOverdraft: any) => {
|
||||||
|
if (allowOverdraft) {
|
||||||
|
this.currentAccountTermsForm.addControl('minOverdraftForInterestCalculation', new UntypedFormControl(''));
|
||||||
|
this.currentAccountTermsForm.addControl('nominalAnnualInterestRateOverdraft', new UntypedFormControl(''));
|
||||||
|
this.currentAccountTermsForm.addControl('overdraftLimit', new UntypedFormControl(''));
|
||||||
|
} else {
|
||||||
|
this.currentAccountTermsForm.removeControl('minOverdraftForInterestCalculation');
|
||||||
|
this.currentAccountTermsForm.removeControl('nominalAnnualInterestRateOverdraft');
|
||||||
|
this.currentAccountTermsForm.removeControl('overdraftLimit');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current account terms form value.
|
||||||
|
*/
|
||||||
|
get currentAccountTerms() {
|
||||||
|
const payload = this.currentAccountTermsForm.getRawValue();
|
||||||
|
delete payload.currencyCode;
|
||||||
|
delete payload.decimal;
|
||||||
|
delete payload.minBalanceForInterestCalculation; // Backend is not accepting minBalanceForInterestCalculation value
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
<!--
|
||||||
|
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 (activeClientMembers) {
|
||||||
|
<div>
|
||||||
|
<table class="flex-98" mat-table [dataSource]="dataSource">
|
||||||
|
<!-- Check Column -->
|
||||||
|
<ng-container matColumnDef="check">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>
|
||||||
|
<mat-checkbox (change)="toggleSelects()" [(ngModel)]="selectAllItems"></mat-checkbox>
|
||||||
|
</th>
|
||||||
|
<td mat-cell *matCellDef="let element">
|
||||||
|
<mat-checkbox (change)="toggleSelect()" [(ngModel)]="element.selected"></mat-checkbox>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<!-- Position Column -->
|
||||||
|
<ng-container matColumnDef="id">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Client ID' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let element">{{ element.id }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<!-- Name Column -->
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Client Name' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let element">{{ element.displayName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumn"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: displayedColumn"></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="layout-row responsive-column align-center gap-2px margin-t">
|
||||||
|
<button mat-raised-button matStepperPrevious>
|
||||||
|
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||||
|
{{ 'labels.buttons.Previous' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button matStepperNext [disabled]="selectedClientMembers.selectedMembers.length === 0">
|
||||||
|
{{ 'labels.buttons.Next' | translate }}
|
||||||
|
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-elevation-z1 {
|
||||||
|
margin: 1em 0 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin-t {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* 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, OnChanges, Input } from '@angular/core';
|
||||||
|
import {
|
||||||
|
MatTableDataSource,
|
||||||
|
MatTable,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCellDef,
|
||||||
|
MatHeaderCell,
|
||||||
|
MatCellDef,
|
||||||
|
MatCell,
|
||||||
|
MatHeaderRowDef,
|
||||||
|
MatHeaderRow,
|
||||||
|
MatRowDef,
|
||||||
|
MatRow
|
||||||
|
} from '@angular/material/table';
|
||||||
|
import { MatCheckbox } from '@angular/material/checkbox';
|
||||||
|
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
|
||||||
|
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-current-account-active-client-members',
|
||||||
|
templateUrl: './current-accounts-active-client-members.component.html',
|
||||||
|
styleUrls: ['./current-accounts-active-client-members.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
MatTable,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCellDef,
|
||||||
|
MatHeaderCell,
|
||||||
|
MatCheckbox,
|
||||||
|
FormsModule,
|
||||||
|
MatCellDef,
|
||||||
|
MatCell,
|
||||||
|
MatHeaderRowDef,
|
||||||
|
MatHeaderRow,
|
||||||
|
MatRowDef,
|
||||||
|
MatRow,
|
||||||
|
MatStepperPrevious,
|
||||||
|
FaIconComponent,
|
||||||
|
MatStepperNext
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CurrentAccountActiveClientMembersComponent implements OnInit {
|
||||||
|
@Input() activeClientMembers?: any;
|
||||||
|
selectAllItems = false;
|
||||||
|
displayedColumn: string[] = [
|
||||||
|
'check',
|
||||||
|
'id',
|
||||||
|
'name'
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
dataSource: any;
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.dataSource = new MatTableDataSource<any>(this.activeClientMembers);
|
||||||
|
}
|
||||||
|
get selectedClientMembers() {
|
||||||
|
return { selectedMembers: this.activeClientMembers.filter((item: any) => item.selected) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Toggle all checks */
|
||||||
|
toggleSelects() {
|
||||||
|
for (const member of this.activeClientMembers) {
|
||||||
|
member.selected = this.selectAllItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check if all the checks are selected */
|
||||||
|
toggleSelect() {
|
||||||
|
const len = this.activeClientMembers.length;
|
||||||
|
this.selectAllItems =
|
||||||
|
len === 0 ? false : this.activeClientMembers.filter((item: any) => item.selected).length === len;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user