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_dateare the customer’s dates.start_date_with_turnover/end_date_with_turnoveradd the product’s buffers and are what actually blocks the calendar — note the four hours either side above.qtyminusqty_cancelis what still holds stock. Cancelling part of a booking raisesqty_cancelrather than loweringqty, so the original order quantity stays legible.order_typeisofor an order,qfor a quote,mfor a manual hold. A manual hold hasorder_id: 0.stateis derived, not stored, and follows the Bookings grid exactly:UPCOMINGuntil something is sent,OUTwhile units are with the customer,OVERDUEwhen the end date has passed with units still out,RETURNEDonce everything is back,CANCELLEDwhen 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 }
}
}
| Filter | Notes |
|---|---|
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. |
state | UPCOMING, 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: trueto 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"). Passorder_idandorder_item_idto 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: trueto book anyway and deliberately overbook. - On an MSI store,
source_codedecides 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.
