@kreiseck/kasseneck-api 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/README.md +293 -1
  3. package/dist/cjs/client/aufrufe.d.ts +2 -2
  4. package/dist/cjs/client/aufrufe.js +58 -0
  5. package/dist/cjs/inventory/api.d.ts +61 -0
  6. package/dist/cjs/inventory/api.js +54 -0
  7. package/dist/cjs/inventory/auth.d.ts +17 -0
  8. package/dist/cjs/inventory/auth.js +27 -0
  9. package/dist/cjs/inventory/endpunkte.d.ts +91 -0
  10. package/dist/cjs/inventory/endpunkte.js +290 -0
  11. package/dist/cjs/inventory/fehler.d.ts +43 -0
  12. package/dist/cjs/inventory/fehler.js +66 -0
  13. package/dist/cjs/inventory/index.d.ts +21 -0
  14. package/dist/cjs/inventory/index.js +94 -0
  15. package/dist/cjs/inventory/lesen.d.ts +45 -0
  16. package/dist/cjs/inventory/lesen.js +424 -0
  17. package/dist/cjs/inventory/schreiben.d.ts +83 -0
  18. package/dist/cjs/inventory/schreiben.js +315 -0
  19. package/dist/cjs/inventory/typen.d.ts +679 -0
  20. package/dist/cjs/inventory/typen.js +14 -0
  21. package/dist/cjs/inventory/vertrag.d.ts +136 -0
  22. package/dist/cjs/inventory/vertrag.js +291 -0
  23. package/dist/cjs/inventory/webhook.d.ts +52 -0
  24. package/dist/cjs/inventory/webhook.js +116 -0
  25. package/dist/cjs/invoice/index.d.ts +2 -2
  26. package/dist/cjs/invoice/index.js +2 -1
  27. package/dist/cjs/invoice/typen.d.ts +46 -2
  28. package/dist/cjs/invoice/vertrag.d.ts +19 -5
  29. package/dist/cjs/invoice/vertrag.js +37 -5
  30. package/dist/cjs/stored/vokabular.d.ts +1 -1
  31. package/dist/cjs/stored/vokabular.js +5 -3
  32. package/dist/cjs/version.d.ts +1 -1
  33. package/dist/cjs/version.js +1 -1
  34. package/dist/esm/client/aufrufe.d.ts +2 -2
  35. package/dist/esm/client/aufrufe.js +58 -0
  36. package/dist/esm/inventory/api.d.ts +61 -0
  37. package/dist/esm/inventory/api.js +51 -0
  38. package/dist/esm/inventory/auth.d.ts +17 -0
  39. package/dist/esm/inventory/auth.js +24 -0
  40. package/dist/esm/inventory/endpunkte.d.ts +91 -0
  41. package/dist/esm/inventory/endpunkte.js +267 -0
  42. package/dist/esm/inventory/fehler.d.ts +43 -0
  43. package/dist/esm/inventory/fehler.js +57 -0
  44. package/dist/esm/inventory/index.d.ts +21 -0
  45. package/dist/esm/inventory/index.js +20 -0
  46. package/dist/esm/inventory/lesen.d.ts +45 -0
  47. package/dist/esm/inventory/lesen.js +404 -0
  48. package/dist/esm/inventory/schreiben.d.ts +83 -0
  49. package/dist/esm/inventory/schreiben.js +298 -0
  50. package/dist/esm/inventory/typen.d.ts +679 -0
  51. package/dist/esm/inventory/typen.js +13 -0
  52. package/dist/esm/inventory/vertrag.d.ts +136 -0
  53. package/dist/esm/inventory/vertrag.js +288 -0
  54. package/dist/esm/inventory/webhook.d.ts +52 -0
  55. package/dist/esm/inventory/webhook.js +107 -0
  56. package/dist/esm/invoice/index.d.ts +2 -2
  57. package/dist/esm/invoice/index.js +1 -1
  58. package/dist/esm/invoice/typen.d.ts +46 -2
  59. package/dist/esm/invoice/vertrag.d.ts +19 -5
  60. package/dist/esm/invoice/vertrag.js +36 -4
  61. package/dist/esm/stored/vokabular.d.ts +1 -1
  62. package/dist/esm/stored/vokabular.js +5 -3
  63. package/dist/esm/version.d.ts +1 -1
  64. package/dist/esm/version.js +1 -1
  65. package/fixtures/hobex-hps-codes.json +1 -1
  66. package/fixtures/invoice-api-examples/credit-error-reservation.json +26 -0
  67. package/fixtures/invoice-api-examples/issue-error-reservation-article.json +25 -0
  68. package/fixtures/invoice-api-examples/issue-error-vat-id-risk.json +26 -0
  69. package/fixtures/invoice-api-examples/issue-reservation.json +24 -0
  70. package/fixtures/invoice-api-examples/issue-vat-id-risk.json +28 -0
  71. package/fixtures/invoice-api.schema.json +15 -2
  72. package/fixtures/invoice-texts.json +1 -1
  73. package/fixtures/pos-texts.json +1 -1
  74. package/fixtures/surface.json +306 -5
  75. package/fixtures/v3/README.md +9 -1
  76. package/fixtures/v3/antworten/belege.json +2 -2
  77. package/fixtures/v3/antworten/belegmail.json +2 -2
  78. package/fixtures/v3/antworten/kasse-belege.json +2 -2
  79. package/fixtures/v3/antworten/kasse.json +3 -3
  80. package/fixtures/v3/antworten/lager.json +3881 -0
  81. package/fixtures/v3/antworten/rechnungen.json +267 -44
  82. package/fixtures/v3/antworten/storno.json +2 -2
  83. package/fixtures/v3/stored/belege.json +2 -2
  84. package/fixtures/v3/stored/kasse.json +404 -4
  85. package/fixtures/v3/stored/rechnungen.json +174 -18
  86. package/fixtures/v3/v3-vokabular.json +1007 -20
  87. package/fixtures/v3/zahlbetrag-faelle.json +2 -2
  88. package/package.json +14 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,182 @@ Was vor 0.7.0 geschah, steht in der Commit-Historie (`git log`); ab hier wird
4
4
  es hier geführt. Ein Eintrag nennt die Änderung **und ihren Grund** —
5
5
  nur der Grund überlebt den nächsten Umbau.
6
6
 
