feat(cashier): cashier-only cash operations, exchange PAN/denomination checks and Branch Cash Position

- Cash Exchange Entry: customer details with PAN (mandatory above Rs 50,000), cashier card,
  denomination-wise validation against the master and real-time impact preview.
- Inter-Cashier Transfer: sender fixed to the logged-in cashier, both sides' details and
  denomination holdings shown, blocked until both are allocated cash.
- Reconciliation, exchange and inter-cashier operate on the logged-in cashier only.
- New Branch Cash Position screen (vault + cashier drawers, denomination-wise).
- Customer search dialog shows the KYC PAN.
- Cashier menu cleanup: Excess/Closing report and Exchange Detail removed, nav compacted to one row.
- Reverse actions removed from cash transfer, settlement, exchange and inter-cashier.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 18:41:24 +05:30
co-authored by Claude Sonnet 5.5
parent 211027b9d8
commit 915deddb74
19 changed files with 2601 additions and 540 deletions
@@ -12,7 +12,6 @@ import { FormsModule } from '@angular/forms';
import { MatIconModule } from '@angular/material/icon';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { MatInputModule } from '@angular/material/input';
import { MatTabsModule } from '@angular/material/tabs';
import { forkJoin, of } from 'rxjs';
@@ -58,7 +57,6 @@ interface ReconRecord {
MatIconModule,
MatSnackBarModule,
MatFormFieldModule,
MatSelectModule,
MatInputModule,
MatTabsModule
]
@@ -67,9 +65,14 @@ export class ReconciliationComponent implements OnInit {
private service = inject(CashierService);
private snackBar = inject(MatSnackBar);
/**
* A reconciliation is the cashier's own count of their own drawer, so nobody else makes one and there is
* no cashier to pick: the screen is fixed to the logged-in cashier. The server refuses anything else.
*/
ownCashierMissing = false;
ownTellerName = '';
// ── Entry form ──
tellers: { id: number; name: string }[] = [];
cashiers: { id: number; label: string }[] = [];
selectedTellerId: number | null = null;
selectedCashierId: number | null = null;
cashierName = '';
@@ -88,42 +91,28 @@ export class ReconciliationComponent implements OnInit {
ngOnInit(): void {
const d = businessToday();
this.reconDate = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
this.service
.getTellers()
.pipe(catchError(() => of([] as any[])))
.subscribe((list) => {
this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name }));
if (this.tellers.length === 1) {
this.onTellerSelect(this.tellers[0].id);
}
});
this.lockToOwnCashier();
this.loadHistory();
}
onTellerSelect(tellerId: number): void {
this.selectedTellerId = tellerId;
this.selectedCashierId = null;
this.cashierName = '';
this.cashiers = [];
this.rows = [];
if (!tellerId) return;
this.service
.getCashiersForTeller(tellerId)
.pipe(catchError(() => of({ cashiers: [] })))
.subscribe((r: any) => {
this.cashiers = (r?.cashiers || []).map((c: any) => ({ id: c.id, label: c.staffName }));
if (this.cashiers.length === 1) {
this.onCashierSelect(this.cashiers[0].id);
}
});
}
onCashierSelect(cashierId: number): void {
this.selectedCashierId = cashierId;
const c = this.cashiers.find((x) => x.id === cashierId);
this.cashierName = c ? c.label : '';
this.rows = [];
this.fetchExpected();
/** Take the logged-in cashier as the one being reconciled, with their counter for the record. */
private lockToOwnCashier(): void {
this.service.getCurrentCashierInfo().subscribe((info) => {
if (!info) {
this.ownCashierMissing = true;
return;
}
this.selectedCashierId = info.cashierId;
this.cashierName = info.cashierName;
this.service
.getInterCashierCashierSummary(info.cashierId, this.reconDate)
.pipe(catchError(() => of(null)))
.subscribe((r: any) => {
this.selectedTellerId = r?.tellerId ?? null;
this.ownTellerName = r?.tellerName || '';
});
this.fetchExpected();
});
}
fetchExpected(): void {