View Categories

Sending & Returning Rentals

4 min read

Two mutations record stock physically leaving and coming back: sendRentalSerials checks units out to the customer, returnRentalSerials takes them back in. They are the API behind the Rentals > Send and Rentals > Return screens, and they run the same code those screens run — so a warehouse scanner app driving this API and a member of staff using the admin cannot get out of step.

sendRentalSerials needs SalesIgniter_Rental::send; returnRentalSerials needs SalesIgniter_Rental::return. Holding one does not grant the other, which is usually what you want: a returns desk should not be able to send stock out.

Sending units out #

mutation SendOut {
  sendRentalSerials(input: {
    order_id: 4571
    product_id: 123
    serials: ["CR6-0004", "CR6-0011"]
  }) {
    success
    message
    shipment_id
    reservations {
      id
      qty
      qty_shipped
      qty_returned
      serials_shipped
      ship_date
      state
    }
  }
}
{
  "data": {
    "sendRentalSerials": {
      "success": true,
      "message": "Serials sent successfully",
      "shipment_id": 812,
      "reservations": [
        {
          "id": 8123,
          "qty": 2,
          "qty_shipped": 2,
          "qty_returned": 0,
          "serials_shipped": ["CR6-0004", "CR6-0011"],
          "ship_date": "2026-10-03 09:14:00",
          "state": "OUT"
        }
      ]
    }
  }
}
  • The number of serials is the quantity. Two entries sends two units. There is no separate quantity argument.
  • Sending creates a Magento shipment and returns its id. Pass shipment_id yourself to attach to an existing one instead — useful when a single physical shipment covers several products.
  • The serials are marked as out, which takes them off the rentable fleet until they come back.
  • reservations is re-read after the write, so the counts and state you get back are the new truth rather than what you sent.

Products that are not serial-tracked #

serials is still required, and the count is still the quantity — pass one placeholder entry per unit. To send three untracked lighting stands:

sendRentalSerials(input: {
  order_id: 4571
  product_id: 456
  serials: ["STAND-4571-1", "STAND-4571-2", "STAND-4571-3"]
}) { success reservations { qty_shipped } }

Make placeholders unique per unit, and reproducible. They are stored against the booking as though they were serials, and returnRentalSerials finds the booking by matching them — so three entries all reading "STAND" cannot be returned one at a time, and a placeholder you cannot regenerate later cannot be returned at all. Deriving them from the order and line number, as above, works well.

Taking units back #

Returning needs only the serials. The booking each one belongs to is found from the serial itself, so a returns desk scanning a pile of kit does not have to know which order any of it came from — which is the whole point.

mutation TakeBack {
  returnRentalSerials(input: {
    serials: ["CR6-0004", "CR6-0011", "STAND-4571-1"]
  }) {
    success
    message
    reservations {
      id
      order_increment_id
      qty_shipped
      qty_returned
      serials_returned
      return_date
      state
    }
  }
}
  • Serials from several different orders can be returned in one call. reservations comes back with every booking that was touched.
  • The units go back onto the rentable fleet, and state becomes RETURNED once a booking’s last unit is in.
  • A partial return is normal: return what is in front of you and the booking stays OUT (or OVERDUE) until the rest arrives.
  • A serial that matches no open booking is not an error — success is true and it simply contributes nothing to reservations. Scanning something twice, or scanning a unit that was already booked in, is a normal thing to happen at a returns desk and does not need to fail the whole batch.

When it goes wrong #

Bad arguments are rejected before anything is written: an empty serials list, a blank entry inside it, or a non-positive order_id/product_id all come back as graphql-input errors with nothing recorded. Anything the send/return processor itself refuses — no matching booking, more units than the booking holds — comes back the same way, carrying the processor’s own message. success is never true on a failure.

One caution: lines split across bookings #

If a single order line is held by more than one booking for the same product — two MSI sources, or a line split across depots — a send addressed to that order and product applies the full quantity to each of those bookings. Send against such an order one booking at a time, or check reservations in the response and correct before the kit leaves. This behaviour is inherited from the existing send/return processor and is the same through the admin screens and the REST API.

Use rentalOrders to see, before sending, whether a line has one booking or several.