feat(cashier): add full-screen cashier teller workstation dashboard
- Add Transaction Entry screen with full account details panel (personal info, joint holders, nominees, KYC, balances) - Role-based layout: CASHIER role hides nav/sidebar/ticker, redirects to /cashier/transaction-entry on login - Denomination entry with opening/closing position preview (notes first then coins, stepper +/- round button inputs) - Auto-fetch savings/current/FD/RD accounts with associations, client KYC, nominees, addresses, family members (S/D/W/H/O) - Submit deposit/withdrawal via POST /transactions with denominationBreakdown - Cashier position auto-loads for CASHIER role (no filter dropdowns) - Pagination added to cash-history tabs and currency-exchange-detail - Cashier menu simplified for cashier role permissions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
c8e9ab8f84
commit
9094b9a0eb
@@ -12,6 +12,7 @@ import { FormsModule } from '@angular/forms';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
|
||||
import { forkJoin, of } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { CashierService } from '../cashier.service';
|
||||
@@ -65,7 +66,8 @@ interface DetailView {
|
||||
FormsModule,
|
||||
MatIconModule,
|
||||
MatSnackBarModule,
|
||||
MatTabsModule
|
||||
MatTabsModule,
|
||||
MatPaginatorModule
|
||||
]
|
||||
})
|
||||
export class CashHistoryComponent implements OnInit {
|
||||
@@ -78,6 +80,23 @@ export class CashHistoryComponent implements OnInit {
|
||||
exchanges: TxnRow[] = [];
|
||||
loading = false;
|
||||
|
||||
// Pagination — per tab
|
||||
pageSize = 20;
|
||||
pageSizeOptions = [
|
||||
10,
|
||||
20,
|
||||
50,
|
||||
100
|
||||
];
|
||||
transfersTotal = 0;
|
||||
transfersOffset = 0;
|
||||
settlementsTotal = 0;
|
||||
settlementsOffset = 0;
|
||||
interTotal = 0;
|
||||
interOffset = 0;
|
||||
exchangesTotal = 0;
|
||||
exchangesOffset = 0;
|
||||
|
||||
private denomKindMap = new Map<number, 'note' | 'coin'>();
|
||||
|
||||
reversingId: number | null = null;
|
||||
@@ -96,15 +115,55 @@ export class CashHistoryComponent implements OnInit {
|
||||
this.load();
|
||||
}
|
||||
|
||||
onPageChange(tab: 'transfers' | 'settlements' | 'inter' | 'exchanges', event: PageEvent): void {
|
||||
this.pageSize = event.pageSize;
|
||||
if (tab === 'transfers') {
|
||||
this.transfersOffset = event.pageIndex * event.pageSize;
|
||||
this.loadTransfers();
|
||||
}
|
||||
if (tab === 'settlements') {
|
||||
this.settlementsOffset = event.pageIndex * event.pageSize;
|
||||
this.loadSettlements();
|
||||
}
|
||||
if (tab === 'inter') {
|
||||
this.interOffset = event.pageIndex * event.pageSize;
|
||||
this.loadInter();
|
||||
}
|
||||
if (tab === 'exchanges') {
|
||||
this.exchangesOffset = event.pageIndex * event.pageSize;
|
||||
this.loadExchanges();
|
||||
}
|
||||
}
|
||||
|
||||
load(): void {
|
||||
this.transfersOffset = 0;
|
||||
this.settlementsOffset = 0;
|
||||
this.interOffset = 0;
|
||||
this.exchangesOffset = 0;
|
||||
this.loading = true;
|
||||
forkJoin({
|
||||
transfers: this.service.getCashTransfers().pipe(catchError(() => of([]))),
|
||||
settlements: this.service.getCashSettlements().pipe(catchError(() => of([]))),
|
||||
inter: this.service.getInterCashierTransfers().pipe(catchError(() => of([]))),
|
||||
exchanges: this.service.getCashExchanges().pipe(catchError(() => of([])))
|
||||
transfers: this.service
|
||||
.getCashTransfers(0, this.pageSize)
|
||||
.pipe(catchError(() => of({ pageItems: [], totalFilteredRecords: 0 }))),
|
||||
settlements: this.service
|
||||
.getCashSettlements(0, this.pageSize)
|
||||
.pipe(catchError(() => of({ pageItems: [], totalFilteredRecords: 0 }))),
|
||||
inter: this.service
|
||||
.getInterCashierTransfers(0, this.pageSize)
|
||||
.pipe(catchError(() => of({ pageItems: [], totalFilteredRecords: 0 }))),
|
||||
exchanges: this.service
|
||||
.getCashExchanges(0, this.pageSize)
|
||||
.pipe(catchError(() => of({ pageItems: [], totalFilteredRecords: 0 })))
|
||||
}).subscribe(({ transfers, settlements, inter, exchanges }) => {
|
||||
this.transfers = (transfers as any[]).map((t) => ({
|
||||
this.transfersTotal = transfers.totalFilteredRecords ?? 0;
|
||||
this.settlementsTotal = settlements.totalFilteredRecords ?? 0;
|
||||
this.interTotal = inter.totalFilteredRecords ?? 0;
|
||||
this.exchangesTotal = exchanges.totalFilteredRecords ?? 0;
|
||||
const transferItems = transfers.pageItems ?? [];
|
||||
const settlementItems = settlements.pageItems ?? [];
|
||||
const interItems = inter.pageItems ?? [];
|
||||
const exchangeItems = exchanges.pageItems ?? [];
|
||||
this.transfers = (transferItems as any[]).map((t) => ({
|
||||
id: t.id,
|
||||
date: t.transferDate,
|
||||
fromParty: t.fromParty,
|
||||
@@ -119,7 +178,7 @@ export class CashHistoryComponent implements OnInit {
|
||||
reverseReason: t.reverseReason ?? null,
|
||||
type: 'transfer'
|
||||
}));
|
||||
this.settlements = (settlements as any[]).map((s) => ({
|
||||
this.settlements = (settlementItems as any[]).map((s) => ({
|
||||
id: s.id,
|
||||
date: s.settlementDate,
|
||||
fromParty: s.fromParty,
|
||||
@@ -135,7 +194,7 @@ export class CashHistoryComponent implements OnInit {
|
||||
reverseReason: s.reverseReason ?? null,
|
||||
type: 'settlement'
|
||||
}));
|
||||
this.interCashier = (inter as any[]).map((i) => ({
|
||||
this.interCashier = (interItems as any[]).map((i) => ({
|
||||
id: i.id,
|
||||
date: i.transferDate,
|
||||
fromParty: i.fromParty,
|
||||
@@ -150,7 +209,7 @@ export class CashHistoryComponent implements OnInit {
|
||||
reverseReason: i.reverseReason ?? null,
|
||||
type: 'inter'
|
||||
}));
|
||||
this.exchanges = (exchanges as any[]).map((e) => ({
|
||||
this.exchanges = (exchangeItems as any[]).map((e) => ({
|
||||
id: e.id,
|
||||
date: e.exchangeDate,
|
||||
fromParty: e.accountNo ? `Account ${e.accountNo}` : 'Walk-in Customer',
|
||||
@@ -169,6 +228,103 @@ export class CashHistoryComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
private loadTransfers(): void {
|
||||
this.service
|
||||
.getCashTransfers(this.transfersOffset, this.pageSize)
|
||||
.pipe(catchError(() => of({ pageItems: [], totalFilteredRecords: 0 })))
|
||||
.subscribe((res: any) => {
|
||||
this.transfersTotal = res.totalFilteredRecords ?? 0;
|
||||
this.transfers = (res.pageItems ?? []).map((t: any) => ({
|
||||
id: t.id,
|
||||
date: t.transferDate,
|
||||
fromParty: t.fromParty,
|
||||
toParty: t.toParty,
|
||||
amount: t.transferAmount,
|
||||
note: t.note,
|
||||
isReversed: t.isReversed,
|
||||
performedByUsername: t.performedByUsername,
|
||||
createdAt: t.createdAt,
|
||||
reversedBy: t.reversedBy ?? null,
|
||||
reversedAt: t.reversedAt ?? null,
|
||||
reverseReason: t.reverseReason ?? null,
|
||||
type: 'transfer'
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private loadSettlements(): void {
|
||||
this.service
|
||||
.getCashSettlements(this.settlementsOffset, this.pageSize)
|
||||
.pipe(catchError(() => of({ pageItems: [], totalFilteredRecords: 0 })))
|
||||
.subscribe((res: any) => {
|
||||
this.settlementsTotal = res.totalFilteredRecords ?? 0;
|
||||
this.settlements = (res.pageItems ?? []).map((s: any) => ({
|
||||
id: s.id,
|
||||
date: s.settlementDate,
|
||||
fromParty: s.fromParty,
|
||||
fromCashierId: s.fromCashierId ?? undefined,
|
||||
toParty: s.toParty,
|
||||
amount: s.settlementAmount,
|
||||
note: s.note,
|
||||
isReversed: s.isReversed,
|
||||
performedByUsername: s.performedByUsername,
|
||||
createdAt: s.createdAt,
|
||||
reversedBy: s.reversedBy ?? null,
|
||||
reversedAt: s.reversedAt ?? null,
|
||||
reverseReason: s.reverseReason ?? null,
|
||||
type: 'settlement'
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private loadInter(): void {
|
||||
this.service
|
||||
.getInterCashierTransfers(this.interOffset, this.pageSize)
|
||||
.pipe(catchError(() => of({ pageItems: [], totalFilteredRecords: 0 })))
|
||||
.subscribe((res: any) => {
|
||||
this.interTotal = res.totalFilteredRecords ?? 0;
|
||||
this.interCashier = (res.pageItems ?? []).map((i: any) => ({
|
||||
id: i.id,
|
||||
date: i.transferDate,
|
||||
fromParty: i.fromParty,
|
||||
toParty: i.toParty,
|
||||
amount: i.transferAmount,
|
||||
note: i.note,
|
||||
isReversed: i.isReversed,
|
||||
performedByUsername: i.performedByUsername,
|
||||
createdAt: i.createdAt,
|
||||
reversedBy: i.reversedBy ?? null,
|
||||
reversedAt: i.reversedAt ?? null,
|
||||
reverseReason: i.reverseReason ?? null,
|
||||
type: 'inter'
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private loadExchanges(): void {
|
||||
this.service
|
||||
.getCashExchanges(this.exchangesOffset, this.pageSize)
|
||||
.pipe(catchError(() => of({ pageItems: [], totalFilteredRecords: 0 })))
|
||||
.subscribe((res: any) => {
|
||||
this.exchangesTotal = res.totalFilteredRecords ?? 0;
|
||||
this.exchanges = (res.pageItems ?? []).map((e: any) => ({
|
||||
id: e.id,
|
||||
date: e.exchangeDate,
|
||||
fromParty: e.accountNo ? `Account ${e.accountNo}` : 'Walk-in Customer',
|
||||
toParty: e.performedByUsername || '—',
|
||||
amount: e.exchangeAmount,
|
||||
note: e.note,
|
||||
isReversed: e.isReversed ?? false,
|
||||
performedByUsername: e.performedByUsername,
|
||||
createdAt: e.createdAt,
|
||||
reversedBy: e.reversedBy ?? null,
|
||||
reversedAt: e.reversedAt ?? null,
|
||||
reverseReason: e.reverseReason ?? null,
|
||||
type: 'exchange'
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
viewDetail(row: TxnRow): void {
|
||||
this.detail = {
|
||||
row,
|
||||
|
||||
Reference in New Issue
Block a user