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>
|
||||
}
|
||||
|
||||
<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>
|
||||
|
||||
<div class="dip-info flex-98">
|
||||
@@ -356,7 +389,7 @@
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext>
|
||||
<button mat-raised-button matStepperNext [disabled]="sanctionsBlocked">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</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 {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
+57
-2
@@ -31,7 +31,10 @@ import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
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
|
||||
@@ -59,6 +62,7 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy {
|
||||
private settingsService = inject(SettingsService);
|
||||
private clientService = inject(ClientsService);
|
||||
externalNationalIdService = inject(ExternalNationalIdService);
|
||||
private dialog = inject(MatDialog);
|
||||
|
||||
/** Subject to trigger unsubscription on destroy */
|
||||
private destroy$ = new Subject<void>();
|
||||
@@ -98,6 +102,10 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy {
|
||||
savingProductOptions: any;
|
||||
/** Mobile number DIP verification status */
|
||||
mobileVerified = false;
|
||||
/** Sanctions verification state */
|
||||
sanctionsVerified = false;
|
||||
/** Sanctions match was found — blocks account opening */
|
||||
sanctionsBlocked = false;
|
||||
|
||||
/**
|
||||
* @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() {
|
||||
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 {
|
||||
|
||||
@@ -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',
|
||||
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',
|
||||
loadChildren: () =>
|
||||
|
||||
@@ -274,6 +274,13 @@
|
||||
>
|
||||
{{ 'labels.buttons.New Fixed Deposits Account' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*mifosxHasPermission="'CREATE_CURRENTACCOUNT'"
|
||||
[routerLink]="['current-accounts', 'create']"
|
||||
>
|
||||
{{ 'labels.buttons.New Current Account' | translate }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
<mat-menu #Actions="matMenu">
|
||||
|
||||
@@ -490,6 +490,15 @@ export class ClientsService {
|
||||
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.
|
||||
* @param clientId Client ID
|
||||
@@ -550,6 +559,16 @@ export class ClientsService {
|
||||
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> {
|
||||
return this.http.get(`/clients/${clientId}/form60`);
|
||||
}
|
||||
|
||||
@@ -116,6 +116,10 @@ export class CreateClientComponent {
|
||||
|
||||
areFormvalids(): boolean {
|
||||
let areValids = this.clientGeneralForm.valid;
|
||||
// Block form if sanctions match found
|
||||
if (this.clientGeneralStep.sanctionsBlocked) {
|
||||
return false;
|
||||
}
|
||||
if (this.clientTemplate.isAddressEnabled) {
|
||||
areValids = areValids && this.clientAddressStep.address.address.length > 0;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,57 @@
|
||||
<mat-sidenav-content class="sidenav">
|
||||
<!-- 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 -->
|
||||
@if (progressBarMode !== 'none') {
|
||||
<div>
|
||||
|
||||
@@ -36,6 +36,51 @@ $color: #e2e4ec;
|
||||
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 {
|
||||
0% {
|
||||
left: 0;
|
||||
|
||||
@@ -23,6 +23,8 @@ import { ToolbarComponent } from './toolbar/toolbar.component';
|
||||
import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component';
|
||||
import { ContentComponent } from './content/content.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';
|
||||
|
||||
/**
|
||||
@@ -43,7 +45,9 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
BreadcrumbComponent,
|
||||
ContentComponent,
|
||||
FooterComponent,
|
||||
AsyncPipe
|
||||
AsyncPipe,
|
||||
FaIconComponent,
|
||||
MatMenuModule
|
||||
]
|
||||
})
|
||||
export class ShellComponent implements OnInit, OnDestroy {
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
>
|
||||
<fa-icon class="mr-05" icon="university" size="lg"></fa-icon>
|
||||
{{ 'labels.menus.Institution' | translate }}
|
||||
<fa-icon icon="caret-down" class="ml-05"></fa-icon>
|
||||
</a>
|
||||
|
||||
<span class="hide-lt-md">
|
||||
@@ -79,6 +80,7 @@
|
||||
>
|
||||
<fa-icon class="mr-05" icon="chart-bar" size="lg"></fa-icon>
|
||||
{{ 'labels.menus.Reports' | translate }}
|
||||
<fa-icon icon="caret-down" class="ml-05"></fa-icon>
|
||||
</a>
|
||||
<a
|
||||
#admin
|
||||
@@ -90,35 +92,22 @@
|
||||
>
|
||||
<fa-icon class="mr-05" icon="shield-alt" size="lg"></fa-icon>
|
||||
{{ 'labels.menus.Admin' | translate }}
|
||||
<fa-icon icon="caret-down" class="ml-05"></fa-icon>
|
||||
</a>
|
||||
<a #configWizard class="tab-link" (click)="openDialog()" tabindex="0" *mifosxHasPermission="'READ_CONFIG_WIZARD'">
|
||||
<fa-icon class="mr-05" icon="info" size="lg"></fa-icon>
|
||||
{{ 'labels.menus.Configuration Wizard' | translate }}
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<mifosx-notifications-tray #notificationsTray class="hide-lt-md" tabindex="0"></mifosx-notifications-tray>
|
||||
</div>
|
||||
@@ -134,11 +123,6 @@
|
||||
</div>
|
||||
</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">
|
||||
<button mat-menu-item routerLink="/clients" tabindex="0" *mifosxHasPermission="'READ_CLIENT'">
|
||||
{{ 'labels.menus.Clients' | translate }}
|
||||
@@ -160,6 +144,12 @@
|
||||
</span>
|
||||
</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 -->
|
||||
<mat-menu #reportsMenu="matMenu" [overlapTrigger]="false">
|
||||
<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)">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</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
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
@@ -385,7 +327,7 @@
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="popover.close(); showPopover(templateLanguageSelector, languageSelector)"
|
||||
(click)="popover.close(); showPopover(templateConfigWizard, configWizard)"
|
||||
>
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
|
||||
@@ -8,10 +8,7 @@
|
||||
|
||||
#mifosx-toolbar {
|
||||
position: relative;
|
||||
box-shadow:
|
||||
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%);
|
||||
box-shadow: none;
|
||||
|
||||
.tab-nav-bar {
|
||||
display: flex;
|
||||
@@ -21,7 +18,7 @@
|
||||
|
||||
.tab-link {
|
||||
min-width: auto;
|
||||
padding: 15px 1rem;
|
||||
padding: 15px 0.5rem;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
@@ -34,21 +31,6 @@
|
||||
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 {
|
||||
width: 32px;
|
||||
@@ -57,6 +39,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.ml-05 {
|
||||
margin-left: 0.35rem;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ml-1 {
|
||||
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 {
|
||||
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: 2, code: 'chargeAppliesTo.savings', value: 'Savings' },
|
||||
{ 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