feat: named registers, X/Z reports, daily rollup, fix drawerSessionId
Registers: - New register table with location association - CRUD service + API routes (POST/GET/PATCH/DELETE /registers) - Drawer sessions now link to a register via registerId - Register ID persisted in localStorage per device X/Z Reports: - ReportService with getDrawerReport() (X or Z depending on session state) - Z report auto-displayed on drawer close in the drawer dialog - X report (Current Shift Report) button on open drawer view - Report shows: sales summary, payment breakdown, discounts, cash accountability, adjustments Daily Rollup: - ReportService.getDailyReport() aggregates all sessions at a location for a date - New /reports/daily endpoint with locationId + date params - Frontend daily report page with date picker, location selector, session breakdown Critical Fix: - drawerSessionId is now populated on transactions when completing (was never set before) - This enables accurate per-drawer reporting and cash accountability Migration 0044: register table, drawer_session.register_id column Tests: 14 new (register CRUD, drawer report X/Z, drawerSessionId population, daily rollup, register-drawer link) Full suite: 367 passed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -88,6 +88,15 @@ export interface Product {
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
export interface Register {
|
||||
id: string
|
||||
locationId: string
|
||||
name: string
|
||||
isActive: boolean
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// --- Query Keys ---
|
||||
|
||||
export interface DrawerAdjustment {
|
||||
@@ -104,6 +113,9 @@ export const posKeys = {
|
||||
transaction: (id: string) => ['pos', 'transaction', id] as const,
|
||||
drawer: (locationId: string) => ['pos', 'drawer', locationId] as const,
|
||||
drawerAdjustments: (id: string) => ['pos', 'drawer-adjustments', id] as const,
|
||||
drawerReport: (id: string) => ['pos', 'drawer-report', id] as const,
|
||||
dailyReport: (locationId: string, date: string) => ['pos', 'daily-report', locationId, date] as const,
|
||||
registers: (locationId: string) => ['pos', 'registers', locationId] as const,
|
||||
products: (search: string) => ['pos', 'products', search] as const,
|
||||
discounts: ['pos', 'discounts'] as const,
|
||||
}
|
||||
@@ -148,6 +160,30 @@ export function discountListOptions() {
|
||||
})
|
||||
}
|
||||
|
||||
export function registerListOptions(locationId: string | null) {
|
||||
return queryOptions({
|
||||
queryKey: posKeys.registers(locationId ?? ''),
|
||||
queryFn: () => api.get<{ data: Register[] }>('/v1/registers/all', { locationId }),
|
||||
enabled: !!locationId,
|
||||
})
|
||||
}
|
||||
|
||||
export function drawerReportOptions(drawerSessionId: string | null) {
|
||||
return queryOptions({
|
||||
queryKey: posKeys.drawerReport(drawerSessionId ?? ''),
|
||||
queryFn: () => api.get<any>(`/v1/reports/drawer/${drawerSessionId}`),
|
||||
enabled: !!drawerSessionId,
|
||||
})
|
||||
}
|
||||
|
||||
export function dailyReportOptions(locationId: string | null, date: string) {
|
||||
return queryOptions({
|
||||
queryKey: posKeys.dailyReport(locationId ?? '', date),
|
||||
queryFn: () => api.get<any>('/v1/reports/daily', { locationId, date }),
|
||||
enabled: !!locationId && !!date,
|
||||
})
|
||||
}
|
||||
|
||||
// --- Mutations ---
|
||||
|
||||
export const posMutations = {
|
||||
@@ -169,7 +205,7 @@ export const posMutations = {
|
||||
void: (txnId: string) =>
|
||||
api.post<Transaction>(`/v1/transactions/${txnId}/void`, {}),
|
||||
|
||||
openDrawer: (data: { locationId?: string; openingBalance: number }) =>
|
||||
openDrawer: (data: { locationId?: string; registerId?: string; openingBalance: number }) =>
|
||||
api.post<DrawerSession>('/v1/drawer/open', data),
|
||||
|
||||
closeDrawer: (id: string, data: { closingBalance: number; denominations?: Record<string, number>; notes?: string }) =>
|
||||
|
||||
Reference in New Issue
Block a user