7
+ ## 1.5.0
8
+
9
+ Inventory API, write side and reservations: `./inventory` gains the 13
10
+ endpoints of backend stage 5b, and the invoice API learns `reservationId`.
11
+ Reason: a shop that only reads stock still oversells. It has to hold the goods
12
+ at checkout, book what arrives and leaves, keep its articles in sync, and turn
13
+ the reservation into a sale when the order is invoiced, all with retries that
14
+ never book twice. The invoice API also catches up with the VAT ID check of
15
+ the backend (stage 4, in the 0.x line since 0.33.0).
16
+
17
+ Additive, no breaking change; existing calls send the same bytes as in 1.4.0.
18
+ New response fields are added to the models.
19
+
20
+ - **Articles**: `createArticle`, `updateArticle`, `deactivateArticle`, each
21
+ answering with the article as `getArticle` returns it. Fields: `name`,
22
+ `description`, `unitPriceCents`, `vatRate`, `unit`, `number`, `ean` (with
23
+ `ean` a foreign article with that code, otherwise the server assigns the next
24
+ own code), `groupId`, `revenueGroupId`, `stockTracked`, `stockLocationIds`,
25
+ `minStockByLocation`, `minStock` (legacy, triggers nothing), `stockKind`,
26
+ `purchasePriceMicros` (only with the permission `costs`), `externalIds` and
27
+ `metadata`. In an update `null` clears an optional field; `externalIds` and
28
+ `metadata` are replaced, `minStockByLocation` is merged per location.
29
+ - **Booking**: `receiveGoods`, `transferStock`, `recordStockLoss`,
30
+ `changeStockCondition`, `reverseStockMovement`, each answering with a
31
+ `StockOperation` (`operationId`, `movementIds`, `lotIds`, `warnings[]`).
32
+ `previewGoodsReceipt` is `receiveGoods` with `dryRun: true`: it books nothing,
33
+ needs no key (a `null` key counts as none and is not sent) and returns the
34
+ values only with the permission `costs`. `receiveGoods` with `dryRun: true`
35
+ is refused before sending: a preview answers without an operation, so it
36
+ goes through `previewGoodsReceipt`.
37
+ Transfers, losses and condition changes never overdraw (`exceeds_stock`).
38
+ Catalogs `STOCK_LOSS_REASONS`, `WITHDRAWAL_TYPES`, `LANDED_COST_TYPES`,
39
+ `LANDED_COST_ALLOCATIONS`; warnings `INVENTORY_WARNING_CODES`
40
+ (`insufficient_stock`, `below_minimum`, `return_exceeds_sale`,
41
+ `reservation_exceeded`) with `isInventoryWarningCode`. A warning is not an
42
+ error: the booking took effect.
43
+ - **Reservations**: `createReservation` (all or nothing, measured against
44
+ `available`; `insufficient_available` lists the missing positions in
45
+ `inventoryShortfalls(error)`), `extendReservation`, `releaseReservation` (all,
46
+ or per position), `getReservation`, `listReservations` and
47
+ `iterateReservations`. `Reservation` carries `status` (`active`, `redeemed`,
48
+ `released`, `expired`; `RESERVATION_STATUSES`), `reference`, `items[]` with
49
+ `quantity`, `redeemed`, `released`, and `expiresAt`. `expiresInMinutes` is
50
+ 5 to 43,200.
51
+ - **Idempotency**: every write takes `idempotencyKey` (1 to 120 characters,
52
+ required in the type). The client refuses to send a write without a valid
53
+ key, because a write without one cannot be retried safely; it never trims or
54
+ shortens the key. Retry after a timeout with the **same** key: the call takes
55
+ effect once and returns the stored answer; the same key with other content
56
+ gives `idempotency_conflict`.
57
+ - **Checked before sending**, nothing else: the key, required ids, integer
58
+ quantities, amounts and prices (`1.5` is never 1.5 pieces), the range of
59
+ `expiresInMinutes`, an update without a field, an empty release list,
60
+ `receiveGoods` with `dryRun: true` (use `previewGoodsReceipt`). All of
61
+ these throw `KasseneckValidationError` with `scope: 'request'`.
62
+ - **Models**: `Article` gains `description`, `stockKind` and
63
+ `minStockByLocation` (missing on an older server: `null`, `null`, `{}`).
64
+ `StockMovement` gains `reservedDelta` (0 for every movement that is not a
65
+ reservation) and `stockAfter.reserved` (only on reservation movements,
66
+ otherwise `null`); the movement type `reservation` has `quantityDelta: 0`.
67
+ The minimum stock (`below_minimum`, `listStock({ belowMinimum })`,
68
+ `stock.below_minimum`) is now measured against `available = onHand −
69
+ reserved`, so a reservation alone can trigger it. A fractional value in any
70
+ of these fields throws `KasseneckValidationError` with `scope: 'response'`.
71
+ - **Events**: `reservation.expired`, `reservation.released` and
72
+ `reservation.redeemed` in `INVENTORY_WEBHOOK_EVENTS` and
73
+ `parseInventoryWebhookEvent`, carrying the reservation with its status
74
+ afterwards. `released` and `redeemed` fire on every effective release or
75
+ redemption, also a partial one (status stays `active`).
76
+ - **Errors**: `INVENTORY_ERROR_CODES` keeps its 12 codes in front and appends
77
+ the codes of the write side in contract order (`idempotency_key_required`,
78
+ `idempotency_conflict`, `exceeds_stock`, `code_taken`,
79
+ `external_id_taken`, `stock_kind_locked`, `article_inactive`,
80
+ `insufficient_available`, `reservation_not_found`,
81
+ `reservation_not_active` …).
82
+ - **Invoice API, reservations**: `issueInvoice` items take `reservationId`
83
+ (`IssueInvoiceItemInput`, needs `articleId`; not on credit notes, where the
84
+ server rejects it). New codes at the end of `INVOICE_ERROR_CODES`:
85
+ `reservation_not_found`, `reservation_mismatch`, `reservation_not_active`;
86
+ new notice `reservation_expired` (with `reservationId`): the invoice is
87
+ issued anyway and sells without the reservation. Three new examples in
88
+ `fixtures/invoice-api-examples/`.
89
+ - **Invoice API, VAT ID check**: an invoice without VAT that relies on the
90
+ customer's VAT ID (intra-Community supply, reverse charge) is only issued
91
+ with a result of the VAT ID check (FinanzOnline, otherwise VIES) on the day
92
+ of issue. Reason: the caller has to decide by the code whether to retry
93
+ later or not to issue at all.
94
+ - Codes `vat_id_invalid` and `vat_id_check_pending` in
95
+ `INVOICE_ERROR_CODES`, after `amount_too_large` and before the
96
+ reservation codes (the backend order). `vat_id_check_pending` carries
97
+ `details.retryAfter` (seconds until the next sensible attempt, with the
98
+ same `idempotencyKey`).
99
+ - `IssueInvoiceRequest.acceptVatIdRisk` (optional boolean): issues despite
100
+ a pending check, the issuer bears the risk. An invalid VAT ID blocks even
101
+ with it. It does not count towards the idempotency of the request.
102
+ - `Invoice.vatIdProof` (`InvoiceVatIdProof`: `checkedOn`, `source`
103
+ `finanzonline` | `vies`, `level` 1 | 2, `code`) and `Invoice.vatIdRisk`
104
+ (`InvoiceVatIdRisk`: `acceptedOn`), otherwise `null`.
105
+ - `fixtures/invoice-api.schema.json` regenerated (`acceptVatIdRisk`, the
106
+ two codes), two new examples `issue-vat-id-risk.json` and
107
+ `issue-error-vat-id-risk.json`.
108
+ - **Contract**: `fixtures/v3/` pulled again from the backend (the 13 new
109
+ endpoints, catalogs, `warningCodes`, events and the 5b cases in
110
+ `antworten/lager.json`, the VAT ID cases in `antworten/rechnungen.json`);
111
+ `PUBLIC_CALLS` and `ALL_CALLS` gain the 13 names (87 public calls);
112
+ `surface.json` gains the new lists under `inventory` and the two invoice
113
+ codes.
114
+
115
+ ## 1.4.0
116
+
117
+ Inventory API, read side and account webhooks: new subpath `./inventory`.
118
+ Reason: since stage 5a the backend offers articles, locations, stock and the
119
+ stock ledger under `/v3` with the account's `api_key`, plus webhooks that
120
+ report every stock change within seconds. A shop that shows stock or keeps
121
+ its own copy needs a typed client and, above all, a signature check it
122
+ cannot get wrong.
123
+
124
+ Additive, no breaking change; existing calls send and read the same bytes as
125
+ in 1.3.0.
126
+
127
+ - **`createInventoryClient({ apiKey })`** (`./inventory`, server only, base
128
+ `https://api.kasseneck.at/v3`): `getArticle`, `listArticles`,
129
+ `lookupArticleByCode` (code as text or `{ code }`, or
130
+ `{ externalSystem, externalId }`), `listLocations`, `getStock`, `listStock`,
131
+ `listStockMovements`, and the async iterators `iterateArticles`,
132
+ `iterateStock`, `iterateStockMovements` that follow `nextCursor` until
133
+ `null` (the same cursor twice throws instead of looping). Webhook management:
134
+ `createWebhook`, `updateWebhook`, `deleteWebhook`, `listWebhooks`,
135
+ `sendWebhookTest`, `rotateWebhookSecret`, `listWebhookDeliveries`. Every call
136
+ is also exported as a free function taking the transport.
137
+ - **Models**: `Article` (incl. `stockLocationIds`; `purchasePriceMicros` only
138
+ with the account permission `costs`, otherwise the field is absent, not
139
+ `null`), `StockLevel` (`onHand`, `reserved`, `available`, `defective`,
140
+ `sequence`, `updatedAt`), `StockValue`, `StockMovement`, `Location`,
141
+ `InventoryWebhook` (`consecutiveFailures`), `InventoryWebhookDelivery`
142
+ (`deliveryId`; status `delivered`, `pending`, `failed`, `dropped`); the same
143
+ names as for partner webhooks. Quantities are integer thousandths, money
144
+ integer cents, purchase prices integer micro-euros; `available` may be
145
+ negative. A fractional or missing quantity, amount or `sequence` in a
146
+ response throws `KasseneckValidationError` with `scope: 'response'`, as for
147
+ register stock since 1.2.0. A `Date` in `updatedSince`, `changedSince`,
148
+ `from` or `to` goes out as ISO 8601 UTC.
149
+ - **Incoming webhooks**: `verifyInventoryWebhookSignature(secret, header, rawBody,
150
+ { toleranceSec = 300, now })` resolves to `true` or `false` and never
151
+ throws: HMAC-SHA256 over `"<t>.<raw body>"`, constant-time comparison,
152
+ 300 seconds in both directions, several `v1=` parts allowed; checked against
153
+ the backend's test vector (t=1700000000). Asynchronous because it uses
154
+ WebCrypto, as `./partner` does. `parseInventoryWebhookEvent(rawBody)` returns a typed
155
+ `InventoryWebhookEvent` for `stock.changed` (`cause`, `movementId`),
156
+ `stock.below_minimum` (when `onHand` drops below the minimum stock set for
157
+ the location; the article's `minStock` alone never triggers it, the same rule
158
+ as `listStock({ belowMinimum: true })`) and `article.created|updated|deactivated`, with
159
+ `accountId` instead of `partnerId` in the envelope; an event type this
160
+ version does not know returns `null`, a body that is no envelope throws.
161
+ Both carry `Inventory` in their name on purpose: `./partner` has a
162
+ `verifyWebhookSignature` with an options object that resolves to
163
+ `{ ok, reason }`, and an object is truthy, so a mix-up in plain JavaScript
164
+ would accept every delivery.
165
+ - **Errors** through the existing classes: `INVENTORY_ERROR_CODES`
166
+ (`rate_limited` with `retryAfterSec`, also after 20 `sendWebhookTest` per
167
+ account and calendar day in Vienna, `inventory_api_not_enabled`,
168
+ `module_inactive`, `article_not_found`, `invalid_cursor`, `webhook_limit`
169
+ and the other webhook codes …), `INVENTORY_REQUEST_ERROR_CODES`, `isInventoryError`,
170
+ `inventoryErrorCode`, `inventoryFieldErrors`, `inventoryRetryAfterSec`.
171
+ - **Lists as data**: `INVENTORY_ENDPOINTS`, `INVENTORY_WEBHOOK_EVENTS`,
172
+ `INVENTORY_WEBHOOK_ENVELOPE_FIELDS`, `LOCATION_TYPES`,
173
+ `STOCK_MOVEMENT_TYPES`, `STOCK_MOVEMENT_SOURCES`, `STOCK_CONDITIONS`,
174
+ `STOCK_CHANGE_CAUSES`, `WEBHOOK_DELIVERY_STATUSES`, each held against the
175
+ backend contract. A goods receipt is `goods_receipt` (movement type and
176
+ cause), opening stock taken over is `takeover` (also as cause); `receipt`
177
+ only ever means a sales receipt (`source.type`).
178
+ - **Contract**: `fixtures/v3/` pulled again from the backend (the 14 new
179
+ endpoints, catalogs and events, and `antworten/lager.json` with real
180
+ responses and delivered webhook events that the tests read); `PUBLIC_CALLS` and `ALL_CALLS` gain the 14
181
+ names; `surface.json` gains the key `inventory` with the lists above.
182
+
7
183
  ## 1.3.0
