View Categories

Availability & Calendar Queries

5 min read

Two queries answer the only question a rental storefront really asks: can I have this, on these dates? Use rentalProductAvailability for a specific range the customer has picked, and rentalProductCalendar to paint a date picker for a whole month or season in one request. Neither needs a token.

Both are read from the reservations table at the moment you ask, so they never serve a stale answer, and both work in the product’s turnover-inclusive dates — see the overview for why that distinction matters.

rentalProductAvailability #

Ask whether a quantity of a product is free over one date range.

query CheckDates {
  rentalProductAvailability(input: {
    sku: "CANON-R6"
    start_date: "2026-10-03 09:00:00"
    end_date: "2026-10-05 17:00:00"
    qty: 2
  }) {
    is_available
    available_qty
    total_qty
    booked_qty
    requested_qty
    next_available_date
    error { code message }
  }
}
{
  "data": {
    "rentalProductAvailability": {
      "is_available": false,
      "available_qty": 1,
      "total_qty": 4,
      "booked_qty": 3,
      "requested_qty": 2,
      "next_available_date": "2026-10-06 09:00:00",
      "error": {
        "code": "NOT_ENOUGH_QUANTITY",
        "message": "Only 1 of this item is available for the dates selected."
      }
    }
  }
}

Reading the numbers #

The quantities describe the busiest moment anywhere inside the range, not the start of it and not an average. A five-day hire where three of four units are out for only the middle afternoon still reports available_qty: 1, because that afternoon is the constraint on the whole booking. That is the number you want — anything else would sell a booking that cannot be fulfilled.

  • total_qty — units in the fleet, before any bookings.
  • booked_qty — units out at that busiest moment.
  • available_qtytotal_qty minus booked_qty.
  • is_available — whether the booking can actually be made. This is not simply available_qty >= requested_qty: it also enforces the product’s minimum and maximum rental period, disabled days, store opening hours and any same-day rule. A range can be refused with plenty of stock free.
  • next_available_date — the soonest start that would work, when the answer is no. null if it cannot be determined cheaply.

Why a range was refused #

When is_available is false, error.code says why. error.message is translated through Magento’s i18n and is safe to show a customer as-is.

error.codeWhat went wrong
NOT_ENOUGH_QUANTITYFewer units free than asked for.
BOOKED_DATESSomething inside the range is fully booked out.
MINIMUM_PERIODShorter than the product’s minimum hire.
MAXIMUM_PERIODLonger than the product’s maximum hire.
DISABLED_DATESThe range covers a day the product cannot go out on.
START_DATE_DISABLED / END_DATE_DISABLEDThat particular day is not selectable — typically a closed day for collections or returns.
START_DATE_NOT_AVAILABLEThe start is earlier than the product’s first available date.
SAME_DATES_ENFORCEDThe product must start and end on the same day.
SELECT_START_END_DATESBoth dates are required.
NOT_ENOUGH_QUANTITY_BUYOUTAs above, for a buyout rather than a hire.
UNKNOWNA refusal this version of the schema does not have a name for.

rentalProductCalendar #

Everything a date picker needs for one product over a window, in one round trip. Do not call rentalProductAvailability once per day to paint a month — this query builds the availability picture once and answers every day from it.

query PaintPicker {
  rentalProductCalendar(input: {
    sku: "CANON-R6"
    start_date: "2026-10-01"
    end_date: "2026-10-31"
    qty: 1
  }) {
    total_qty
    first_available_date
    first_date_all_reserved
    fully_booked_days
    disabled_days
    days {
      date
      available_qty
      booked_qty
      is_available
      is_disabled
    }
  }
}
{
  "data": {
    "rentalProductCalendar": {
      "total_qty": 4,
      "first_available_date": "2026-10-01",
      "first_date_all_reserved": "2026-10-12",
      "fully_booked_days": ["2026-10-12", "2026-10-13"],
      "disabled_days": ["2026-10-04", "2026-10-11", "2026-10-18", "2026-10-25"],
      "days": [
        { "date": "2026-10-01", "available_qty": 4, "booked_qty": 0, "is_available": true,  "is_disabled": false },
        { "date": "2026-10-04", "available_qty": 4, "booked_qty": 0, "is_available": false, "is_disabled": true  },
        { "date": "2026-10-12", "available_qty": 0, "booked_qty": 4, "is_available": false, "is_disabled": false }
      ]
    }
  }
}
  • days has one entry per day in the window, inclusive of both ends.
  • is_disabled means the product cannot go out that day regardless of stock — a Sunday, a configured closed day, a blackout period. Note the 4th of October above: four units free, still not bookable.
  • fully_booked_days are days with nothing free. A day can appear in both lists.
  • is_available already combines both: it is true only when at least qty units are free and the day is not disabled. If you only read one field per day, read this one.
  • Raising qty narrows the calendar — a day with two units free is available for qty: 1 and not for qty: 3. Pass the quantity the customer has actually selected.

The window is capped at 400 days. A longer one is refused rather than served slowly. Request a season at a time and cache it on the client; a month is the natural unit for most pickers.

Checking dates while editing an existing booking #

If you re-check availability while editing a booking that already exists, its own units are counted against it and a booking that fits perfectly well looks unavailable. Both queries take exclude_reservation_ids (and exclude_order_id for a whole order) to answer as though those bookings were not there.

query CanIMoveThisBooking {
  rentalProductAvailability(input: {
    sku: "CANON-R6"
    start_date: "2026-10-04 09:00:00"
    end_date: "2026-10-07 17:00:00"
    qty: 2
    exclude_reservation_ids: [8123]
  }) {
    is_available
    available_qty
  }
}

These two inputs need a token holding SalesIgniter_Rental::manualedit. They let a caller see availability as if arbitrary bookings did not exist, which is a back-office capability, so an unauthenticated caller supplying them is refused rather than having them silently ignored.

Multi Source Inventory #

On a store running the MSI add-on, pass source_code or stock_id to ask about one location rather than the whole fleet — what a branch-picker on a multi-depot storefront needs. On a store without MSI both inputs are simply ignored.

rentalProductAvailability(input: {
  sku: "CANON-R6"
  start_date: "2026-10-03 09:00:00"
  end_date: "2026-10-05 17:00:00"
  source_code: "manchester"
}) { is_available available_qty }