Skip to content

Loans

A loan hands equipment to a borrower who brings it back later: an employee, a customer or a construction site. This page follows one loan from handover to closing. The same data is in test-vectors/loan-state.json, so an implementation can check its derived values against it.

The example. On 12 October 2026 the Northring construction site borrows:

Position Article Quantity Serials Replacement value Accessories
1 Cordless drill 18 V 1 DR-1001 189.90 Charger, Battery 4 Ah, Case
2 Cable drum 25 m 3 40.00

Due on 19 October (due_at 2026-10-18T22:00:00Z, Berlin). The loan template has the penalty switched on with the suggested scale.

sequenceDiagram
  autonumber
  actor B as Borrower
  actor I as Issuer
  participant D as Device
  participant S as Server
  I->>D: pick borrower, scan items, values, condition, photos, accessories, due date
  Note over D: check stock line by line,<br/>customers and sites need a due date
  D->>B: show conditions (terms + penalty clause)
  B->>D: signs (required)
  I->>D: signs (optional)
  Note over D: number UE-2026-3F-0001 (device counter)
  loop each item
    Note over D: write movement issue,<br/>loan_id, note = number
  end
  Note over D: store photos, signatures, handover PDF
  Note over D: write loan (items, terms, penalty, signatures, pdf)
  D->>S: PUT attachments, push [movements..., loan]
  S-->>D: applied
  1. Checks. At least one item; a due date for customers and sites; the borrower's signature; every line passes the stock checks of an issue against the stock left by the lines before.
  2. Number. From the device's loan counter: UE-{YYYY}-{DEV}-{SEQ:4}.
  3. Penalty. For an external borrower with a due date, the template's scale if a penalty applies, else none.
  4. Conditions. The template's text for the kind of borrower (or the standard text for the jurisdiction), {company} replaced, the penalty clause appended. Frozen in terms.
  5. Movements. One issue per item with loan_id, the loan number in note, the item's serials.
  6. Loan record with copies of the borrower, every item with movement_id, the signatures, terms, penalty and the handover protocol pdf.

Records written (shortened to the fields that matter here):

