feat(cashier): morning opening balance panel across settlement, reconciliation, position & history

- Cash Settlement form: morning panel (denomination pills) + Morning column in denomination table
- Reconciliation form: morning panel (denomination pills) + Morning column in denomination table
- Cashier Position Report: Morning vs Current comparison bar + Morning column (single-cashier mode)
- Cash History – Settlement detail panel: Morning Received vs Settled comparison bar + Morning column
- Cash History – denomination Notes/Coins separation in detail panel for all transaction types
- CashierService: added getCashierMorningBalance() calling GET /cashreports/cashier-morning
- All morning data loaded via forkJoin alongside existing API calls (no extra loading states)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-05-31 08:54:27 +05:30
co-authored by Claude Sonnet 4.6
parent 6433ae068d
commit 51495f8eb8
14 changed files with 912 additions and 60 deletions
@@ -21,6 +21,7 @@ interface TxnRow {
date: string;
fromParty: string;
toParty: string;
fromCashierId?: number;
amount: number;
note: string;
isReversed: boolean;
@@ -35,6 +36,8 @@ interface TxnRow {
interface DenomDetail {
denominationId: number | null;
denominationValue: number;
kind: 'note' | 'coin';
morningCount: number;
count: number;
totalAmount: number;
}
@@ -42,6 +45,7 @@ interface DenomDetail {
interface DetailView {
row: TxnRow;
denominations: DenomDetail[];
morningTotal: number;
loading: boolean;
}
@@ -71,6 +75,8 @@ export class CashHistoryComponent implements OnInit {
interCashier: TxnRow[] = [];
loading = false;
private denomKindMap = new Map<number, 'note' | 'coin'>();
reversingId: number | null = null;
reversingType: string | null = null;
reverseReason = '';
@@ -78,6 +84,12 @@ export class CashHistoryComponent implements OnInit {
detail: DetailView | null = null;
ngOnInit(): void {
this.service
.getActiveDenominations()
.pipe(catchError(() => of([])))
.subscribe((list: any[]) => {
(list || []).forEach((d: any) => this.denomKindMap.set(d.id, d.kind === 'coin' ? 'coin' : 'note'));
});
this.load();
}
@@ -107,6 +119,7 @@ export class CashHistoryComponent implements OnInit {
id: s.id,
date: s.settlementDate,
fromParty: s.fromParty,
fromCashierId: s.fromCashierId ?? undefined,
toParty: s.toParty,
amount: s.settlementAmount,
note: s.note,
@@ -138,7 +151,31 @@ export class CashHistoryComponent implements OnInit {
}
viewDetail(row: TxnRow): void {
this.detail = { row, denominations: [], loading: true };
this.detail = { row, denominations: [], morningTotal: 0, loading: true };
if (row.type === 'settlement' && row.fromCashierId) {
forkJoin({
detail: this.service.getCashSettlementById(row.id).pipe(catchError(() => of(null))),
morning: this.service.getCashierMorningBalance(row.fromCashierId, row.date).pipe(catchError(() => of(null)))
}).subscribe(({ detail, morning }) => {
if (!this.detail) return;
const morningMap = new Map<number, number>();
((morning?.denominations || []) as any[]).forEach((d: any) =>
morningMap.set(d.denominationId, d.morningCount ?? 0)
);
this.detail.morningTotal = Number(morning?.totalMorning ?? 0);
this.detail.denominations = ((detail?.details || []) as any[]).map((d: any) => ({
denominationId: d.denominationId ?? null,
denominationValue: d.denominationValue,
kind: this.denomKindMap.get(d.denominationId) ?? (d.denominationValue <= 5 ? 'coin' : 'note'),
morningCount: morningMap.get(d.denominationId) ?? 0,
count: d.count,
totalAmount: d.totalAmount
}));
this.detail.loading = false;
});
return;
}
let obs$;
if (row.type === 'transfer') obs$ = this.service.getCashTransferById(row.id);
@@ -147,9 +184,11 @@ export class CashHistoryComponent implements OnInit {
obs$.pipe(catchError(() => of(null))).subscribe((data: any) => {
if (this.detail) {
this.detail.denominations = (data?.details || []).map((d: any) => ({
this.detail.denominations = ((data?.details || []) as any[]).map((d: any) => ({
denominationId: d.denominationId ?? null,
denominationValue: d.denominationValue,
kind: this.denomKindMap.get(d.denominationId) ?? (d.denominationValue <= 5 ? 'coin' : 'note'),
morningCount: 0,
count: d.count,
totalAmount: d.totalAmount
}));
@@ -200,6 +239,18 @@ export class CashHistoryComponent implements OnInit {
return type === 'transfer' ? 'Cash Transfer' : type === 'settlement' ? 'Cash Settlement' : 'Inter-Cashier Transfer';
}
notes(denoms: DenomDetail[]): DenomDetail[] {
return denoms.filter((d) => d.kind === 'note').sort((a, b) => b.denominationValue - a.denominationValue);
}
coins(denoms: DenomDetail[]): DenomDetail[] {
return denoms.filter((d) => d.kind === 'coin').sort((a, b) => b.denominationValue - a.denominationValue);
}
subtotal(denoms: DenomDetail[]): number {
return denoms.reduce((s, d) => s + Number(d.totalAmount), 0);
}
totalCount(denoms: DenomDetail[]): number {
return denoms.reduce((s, d) => s + d.count, 0);
}