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:
co-authored by
Claude Sonnet 4.6
parent
6433ae068d
commit
51495f8eb8
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user