[
{ "entity": "movement", "id": "A3B5C7D9-2E4F-4061-8A3C-5E7F9B1D3F52", "op": "upsert", "hlc": "001791802800000-0000-3F2A9C1B",
"data": { "type": "issue", "article_id": "4A7C2E91-0B3D-4F6A-8E15-7C9B2D4F6A80", "from_location_id": "2B0E6C1A-7D44-4F1B-9C3E-5A8D2F6B1C07",
"quantity": 1, "serials": ["DR-1001"], "note": "UE-2026-3F-0001", "loan_id": "E2C4A6B8-1D3F-4A5C-9E7B-0F2D4B6A8C91",
"booked_at": "2026-10-12T11:00:00Z", "booked_by": "Erika Mustermann" } },
{ "entity": "movement", "id": "B4C6D8E0-3F5A-4172-9B4D-6F8A0C2E4A63", "op": "upsert", "hlc": "001791802800000-0001-3F2A9C1B",
"data": { "type": "issue", "article_id": "0E2A4C6E-8B1D-4F3A-A5C7-9E1B3D5F7A24", "from_location_id": "2B0E6C1A-7D44-4F1B-9C3E-5A8D2F6B1C07",
"quantity": 3, "note": "UE-2026-3F-0001", "loan_id": "E2C4A6B8-1D3F-4A5C-9E7B-0F2D4B6A8C91",
"booked_at": "2026-10-12T11:00:00Z", "booked_by": "Erika Mustermann" } },
{ "entity": "loan", "id": "E2C4A6B8-1D3F-4A5C-9E7B-0F2D4B6A8C91", "op": "upsert", "hlc": "001791802800000-0002-3F2A9C1B",
"data": {
"number": "UE-2026-3F-0001",
"borrower_id": "5E2A8C14-9D3B-4A7F-B6C0-1E4D7F2A9B35",
"borrower": { "kind": "site", "name": "Northring construction site", "city": "Berlin" },
"borrower_kind": "site",
"issued_at": "2026-10-12T11:00:00Z",
"issued_by": "Erika Mustermann",
"due_at": "2026-10-18T22:00:00Z",
"items": [
{ "position": 1, "article_id": "4A7C2E91-0B3D-4F6A-8E15-7C9B2D4F6A80", "sku": "200310", "name": "Cordless drill 18 V", "unit": "Stk",
"quantity": 1, "serials": ["DR-1001"], "from_location_id": "2B0E6C1A-7D44-4F1B-9C3E-5A8D2F6B1C07", "location_code": "N-A01",
"replacement_value": 189.9, "condition": "new", "accessories": ["Charger", "Battery 4 Ah", "Case"],
"movement_id": "A3B5C7D9-2E4F-4061-8A3C-5E7F9B1D3F52" },
{ "position": 2, "article_id": "0E2A4C6E-8B1D-4F3A-A5C7-9E1B3D5F7A24", "sku": "300120", "name": "Cable drum 25 m", "unit": "Stk",
"quantity": 3, "serials": [], "from_location_id": "2B0E6C1A-7D44-4F1B-9C3E-5A8D2F6B1C07", "location_code": "N-A01",
"replacement_value": 40, "accessories": [], "movement_id": "B4C6D8E0-3F5A-4172-9B4D-6F8A0C2E4A63" }
],
"signatures": [
{ "role": "recipient", "name": "Max Mustermann", "signed_at": "2026-10-12T11:02:00Z", "image": "acc6bcb1913432eed9c60781607713d63c802ff23893d35b88b1903ed04c2f50" }
],
"returns": [],
"terms": "... conditions as signed, with the penalty clause ...",
"penalty": { "tiers": [{ "after_days": 2, "per_day": 5 }, { "after_days": 14, "per_day": 10 }], "cap_amount": null, "cap_percent": 30 },
"pdf": "28b405d5ad469ff43888136a56c515bc0762856c8052bd09830433634d8997c0"
} }
]

State: open, nothing returned; total value 189.90 + 3 × 40.00 = 309.90. Stock at N-A01: drill −1, cable drums −3. DR-1001 is issued and shown as on loan to the Northring site, due 19 October.

On 20 October at 09:30 (Berlin), one day late, the site brings back the drill (damaged) with the charger and the case, and two of the three cable drums.

sequenceDiagram
  autonumber
  actor B as Borrower
  actor I as Receiver
  participant D as Device
  participant S as Server
  I->>D: select lines: quantity, serials, condition, location, photos, accessories
  Note over D: check against what is outstanding
  B->>D: signs (required)
  I->>D: signs (required)
  loop each line with quantity > 0 and condition ok or damaged
    Note over D: write movement receipt,<br/>loan_id, to_location_id, serials
  end
  Note over D: append return (lines, signatures, return PDF)<br/>write loan version n+1
  D->>S: PUT attachments, push [movements..., loan]
  S-->>D: applied

Nothing is booked until both signatures are there. Then:

  • a receipt movement for each line that brings units back (ok or damaged), with loan_id, the loan number in note, to_location_id and the returned serials,
  • a new version of the loan with one more entry in returns.

The return appended to the loan:

