Skip to content

Sales Reports

The Report page (/point-of-sale/report) provides a lane-level sales summary with filters by date, staff, and shift. Shows orders, revenue, payment breakdowns, and shift close workflow.

Sales Report

┌──────────────────────────────────────────────────────────┐
│ Offline notice (if applicable) │
├──────────────────────────────────────────────────────────┤
│ Filter toolbar: Date range | Staff | Refresh | Reset │
├──────────────────────────────────────────────────────────┤
│ Three-column grid: │
│ ┌─────────────┬─────────────┬─────────────┐ │
│ │ Sales │ Cashflow │ Handover │ │
│ │ - Orders │ - Income │ - Open shift│ │
│ │ - Returns │ - By method │ - Totals │ │
│ │ - Goods │ - Expenses │ - Close btn │ │
│ └─────────────┴─────────────┴─────────────┘ │
└──────────────────────────────────────────────────────────┘

Default: Today only (midnight to midnight in store timezone)

Custom range:

  1. Click date button → Calendar dropdown opens
  2. Select start date (from)
  3. Select end date (to)
  4. Range applies to all stats

Timezone handling:

  • Uses store’s configured timezone (dashboard_prefs.timezone)
  • Converts browser date to YMD in store zone
  • API receives date_from, date_to, timezone params

Options:

  • All employees: Orders from all staff
  • Cashier only: POS-created orders only (excludes admin/wholesale)

Default: All employees

Requires pos_staff_only=true API param when “Cashier only” selected.

Searches orders within date range by:

  • Order number
  • Customer name
  • Product name

Debounced 350ms, updates all cards when query changes.

Circular arrow icon — refetches:

  • Report summary (/vendor/point-of-sale/report/summary)
  • Current open shift (if any)
  • Shift totals

Clears all filters:

  • Date range → Today
  • Staff filter → All
  • Search → Empty

Section: Orders

MetricDescription
Sales ordersCount + amount of completed orders
Return ordersCount + amount of returns (negative)

Section: Goods

MetricDescription
Qty soldSum of line quantities (positive sales)
Goods sold amountRevenue from sold goods
Qty returnedSum of returned quantities (negative)
Goods returned amountRefunded amount

All amounts formatted as currency with separators.

Income section

Shows total income with breakdown by payment method:

MethodShows
CashCash payments received
CardTerminal + manual card entries
Bank TransferManual bank transfer tracking
E-walletMobile payment methods
Customer DebtBilled to customer accounts

Income total = sum of all payment methods (excludes returns).

Expense section

Shows total expenses (manual expense entries, not automated).

Debt section (expandable)

Collapsible section showing:

  • Outstanding debt owed to store
  • Debt collected
  • New debt issued

Click section header to expand/collapse.

Purpose: Track shift totals and close handover between cashiers.

If current user has open shift, shows:

  • Shift label: e.g., “Morning shift”, “Lane 1”
  • Opened at: Timestamp (formatted with date-fns)
  • Staff: Employee who opened shift
  • Lane: POS register label (if multi-lane)

Total in shift:

  • Cash collected during shift
  • Updates in real-time as orders placed
  • Source: order_stats.cash_total from shift detail API

Total end shift:

  • All payments (cash + card + other) during shift
  • Source: order_stats.total_amount from shift detail

“Loading shift totals” appears when:

  • Shift exists but detail query pending
  • Offline and cannot fetch shift data

Unavailable state:

  • Offline and shift detail failed to load
  • Shows ”—” for totals
  • Amber text: “Shift totals unavailable offline”

Conditions to show:

  • Work shifts enabled in store config
  • Current user has open shift
  • Not loading shift detail

Click behavior:

  1. Opens Close Shift Modal (shared component from Dashboard)
  2. Shows shift totals, expected cash, actual cash input
  3. Variance calculation (expected - actual)
  4. Confirm → Closes shift, navigates back to report

After close:

  • Shift no longer appears in “Open shift” section
  • Can open new shift from Work Shift page

If work shifts enabled but no open shift:

  • Shows message: “No open shift for this lane”
  • User must open shift from Work Shift page before processing sales

When viewing historical date range (not live shift):

  • “Total in shift” shows cash for date range
  • “Total end shift” shows all payments for date range
  • These are not tied to a specific shift
  • Footer hint: “Totals based on date range, not current shift”

Amber banner at top when offline:

  • “Report unavailable offline — last known data shown”
  • Or: “Cannot load report data”

Report attempts stale-while-revalidate but may fail:

  • Shows cached data if available
  • Error state if no cache

Cannot close shift when offline:

  • Close button disabled
  • Tooltip: “Shift close requires internet connection”

Store configuration flag: enableWorkShift = true

Effect:

  • Handover card shows open shift section
  • Report can filter by shift-scoped data
  • Orders tagged with work_shift_id

Configuration flag: enableWorkShift = false

Effect:

  • Handover card shows date range totals only
  • No shift open/close workflow
  • Orders not tagged with shift ID

All date filters converted to store timezone via:

  • usePosStoreTimezone(storeId) hook
  • Returns IANA timezone from dashboard_prefs.timezone
  • Fallback: Browser timezone → UTC

API receives:

  • date_from: “YYYY-MM-DD” in store zone
  • date_to: “YYYY-MM-DD” in store zone
  • timezone: IANA string (e.g., “America/Los_Angeles”)

Auto-refresh: None (must manually refresh)

On refresh:

  1. Fetches latest report summary
  2. Fetches current open shift (if any)
  3. Fetches shift detail (if open shift exists)
  4. Updates all cards simultaneously

Toast message: “Report refreshed” after successful fetch.

Red banner appears if report fetch fails:

  • Shows error message from API
  • Or fallback: “Could not load report”
  • Retry by clicking Refresh

If shift detail fails:

  • Totals show ”—”
  • Amber text: “Shift totals unavailable”
  • Does not block close shift (uses report summary instead)