View Categories

Reservations & Bookings

4 min read

A reservation — a booking — is the record that holds a quantity of a product for a date range. Creating one takes those units off the calendar, deleting one puts them back, and the send and return mutations move stock against it. Every rental order has at least one; you can also create them with no order behind them, which is how you hold kit back for a staff job or a repair without inventing a sale.

All the fields here need a token holding SalesIgniter_Rental::manualedit.

The booking object #

{
  "id": 8123,
  "product_id": 123,
  "sku": "CANON-R6",
  "product_name": "Canon EOS R6 body",
  "order_id": 4571,
  "order_increment_id": "000004571",
  "order_item_id": 9902,
  "order_type": "o",
  "qty": 2,
  "qty_cancel": 0,
  "qty_shipped": 2,
  "qty_returned": 0,
  "start_date": "2026-10-03 09:00:00",
  "end_date": "2026-10-05 17:00:00",
  "start_date_with_turnover": "2026-10-03 05:00:00",
  "end_date_with_turnover": "2026-10-05 21:00:00",
  "pickup": null,
  "dropoff": null,
  "ship_date": "2026-10-03 09:14:00",
  "return_date": null,
  "cancel_date": null,
  "serials_shipped": ["CR6-0004", "CR6-0011"],
  "serials_returned": [],
  "source_code": "manchester",
  "comments": "",
  "state": "OUT"
}
  • start_date/end_date are the customer’s dates. start_date_with_turnover/end_date_with_turnover add the product’s buffers and are what actually blocks the calendar — note the four hours either side above.
  • qty minus qty_cancel is what still holds stock. Cancelling part of a booking raises qty_cancel rather than lowering qty, so the original order quantity stays legible.
  • order_type is o for an order, q for a quote, m for a manual hold. A manual hold has order_id: 0.
  • state is derived, not stored, and follows the Bookings grid exactly: UPCOMING until something is sent, OUT while units are with the customer, OVERDUE when the end date has passed with units still out, RETURNED once everything is back, CANCELLED when every unit has been cancelled.

Finding bookings #

query WhatIsOutThisWeekend {
  rentalReservations(
    filter: {
      overlapping: { from: "2026-10-03 00:00:00", to: "2026-10-05 23:59:59" }
      state: { eq: "OUT" }
    }
    sort: { start_date: ASC }
    pageSize: 50
  ) {
    total_count
    items { id sku qty start_date end_date state serials_shipped }
    page_info { current_page total_pages }
  }
}
FilterNotes
overlapping: {from, to}Bookings whose turnover-inclusive range touches the given one. This is the calendar’s own question — use it when you want to know what a date range is competing with.
start_date / end_date{from, to} ranges against the customer-facing dates.
stateUPCOMING, OUT, OVERDUE, RETURNED, CANCELLED. Filtering happens in the database, so total_count and paging are correct rather than counting rows that were then thrown away.
product_id, sku, order_id, order_increment_id, order_type, source_code{eq} or {in}.

Sort on id, start_date, end_date, product_id or order_id; the default is newest first. Fetch a single booking with rentalReservation(id: 8123).

Creating a booking #

mutation HoldKitBack {
  createRentalReservation(input: {
    sku: "CANON-R6"
    qty: 1
    start_date: "2026-11-02 09:00:00"
    end_date: "2026-11-04 17:00:00"
    comments: "Held for the trade show stand"
  }) {
    reservation { id qty start_date start_date_with_turnover state }
  }
}
  • You give the customer’s dates. The turnover-inclusive ones are worked out from the product’s configuration. Pass not_use_turnover: true to skip that, which is often right for an internal hold that is not a real hire.
  • With no order_id, you get a manual hold (order_type: "m"). Pass order_id and order_item_id to attach the booking to a real order.
  • Availability is checked first. Dates that are not free are refused, with the same reason codes the availability query returns. Pass force: true to book anyway and deliberately overbook.
  • On an MSI store, source_code decides which depot the units come from.

Moving or resizing a booking #

mutation PushItBackTwoDays {
  updateRentalReservation(
    id: 8123
    input: { start_date: "2026-10-05 09:00:00", end_date: "2026-10-07 17:00:00" }
  ) {
    reservation { id start_date end_date start_date_with_turnover state }
  }
}

Only the fields you supply are written; everything else is left exactly as it was. When the dates or quantity change, availability for the new dates is re-checked ignoring this booking’s own units — otherwise a booking would always find itself in the way and no booking could ever be extended by a day.

Cancelling and deleting #

To free some units but keep the record — a customer dropping from three cameras to one:

mutation DropTwo {
  cancelRentalReservationQty(id: 8123, qty: 2) {
    reservation { id qty qty_cancel state }
  }
}

The two units go back on the calendar immediately, qty stays at 3 and qty_cancel becomes 2. Cancelling more than is left is refused, and the error tells you both numbers. Cancel every unit and state becomes CANCELLED.

To remove the record entirely:

mutation Remove {
  deleteRentalReservation(id: 8123) { success id }
}

Prefer cancelling to deleting on a booking that belongs to a real order — deleting removes the history along with the hold, and the order then has a line with nothing reserved against it. Deleting is the right move for manual holds.