{
"id": "F1A3C5E7-9B2D-4F46-8A0C-2E4B6D8F0A17",
"returned_at": "2026-10-20T07:30:00Z",
"received_by": "Erika Mustermann",
"items": [
{ "position": 1, "quantity": 1, "serials": ["DR-1001"], "condition": "damaged", "note": "Chuck wobbles",
"photos": ["84358bf14527752ea15b31417c718a0e6a7a92ad904b2e1f856574695b9bb611"],
"accessories": ["charger", "Case"], "lost_accessories": [],
"to_location_id": "2B0E6C1A-7D44-4F1B-9C3E-5A8D2F6B1C07", "movement_id": "C7E9A1B3-5D7F-4291-B3D5-7F9A1C3E5B64" },
{ "position": 2, "quantity": 2, "serials": [], "condition": "ok", "photos": [], "accessories": [], "lost_accessories": [],
"to_location_id": "2B0E6C1A-7D44-4F1B-9C3E-5A8D2F6B1C07", "movement_id": "D8FA0B2C-6E8A-43A2-8C4E-0A2C4E6A8B75" }
],
"signatures": [
{ "role": "recipient", "name": "Max Mustermann", "signed_at": "2026-10-20T07:31:00Z", "image": "f4ea7a3453ab1b8fdeeaf7ee8d9fd4104e43956b95855a61c1ef9f3e180d6f6d" },
{ "role": "issuer", "name": "Erika Mustermann", "signed_at": "2026-10-20T07:31:30Z", "image": "be7d9df48d637d1f1e1684b07569c572999cc50eca2862a73f7dc25798f2b082" }
],
"pdf": "e65034da1a35513b1e77bb281f4bcb7fbc39abd8712d54ddfea59aaa974a2db8"
}

Accessory names are compared case-insensitively, so charger closes "Charger".

State after the return (20 October, 10:00 Berlin):

Position Outstanding quantity Outstanding serials Outstanding accessories
1 0 none Battery 4 Ah
2 1 none none

The loan is overdue (1 overdue day). The penalty after 1 day is 0 (2 days of grace). On 25 October, still with these items out, it is 6 overdue days and 4 × 5.00 = 20.00. The return protocol states that the return was 1 day late; it shows a penalty only when the amount for those days is above 0.

Accessory-only return and items reported lost

Section titled “Accessory-only return and items reported lost”

On 25 October at 11:00 the site reports that the battery and the third cable drum were lost on the site. A return line may have quantity 0 and carry only accessories:

{
"id": "0B2D4F61-8A3C-4E5F-9B7D-1C3E5A7B9D02",
"returned_at": "2026-10-25T10:00:00Z",
"received_by": "Erika Mustermann",
"items": [
{ "position": 1, "quantity": 0, "serials": [], "condition": "ok", "photos": [], "accessories": [], "lost_accessories": ["Battery 4 Ah"] },
{ "position": 2, "quantity": 1, "serials": [], "condition": "lost", "note": "Not found on site", "photos": [], "accessories": [], "lost_accessories": [] }
],
"signatures": [
{ "role": "recipient", "name": "Max Mustermann", "signed_at": "2026-10-25T10:01:00Z", "image": "9d1c3e5a7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a" },
{ "role": "issuer", "name": "Erika Mustermann", "signed_at": "2026-10-25T10:01:30Z", "image": "7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5e7b9d" }
]
}
  • No movement is written: a lost unit does not come back into stock, and accessories are not stock. Stock of cable drums at N-A01 stays 1 lower than before the loan; that is the loss.
  • The return protocol lists the lost drum with its replacement value (40.00) and states that the usual replacement value applies to the battery, which has no stated value.

An item reported lost as a whole writes off its accessories with it: had the drill itself been reported lost (quantity: 1, condition: lost), the charger, battery and case would no longer be outstanding without listing them.

After the second return nothing is outstanding: quantities 0, no serials, no accessories. The loan is closed. There is no separate "close" record; status is derived. A closed loan has no accrued penalty.

Step Status Overdue days Accrued penalty
19 Oct 23:59 Berlin, before any return open 0 none
20 Oct 10:00, after the partial return overdue 1 none (0)
25 Oct 10:00, before the second return overdue 6 20.00
25 Oct 11:05, after the second return closed 6 none

Loans to employees work the same way with these differences: the due date is optional, employee_terms (or the standard employee conditions) apply, and no penalty is ever agreed. Without a due date the loan is never overdue.

All returns of a loan are entries in one record. If two devices record returns of the same loan offline, both write a new version with their own return appended; the later version wins and the other return disappears from the loan, while its receipt movements stay (movements are never lost). The device whose return was lost sees the winning version on its next pull. To avoid this, a loan should be returned on one device at a time; a client MAY warn when it holds an unsent return of a loan for which it pulls a newer version.