@kreiseck/kasseneck-api 1.4.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 (81) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +192 -11
  3. package/dist/cjs/client/aufrufe.d.ts +2 -2
  4. package/dist/cjs/client/aufrufe.js +28 -0
  5. package/dist/cjs/inventory/api.d.ts +19 -3
  6. package/dist/cjs/inventory/api.js +18 -2
  7. package/dist/cjs/inventory/endpunkte.d.ts +22 -2
  8. package/dist/cjs/inventory/endpunkte.js +6 -1
  9. package/dist/cjs/inventory/fehler.d.ts +11 -1
  10. package/dist/cjs/inventory/fehler.js +19 -0
  11. package/dist/cjs/inventory/index.d.ts +6 -4
  12. package/dist/cjs/inventory/index.js +32 -2
  13. package/dist/cjs/inventory/lesen.d.ts +8 -1
  14. package/dist/cjs/inventory/lesen.js +131 -1
  15. package/dist/cjs/inventory/schreiben.d.ts +83 -0
  16. package/dist/cjs/inventory/schreiben.js +315 -0
  17. package/dist/cjs/inventory/typen.d.ts +371 -11
  18. package/dist/cjs/inventory/vertrag.d.ts +76 -16
  19. package/dist/cjs/inventory/vertrag.js +138 -13
  20. package/dist/cjs/inventory/webhook.d.ts +3 -1
  21. package/dist/cjs/inventory/webhook.js +7 -1
  22. package/dist/cjs/invoice/index.d.ts +2 -2
  23. package/dist/cjs/invoice/index.js +2 -1
  24. package/dist/cjs/invoice/typen.d.ts +46 -2
  25. package/dist/cjs/invoice/vertrag.d.ts +19 -5
  26. package/dist/cjs/invoice/vertrag.js +37 -5
  27. package/dist/cjs/stored/vokabular.d.ts +1 -1
  28. package/dist/cjs/stored/vokabular.js +5 -3
  29. package/dist/cjs/version.d.ts +1 -1
  30. package/dist/cjs/version.js +1 -1
  31. package/dist/esm/client/aufrufe.d.ts +2 -2
  32. package/dist/esm/client/aufrufe.js +28 -0
  33. package/dist/esm/inventory/api.d.ts +19 -3
  34. package/dist/esm/inventory/api.js +18 -2
  35. package/dist/esm/inventory/endpunkte.d.ts +22 -2
  36. package/dist/esm/inventory/endpunkte.js +6 -5
  37. package/dist/esm/inventory/fehler.d.ts +11 -1
  38. package/dist/esm/inventory/fehler.js +18 -1
  39. package/dist/esm/inventory/index.d.ts +6 -4
  40. package/dist/esm/inventory/index.js +5 -3
  41. package/dist/esm/inventory/lesen.d.ts +8 -1
  42. package/dist/esm/inventory/lesen.js +127 -1
  43. package/dist/esm/inventory/schreiben.d.ts +83 -0
  44. package/dist/esm/inventory/schreiben.js +298 -0
  45. package/dist/esm/inventory/typen.d.ts +371 -11
  46. package/dist/esm/inventory/vertrag.d.ts +76 -16
  47. package/dist/esm/inventory/vertrag.js +137 -12
  48. package/dist/esm/inventory/webhook.d.ts +3 -1
  49. package/dist/esm/inventory/webhook.js +8 -2
  50. package/dist/esm/invoice/index.d.ts +2 -2
  51. package/dist/esm/invoice/index.js +1 -1
  52. package/dist/esm/invoice/typen.d.ts +46 -2
  53. package/dist/esm/invoice/vertrag.d.ts +19 -5
  54. package/dist/esm/invoice/vertrag.js +36 -4
  55. package/dist/esm/stored/vokabular.d.ts +1 -1
  56. package/dist/esm/stored/vokabular.js +5 -3
  57. package/dist/esm/version.d.ts +1 -1
  58. package/dist/esm/version.js +1 -1
  59. package/fixtures/hobex-hps-codes.json +1 -1
  60. package/fixtures/invoice-api-examples/credit-error-reservation.json +26 -0
  61. package/fixtures/invoice-api-examples/issue-error-reservation-article.json +25 -0
  62. package/fixtures/invoice-api-examples/issue-error-vat-id-risk.json +26 -0
  63. package/fixtures/invoice-api-examples/issue-reservation.json +24 -0
  64. package/fixtures/invoice-api-examples/issue-vat-id-risk.json +28 -0
  65. package/fixtures/invoice-api.schema.json +15 -2
  66. package/fixtures/invoice-texts.json +1 -1
  67. package/fixtures/pos-texts.json +1 -1
  68. package/fixtures/surface.json +152 -9
  69. package/fixtures/v3/antworten/belege.json +2 -2
  70. package/fixtures/v3/antworten/belegmail.json +2 -2
  71. package/fixtures/v3/antworten/kasse-belege.json +2 -2
  72. package/fixtures/v3/antworten/kasse.json +3 -3
  73. package/fixtures/v3/antworten/lager.json +2491 -130
  74. package/fixtures/v3/antworten/rechnungen.json +267 -44
  75. package/fixtures/v3/antworten/storno.json +2 -2
  76. package/fixtures/v3/stored/belege.json +2 -2
  77. package/fixtures/v3/stored/kasse.json +3 -3
  78. package/fixtures/v3/stored/rechnungen.json +174 -18
  79. package/fixtures/v3/v3-vokabular.json +593 -23
  80. package/fixtures/v3/zahlbetrag-faelle.json +2 -2
  81. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,114 @@ 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
