Files
cbsnexora/src/app/tasks/checker-inbox-and-tasks-tabs/share-savings-debit-requests/share-savings-debit-requests.component.ts
T

121 lines
3.8 KiB
TypeScript

/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Component, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import { CashierService } from '../../../cashier/cashier.service';
/**
* Approval Center tab — share applications a clerk has asked to settle out of the member's own
* savings, awaiting a manager.
*
* Approving is the moment the member's balance actually changes: it withdraws the share value plus
* entry fee from their savings and issues the shares. Nothing has moved before that, which is why
* this step exists at all — the cash route needs no checker because the cashier's receipt is
* already a second pair of eyes, whereas here nobody has counted anything.
*
* A manager cannot approve a request they raised themselves; the server rejects that, and the
* message is surfaced on the row rather than swallowed.
*/
@Component({
selector: 'mifosx-share-savings-debit-requests',
templateUrl: './share-savings-debit-requests.component.html',
styleUrls: [
'../../../cashier/cashier-shared.scss',
'../../../clerk/clerk-shared.scss',
'./share-savings-debit-requests.component.scss'
],
standalone: true,
imports: [
CommonModule,
FormsModule
]
})
export class ShareSavingsDebitRequestsComponent implements OnInit {
private cashierService = inject(CashierService);
rows: any[] = [];
loading = false;
loaded = false;
actioningId: number | null = null;
actionError: Record<number, string> = {};
/** Which row has its reject box open, and what reason has been typed into it. */
expandedRejectId: number | null = null;
rejectReason: Record<number, string> = {};
ngOnInit(): void {
this.load();
}
load(): void {
this.loading = true;
this.cashierService
.getShareSavingsDebitRequests('PENDING')
.pipe(catchError(() => of([])))
.subscribe((res: any) => {
this.rows = res ?? [];
this.loading = false;
this.loaded = true;
});
}
approve(row: any): void {
this.actioningId = row.id;
delete this.actionError[row.id];
this.cashierService.approveShareSavingsDebit(row.id).subscribe({
next: () => {
this.actioningId = null;
this.load();
},
error: (err: any) => {
this.actioningId = null;
this.actionError[row.id] =
err?.error?.defaultUserMessage || err?.error?.message || 'Could not approve this request.';
}
});
}
toggleReject(row: any): void {
this.expandedRejectId = this.expandedRejectId === row.id ? null : row.id;
}
reject(row: any): void {
this.actioningId = row.id;
delete this.actionError[row.id];
this.cashierService.rejectShareSavingsDebit(row.id, this.rejectReason[row.id]?.trim()).subscribe({
next: () => {
this.actioningId = null;
this.expandedRejectId = null;
this.load();
},
error: (err: any) => {
this.actioningId = null;
this.actionError[row.id] =
err?.error?.defaultUserMessage || err?.error?.message || 'Could not reject this request.';
}
});
}
/**
* Whether the savings account still covers what the request asks for.
*
* The balance can move between a clerk raising the request and a manager reaching it, and the
* server refuses a short account — showing it on the row means the manager sees why before
* clicking rather than after.
*/
isShort(row: any): boolean {
return Number(row.savings_balance ?? 0) < Number(row.total_amount ?? 0);
}
}