Files
cbsnexora/src/app/chequebook/cheque-book-detail/cheque-book-detail.component.ts
T
Jaanu423andClaude Sonnet 5 0cc79ccd57 feat(clerk): add clerk-facing Cheque Book screen and shell nav wiring
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>
2026-07-15 16:08:43 +05:30

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
);
}
});
}
}