+
7
115
  ## 1.4.0
8
116
 
9
117
  Inventory API, read side and account webhooks: new subpath `./inventory`.
package/README.md CHANGED
@@ -212,7 +212,7 @@ adapter:
212
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. |
213
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.** |
214
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, manage account webhooks, verify and parse incoming stock and article events. **Belongs on a server.** |
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.** |
216
216
  | `…/react` | Thin React adapter that renders a receipt layout or a receipt sheet. Needs React. |
217
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. |
218
218
 
@@ -1351,6 +1351,26 @@ was classified.
1351
1351
  `previewInvoice` and `recordInvoicePayment`. An intra-Community supply carries
1352
1352
  `recapitulative_statement_due` (recapitulative statement).
1353
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
+
1354
1374
  **After a timeout, retry with the same `idempotencyKey`**, never with a new one:
1355
1375
  you then get the invoice that was already issued (`replayed: true`). The same
1356
1376
  key with different data gives `idempotency_conflict`.
@@ -1425,10 +1445,16 @@ the old formula: `fixtures/invoice-totals.json`.
1425
1445
  For online shops and other systems that show or mirror the stock of a
1426
1446
  Kasseneck account: read articles, locations, stock per location and the stock
1427
1447
  ledger, and get every stock change pushed by webhook within seconds, also the
1428
- ones made at the register in the shop. Uses the `api_key` of the account and
1429
- belongs on a **server**. Reading needs the module `lager`; purchase prices and
1430
- stock values appear only when the account has the permission `costs`
1431
- (otherwise the fields are absent, not `null`).
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`).
1432
1458
 
1433
1459
  **Integers with a fixed scale:** quantities in thousandths of the base unit
1434
1460
  (`1000` = 1 piece, `250` = 0.250 kg), money in cents, purchase prices in
@@ -1500,16 +1526,22 @@ app.post('/kasseneck-webhook', express.raw({ type: '*/*' }), async (req, res) =>
1500
1526
  - **Events.** `stock.changed` carries the current state of one article at one
1501
1527
  location (`onHand`, `reserved`, `available`, `defective`, `sequence`,
1502
1528
  `updatedAt`) plus `cause` (`sale`, `invoice`, `goods_receipt`, `transfer`,
1503
- `takeover` …) and `movementId`; changes within 10 seconds are combined into
1504
- one delivery. `stock.below_minimum` fires once when `onHand` drops below the
1505
- minimum stock set for the location; `minStock` in the payload is that
1506
- threshold. The article's own `minStock` never triggers it, and
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
1507
1535
  `listStock({ belowMinimum: true })` follows the same rule. In movements,
1508
1536
  `goods_receipt` is a goods receipt; `receipt` only ever means a sales receipt
1509
1537
  (`source.type`).
1510
1538
  `article.created`, `article.updated` and `article.deactivated` carry the
1511
- article as `getArticle` returns it, without purchase prices. Deduplicate on
1512
- `event.id`; deliveries are retried after 1 min, 5 min, 30 min, 2 h and 12 h.
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.
1513
1545
  - **Safety net without webhooks.** `listStock({ changedSince })` and
1514
1546
  `listArticles({ updatedSince })` are sorted by `updatedAt` ascending and
1515
1547
  include the boundary, so remembering the last `updatedAt` and asking again
@@ -1528,6 +1560,155 @@ app.post('/kasseneck-webhook', express.raw({ type: '*/*' }), async (req, res) =>
1528
1560
  `invalid_webhook_url`, `event_not_subscribed` and `webhook_inactive` are
1529
1561
  decided on the code with `isInventoryError(error, code)`.
1530
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
+
1531
1712
  ## Migrating from 0.x
1532
1713
 
1533
1714
  1.0 is one breaking step: the wire, the exported names and the contract files
@@ -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", "getArticle", "listArticles", "lookupArticleByCode", "listLocations", "getStock", "listStock", "listStockMovements", "createWebhook", "updateWebhook", "deleteWebhook", "listWebhooks", "sendWebhookTest", "rotateWebhookSecret", "listWebhookDeliveries"];
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", "getArticle", "listArticles", "lookupArticleByCode", "listLocations", "getStock", "listStock", "listStockMovements", "createWebhook", "updateWebhook", "deleteWebhook", "listWebhooks", "sendWebhookTest", "rotateWebhookSecret", "listWebhookDeliveries"];
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
@@ -90,6 +90,20 @@ exports.ALL_CALLS = [
90
90
  'sendWebhookTest',
91
91
  'rotateWebhookSecret',
92
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',
93
107
  ];
94
108
  /**
95
109
  * Die oeffentlichen Endpunkte unter `/v3` (`https://api.kasseneck.at/v3/<name>`),
