Build my booking setup

Reporting & Audit

Every number traces back to who, what and when.

Gross sales, cost and margin never mix currencies into one misleading total. And every edit to a price, an availability rule or a tax assignment logs exactly what changed, from what value to what, by whom.

  • By currency, always Gross sales, cost and margin never mix currencies into one misleading total
  • 8 entity types Pricing rules, exceptions and dynamic rules, availability rules and exceptions, tax assignments, exceptions and categories all log every change
  • Constant query count The report runs the same number of queries whether the range holds 3 orders or 3 million

Four pieces of accountability.

Each one answers a different question: how much did we sell, what did we lose, can I get it out, and who touched what.

01

Sales, broken down honestly

Orders, tickets sold, gross sales, cost and margin, by currency, with a trend chart and a breakdown by channel and top product, never one blended figure that hides what actually happened.

02

Cancellations, kept apart from revenue

Cancelled, expired and refunded orders get their own section, so a booking that never charged anything is never confused with a sale that was lost after the fact.

03

A CSV export that streams

Order-level exports write directly to the response in chunks as they go, so a full year of orders exports the same way a single day does, without ever loading it all into memory at once.

04

An audit trail for every commercial change

Every edit to a pricing rule, a price exception, a dynamic rule, an availability rule or exception, or a tax assignment logs the old value, the new value, who made the change and when.

How it resolves

How a report stays accurate and fast at once

Five steps behind every report run and every logged change.

  1. 1

    Every figure filters by paid status and receipt date

    The report only counts orders that actually reached paid, using the date the receipt was assigned, not when the cart was first created.

  2. 2

    Aggregation happens in the database, not in a loop

    Sums, counts and groupings run as single SQL aggregate queries. The number of queries the report runs depends on how many currencies are involved, never on how many orders there are.

  3. 3

    Cancellations get computed from the event log

    Since an order's status is a single current value, cancellations and refunds get read from the full history of status events instead, and kept as their own separate section.

  4. 4

    A change gets diffed and logged

    Editing a price rule, an availability exception or a tax assignment writes exactly which fields changed, from what value to what, alongside who did it and when.

  5. 5

    The export streams in chunks

    A CSV download reads matching orders 500 at a time, writing each chunk straight into the response as it goes, so file size never depends on how much memory is available.

The API, as it is

What the sales report actually returns

A per-currency summary of orders, gross sales, cost and margin, alongside a cancellations and refunds section kept visibly separate from real revenue.

GET /admin/reports/sales
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
{
  "currency": "EUR",
  "summary": {
    "orders_count": 128,
    "tickets_count": 341,
    "gross_sales": 15230.5,
    "cost_total": 6100,
    "net_total": 9130.5
  },
  "cancellations_and_refunds": {
    "cancelled_count": 4,
    "expired_count": 9,
    "refunded_count": 2,
    "refunded_amount": 184
  }
}
200 Report resolved

The detail almost nobody explains

An order can never actually reach "refunded" as a status

A refund shows up as a property of the payment transaction, not as the order's own status, because an order can be partially refunded, fully refunded, or refunded after being cancelled for an unrelated reason. Treating it as one order-level status would force picking a single label for situations that are genuinely different, so the report reads refunds from the transaction and event history instead, and keeps that section visibly separate from real revenue.

Frequently asked questions

Not by default. Every core figure, gross sales, cost, margin, is grouped and shown per currency. An optional combined total in a reference currency is available separately, always labeled as an estimate, and any currency missing a reliable exchange rate stays excluded rather than guessed at.

No. Cancellations, expirations and refunds live in their own section, entirely separate from gross sales, so a reservation that was never actually paid is never confused with money that came in and then went back out.

It doesn't scale with order count. Every figure comes from a database aggregate query, not a loop over rows, so the number of queries run stays the same whether the range covers three orders or three million.

The specific fields that changed, their old value, their new value, who made the change and exactly when, covering pricing rules and exceptions, dynamic pricing rules, availability rules and exceptions, tax assignments and exceptions, and tax categories.

No. The export streams orders in chunks of 500 straight into the response as it reads them, so exporting a full year works the same way as exporting a single day.

More bookings. More channels. The same calm.

Tiqory keeps availability, pricing, payments and access working together as your operation grows. Tell us where you are headed; we’ll show you how to get there without starting over.