Account Statement is no longer a jsPDF file that downloads the moment the dialog opens. It now shows a live, on-screen preview styled the same way the working Passbook screen already is (bank header, branch line, account-details band, balance strip, transaction table), with its own date-range filter, and only prints via the browser's print dialog on an explicit click -- exactly the same clone-into-a-popup-window mechanism Passbook already uses, so "Save as PDF" is the reader's own choice rather than something forced on open. Both Passbook and the new Statement preview pull the bank name, license number, and branch name/address/IFSC/MICR from Institution Profile and Branch Profile instead of a hardcoded bank name -- a printed passbook or statement now actually identifies which bank and branch issued it. Cheque Book's own issuance detail screen gets a matching "Print" action, laying out a one-page issuance-slip PDF from data already on that screen (which book/series, leaf range, issued-to account, issue date). Cash Hold and Clearing Hold on the Transaction Entry balance strip were both hardcoded 0.00 placeholders; they now read the account's real hold-category breakdown, and a stray "Freeze Amount" placeholder a few rows up is wired to the working getter that was already sitting right next to it. A clerk withdrawal's own terminal status is WITHDRAWN, not APPROVED -- the Day Book screen had the same status-string gap the backend GL report did: neither its running CR/DR totals nor its status badge recognized a completed withdrawal, so real money that had gone out never moved the totals off zero. The Closing Cash Report's GL Entries panel also never refreshed for a newly picked date once it had already been opened once -- "Load" hid the panel instead of refreshing it, leaving a stale, wrong-date table under a header that had already relabelled itself correctly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
144 lines
5.2 KiB
TypeScript
144 lines
5.2 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';
|
|
import { buildChequeIssuanceSlipPdf } from './cheque-issuance-slip-pdf';
|
|
|
|
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' });
|
|
}
|
|
|
|
printIssuanceSlip(): void {
|
|
const doc = buildChequeIssuanceSlipPdf(this.book);
|
|
doc.save(`cheque-book-issuance-${this.book.id}.pdf`);
|
|
}
|
|
|
|
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
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|