@@ -176,6 +190,20 @@ exports.PUBLIC_CALLS = [
176
190
  'sendWebhookTest',
177
191
  'rotateWebhookSecret',
178
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',
179
207
  ];
180
208
  /**
181
209
  * Die Endpunkte des Kassenwegs (`https://kasse.kasseneck.at/api/v3/<name>`,
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Fassade ueber den Aufrufen der Lager-API: Schluessel einmal binden, dann
3
- * rufen. Wie [createInvoiceApi] bewusst keine Klasse; die Aufrufe sind freie
2
+ * Fassade ueber den Aufrufen der Lager-API (lesen, Webhooks, schreiben,
3
+ * reservieren): Schluessel einmal binden, dann rufen. Wie [createInvoiceApi] bewusst keine Klasse; die Aufrufe sind freie
4
4
  * Funktionen (endpunkte.ts) und bleiben einzeln importierbar.
5
5
  */
6
6
  import { type FetchLike } from '../client/transport.js';
7
- import type { Article, ArticleListQuery, ArticleLookup, ArticlePage, CreateInventoryWebhookOptions, InventoryWebhook, InventoryWebhookDelivery, InventoryWebhookList, InventoryWebhookPatch, InventoryWebhookTestResult, InventoryWebhookWithSecret, Location, StockLevel, StockListQuery, StockMovement, StockMovementPage, StockMovementQuery, StockPage, StockResult } from './typen.js';
7
+ import type { Article, ChangeStockConditionRequest, CreateArticleRequest, CreateReservationRequest, DeactivateArticleRequest, ExtendReservationRequest, GoodsReceiptPreview, GoodsReceiptPreviewRequest, ReceiveGoodsRequest, RecordStockLossRequest, ReleaseReservationRequest, Reservation, ReservationListQuery, ReservationPage, ReverseStockMovementRequest, StockOperation, TransferStockRequest, UpdateArticleRequest, ArticleListQuery, ArticleLookup, ArticlePage, CreateInventoryWebhookOptions, InventoryWebhook, InventoryWebhookDelivery, InventoryWebhookList, InventoryWebhookPatch, InventoryWebhookTestResult, InventoryWebhookWithSecret, Location, StockLevel, StockListQuery, StockMovement, StockMovementPage, StockMovementQuery, StockPage, StockResult } from './typen.js';
8
8
  import type { InventoryWebhookEventType } from './vertrag.js';
9
9
  export interface InventoryClientOptions {
10
10
  /** `api_key` des Kontos (`kr_live_…` / `kr_test_…`). Gehoert auf einen Server. */
@@ -41,5 +41,21 @@ export interface InventoryClient {
41
41
  webhookId?: string;
42
42
  limit?: number;
43
43
  }): Promise<InventoryWebhookDelivery[]>;
