README docs frontend-handout/07-DAILY-SALES.md

07 — Daily sales report

End-of-day sales summary for POS (web-pos, iOS, Android). Same payload as legacy GET /api/v2/today-sales-v3.


Endpoint

GET /v1/daily-sales
Authorization: Bearer <jwt>
Environment Base URL example
Local http://localhost:8080/v1/daily-sales
Staging / prod {INVOICE_SERVICE_URL}/daily-sales

Legacy: GET /api/v2/today-sales-v3GET /v1/daily-sales

web-pos: invoiceApiSlice.getDailySales — not the TRACK API (apiSlice).


Authentication

Tenant from JWT only (customer_id, location_id). Do not send customer/location only in the body.

Authorization: Bearer <jwt>

Query parameters

Param Type Description
date string Single day DD-MM-YYYY (web-pos uses this)
from_date string Range start DD-MM-YYYY
to_date string Range end DD-MM-YYYY
paid_at string Filter by paid date DD-MM-YYYY
brand_id number | string Single brand
brands string Comma-separated brand ids
close_by_user_id number Filter by user who closed the order
shift 1 | true Current user's open shift only
shift_id number Specific shift
location_id number Override location (if JWT allows)
force_location_ids string Multi-location (comma-separated ids)
app_version string Modifier qty formatting
build_number number | string With app_version

Defaults: If no dates sent, uses today. Invalid date → 400.

Example (web-pos daily-sales page):

GET /v1/daily-sales?date=09-07-2026&close_by_user_id=4734&app_version=1.1.1&build_number=16

Response

Plain JSON object — not { success, data }.

What to render first (web-pos)

UI section Response fields
Summary cards order_type (keys → counts)
Product tables itemsCat + items / addons / modifiers / refund_items
User statistics table user_statistics
Payment methods table itemsPayment; footer total = total_payable ?? totalAmount
Print / PDF Full object

order_type keys

Counts by order type. Keys may include:

  • dine_in, drive_thru, takeaway, delivery, online, …
  • void_orders — cancelled invoice count
  • refunded_items — refunded line count

Display: replace _ with spaces and title-case.

itemsCat + line arrays

itemsCat lists category totals. Match each row's type to:

itemsCat.type Source array
Product type name items (filter item.type)
Add-ons addons
modifiers modifiers
Refund Items refund_items

web-pos pattern: mapByCategory(data) groups itemsCat with the matching array.

itemsPayment

{
  "payment_method": "cash",
  "total": 1200.5,
  "transactions": 45,
  "tip_amount": 12.0
}

Synthetic rows may appear: Unpaid Order, Discount Amount, Tip Amount, Points, Top-ups (Cash & Card).

Key totals

Field Meaning
totalAmount / total_sales Completed sales total
total_payable Use for payment footer
total_vat VAT
gross_sales Before discounts
avg_invoice_bill Average ticket
total_orders Completed count

Wallet & cash drawer

Field Meaning
wallet_orders.total_amount Paid via wallet
wallet_topups.amount Top-up total
withdraw_amount Cash out
credit_amount Cash in

Users

user_statistics contains four blocks:

  • open_orders — pending
  • completed_orders — completed
  • created_by_users — by creator
  • closed_by_users — by closer

Each: { users: [{ user, order_count, total_amount, orders? }], total_amount, total_orders }.

Dates & meta

Field Meaning
date_range.from / .to Resolved range YYYY-MM-DD
time Local time string
ts Server timestamp (ms)
shift_filter_applied Shift filter was on
active_shift_id Shift id when filtered

RTK Query (web-pos)

// invoiceApiSlice — baseUrl: NEXT_PUBLIC_INVOICE_SERVICE_URL
getDailySales: builder.query({
  query: ({ params = {} } = {}) => ({
    url: "/daily-sales",
    method: "GET",
    params: {
      ...params,
      app_version: appConfig.app_version,
      build_number: appConfig.build_number,
    },
  }),
}),
const { data, isLoading, error } = useGetDailySalesQuery({
  params: { date: "09-07-2026", close_by_user_id: 4734 },
});
// `data` is the report object directly

Errors

HTTP When
400 Bad date, missing business hours, missing JWT claims
401 Invalid token
403 Location not allowed
500 Server error

Error envelope (middleware): { success: false, code, message }.

Some report-level 400s return a plain { message } body (legacy parity).

Empty shift: When shift=1 but user has no open shift → 200 with zeros (not an error).


Business day

Sales are for the business day, not midnight–midnight:

  • Opening = opening_hour on calendar date D (location timezone)
  • Closing = opening_hour on D+1

Configured in config table (time_zone, opening_hour).


Do not

  • Call TRACK /api/v2/today-sales-v3 when invoice service is configured — use /v1/daily-sales.
  • Expect { data: { … } } wrapper on success.
  • Sum payment rows blindly for footer — use total_payable or totalAmount.

Engineering reference

Full field list, architecture, and migration notes: DAILY_SALES_API.md