Material capacity ================= Current version: 1.0.0 A material has a default quantity (``aantal``), see :doc:`materialen`. On top of that default you can define *capacity entries*: time-bounded overrides that change how much of a material is available during a specific period. This is used, for example, when you have fewer (or more) items available on certain days or during certain hours. Concepts -------- * Every entry belongs to a single material and has a ``begin``, an ``end`` and an ``amount`` (the quantity available during that period). * Entries for one material never overlap. When you create or update an entry that overlaps existing entries, the new entry always wins: existing entries are trimmed, split in two, or removed so that the new period is applied exactly. Because of this, writing an entry can change the ``begin``/``end`` of neighbouring entries. * An entry may not cross the daily *daybreak* at ``04:00``. In Recras a day runs from ``04:00`` until ``04:00`` the next day, so a single entry has to stay within one such day (e.g. ``08:00``–``17:00`` is fine, ``02:00``–``06:00`` is not). * Entries that are part of a recurring series share the same ``internal_reference``. The *series* endpoints let you read or delete a whole series (or the tail of it) at once. Recurrence is expanded on the client: each occurrence is sent as an individual entry, and the shared reference ties them together. Permissions ----------- Reading capacity entries only requires a valid API key. Creating, updating and deleting entries requires the ``editMateriaal`` permission. The capacity entry object ------------------------- .. sourcecode:: json { "id": 42, "material_id": 1, "begin": "2027-01-01T08:00:00+01:00", "end": "2027-01-01T12:00:00+01:00", "amount": 2, "internal_reference": null } :json int id: ID number, may not be changed :json int material_id: The material this entry belongs to. Taken from the URL, not from the body. :json string begin: Start of the period, ISO 8601 datetime :json string end: End of the period, ISO 8601 datetime :json int amount: Quantity of the material available during this period, at least ``0`` :json string internal_reference: Optional label (max 50 characters) used to group the entries of a recurring series. ``null`` when the entry is not part of a series. Read ---- .. http:get:: /api2/materialen/(int:material_id)/capacity The capacity entries of a material, ordered by ``begin`` ascending. **Example request**: .. sourcecode:: http GET /api2/materialen/1/capacity?beginAfter=2027-01-01T00:00:00&endBefore=2027-01-01T23:59:59 HTTP/1.1 Host: demo.recras.nl Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "id": 42, "material_id": 1, "begin": "2027-01-01T08:00:00+01:00", "end": "2027-01-01T12:00:00+01:00", "amount": 2, "internal_reference": null }, { "id": 43, "material_id": 1, "begin": "2027-01-01T14:00:00+01:00", "end": "2027-01-01T17:00:00+01:00", "amount": 5, "internal_reference": null } ] :query datetime beginAfter: Only return entries whose ``begin`` is on or after this datetime :query datetime beginBefore: Only return entries whose ``begin`` is on or before this datetime :query datetime endAfter: Only return entries whose ``end`` is strictly after this datetime :query datetime endBefore: Only return entries whose ``end`` is on or before this datetime :statuscode 200: no error .. http:get:: /api2/materialen/(int:material_id)/capacity/(int:id) A specific capacity entry. **Example request**: .. sourcecode:: http GET /api2/materialen/1/capacity/42 HTTP/1.1 Host: demo.recras.nl Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": 42, "material_id": 1, "begin": "2027-01-01T08:00:00+01:00", "end": "2027-01-01T12:00:00+01:00", "amount": 2, "internal_reference": null } :statuscode 200: OK :statuscode 404: No capacity entry with this ``id`` for this material Create ------ .. http:post:: /api2/materialen/(int:material_id)/capacity Create a single capacity entry. If the new period overlaps existing entries, those entries are trimmed, split or removed so that the new entry is applied exactly (see `Concepts`_). **Example request**: .. sourcecode:: http POST /api2/materialen/1/capacity HTTP/1.1 Host: demo.recras.nl Accept: application/json { "begin": "2027-01-01T08:00:00", "end": "2027-01-01T12:00:00", "amount": 2 } **Example response**: .. sourcecode:: http HTTP/1.1 201 Created Content-Type: application/json { "id": 42, "material_id": 1, "begin": "2027-01-01T08:00:00+01:00", "end": "2027-01-01T12:00:00+01:00", "amount": 2, "internal_reference": null } :json string begin: Start of the period, ISO 8601 datetime. Required. :json string end: End of the period, ISO 8601 datetime. Required, must be after ``begin``. :json int amount: Quantity available during this period. Required, at least ``0``. :json string internal_reference: Optional series label, max 50 characters. :statuscode 201: Capacity entry created :statuscode 406: Error in the input, see `Validation errors`_ :statuscode 403: User does not have the ``editMateriaal`` permission Update ------ .. http:put:: /api2/materialen/(int:material_id)/capacity/(int:id) Replace the ``begin``, ``end`` and ``amount`` of an existing entry. As with Create, overlapping neighbouring entries are adjusted to make room. **Example request**: .. sourcecode:: http PUT /api2/materialen/1/capacity/42 HTTP/1.1 Host: demo.recras.nl Accept: application/json { "begin": "2027-01-01T08:00:00", "end": "2027-01-01T13:00:00", "amount": 4 } **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": 42, "material_id": 1, "begin": "2027-01-01T08:00:00+01:00", "end": "2027-01-01T13:00:00+01:00", "amount": 4, "internal_reference": null } :json string begin: Start of the period, ISO 8601 datetime. Required. :json string end: End of the period, ISO 8601 datetime. Required, must be after ``begin``. :json int amount: Quantity available during this period. Required, at least ``0``. :json string internal_reference: Optional series label, max 50 characters. :statuscode 200: Capacity entry updated :statuscode 404: No capacity entry with this ``id`` for this material :statuscode 406: Error in the input, see `Validation errors`_ :statuscode 403: User does not have the ``editMateriaal`` permission Delete ------ .. http:delete:: /api2/materialen/(int:material_id)/capacity/(int:id) Delete a single capacity entry. Deleting an entry does not restore any neighbouring entries that were trimmed earlier. **Example request**: .. sourcecode:: http DELETE /api2/materialen/1/capacity/42 HTTP/1.1 Host: demo.recras.nl **Example response**: .. sourcecode:: http HTTP/1.1 200 OK :statuscode 200: Capacity entry deleted :statuscode 404: No capacity entry with this ``id`` for this material :statuscode 403: User does not have the ``editMateriaal`` permission Batch update ------------ .. http:post:: /api2/materialen/(int:material_id)/capacity/batchupdate Apply many capacity entries in one request. This is the recommended way to store a recurring series: expand the recurrence on the client and send one entry per occurrence, all sharing the same ``internal_reference``. The entries in the payload must not overlap **each other**. They may overlap existing entries of the material; those existing entries are trimmed, split or removed to make room, exactly like Create does for a single entry. At most ``1000`` entries can be sent in a single request. **Example request**: .. sourcecode:: http POST /api2/materialen/1/capacity/batchupdate HTTP/1.1 Host: demo.recras.nl Accept: application/json [ { "begin": "2027-01-01T08:00:00", "end": "2027-01-01T12:00:00", "amount": 2, "internal_reference": "winter-2027" }, { "begin": "2027-01-02T08:00:00", "end": "2027-01-02T12:00:00", "amount": 2, "internal_reference": "winter-2027" } ] **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json {} The response body is empty. Read the entries back with the `Read`_ or `Series`_ endpoints to see the resulting state. :json string begin: Start of the period, ISO 8601 datetime. Required. :json string end: End of the period, ISO 8601 datetime. Required, must be after ``begin``. :json int amount: Quantity available during this period. Required, at least ``0``. :json string internal_reference: Optional series label, max 50 characters. :statuscode 200: Entries applied :statuscode 406: Error in the input, see `Validation errors`_ :statuscode 403: User does not have the ``editMateriaal`` permission Series ------ .. http:get:: /api2/materialen/(int:material_id)/capacity/series/(string:internal_reference) All entries of a material that share the given ``internal_reference``, ordered by ``begin`` ascending. **Example request**: .. sourcecode:: http GET /api2/materialen/1/capacity/series/winter-2027 HTTP/1.1 Host: demo.recras.nl Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "id": 42, "material_id": 1, "begin": "2027-01-01T08:00:00+01:00", "end": "2027-01-01T12:00:00+01:00", "amount": 2, "internal_reference": "winter-2027" }, { "id": 43, "material_id": 1, "begin": "2027-01-02T08:00:00+01:00", "end": "2027-01-02T12:00:00+01:00", "amount": 2, "internal_reference": "winter-2027" } ] :statuscode 200: no error .. http:delete:: /api2/materialen/(int:material_id)/capacity/series/(string:internal_reference) Delete every entry of the series (all entries sharing this ``internal_reference``). **Example request**: .. sourcecode:: http DELETE /api2/materialen/1/capacity/series/winter-2027 HTTP/1.1 Host: demo.recras.nl **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json {} :statuscode 200: Series deleted :statuscode 403: User does not have the ``editMateriaal`` permission .. http:delete:: /api2/materialen/(int:material_id)/capacity/series/(string:internal_reference)/from Delete this and all following occurrences of a series, i.e. every entry of the series whose ``begin`` is on or after the given ``begin`` query parameter. Occurrences before that moment are kept. This is the "this and following events" delete of a recurring series. **Example request**: .. sourcecode:: http DELETE /api2/materialen/1/capacity/series/winter-2027/from?begin=2027-01-02T00:00:00 HTTP/1.1 Host: demo.recras.nl **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json {} :query datetime begin: Required. Only entries of the series whose ``begin`` is on or after this datetime are deleted. :statuscode 200: Occurrences deleted :statuscode 406: The ``begin`` query parameter is missing :statuscode 403: User does not have the ``editMateriaal`` permission Upcoming -------- .. http:get:: /api2/materialen/capacity/upcoming The ids of all materials that have at least one capacity entry ending in the future. Useful to know which materials currently use variable capacity without fetching every material's entries. **Example request**: .. sourcecode:: http GET /api2/materialen/capacity/upcoming HTTP/1.1 Host: demo.recras.nl Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "material_ids": [1, 7, 13] } :statuscode 200: no error .. _capacity_validation_errors: Validation errors ----------------- When the input is invalid, the API responds with status ``406`` and a JSON array of message objects: .. sourcecode:: json [ { "message": "\"begin\" must be before \"end\"" } ] The following messages can occur: ============================================ ================================================================ Message Meaning ============================================ ================================================================ ``"begin" must be before "end"`` ``end`` is not strictly after ``begin``. ``"amount" must be at least 0`` A negative ``amount`` was given. ``"internal_reference" is too long`` ``internal_reference`` is longer than 50 characters. ``Periods should not overlap with others`` Two entries in the same `Batch update`_ payload overlap. ``ERR_CAPACITY_PASSES_DAYBREAK`` An entry crosses the ``04:00`` daybreak. ``ERR_CAPACITY_TOO_MANY_OCCURRENCES`` More than 1000 entries were sent in one `Batch update`_ request. ``Query parameter "begin" is required`` The ``begin`` query parameter is missing on a series *from* delete. ============================================ ================================================================ Malformed or missing ``begin``/``end``/``amount`` fields (wrong type, not a valid datetime) also result in a ``406`` response with a mapping error message.