44
+ createArticle(request: CreateArticleRequest): Promise<Article>;
45
+ updateArticle(request: UpdateArticleRequest): Promise<Article>;
46
+ deactivateArticle(request: DeactivateArticleRequest): Promise<Article>;
47
+ receiveGoods(request: ReceiveGoodsRequest): Promise<StockOperation>;
48
+ /** `receiveGoods` mit `dryRun: true`: rechnet, schreibt nichts, Schluessel freigestellt. */
49
+ previewGoodsReceipt(request: GoodsReceiptPreviewRequest): Promise<GoodsReceiptPreview>;
50
+ transferStock(request: TransferStockRequest): Promise<StockOperation>;
51
+ recordStockLoss(request: RecordStockLossRequest): Promise<StockOperation>;
52
+ changeStockCondition(request: ChangeStockConditionRequest): Promise<StockOperation>;
53
+ reverseStockMovement(request: ReverseStockMovementRequest): Promise<StockOperation>;
54
+ createReservation(request: CreateReservationRequest): Promise<Reservation>;
55
+ extendReservation(request: ExtendReservationRequest): Promise<Reservation>;
56
+ releaseReservation(request: ReleaseReservationRequest): Promise<Reservation>;
57
+ getReservation(reservationId: string): Promise<Reservation>;
58
+ listReservations(query?: ReservationListQuery): Promise<ReservationPage>;
59
+ iterateReservations(query?: ReservationListQuery): AsyncGenerator<Reservation, void, undefined>;
44
60
  }
45
61
  export declare function createInventoryClient(options: InventoryClientOptions): InventoryClient;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
- * Fassade ueber den Aufrufen der Lager-API: Schluessel einmal binden, dann
4
- * rufen. Wie [createInvoiceApi] bewusst keine Klasse; die Aufrufe sind freie
3
+ * Fassade ueber den Aufrufen der Lager-API (lesen, Webhooks, schreiben,
4
+ * reservieren): Schluessel einmal binden, dann rufen. Wie [createInvoiceApi] bewusst keine Klasse; die Aufrufe sind freie
5
5
  * Funktionen (endpunkte.ts) und bleiben einzeln importierbar.
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -9,6 +9,7 @@ exports.createInventoryClient = createInventoryClient;
9
9
  const transport_js_1 = require("../client/transport.js");
10
10
  const auth_js_1 = require("./auth.js");
11
11
  const endpunkte_js_1 = require("./endpunkte.js");
