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>
208 lines
8.0 KiB
TypeScript
208 lines
8.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/.
|
||
*/
|
||
|
||
import { Component, OnInit, inject } from '@angular/core';
|
||
import { CommonModule } from '@angular/common';
|
||
import { ActivatedRoute, Router } from '@angular/router';
|
||
import { MatDialog } from '@angular/material/dialog';
|
||
import { ChequeBookService } from '../../chequebook/chequebook.service';
|
||
import { LeafDetailDialogComponent } from '../../chequebook/cheque-book-detail/leaf-detail-dialog/leaf-detail-dialog.component';
|
||
import { ClerkLeafActionDialogComponent, LeafAction } from './leaf-action-dialog/leaf-action-dialog.component';
|
||
|
||
/** Cheque-book detail for the Clerk Dashboard — a fullscreen clerk-styled view of one book, its leaf
|
||
* register, and the same maker-side leaf actions (stop-payment, revoke, report-lost, cancel-leaf,
|
||
* mark-used) as the back-office ChequeBookDetailComponent, so a clerk can act on a leaf without leaving
|
||
* the clerk shell/nav. Every action still goes through the existing maker-checker approval flow server-side.
|
||
* Each action opens as a popup (ClerkLeafActionDialogComponent) rather than an inline form so it never
|
||
* pushes the leaf grid around and gets the full dialog width to lay out its fields. */
|
||
@Component({
|
||
selector: 'mifosx-clerk-cheque-book-detail',
|
||
standalone: true,
|
||
imports: [CommonModule],
|
||
styleUrl: './cheque-book-detail.component.scss',
|
||
template: `
|
||
<div class="cb-page">
|
||
<!-- Header -->
|
||
<div class="cb-header">
|
||
<div class="cb-header__left">
|
||
<svg
|
||
class="cb-header__icon"
|
||
width="26"
|
||
height="26"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.8"
|
||
>
|
||
<rect x="1" y="4" width="22" height="16" rx="2" />
|
||
<line x1="1" y1="10" x2="23" y2="10" />
|
||
</svg>
|
||
<div>
|
||
<div class="cb-header__title">Cheque Book #{{ book.id }}</div>
|
||
<div class="cb-header__sub">
|
||
{{ book.accountNo || book.accountId }}
|
||
@if (book.clientName) {
|
||
· {{ book.clientName }}
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<button type="button" class="cb-btn" (click)="goBack()">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<polyline points="15 18 9 12 15 6" />
|
||
</svg>
|
||
Back
|
||
</button>
|
||
</div>
|
||
|
||
@if (loading) {
|
||
<div class="cb-status-msg">Loading cheque book…</div>
|
||
} @else {
|
||
<div class="cb-body">
|
||
<!-- Info card -->
|
||
<div class="cb-info-card">
|
||
<div class="cb-info-grid">
|
||
<div>
|
||
<span class="cb-info-grid__k">Account Type</span>
|
||
<span class="cb-info-grid__v">{{ book.accountType }}</span>
|
||
</div>
|
||
<div>
|
||
<span class="cb-info-grid__k">Office</span>
|
||
<span class="cb-info-grid__v">{{ book.officeName }}</span>
|
||
</div>
|
||
<div>
|
||
<span class="cb-info-grid__k">Series</span>
|
||
<span class="cb-info-grid__v">{{ book.seriesCode ?? '—' }}</span>
|
||
</div>
|
||
<div>
|
||
<span class="cb-info-grid__k">Leaf Range</span>
|
||
<span class="cb-info-grid__v">
|
||
{{ book.rangeStart ? book.rangeStart + ' – ' + book.rangeEnd : '—' }}
|
||
</span>
|
||
</div>
|
||
<div>
|
||
<span class="cb-info-grid__k">Quantity</span>
|
||
<span class="cb-info-grid__v">{{ book.noOfBooks }} book{{ book.noOfBooks === 1 ? '' : 's' }}</span>
|
||
</div>
|
||
<div>
|
||
<span class="cb-info-grid__k">Status</span>
|
||
<span
|
||
class="cb-pill"
|
||
[class.cb-pill--ok]="book.status === 'ISSUED'"
|
||
[class.cb-pill--bad]="book.status === 'REJECTED' || book.status === 'WITHDRAWN'"
|
||
[class.cb-pill--neutral]="
|
||
book.status !== 'ISSUED' && book.status !== 'REJECTED' && book.status !== 'WITHDRAWN'
|
||
"
|
||
>{{ book.status }}</span
|
||
>
|
||
</div>
|
||
<div>
|
||
<span class="cb-info-grid__k">Issue Date</span>
|
||
<span class="cb-info-grid__v">{{ book.issueDate ?? '—' }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Action toolbar — only for an issued book; each button pops up its own action dialog -->
|
||
@if (book.status === 'ISSUED') {
|
||
<div class="cb-action-toolbar">
|
||
<button type="button" class="cb-action-btn cb-action-btn--warn" (click)="openAction('stoppayment')">
|
||
Stop Payment
|
||
</button>
|
||
<button type="button" class="cb-action-btn" (click)="openAction('revokestoppayment')">
|
||
Revoke Stop Payment
|
||
</button>
|
||
<button type="button" class="cb-action-btn cb-action-btn--warn" (click)="openAction('reportlost')">
|
||
Report Lost
|
||
</button>
|
||
<button type="button" class="cb-action-btn" (click)="openAction('cancelleaf')">Cancel Leaf</button>
|
||
<button type="button" class="cb-action-btn cb-action-btn--primary" (click)="openAction('markused')">
|
||
Mark as Used
|
||
</button>
|
||
<button type="button" class="cb-action-btn cb-action-btn--warn" (click)="openAction('return')">
|
||
Flag Returned
|
||
</button>
|
||
</div>
|
||
}
|
||
|
||
<!-- Leaf grid -->
|
||
<div class="cb-info-card">
|
||
@if (leaves.length === 0) {
|
||
<div class="cb-no-books">No leaves to display</div>
|
||
} @else {
|
||
<div class="cb-leaf-grid">
|
||
@for (l of leaves; track l.leafSerialNo) {
|
||
<button
|
||
type="button"
|
||
class="cb-leaf-circle"
|
||
[class.cb-leaf-circle--ok]="l.status === 'ACTIVE'"
|
||
[class.cb-leaf-circle--used]="l.status === 'USED'"
|
||
[class.cb-leaf-circle--warn]="l.status === 'STOP_PAYMENT'"
|
||
[class.cb-leaf-circle--bad]="l.status === 'LOST'"
|
||
[class.cb-leaf-circle--returned]="l.status === 'RETURNED'"
|
||
(click)="selectLeaf(l)"
|
||
>
|
||
{{ l.leafSerialNo }}
|
||
</button>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
`
|
||
})
|
||
export class ClerkChequeBookDetailComponent implements OnInit {
|
||
private route = inject(ActivatedRoute);
|
||
private router = inject(Router);
|
||
private chequeBookService = inject(ChequeBookService);
|
||
private dialog = inject(MatDialog);
|
||
|
||
bookId!: string;
|
||
book: any = {};
|
||
leaves: any[] = [];
|
||
loading = false;
|
||
|
||
ngOnInit(): void {
|
||
this.bookId = this.route.snapshot.paramMap.get('id') as string;
|
||
this.loadAll();
|
||
}
|
||
|
||
loadAll(): void {
|
||
this.loading = true;
|
||
this.chequeBookService.getBook(this.bookId).subscribe((data: any) => {
|
||
this.book = data || {};
|
||
this.loading = false;
|
||
});
|
||
this.chequeBookService.getLeaves(this.bookId).subscribe((data: any[]) => {
|
||
this.leaves = data || [];
|
||
});
|
||
}
|
||
|
||
selectLeaf(leaf: any): void {
|
||
this.dialog.open(LeafDetailDialogComponent, { data: leaf, width: '420px' });
|
||
}
|
||
|
||
openAction(action: LeafAction): void {
|
||
const ref = this.dialog.open(ClerkLeafActionDialogComponent, {
|
||
width: '480px',
|
||
data: { bookId: this.bookId, action }
|
||
});
|
||
ref.afterClosed().subscribe((result) => {
|
||
if (result?.success) {
|
||
this.loadAll();
|
||
}
|
||
});
|
||
}
|
||
|
||
goBack(): void {
|
||
this.router.navigate(['/clerk/account-facilities/cheque-book']);
|
||
}
|
||
}
|