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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
7abfa394df
commit
0cc79ccd57
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* 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']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user