Files
cbsnexora/src/app/tasks/checker-inbox-and-tasks-tabs/share-closure-requests/share-closure-requests.component.ts
T
niteshandClaude Sonnet 5 7f836f6f71 feat(shares): add Share Account Closure maker-checker workflow
Clerk submits a closure request only once a share account holds zero
shares; manager validates (shares still zero, account active, no active
loan/guarantee, KYC/AML standing, maker!=checker) and approves, which
executes the real Fineract "close" command. The direct native "Close"
action is removed from the account menu — it silently force-redeemed any
remaining shares, bypassing the withdrawal approval flow entirely — and
replaced with an "Eligible for Closure" banner. When a withdrawal approval
brings a share account's holding to zero, a closure request is now
auto-submitted (still requires manager approval) so the clerk doesn't have
to separately open "New Share Closure Request".

Also fixes an unrelated build break introduced by a merged commit: a
missing `suppressMonthlyAnnualLink` property declaration, and two files
(branch-scope.service.ts, gl-reports-data.ts) that existed on `demo` but
were never brought into `dev` alongside the new TDS report components
that depend on them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 11:43:56 +05:30

129 lines
3.5 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 { ClerkShareClosureService } from '../../../cashier/clerk-share-closure.service';
import { ShareClosureRequestComponent } from '../../../clerk/share-closure-request/share-closure-request.component';
/** Approval Center — Share Closure Requests tab. A Bank Manager (checker) reviews, approves or
* rejects share account closure requests submitted from the Clerk Dashboard. Mirrors
* ShareWithdrawalRequestsComponent. Clicking "Review" opens the review screen in a popup on top of
* this list, rather than navigating away, so approving/rejecting drops the manager straight back
* into the (now refreshed) list. */
@Component({
selector: 'mifosx-share-closure-requests',
templateUrl: './share-closure-requests.component.html',
styleUrls: [
'../../../cashier/cashier-shared.scss',
'../../../clerk/clerk-shared.scss',
'../share-withdrawal-requests/share-withdrawal-requests.component.scss'
],
standalone: true,
imports: [
CommonModule,
FormsModule,
ShareClosureRequestComponent
]
})
export class ShareClosureRequestsComponent implements OnInit {
private service = inject(ClerkShareClosureService);
requests: any[] = [];
totalRecords = 0;
loading = false;
loaded = false;
filterStatus = 'PENDING';
showReviewModal = false;
reviewRequestId: number | null = null;
get statusOptions(): { value: string; label: string }[] {
return [
{ value: 'PENDING', label: 'Pending' },
{ value: 'APPROVED', label: 'Approved' },
{ value: 'REJECTED', label: 'Rejected' }
];
}
ngOnInit(): void {
this.load();
}
load(): void {
this.loading = true;
this.service
.getPendingRequests(this.filterStatus, 0, 50)
.pipe(catchError(() => of({ pageItems: [], totalFilteredRecords: 0 })))
.subscribe((res: any) => {
this.requests = res?.pageItems ?? [];
this.totalRecords = Number(res?.totalFilteredRecords ?? 0);
this.loading = false;
this.loaded = true;
});
}
onFilterChange(): void {
this.load();
}
review(req: any): void {
this.reviewRequestId = req.id;
this.showReviewModal = true;
}
closeReviewModal(): void {
this.showReviewModal = false;
this.reviewRequestId = null;
}
onReviewActionCompleted(): void {
this.closeReviewModal();
this.load();
}
statusClass(status: string): string {
const map: Record<string, string> = {
PENDING: 'inv-badge--active',
APPROVED: 'inv-badge--matured',
REJECTED: 'inv-badge--written_off'
};
return `inv-badge ${map[status] ?? ''}`;
}
statusLabel(status: string): string {
const map: Record<string, string> = {
PENDING: 'Pending',
APPROVED: 'Approved',
REJECTED: 'Rejected'
};
return map[status] ?? status;
}
formatDateTime(val: any): string {
if (!val) return '—';
try {
const d = new Date(val);
return d.toLocaleString('en-IN', {
day: '2-digit',
month: 'short',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
} catch {
return String(val);
}
}
}