8
184
 
9
185
  Article codes on `PosArticle` and the stock words of the register in the
package/README.md CHANGED
@@ -49,6 +49,7 @@ models, same enum values, checked against each other in tests.
49
49
  - [Card payments](#card-payments)
50
50
  - [Partner API (`./partner`)](#partner-api-partner)
51
51
  - [Invoice API (`./invoice`)](#invoice-api-invoice)
52
+ - [Inventory API (`./inventory`)](#inventory-api-inventory)
52
53
  - [Migrating from 0.x](#migrating-from-0x)
53
54
  - [Development](#development)
54
55
  - [Contract files for the twin packages](#contract-files-for-the-twin-packages)
@@ -211,6 +212,7 @@ adapter:
211
212
  | `…/stored` | Stored Firestore documents (inner form, German) as the same English models the `/v3` wire returns: receipts with company and layout, register settings, articles. For clients that read Firestore directly, such as the admin panel. |
212
213
  | `…/invoice` | Invoice API: create and search customers, issue finalised invoices, credit notes and cancellation, PDF and e-invoice XML, the contract as data. **Belongs on a server.** |
213
214
  | `…/invoice/calc` | Pure calculation core for invoice totals (integers, no transport, no dependency beyond types). Safe to run in the browser. |
215
+ | `…/inventory` | Inventory API: read articles, locations, stock and the stock ledger; create and update articles, book goods receipts, transfers and losses, reserve stock at checkout; manage account webhooks, verify and parse incoming stock, article and reservation events. **Belongs on a server.** |
214
216
  | `…/react` | Thin React adapter that renders a receipt layout or a receipt sheet. Needs React. |
215
217
  | `…/fixtures/*` | Golden receipts (JSON): inputs `receipts/<name>.json`, promised line output `expected/<name>.lines.json`, `manifest.json` with checksums. The backend, the browser register and the Flutter package check against the same files. |
216
218
 
@@ -408,6 +410,7 @@ the received body.
408
410
  | `POS_ERROR_CODES` | `isPosError` (`…/pos`) | settings, articles, printers, tip recipients |
409
411
  | `PARTNER_ERROR_CODES` + `PARTNER_REQUEST_ERROR_CODES` | `isPartnerError` (`…/partner`) | partner API |
410
412
  | `INVOICE_ERROR_CODES` + `INVOICE_REQUEST_ERROR_CODES` | `isInvoiceError` (`…/invoice`) | invoice API |
413
+ | `INVENTORY_ERROR_CODES` + `INVENTORY_REQUEST_ERROR_CODES` | `isInventoryError` (`…/inventory`) | inventory API |
411
414
 
412
415
  Every group has the same helpers: `is…ErrorCode(value)`, `…ErrorCode(error)`
413
416
  (the code if the group knows it), `is…Error(error, code?)` (a type guard; without
@@ -1348,6 +1351,26 @@ was classified.
1348
1351
  `previewInvoice` and `recordInvoicePayment`. An intra-Community supply carries
1349
1352
  `recapitulative_statement_due` (recapitulative statement).
1350
1353
 
1354
+ **VAT ID check.** An invoice without VAT that relies on the customer's VAT ID
1355
+ (intra-Community supply, reverse charge) is only issued with a result of the
1356
+ VAT ID check (FinanzOnline, otherwise VIES) on the day of issue.
1357
+ `vat_id_invalid` blocks the invoice. `vat_id_check_pending` means there is no
1358
+ result yet: retry after `retryAfter` seconds with the same `idempotencyKey`,
1359
+ or send `acceptVatIdRisk: true` to issue anyway, at the issuer's risk. The
1360
+ invoice then carries `vatIdRisk` (`acceptedOn`), otherwise the frozen proof
1361
+ `vatIdProof` (`checkedOn`, `source`, `level`, `code`); both are `null` where
1362
+ nothing was checked.
1363
+
1364
+ ```ts
1365
+ try {
1366
+ await invoices.issueInvoice(request);
1367
+ } catch (error) {
1368
+ if (!isInvoiceError(error, 'vat_id_check_pending')) throw error;
1369
+ const retryAfter = error.details['retryAfter']; // seconds
1370
+ // later: same request and key, or with acceptVatIdRisk: true
1371
+ }
1372
+ ```
1373
+
1351
1374
  **After a timeout, retry with the same `idempotencyKey`**, never with a new one:
1352
1375
  you then get the invoice that was already issued (`replayed: true`). The same
1353
1376
  key with different data gives `idempotency_conflict`.
@@ -1417,6 +1440,275 @@ but € 23.49 with the core and on the invoice. It stays unchanged for existing
1417
1440
  callers; use `calculateInvoice` or `previewInvoice` in new code. Test cases for
1418
1441
  the old formula: `fixtures/invoice-totals.json`.
1419
1442
 
1443
+ ## Inventory API (`./inventory`)
1444
+
1445
+ For online shops and other systems that show or mirror the stock of a
1446
+ Kasseneck account: read articles, locations, stock per location and the stock
1447
+ ledger, and get every stock change pushed by webhook within seconds, also the
1448
+ ones made at the register in the shop. Since 1.5.0 also the write side:
1449
+ create and update articles, book goods receipts, transfers, losses and
1450
+ condition changes, and reserve stock at checkout (see
1451
+ [Shop: reserve and invoice](#shop-reserve-and-invoice)). Uses the `api_key` of
1452
+ the account and belongs on a **server**. Reading needs the module `lager`,
1453
+ writing additionally the account switch *Lager-API schreiben*, which Kasseneck
1454
+ turns on (always on in the test environment, `kr_test_…`; otherwise
1455
+ `inventory_api_not_enabled`). Purchase prices and stock values appear only when
1456
+ the account has the permission `costs` (otherwise the fields are absent, not
1457
+ `null`).
1458
+
1459
+ **Integers with a fixed scale:** quantities in thousandths of the base unit
1460
+ (`1000` = 1 piece, `250` = 0.250 kg), money in cents, purchase prices in
1461
+ micro-euros. `available = onHand − reserved` and may be negative: the register
1462
+ never refuses a sale. A fractional quantity in a response throws
1463
+ `KasseneckValidationError` with `scope: 'response'`; it is never read as `0`.
1464
+
1465
+ ```ts
1466
+ import express from 'express';
1467
+ import { createInventoryClient, verifyInventoryWebhookSignature, parseInventoryWebhookEvent } from '@kreiseck/kasseneck-api/inventory';
1468
+
1469
+ const inventory = createInventoryClient({ apiKey: process.env.KASSENECK_API_KEY! });
1470
+
1471
+ // 1. Read: an article by its EAN, then its stock per location.
1472
+ const article = await inventory.lookupArticleByCode('9001234567896');
1473
+ const { stock } = await inventory.getStock(article.id);
1474
+ // stock[0]: { articleId, locationId, onHand: 12000, reserved: 2000, available: 10000, defective: 0, sequence: 42, updatedAt }
1475
+
1476
+ // 2. Initial sync, page by page over nextCursor.
1477
+ for await (const row of inventory.iterateStock()) {
1478
+ await shop.saveStock(row.articleId, row.locationId, row.available, row.sequence);
1479
+ }
1480
+
1481
+ // 3. Subscribe once. The secret is shown only in this response.
1482
+ const { secret } = await inventory.createWebhook({
1483
+ url: 'https://shop.example.com/kasseneck-webhook',
1484
+ events: ['stock.changed', 'stock.below_minimum'],
1485
+ description: 'Bäckerei Kornblum online shop',
1486
+ });
1487
+
1488
+ // 4. Receive. express.raw BEFORE any JSON parser: the signature covers the bytes as received.
1489
+ const app = express();
1490
+ app.post('/kasseneck-webhook', express.raw({ type: '*/*' }), async (req, res) => {
1491
+ // await is required: the check is asynchronous, and a forgotten await
1492
+ // yields a Promise, which is truthy, so every delivery would pass.
1493
+ if (!(await verifyInventoryWebhookSignature(secret, req.header('X-Kasseneck-Signature'), req.body))) {
1494
+ return res.sendStatus(400);
1495
+ }
1496
+ let event;
1497
+ try {
1498
+ event = parseInventoryWebhookEvent(req.body); // throws on a malformed envelope
1499
+ } catch {
1500
+ return res.sendStatus(400);
1501
+ }
1502
+ res.sendStatus(200); // answer within 10 s, work afterwards
1503
+ if (!event || event.test) return; // unknown type of a later version, or a test delivery
1504
+ if (event.type === 'stock.changed') {
1505
+ // State, not delta: keep it only if sequence is higher than the stored one.
1506
+ await shop.saveStockIfNewer(event.data.articleId, event.data.locationId, event.data.available, event.data.sequence);
1507
+ }
1508
+ });
1509
+ ```
1510
+
1511
+ - **Signature.** `verifyInventoryWebhookSignature(secret, header, rawBody, { toleranceSec, now })`
1512
+ resolves to `true` or `false` and never throws. It is the same procedure as
1513
+ for partner webhooks: `X-Kasseneck-Signature: t=<unix seconds>,v1=<hex>`
1514
+ with HMAC-SHA256 over `"<t>.<raw body>"`, compared in constant time, and a
1515
+ window of 300 seconds in both directions against replays. It is asynchronous
1516
+ because it uses WebCrypto: always `await` it, a forgotten `await` leaves a
1517
+ Promise, which is truthy, and lets every delivery through. The name differs
1518
+ on purpose from `verifyWebhookSignature` in `./partner`, which takes an
1519
+ options object and resolves to `{ ok, reason }`. After `rotateWebhookSecret`
1520
+ only the new secret is valid; pass both during your own switch-over
1521
+ (`secret` may be a list, one match is enough).
1522
+ - **Parsing.** `parseInventoryWebhookEvent(rawBody)` returns `null` for an
1523
+ event type this version does not know (answer 2xx and skip it) and throws
1524
+ `KasseneckValidationError` on a body that is no envelope or carries a
1525
+ fractional quantity; verify first, then parse inside `try`.
1526
+ - **Events.** `stock.changed` carries the current state of one article at one
1527
+ location (`onHand`, `reserved`, `available`, `defective`, `sequence`,
1528
+ `updatedAt`) plus `cause` (`sale`, `invoice`, `goods_receipt`, `transfer`,
1529
+ `takeover`, `reservation` …) and `movementId`; changes within 10 seconds are
1530
+ combined into one delivery. `stock.below_minimum` fires once when
1531
+ `available` (`onHand − reserved`, so a reservation alone can trigger it)
1532
+ drops below the minimum stock set for the location (`minStockByLocation` of
1533
+ the article); `minStock` in the payload is that threshold. The article's own
1534
+ `minStock` is a legacy field and never triggers it, and
1535
+ `listStock({ belowMinimum: true })` follows the same rule. In movements,
1536
+ `goods_receipt` is a goods receipt; `receipt` only ever means a sales receipt
1537
+ (`source.type`).
1538
+ `article.created`, `article.updated` and `article.deactivated` carry the
1539
+ article as `getArticle` returns it, without purchase prices.
1540
+ `reservation.expired`, `reservation.released` and `reservation.redeemed`
1541
+ carry the reservation as `getReservation` returns it, with its status after
1542
+ the change; `released` and `redeemed` also fire for a partial release or
1543
+ redemption (status still `active`). Deduplicate on `event.id`; deliveries
1544
+ are retried after 1 min, 5 min, 30 min, 2 h and 12 h.
1545
+ - **Safety net without webhooks.** `listStock({ changedSince })` and
1546
+ `listArticles({ updatedSince })` are sorted by `updatedAt` ascending and
1547
+ include the boundary, so remembering the last `updatedAt` and asking again
1548
+ loses nothing. Lists take `limit` (1–200, default 50) and `cursor`.
1549
+ - **Test deliveries.** `sendWebhookTest` sends a recognisably invented payload
1550
+ with `test: true` in the envelope. At most 20 per account and calendar day in
1551
+ Vienna (live and test environment count separately; rejected calls do not
1552
+ count); after that `rate_limited` with the wait until midnight in Vienna.
1553
+ Deliveries carry `deliveryId` (the header `X-Kasseneck-Delivery`), webhooks
1554
+ `consecutiveFailures`, the same names as for partner webhooks.
1555
+ - **Errors.** `rate_limited` (about 20 requests per second per account, or the
1556
+ daily limit of `sendWebhookTest`) carries the wait in
1557
+ `inventoryRetryAfterSec(error)`. `inventory_api_not_enabled`,
1558
+ `module_inactive`, `article_not_found`, `invalid_cursor`,
1559
+ `webhook_not_found`, `webhook_limit` (5 per account),
1560
+ `invalid_webhook_url`, `event_not_subscribed` and `webhook_inactive` are
1561
+ decided on the code with `isInventoryError(error, code)`.
1562
+
1563
+ ### Writing: articles and bookings
1564
+
1565
+ Every write takes an `idempotencyKey` (1 to 120 characters). The same request
1566
+ with the same key takes effect exactly once; repeating it returns the stored
1567
+ answer of the first call, the same key with different content gives
1568
+ `idempotency_conflict`. After a timeout or a network error, repeat with the
1569
+ **same** key, never with a new one. The client refuses to send a write without
1570
+ a valid key, and checks before sending only what is certainly wrong without the
1571
+ network (required ids, integer quantities, amounts and prices, the range of
1572
+ `expiresInMinutes`); everything else the server decides and reports with its
1573
+ code.
1574
+
1575
+ ```ts
1576
+ import { createInventoryClient, isInventoryError, inventoryFieldErrors } from '@kreiseck/kasseneck-api/inventory';
1577
+
1578
+ const inventory = createInventoryClient({ apiKey: process.env.KASSENECK_API_KEY! });
1579
+
1580
+ // A foreign article with its EAN; without ean the server assigns an own code.
1581
+ const roll = await inventory.createArticle({
1582
+ idempotencyKey: 'shop-article-1001',
1583
+ name: 'Kaisersemmel',
1584
+ ean: '9001234567896',
1585
+ unitPriceCents: 65,
1586
+ vatRate: 10,
1587
+ stockTracked: true,
1588
+ minStockByLocation: { haupt: 20000 }, // thousandths: warn below 20 pieces
1589
+ externalIds: { shop: '1001' }, // lookupArticleByCode({ externalSystem: 'shop', externalId: '1001' })
1590
+ });
1591
+
1592
+ // Goods receipt: 60 pieces at 0.38 EUR each, plus freight spread by value.
1593
+ const receipt = await inventory.receiveGoods({
1594
+ idempotencyKey: 'shop-goods-receipt-118',
1595
+ items: [{ articleId: roll.id, quantity: 60000, unitPriceMicros: 380000 }],
1596
+ landedCosts: [{ type: 'freight', amountCents: 450 }],
1597
+ reference: 'LS-2026-118',
1598
+ });
1599
+ // receipt: { operationId, movementIds, lotIds, warnings: [] }
1600
+
1601
+ try {
1602
+ await inventory.recordStockLoss({ idempotencyKey: 'shop-loss-7', reason: 'breakage', items: [{ articleId: roll.id, quantity: 2000 }] });
1603
+ } catch (error) {
1604
+ if (isInventoryError(error, 'exceeds_stock')) { /* losses, transfers and condition changes never overdraw */ }
1605
+ else if (isInventoryError(error, 'validation')) console.log(inventoryFieldErrors(error));
1606
+ else throw error;
1607
+ }
1608
+ ```
1609
+
1610
+ - **Articles.** `updateArticle({ idempotencyKey, articleId, …fields })` changes
1611
+ only the fields it names; `null` clears an optional one. `externalIds` and
1612
+ `metadata` are replaced as a whole, `minStockByLocation` is merged per
1613
+ location (`{ haupt: null }` removes just that one). `stockKind` is fixed after
1614
+ the first movement (`stock_kind_locked`). `deactivateArticle` frees the code
1615
+ and the external ids. `code_taken` and `external_id_taken` carry `field` and
1616
+ the `articleId` that holds the code in `error.details`. `minStock` is a
1617
+ legacy field without effect; use `minStockByLocation`.
1618
+ - **Bookings.** `receiveGoods`, `transferStock`, `recordStockLoss` (`reason`
1619
+ from `STOCK_LOSS_REASONS`; `other` needs `note`, `withdrawal` needs
1620
+ `withdrawalType`), `changeStockCondition` (`sellable` ↔ `defective`) and
1621
+ `reverseStockMovement({ operationId, reason })` answer with a
1622
+ `StockOperation`. `warnings[]` are notices from `INVENTORY_WARNING_CODES`,
1623
+ never errors: the booking took effect. Prices may be sent without the
1624
+ permission `costs`; values come back only in `previewGoodsReceipt`, which is
1625
+ `receiveGoods` with `dryRun: true` (books nothing, needs no key; a `null`
1626
+ key counts as none and is not sent). `receiveGoods` itself refuses
1627
+ `dryRun: true` before sending (`KasseneckValidationError`, `scope:
1628
+ 'request'`): a preview is not a booking, call `previewGoodsReceipt`.
1629
+
1630
+ ### Shop: reserve and invoice
1631
+
1632
+ Hold the goods while the customer pays, turn the reservation into a sale with
1633
+ the invoice, and let the server release what was never paid:
1634
+
1635
+ ```ts
1636
+ import { createInventoryClient, inventoryShortfalls, isInventoryError, parseInventoryWebhookEvent } from '@kreiseck/kasseneck-api/inventory';
1637
+ import { createInvoiceApi } from '@kreiseck/kasseneck-api/invoice';
1638
+
1639
+ const inventory = createInventoryClient({ apiKey: process.env.KASSENECK_API_KEY! });
1640
+ const invoices = createInvoiceApi({ apiKey: process.env.KASSENECK_API_KEY! });
1641
+
1642
+ // 1. Checkout: reserve everything or nothing, measured against `available`.
1643
+ async function checkout(order: Order) {
1644
+ try {
1645
+ return await inventory.createReservation({
1646
+ idempotencyKey: `checkout-${order.id}`, // the same key on every retry of this checkout
1647
+ reference: `Bestellung ${order.number}`,
1648
+ expiresInMinutes: 30,
1649
+ items: [{ articleId: 'kaisersemmel', quantity: 6000 }], // 6 pieces, at the default location
1650
+ });
1651
+ } catch (error) {
1652
+ if (!isInventoryError(error, 'insufficient_available')) throw error;
1653
+ // Nothing was reserved. Tell the customer what is short:
1654
+ // [{ articleId, locationId, requested: 6000, available: 2000 }]
1655
+ return showShortage(inventoryShortfalls(error));
1656
+ }
1657
+ }
1658
+
1659
+ // 2. Paid: issue the invoice; the position redeems the reservation.
1660
+ async function paid(order: Order, reservationId: string) {
1661
+ const { invoice, notice } = await invoices.issueInvoice({
1662
+ idempotencyKey: `invoice-${order.id}`,
1663
+ priceMode: 'gross',
1664
+ serviceStart: '2026-10-06',
1665
+ orderReference: `Bestellung ${order.number}`,
1666
+ items: [{
1667
+ description: 'Kaisersemmel', quantity: 6, unitPriceCents: 65, vatRate: 10,
1668
+ articleId: 'kaisersemmel', reservationId,
1669
+ }],
1670
+ payment: { method: 'online' },
1671
+ });
1672
+ // Expired before the invoice: no error, the invoice sells without it.
1673
+ if (notice?.some((n) => n.code === 'reservation_expired')) log('sold without reservation', invoice.number);
1674
+ }
1675
+
1676
+ // 3. Abandoned cart: give the goods back at once instead of waiting for expiry.
1677
+ async function abandoned(order: Order, reservationId: string) {
1678
+ await inventory.releaseReservation({ idempotencyKey: `release-${order.id}`, reservationId });
1679
+ }
1680
+
1681
+ // 4. Webhooks (subscribe to reservation.* with createWebhook, verify as above).
1682
+ function onDelivery(rawBody: Buffer) {
1683
+ const event = parseInventoryWebhookEvent(rawBody);
1684
+ if (event && !event.test && event.type === 'reservation.expired') {
1685
+ return shop.cancelUnpaidOrder(event.data.reference); // the stock is available again
1686
+ }
1687
+ }
1688
+ ```
1689
+
1690
+ - **Redeeming.** A position with `reservationId` needs `articleId`. The server
1691
+ checks it when the invoice is issued: `reservation_not_found` (unknown or
1692
+ another account), `reservation_not_active` (already redeemed or released),
1693
+ `reservation_mismatch` (no open quantity of this article at the invoice's
1694
+ stock location, `stockLocationId` or the default location); nothing is issued
1695
+ then. An expired reservation is no error but the notice `reservation_expired`
1696
+ (with `reservationId`). The reservation is redeemed when the invoice is
1697
+ booked into stock: selling less than reserved releases the rest, selling
1698
+ more gives the warning `reservation_exceeded`. Credit notes take no
1699
+ `reservationId`.
1700
+ - **Expiry.** Without `expiresInMinutes` the account's default applies (7 days);
1701
+ 5 to 43,200 minutes are allowed. `extendReservation` sets a new expiry from
1702
+ now, only for an active reservation that is not yet due
1703
+ (`reservation_not_active`). `releaseReservation` without `items` releases
1704
+ everything, with `items` per position (without `quantity` the whole rest);
1705
+ the status becomes `released` only when nothing is open.
1706
+ - **Events.** `reservation.expired`, `reservation.released` and
1707
+ `reservation.redeemed` carry the reservation with its status afterwards.
1708
+ `listReservations({ status: 'active' })` or `({ reference })` is the safety
1709
+ net without webhooks; the stock ledger shows reservations as movements of
1710
+ type `reservation` with `quantityDelta: 0` and the change in `reservedDelta`.
1711
+
1420
1712
  ## Migrating from 0.x
1421
1713
 
1422
1714
  1.0 is one breaking step: the wire, the exported names and the contract files
@@ -1492,7 +1784,7 @@ in machine form what both sides agreed on, among them:
1492
1784
  |---|---|---|
1493
1785
  | `pos-settings-defaults.json` | field names and defaults of the register settings as sent on `/api/v3` (`business`, `device`), derived from the backend contract in `fixtures/v3/` | `npm run fixtures:kasse` |
1494
1786
  | `stored/pos-settings-defaults.json` | the same defaults in the stored (internal, German) form (`betrieb`, `geraet`) | `npm run fixtures:kasse` |
1495
- | `surface.json` | base URLs (`baseUrls.public`, `baseUrls.pos`), the calls per path (`calls`, and the backend's `routes`), settings value lists (`enums`, keyed by field), other register lists (`pos`), register error codes, permission keys, shortcut actions, partner and invoice lists | `npm run fixtures:oberflaeche` |
1787
+ | `surface.json` | base URLs (`baseUrls.public`, `baseUrls.pos`), the calls per path (`calls`, and the backend's `routes`), settings value lists (`enums`, keyed by field), other register lists (`pos`), register error codes, permission keys, shortcut actions, partner, invoice and inventory lists | `npm run fixtures:oberflaeche` |
1496
1788
  | `hobex-hps-codes.json` | measured HPS result codes, their meaning and whether they settle an outcome (the contract behind `isConclusive`) | `npm run fixtures:hobex-hps-codes` |
1497
1789
  | `pos-texts.json` | the register's message catalogue | `npm run fixtures:texte` |
1498
1790
  | `invoice-texts.json` | invoice texts in both languages | `npm run fixtures:rechnungstexte` |
@@ -13,7 +13,7 @@
13
13
  * umhuellt, muss ihn weiterhin absetzen koennen.
14
14
  */
15
15
  import type { TransportBodyFields } from './transport.js';
16
- export declare const ALL_CALLS: readonly ["activateCashregister", "cancelInvoice", "cancelReceipt", "createCreditNote", "createCustomer", "createCustomerCashregister", "createPartnerCustomer", "checkPartnerCustomerEmail", "createPartnerWebhook", "createPaymentLinkStripe", "createPrintJob", "createReceipt", "deletePartnerWebhook", "downloadDailyReport", "downloadReport", "endRegisterSession", "financeWebService", "generateFullReceiptId", "getCustomer", "getCustomerCredentials", "getCustomerSignatureStatus", "getFirstReceiptDate", "getInvoice", "getInvoicePdf", "getInvoiceSetupStatus", "getInvoiceXml", "getKasseSettings", "getPartnerCustomer", "getPartnerInfo", "getPrintJob", "getReceipt", "getReportV2", "hobexPayApi", "hobexRefundApi", "issueInvoice", "listCustomerCashregisters", "listInvoices", "listBrands", "listMyArticleGroups", "listMyArticles", "listMyCashregisters", "listMyPrinters", "listMyReceipts", "listMyStock", "listMyStockLocations", "listMyTipRecipients", "listPartnerCustomers", "listPartnerWebhookDeliveries", "listPartnerWebhooks", "listRegisterUsersForDevice", "listRegisterSessionsForDevice", "pairRegisterDevice", "recordInvoicePayment", "registerPinLogin", "registerUserLogin", "renewRegisterSession", "reportCustomerContract", "requestCustomerSignature", "searchCustomers", "sendPartnerCustomerFonLink", "sendPartnerWebhookTest", "sendReceiptEmail", "setMyCashregisterStockLocation", "setMyKasseLogo", "setMyKasseSettings", "setMyRegisterDeviceSettings", "stripeCaptureIntent", "unpairRegisterDevice", "rotatePartnerWebhookSecret", "updateCustomer", "updatePartnerWebhook"];
16
+ export declare const ALL_CALLS: readonly ["activateCashregister", "cancelInvoice", "cancelReceipt", "createCreditNote", "createCustomer", "createCustomerCashregister", "createPartnerCustomer", "checkPartnerCustomerEmail", "createPartnerWebhook", "createPaymentLinkStripe", "createPrintJob", "createReceipt", "deletePartnerWebhook", "downloadDailyReport", "downloadReport", "endRegisterSession", "financeWebService", "generateFullReceiptId", "getCustomer", "getCustomerCredentials", "getCustomerSignatureStatus", "getFirstReceiptDate", "getInvoice", "getInvoicePdf", "getInvoiceSetupStatus", "getInvoiceXml", "getKasseSettings", "getPartnerCustomer", "getPartnerInfo", "getPrintJob", "getReceipt", "getReportV2", "hobexPayApi", "hobexRefundApi", "issueInvoice", "listCustomerCashregisters", "listInvoices", "listBrands", "listMyArticleGroups", "listMyArticles", "listMyCashregisters", "listMyPrinters", "listMyReceipts", "listMyStock", "listMyStockLocations", "listMyTipRecipients", "listPartnerCustomers", "listPartnerWebhookDeliveries", "listPartnerWebhooks", "listRegisterUsersForDevice", "listRegisterSessionsForDevice", "pairRegisterDevice", "recordInvoicePayment", "registerPinLogin", "registerUserLogin", "renewRegisterSession", "reportCustomerContract", "requestCustomerSignature", "searchCustomers", "sendPartnerCustomerFonLink", "sendPartnerWebhookTest", "sendReceiptEmail", "setMyCashregisterStockLocation", "setMyKasseLogo", "setMyKasseSettings", "setMyRegisterDeviceSettings", "stripeCaptureIntent", "unpairRegisterDevice", "rotatePartnerWebhookSecret", "updateCustomer", "updatePartnerWebhook", "getArticle", "listArticles", "lookupArticleByCode", "listLocations", "getStock", "listStock", "listStockMovements", "createWebhook", "updateWebhook", "deleteWebhook", "listWebhooks", "sendWebhookTest", "rotateWebhookSecret", "listWebhookDeliveries", "createArticle", "updateArticle", "deactivateArticle", "receiveGoods", "transferStock", "recordStockLoss", "changeStockCondition", "reverseStockMovement", "createReservation", "extendReservation", "releaseReservation", "getReservation", "listReservations"];
17
17
  export type ApiCall = typeof ALL_CALLS[number];
18
18
  /**
19
19
  * Die oeffentlichen Endpunkte unter `/v3` (`https://api.kasseneck.at/v3/<name>`),
@@ -21,7 +21,7 @@ export type ApiCall = typeof ALL_CALLS[number];
21
21
  * (`fixtures/v3/v3-vokabular.json`, `endpoints.public`, umbenannt nach
22
22
  * `names`). Ein Test haelt beide Listen deckungsgleich.
23
23
  */
24
- export declare const PUBLIC_CALLS: readonly ["createReceipt", "getReceipt", "cancelReceipt", "sendReceiptEmail", "generateFullReceiptId", "downloadReceipt", "getFirstReceiptDate", "getReportV2", "downloadReport", "downloadDailyReport", "createPaymentLinkStripe", "stripeCaptureIntent", "hobexPayApi", "hobexRefundApi", "hobexGetStatus", "financeWebService", "listMyTipRecipients", "getPartnerInfo", "createPartnerCustomer", "checkPartnerCustomerEmail", "listPartnerCustomers", "getPartnerCustomer", "sendPartnerCustomerFonLink", "createPartnerWebhook", "updatePartnerWebhook", "deletePartnerWebhook", "listPartnerWebhooks", "sendPartnerWebhookTest", "rotatePartnerWebhookSecret", "listPartnerWebhookDeliveries", "requestCustomerSignature", "getCustomerSignatureStatus", "createCustomerCashregister", "activateCashregister", "listCustomerCashregisters", "getCustomerCredentials", "reportCustomerContract", "getPartnerBilling", "getPartnerBillingMonth", "createCustomer", "getCustomer", "updateCustomer", "searchCustomers", "issueInvoice", "cancelInvoice", "createCreditNote", "getInvoice", "listInvoices", "getInvoicePdf", "getInvoiceXml", "getInvoiceSetupStatus", "listBrands", "recordInvoicePayment", "createInvoiceItem", "updateInvoiceItem", "withdrawInvoiceItem", "getInvoiceItem", "listInvoiceItems", "setCustomerMandate", "revokeCustomerMandate"];
24
+ export declare const PUBLIC_CALLS: readonly ["createReceipt", "getReceipt", "cancelReceipt", "sendReceiptEmail", "generateFullReceiptId", "downloadReceipt", "getFirstReceiptDate", "getReportV2", "downloadReport", "downloadDailyReport", "createPaymentLinkStripe", "stripeCaptureIntent", "hobexPayApi", "hobexRefundApi", "hobexGetStatus", "financeWebService", "listMyTipRecipients", "getPartnerInfo", "createPartnerCustomer", "checkPartnerCustomerEmail", "listPartnerCustomers", "getPartnerCustomer", "sendPartnerCustomerFonLink", "createPartnerWebhook", "updatePartnerWebhook", "deletePartnerWebhook", "listPartnerWebhooks", "sendPartnerWebhookTest", "rotatePartnerWebhookSecret", "listPartnerWebhookDeliveries", "requestCustomerSignature", "getCustomerSignatureStatus", "createCustomerCashregister", "activateCashregister", "listCustomerCashregisters", "getCustomerCredentials", "reportCustomerContract", "getPartnerBilling", "getPartnerBillingMonth", "createCustomer", "getCustomer", "updateCustomer", "searchCustomers", "issueInvoice", "cancelInvoice", "createCreditNote", "getInvoice", "listInvoices", "getInvoicePdf", "getInvoiceXml", "getInvoiceSetupStatus", "listBrands", "recordInvoicePayment", "createInvoiceItem", "updateInvoiceItem", "withdrawInvoiceItem", "getInvoiceItem", "listInvoiceItems", "setCustomerMandate", "revokeCustomerMandate", "getArticle", "listArticles", "lookupArticleByCode", "listLocations", "getStock", "listStock", "listStockMovements", "createWebhook", "updateWebhook", "deleteWebhook", "listWebhooks", "sendWebhookTest", "rotateWebhookSecret", "listWebhookDeliveries", "createArticle", "updateArticle", "deactivateArticle", "receiveGoods", "transferStock", "recordStockLoss", "changeStockCondition", "reverseStockMovement", "createReservation", "extendReservation", "releaseReservation", "getReservation", "listReservations"];
25
25
  /**
26
26
  * Die Endpunkte des Kassenwegs (`https://kasse.kasseneck.at/api/v3/<name>`,
27
27
  * Web-Kasse: gleicher Ursprung `/api/v3`), in der Reihenfolge von
@@ -75,6 +75,35 @@ exports.ALL_CALLS = [
75
75
  'rotatePartnerWebhookSecret',
76
76
  'updateCustomer',
77
77
  'updatePartnerWebhook',
78
+ // Lager-API (Stufe 5a, ./inventory)
79
+ 'getArticle',
80
+ 'listArticles',
81
+ 'lookupArticleByCode',
82
+ 'listLocations',
83
+ 'getStock',
84
+ 'listStock',
85
+ 'listStockMovements',
86
+ 'createWebhook',
87
+ 'updateWebhook',
88
+ 'deleteWebhook',
89
+ 'listWebhooks',
90
+ 'sendWebhookTest',
91
+ 'rotateWebhookSecret',
92
+ 'listWebhookDeliveries',
93
+ // Lager-API schreiben und Reservierung (Stufe 5b, ./inventory)
94
+ 'createArticle',
95
+ 'updateArticle',
96
+ 'deactivateArticle',
97
+ 'receiveGoods',
98
+ 'transferStock',
99
+ 'recordStockLoss',
100
+ 'changeStockCondition',
101
+ 'reverseStockMovement',
102
+ 'createReservation',
103
+ 'extendReservation',
104
+ 'releaseReservation',
105
+ 'getReservation',
106
+ 'listReservations',
78
107
  ];
79
108
  /**
80
109
  * Die oeffentlichen Endpunkte unter `/v3` (`https://api.kasseneck.at/v3/<name>`),
@@ -146,6 +175,35 @@ exports.PUBLIC_CALLS = [
146
175
  'listInvoiceItems',
147
176
  'setCustomerMandate',
148
177
  'revokeCustomerMandate',
178
+ // Lager-API lesen und Konto-Webhooks (Backend Stufe 5a), umhuellt in `./inventory`.
179
+ 'getArticle',
180
+ 'listArticles',
181
+ 'lookupArticleByCode',
182
+ 'listLocations',
183
+ 'getStock',
184
+ 'listStock',
185
+ 'listStockMovements',
186
+ 'createWebhook',
187
+ 'updateWebhook',
188
+ 'deleteWebhook',
189
+ 'listWebhooks',
190
+ 'sendWebhookTest',
191
+ 'rotateWebhookSecret',
192
+ 'listWebhookDeliveries',
193
+ // Lager-API schreiben und Reservierung (Backend Stufe 5b), umhuellt in `./inventory`.
194
+ 'createArticle',
195
+ 'updateArticle',
196
+ 'deactivateArticle',
197
+ 'receiveGoods',
198
+ 'transferStock',
199
+ 'recordStockLoss',
200
+ 'changeStockCondition',
201
+ 'reverseStockMovement',
202
+ 'createReservation',
203
+ 'extendReservation',
204
+ 'releaseReservation',
205
+ 'getReservation',
206
+ 'listReservations',
149
207
  ];
150
208
  /**
151
209
  * Die Endpunkte des Kassenwegs (`https://kasse.kasseneck.at/api/v3/<name>`,