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-v3 → GET /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 countrefunded_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— pendingcompleted_orders— completedcreated_by_users— by creatorclosed_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_houron calendar date D (location timezone) - Closing =
opening_houron D+1
Configured in config table (time_zone, opening_hour).
Do not
- Call TRACK
/api/v2/today-sales-v3when invoice service is configured — use/v1/daily-sales. - Expect
{ data: { … } }wrapper on success. - Sum payment rows blindly for footer — use
total_payableortotalAmount.
Engineering reference
Full field list, architecture, and migration notes: DAILY_SALES_API.md