12
+ const schreiben_js_1 = require("./schreiben.js");
12
13
  function createInventoryClient(options) {
13
14
  const rufen = (0, transport_js_1.createTransport)({
14
15
  auth: (0, auth_js_1.inventoryKeyAuth)({ apiKey: options.apiKey }),
@@ -34,5 +35,20 @@ function createInventoryClient(options) {
34
35
  sendWebhookTest: (id, event) => (0, endpunkte_js_1.sendWebhookTest)(rufen, id, event),
35
36
  rotateWebhookSecret: (id) => (0, endpunkte_js_1.rotateWebhookSecret)(rufen, id),
36
37
  listWebhookDeliveries: (o) => (0, endpunkte_js_1.listWebhookDeliveries)(rufen, o),
38
+ createArticle: (r) => (0, schreiben_js_1.createArticle)(rufen, r),
39
+ updateArticle: (r) => (0, schreiben_js_1.updateArticle)(rufen, r),
40
+ deactivateArticle: (r) => (0, schreiben_js_1.deactivateArticle)(rufen, r),
41
+ receiveGoods: (r) => (0, schreiben_js_1.receiveGoods)(rufen, r),
42
+ previewGoodsReceipt: (r) => (0, schreiben_js_1.previewGoodsReceipt)(rufen, r),
43
+ transferStock: (r) => (0, schreiben_js_1.transferStock)(rufen, r),
44
+ recordStockLoss: (r) => (0, schreiben_js_1.recordStockLoss)(rufen, r),
45
+ changeStockCondition: (r) => (0, schreiben_js_1.changeStockCondition)(rufen, r),
46
+ reverseStockMovement: (r) => (0, schreiben_js_1.reverseStockMovement)(rufen, r),
47
+ createReservation: (r) => (0, schreiben_js_1.createReservation)(rufen, r),
48
+ extendReservation: (r) => (0, schreiben_js_1.extendReservation)(rufen, r),
49
+ releaseReservation: (r) => (0, schreiben_js_1.releaseReservation)(rufen, r),
50
+ getReservation: (id) => (0, schreiben_js_1.getReservation)(rufen, id),
51
+ listReservations: (q) => (0, schreiben_js_1.listReservations)(rufen, q),
52
+ iterateReservations: (q) => (0, schreiben_js_1.iterateReservations)(rufen, q),
37
53
  };
38
54
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Die Aufrufe der Lager-API: Artikel, Standorte, Bestand und Bewegungen
3
- * lesen, Konto-Webhooks verwalten (Backend Stufe 5a).
3
+ * lesen, Konto-Webhooks verwalten (Backend Stufe 5a). Die schreibenden
4
+ * Aufrufe (Stufe 5b) stehen in `schreiben.ts` und teilen die Helfer hier.
4
5
  *
5
6
  * Jede Funktion nimmt den Transport als ersten Parameter und ist einzeln
6
7
  * importierbar; die Fassade [createInventoryClient] bindet ihn nur einmal.
@@ -14,8 +15,26 @@
14
15
  * wiederholt werden. Bei `rate_limited` vorher `retryAfterSec` warten
15
16
  * ([inventoryRetryAfterSec]).
16
17
  */
17
- import type { InternerTransport } from '../client/aufrufe.js';
18
+ import type { ApiCall, InternerTransport } from '../client/aufrufe.js';
19
+ import { KasseneckValidationError } from '../client/errors.js';
18
20
  import type { Article, ArticleListQuery, ArticleLookup, ArticlePage, CreateInventoryWebhookOptions, InventoryWebhook, InventoryWebhookDelivery, InventoryWebhookList, InventoryWebhookPatch, InventoryWebhookTestResult, InventoryWebhookWithSecret, Location, StockLevel, StockListQuery, StockMovement, StockMovementPage, StockMovementQuery, StockPage, StockResult } from './typen.js';
21
+ type Params = Record<string, unknown>;
22
+ export declare function anfragefehler(name: ApiCall, grund: string): KasseneckValidationError;
23
+ /** Eine Kennung, die gesendet werden muss: Text mit mindestens einem Zeichen ausser Leerraum. */
24
+ export declare function kennung(name: ApiCall, feld: string, wert: unknown): string;
25
+ /**
26
+ * Die Abfrage einer Liste als Parameter. Unbekannte Felder gehen unveraendert
27
+ * hinaus (der Server weist sie mit `validation` ab), `undefined` faellt weg.
28
+ */
29
+ export declare function abfrage(name: ApiCall, query: object | undefined): Params;
30
+ /**
31
+ * Blaettert ueber alle Seiten, Eintrag fuer Eintrag. Derselbe Cursor zweimal
32
+ * waere eine Endlosschleife: dann endet die Schleife mit einem Antwortfehler.
33
+ */
34
+ export declare function seitenweise<T>(name: ApiCall, start: string | undefined, seite: (cursor: string | undefined) => Promise<{
35
+ eintraege: T[];
36
+ nextCursor: string | null;
37
+ }>): AsyncGenerator<T, void, undefined>;
19
38
  export declare function getArticle(transport: InternerTransport, articleId: string): Promise<Article>;
20
39
  export declare function listArticles(transport: InternerTransport, query?: ArticleListQuery): Promise<ArticlePage>;
21
40
  /** Alle Artikel der Abfrage, Seite fuer Seite ueber `nextCursor`. */
@@ -69,3 +88,4 @@ export declare function listWebhookDeliveries(transport: InternerTransport, opti
69
88
  webhookId?: string;
70
89
  limit?: number;
71
90
  }): Promise<InventoryWebhookDelivery[]>;
91
+ export {};
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Die Aufrufe der Lager-API: Artikel, Standorte, Bestand und Bewegungen
4
- * lesen, Konto-Webhooks verwalten (Backend Stufe 5a).
4
+ * lesen, Konto-Webhooks verwalten (Backend Stufe 5a). Die schreibenden
5
+ * Aufrufe (Stufe 5b) stehen in `schreiben.ts` und teilen die Helfer hier.
5
6
  *
6
7
  * Jede Funktion nimmt den Transport als ersten Parameter und ist einzeln
7
8
  * importierbar; die Fassade [createInventoryClient] bindet ihn nur einmal.
@@ -16,6 +17,10 @@
16
17
  * ([inventoryRetryAfterSec]).
17
18
  */
18
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.anfragefehler = anfragefehler;
21
+ exports.kennung = kennung;
22
+ exports.abfrage = abfrage;
23
+ exports.seitenweise = seitenweise;
19
24
  exports.getArticle = getArticle;
20
25
  exports.listArticles = listArticles;
21
26
  exports.iterateArticles = iterateArticles;
@@ -8,7 +8,8 @@
8
8
  * `KasseneckApiError.code`.
9
9
  */
10
10
  import type { KasseneckApiError } from '../client/errors.js';
11
- import { type InventoryErrorCode, type InventoryRequestErrorCode } from './vertrag.js';
11
+ import type { InventoryShortfall } from './typen.js';
12
+ import { type InventoryErrorCode, type InventoryRequestErrorCode, type InventoryWarningCode } from './vertrag.js';
12
13
  /** Ein Code, den ein Lager-Aufruf liefern kann. */
13
14
  export type InventoryApiErrorCode = InventoryErrorCode | InventoryRequestErrorCode;
14
15
  export declare function isInventoryErrorCode(value: unknown): value is InventoryApiErrorCode;
@@ -31,3 +32,12 @@ export declare function inventoryFieldErrors(error: unknown): InventoryFieldErro
31
32
  * Konto (etwa 20 Anfragen je Sekunde, kurze Spitzen bis 40).
32
33
  */
33
34
  export declare function inventoryRetryAfterSec(error: unknown): number | undefined;
35
+ /**
36
+ * Die Positionen, fuer die beim Reservieren der verfuegbare Bestand nicht
37
+ * reicht (`insufficient_available`, `data.details[]`): je Artikel und Standort
38
+ * angefragt und verfuegbar, in Tausendstel. Leer, wenn der Fehler ein anderer
39
+ * ist. Es fehlen nur diese Positionen; reserviert wurde nichts.
40
+ */
41
+ export declare function inventoryShortfalls(error: unknown): InventoryShortfall[];
42
+ /** `true` fuer einen Hinweis-Code einer Buchung (`warnings[].code`); Hinweise sind nie Fehler. */
43
+ export declare function isInventoryWarningCode(value: unknown): value is InventoryWarningCode;
@@ -14,7 +14,10 @@ exports.inventoryErrorCode = inventoryErrorCode;
14
14
  exports.isInventoryError = isInventoryError;
15
15
  exports.inventoryFieldErrors = inventoryFieldErrors;
16
16
  exports.inventoryRetryAfterSec = inventoryRetryAfterSec;
17
+ exports.inventoryShortfalls = inventoryShortfalls;
18
+ exports.isInventoryWarningCode = isInventoryWarningCode;
17
19
  const fehlercodes_js_1 = require("../client/fehlercodes.js");
20
+ const lesen_js_1 = require("./lesen.js");
18
21
  const vertrag_js_1 = require("./vertrag.js");
19
22
  const BEKANNT = new Set([...vertrag_js_1.INVENTORY_ERROR_CODES, ...vertrag_js_1.INVENTORY_REQUEST_ERROR_CODES]);
20
23
  function isInventoryErrorCode(value) {
@@ -45,3 +48,19 @@ function inventoryRetryAfterSec(error) {
45
48
  const wert = error.details['retryAfterSec'];
46
49
  return typeof wert === 'number' && Number.isFinite(wert) && wert >= 0 ? wert : undefined;
47
50
  }
51
+ /**
52
+ * Die Positionen, fuer die beim Reservieren der verfuegbare Bestand nicht
53
+ * reicht (`insufficient_available`, `data.details[]`): je Artikel und Standort
54
+ * angefragt und verfuegbar, in Tausendstel. Leer, wenn der Fehler ein anderer
55
+ * ist. Es fehlen nur diese Positionen; reserviert wurde nichts.
56
+ */
57
+ function inventoryShortfalls(error) {
58
+ if (inventoryErrorCode(error) !== 'insufficient_available')
59
+ return [];
60
+ return (0, lesen_js_1.fehlmengen)(error.details['details']);
61
+ }
62
+ const HINWEISE = new Set(vertrag_js_1.INVENTORY_WARNING_CODES);
63
+ /** `true` fuer einen Hinweis-Code einer Buchung (`warnings[].code`); Hinweise sind nie Fehler. */
64
+ function isInventoryWarningCode(value) {
65
+ return typeof value === 'string' && HINWEISE.has(value);
66
+ }