Adds a customer-account-scoped Cheque Book view under Clerk > Account Facilities (list + leaf detail, with stop-payment/report-lost/mark-used/ return actions), reachable via a new hover dropdown on the Account Facilities nav item. Also refreshes the Account Facilities page layout, and carries over a few chequebook service/detail-dialog fixes from the back-office module (used-leaf display, leaf-lookup dialog polish). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
138 lines
5.0 KiB
TypeScript
138 lines
5.0 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/.
|
|
*/
|
|
|
|
/** Angular Imports */
|
|
import { Component, OnInit, inject } from '@angular/core';
|
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
|
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
|
import { MatButtonModule } from '@angular/material/button';
|
|
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
|
import { MatDialog } from '@angular/material/dialog';
|
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
import { Observable } from 'rxjs';
|
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
|
|
/** Custom Services */
|
|
import { ChequeBookService } from '../chequebook.service';
|
|
import { LeafDetailDialogComponent } from './leaf-detail-dialog/leaf-detail-dialog.component';
|
|
|
|
type LeafAction = 'stoppayment' | 'revokestoppayment' | 'reportlost' | 'cancelleaf' | 'markused' | 'return';
|
|
|
|
/**
|
|
* One issued cheque book: its leaf register, and the six leaf-range actions (stop-payment, revoke,
|
|
* report-lost, cancel-leaf, mark-used, return) — each defaults to the whole book when no range is given.
|
|
* "Mark as used" is a manual record (this CBS has no clearing/presentment integration to detect it itself)
|
|
* — a branch sets it once it sees a leaf clear on the bank/clearing statement. "Return" flags a leaf as
|
|
* presented-and-dishonoured (insufficient funds, signature mismatch, etc.) — terminal, like report-lost.
|
|
*/
|
|
@Component({
|
|
selector: 'mifosx-cheque-book-detail',
|
|
templateUrl: './cheque-book-detail.component.html',
|
|
styleUrls: ['./cheque-book-detail.component.scss'],
|
|
imports: [
|
|
...STANDALONE_SHARED_IMPORTS,
|
|
RouterLink,
|
|
MatButtonModule,
|
|
MatSnackBarModule,
|
|
FaIconComponent
|
|
]
|
|
})
|
|
export class ChequeBookDetailComponent implements OnInit {
|
|
private route = inject(ActivatedRoute);
|
|
private formBuilder = inject(UntypedFormBuilder);
|
|
private chequeBookService = inject(ChequeBookService);
|
|
private snackBar = inject(MatSnackBar);
|
|
private dialog = inject(MatDialog);
|
|
|
|
private notify(message: string, isError = false): void {
|
|
this.snackBar.open(message, 'Dismiss', { duration: isError ? 6000 : 3000 });
|
|
}
|
|
|
|
bookId!: string;
|
|
book: any = {};
|
|
leaves: any[] = [];
|
|
busy = false;
|
|
|
|
activeAction: LeafAction | null = null;
|
|
actionForm: UntypedFormGroup;
|
|
|
|
constructor() {
|
|
this.actionForm = this.formBuilder.group({
|
|
fromLeafSeq: [''],
|
|
toLeafSeq: [''],
|
|
reason: ['']
|
|
});
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this.bookId = this.route.snapshot.paramMap.get('id') as string;
|
|
this.loadAll();
|
|
}
|
|
|
|
loadAll(): void {
|
|
this.chequeBookService.getBook(this.bookId).subscribe((data: any) => (this.book = data || {}));
|
|
this.chequeBookService.getLeaves(this.bookId).subscribe((data: any[]) => {
|
|
this.leaves = data || [];
|
|
});
|
|
}
|
|
|
|
selectLeaf(leaf: any): void {
|
|
this.dialog.open(LeafDetailDialogComponent, { data: leaf, width: '420px' });
|
|
}
|
|
|
|
startAction(action: LeafAction): void {
|
|
this.activeAction = this.activeAction === action ? null : action;
|
|
this.actionForm.reset();
|
|
const reasonControl = this.actionForm.get('reason');
|
|
if (action === 'revokestoppayment' || action === 'markused') {
|
|
reasonControl?.clearValidators();
|
|
} else {
|
|
reasonControl?.setValidators(Validators.required);
|
|
}
|
|
reasonControl?.updateValueAndValidity();
|
|
}
|
|
|
|
submitAction(): void {
|
|
if (this.actionForm.invalid || this.busy || !this.activeAction) {
|
|
return;
|
|
}
|
|
this.busy = true;
|
|
const raw = this.actionForm.value;
|
|
const payload: any = { locale: 'en' };
|
|
if (raw.fromLeafSeq !== '' && raw.fromLeafSeq != null) payload.fromLeafSeq = raw.fromLeafSeq;
|
|
if (raw.toLeafSeq !== '' && raw.toLeafSeq != null) payload.toLeafSeq = raw.toLeafSeq;
|
|
if (raw.reason) payload.reason = raw.reason;
|
|
|
|
const actionCalls: Record<LeafAction, (bookId: string, payload: any) => Observable<any>> = {
|
|
stoppayment: (id, p) => this.chequeBookService.stopPayment(id, p),
|
|
revokestoppayment: (id, p) => this.chequeBookService.revokeStopPayment(id, p),
|
|
reportlost: (id, p) => this.chequeBookService.reportBookLost(id, p),
|
|
cancelleaf: (id, p) => this.chequeBookService.cancelLeaf(id, p),
|
|
markused: (id, p) => this.chequeBookService.markUsed(id, p),
|
|
return: (id, p) => this.chequeBookService.returnBook(id, p)
|
|
};
|
|
const call = actionCalls[this.activeAction](this.bookId, payload);
|
|
|
|
call.subscribe({
|
|
next: () => {
|
|
this.busy = false;
|
|
this.activeAction = null;
|
|
this.notify('Action applied');
|
|
this.loadAll();
|
|
},
|
|
error: () => {
|
|
this.busy = false;
|
|
this.notify(
|
|
'Action failed — check the leaf range and that the leaves are in a state that allows this action',
|
|
true
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|