@kreiseck/kasseneck-api 1.4.0 → 1.5.1
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.
- package/CHANGELOG.md +166 -0
- package/README.md +255 -36
- package/dist/cjs/client/aufrufe.d.ts +58 -4
- package/dist/cjs/client/aufrufe.js +196 -1
- package/dist/cjs/client/errors.d.ts +32 -19
- package/dist/cjs/client/errors.js +28 -17
- package/dist/cjs/client/index.d.ts +2 -2
- package/dist/cjs/client/index.js +2 -1
- package/dist/cjs/client/transport.d.ts +27 -1
- package/dist/cjs/client/transport.js +25 -27
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/inventory/api.d.ts +19 -3
- package/dist/cjs/inventory/api.js +18 -2
- package/dist/cjs/inventory/endpunkte.d.ts +22 -2
- package/dist/cjs/inventory/endpunkte.js +6 -1
- package/dist/cjs/inventory/fehler.d.ts +11 -1
- package/dist/cjs/inventory/fehler.js +19 -0
- package/dist/cjs/inventory/index.d.ts +6 -4
- package/dist/cjs/inventory/index.js +32 -2
- package/dist/cjs/inventory/lesen.d.ts +8 -1
- package/dist/cjs/inventory/lesen.js +131 -1
- package/dist/cjs/inventory/schreiben.d.ts +86 -0
- package/dist/cjs/inventory/schreiben.js +320 -0
- package/dist/cjs/inventory/typen.d.ts +371 -11
- package/dist/cjs/inventory/vertrag.d.ts +76 -16
- package/dist/cjs/inventory/vertrag.js +138 -13
- package/dist/cjs/inventory/webhook.d.ts +3 -1
- package/dist/cjs/inventory/webhook.js +7 -1
- package/dist/cjs/invoice/endpunkte.d.ts +6 -0
- package/dist/cjs/invoice/endpunkte.js +13 -2
- package/dist/cjs/invoice/index.d.ts +2 -2
- package/dist/cjs/invoice/index.js +2 -1
- package/dist/cjs/invoice/typen.d.ts +46 -2
- package/dist/cjs/invoice/vertrag.d.ts +19 -5
- package/dist/cjs/invoice/vertrag.js +37 -5
- package/dist/cjs/pos/texte.d.ts +11 -6
- package/dist/cjs/pos/texte.js +11 -6
- package/dist/cjs/stored/vokabular.d.ts +1 -1
- package/dist/cjs/stored/vokabular.js +5 -3
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client/aufrufe.d.ts +58 -4
- package/dist/esm/client/aufrufe.js +194 -0
- package/dist/esm/client/errors.d.ts +32 -19
- package/dist/esm/client/errors.js +28 -17
- package/dist/esm/client/index.d.ts +2 -2
- package/dist/esm/client/index.js +1 -1
- package/dist/esm/client/transport.d.ts +27 -1
- package/dist/esm/client/transport.js +26 -28
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/inventory/api.d.ts +19 -3
- package/dist/esm/inventory/api.js +18 -2
- package/dist/esm/inventory/endpunkte.d.ts +22 -2
- package/dist/esm/inventory/endpunkte.js +6 -5
- package/dist/esm/inventory/fehler.d.ts +11 -1
- package/dist/esm/inventory/fehler.js +18 -1
- package/dist/esm/inventory/index.d.ts +6 -4
- package/dist/esm/inventory/index.js +5 -3
- package/dist/esm/inventory/lesen.d.ts +8 -1
- package/dist/esm/inventory/lesen.js +127 -1
- package/dist/esm/inventory/schreiben.d.ts +86 -0
- package/dist/esm/inventory/schreiben.js +303 -0
- package/dist/esm/inventory/typen.d.ts +371 -11
- package/dist/esm/inventory/vertrag.d.ts +76 -16
- package/dist/esm/inventory/vertrag.js +137 -12
- package/dist/esm/inventory/webhook.d.ts +3 -1
- package/dist/esm/inventory/webhook.js +8 -2
- package/dist/esm/invoice/endpunkte.d.ts +6 -0
- package/dist/esm/invoice/endpunkte.js +13 -2
- package/dist/esm/invoice/index.d.ts +2 -2
- package/dist/esm/invoice/index.js +1 -1
- package/dist/esm/invoice/typen.d.ts +46 -2
- package/dist/esm/invoice/vertrag.d.ts +19 -5
- package/dist/esm/invoice/vertrag.js +36 -4
- package/dist/esm/pos/texte.d.ts +11 -6
- package/dist/esm/pos/texte.js +11 -6
- package/dist/esm/stored/vokabular.d.ts +1 -1
- package/dist/esm/stored/vokabular.js +5 -3
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/fixtures/hobex-hps-codes.json +1 -1
- package/fixtures/invoice-api-examples/credit-error-reservation.json +26 -0
- package/fixtures/invoice-api-examples/issue-error-reservation-article.json +25 -0
- package/fixtures/invoice-api-examples/issue-error-vat-id-risk.json +26 -0
- package/fixtures/invoice-api-examples/issue-reservation.json +24 -0
- package/fixtures/invoice-api-examples/issue-vat-id-risk.json +28 -0
- package/fixtures/invoice-api.schema.json +15 -2
- package/fixtures/invoice-texts.json +1 -1
- package/fixtures/pos-texts.json +1 -1
- package/fixtures/surface.json +206 -9
- package/fixtures/v3/antworten/belege.json +2 -2
- package/fixtures/v3/antworten/belegmail.json +2 -2
- package/fixtures/v3/antworten/kasse-belege.json +2 -2
- package/fixtures/v3/antworten/kasse.json +3 -3
- package/fixtures/v3/antworten/lager.json +2491 -130
- package/fixtures/v3/antworten/rechnungen.json +267 -44
- package/fixtures/v3/antworten/storno.json +2 -2
- package/fixtures/v3/stored/belege.json +2 -2
- package/fixtures/v3/stored/kasse.json +3 -3
- package/fixtures/v3/stored/rechnungen.json +174 -18
- package/fixtures/v3/v3-vokabular.json +593 -23
- package/fixtures/v3/zahlbetrag-faelle.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,172 @@ 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.1
|
|
8
|
+
|
|
9
|
+
Every call with an effect reports `outcome: 'unknown'` when it fails after
|
|
10
|
+
sending began: network error, timeout, HTTP 5xx, HTTP 200 with the `/v3`
|
|
11
|
+
marker but an empty, non-JSON or status-less body, and HTML with the marker.
|
|
12
|
+
Reason: until 1.5.0 only the six signing and money calls did; a goods receipt,
|
|
13
|
+
a reservation or `issueInvoice` came back as `'rejected'` ("did not happen")
|
|
14
|
+
although the server may have booked it. Whoever believed that and resent with
|
|
15
|
+
a **new** `idempotencyKey` booked twice.
|
|
16
|
+
|
|
17
|
+
A change to the safe side, no change on the wire; requests send the same body
|
|
18
|
+
as in 1.5.0 (only the `Kasseneck-Client` header carries the new version).
|
|
19
|
+
|
|
20
|
+
- **Which calls**: `UNKNOWN_OUTCOME_CALLS` (exported, sorted), also in
|
|
21
|
+
`fixtures/surface.json` as `unknownOutcomeCalls` for the Dart twin. New in it:
|
|
22
|
+
inventory writes (`createArticle`, `updateArticle`, `deactivateArticle`,
|
|
23
|
+
`receiveGoods`, `transferStock`, `recordStockLoss`, `changeStockCondition`,
|
|
24
|
+
`reverseStockMovement`), reservations (`createReservation`,
|
|
25
|
+
`extendReservation`, `releaseReservation`), inventory and partner webhooks
|
|
26
|
+
(create, update, delete, rotate the secret, test delivery), the invoice API
|
|
27
|
+
(`issueInvoice`, `cancelInvoice`, `createCreditNote`, `recordInvoicePayment`,
|
|
28
|
+
`createCustomer`, `updateCustomer`), invoice items and SEPA mandates
|
|
29
|
+
(`createInvoiceItem`, `updateInvoiceItem`, `withdrawInvoiceItem`,
|
|
30
|
+
`setCustomerMandate`, `revokeCustomerMandate`), the partner calls with an
|
|
31
|
+
effect (`createPartnerCustomer`, `sendPartnerCustomerFonLink`,
|
|
32
|
+
`requestCustomerSignature`, `createCustomerCashregister`,
|
|
33
|
+
`activateCashregister`, `reportCustomerContract`), and on the register
|
|
34
|
+
`pairRegisterDevice`, `unpairRegisterDevice`, `setMyKasseSettings`,
|
|
35
|
+
`setMyKasseLogo`, `setMyRegisterDeviceSettings`,
|
|
36
|
+
`setMyCashregisterStockLocation`, `createPrintJob`, `sendReceiptEmail`.
|
|
37
|
+
- **Which stay `'rejected'`**: reading calls, dry runs (`previewGoodsReceipt`,
|
|
38
|
+
`previewInvoice`, `issueInvoice` with `dryRun: true`), the register sign-in
|
|
39
|
+
sessions (`registerUserLogin`, `registerPinLogin`, `renewRegisterSession`,
|
|
40
|
+
`endRegisterSession`) and `createPaymentLinkStripe`: a repeat books nothing.
|
|
41
|
+
A timeout already during sign-in stays `'rejected'` as before: nothing was
|
|
42
|
+
sent.
|
|
43
|
+
- **What to do**: with an `idempotencyKey`, send the same request with the
|
|
44
|
+
**same** key; it takes effect once and returns the stored answer.
|
|
45
|
+
`createInvoiceItem` is recognised by `source` + `reference`: the same values
|
|
46
|
+
again are safe. Without a key (receipts, cancellations, money calls,
|
|
47
|
+
settings, webhooks, changing or withdrawing invoice items, mandates), read
|
|
48
|
+
the state first. The rule for receipts and payments is unchanged: look it up,
|
|
49
|
+
never resend.
|
|
50
|
+
- **Per call**: the transport takes a fifth argument `{ hasEffect }`
|
|
51
|
+
(`TransportCallOptions`). `true` marks a call with an effect that this
|
|
52
|
+
package does not know. `false` only counts together with
|
|
53
|
+
`params.dryRun === true` (a dry run under the name of the real call; the
|
|
54
|
+
package's own previews use it); without `dryRun: true` a call from the list
|
|
55
|
+
stays `'unknown'`, so no option can turn a real booking into `'rejected'`.
|
|
56
|
+
Without the option the list decides, and an unknown name counts as having no
|
|
57
|
+
effect.
|
|
58
|
+
- **Guard**: every call the package knows (`ALL_CALLS`, `PUBLIC_CALLS`,
|
|
59
|
+
`POS_CALLS`) is classified as with or without effect; a new call without a
|
|
60
|
+
classification fails the tests.
|
|
61
|
+
- `CALLS_WITH_EFFECT` of the register texts and `fixtures/pos-texts.json` are
|
|
62
|
+
unchanged; `messageOutcome` follows the transport first, so the register
|
|
63
|
+
shows the careful sentence for every call above.
|
|
64
|
+
|
|
65
|
+
## 1.5.0
|
|
66
|
+
|
|
67
|
+
Inventory API, write side and reservations: `./inventory` gains the 13
|
|
68
|
+
endpoints of backend stage 5b, and the invoice API learns `reservationId`.
|
|
69
|
+
Reason: a shop that only reads stock still oversells. It has to hold the goods
|
|
70
|
+
at checkout, book what arrives and leaves, keep its articles in sync, and turn
|
|
71
|
+
the reservation into a sale when the order is invoiced, all with retries that
|
|
72
|
+
never book twice. The invoice API also catches up with the VAT ID check of
|
|
73
|
+
the backend (stage 4, in the 0.x line since 0.33.0).
|
|
74
|
+
|
|
75
|
+
Additive, no breaking change; existing calls send the same bytes as in 1.4.0.
|
|
76
|
+
New response fields are added to the models.
|
|
77
|
+
|
|
78
|
+
- **Articles**: `createArticle`, `updateArticle`, `deactivateArticle`, each
|
|
79
|
+
answering with the article as `getArticle` returns it. Fields: `name`,
|
|
80
|
+
`description`, `unitPriceCents`, `vatRate`, `unit`, `number`, `ean` (with
|
|
81
|
+
`ean` a foreign article with that code, otherwise the server assigns the next
|
|
82
|
+
own code), `groupId`, `revenueGroupId`, `stockTracked`, `stockLocationIds`,
|
|
83
|
+
`minStockByLocation`, `minStock` (legacy, triggers nothing), `stockKind`,
|
|
84
|
+
`purchasePriceMicros` (only with the permission `costs`), `externalIds` and
|
|
85
|
+
`metadata`. In an update `null` clears an optional field; `externalIds` and
|
|
86
|
+
`metadata` are replaced, `minStockByLocation` is merged per location.
|
|
87
|
+
- **Booking**: `receiveGoods`, `transferStock`, `recordStockLoss`,
|
|
88
|
+
`changeStockCondition`, `reverseStockMovement`, each answering with a
|
|
89
|
+
`StockOperation` (`operationId`, `movementIds`, `lotIds`, `warnings[]`).
|
|
90
|
+
`previewGoodsReceipt` is `receiveGoods` with `dryRun: true`: it books nothing,
|
|
91
|
+
needs no key (a `null` key counts as none and is not sent) and returns the
|
|
92
|
+
values only with the permission `costs`. `receiveGoods` with `dryRun: true`
|
|
93
|
+
is refused before sending: a preview answers without an operation, so it
|
|
94
|
+
goes through `previewGoodsReceipt`.
|
|
95
|
+
Transfers, losses and condition changes never overdraw (`exceeds_stock`).
|
|
96
|
+
Catalogs `STOCK_LOSS_REASONS`, `WITHDRAWAL_TYPES`, `LANDED_COST_TYPES`,
|
|
97
|
+
`LANDED_COST_ALLOCATIONS`; warnings `INVENTORY_WARNING_CODES`
|
|
98
|
+
(`insufficient_stock`, `below_minimum`, `return_exceeds_sale`,
|
|
99
|
+
`reservation_exceeded`) with `isInventoryWarningCode`. A warning is not an
|
|
100
|
+
error: the booking took effect.
|
|
101
|
+
- **Reservations**: `createReservation` (all or nothing, measured against
|
|
102
|
+
`available`; `insufficient_available` lists the missing positions in
|
|
103
|
+
`inventoryShortfalls(error)`), `extendReservation`, `releaseReservation` (all,
|
|
104
|
+
or per position), `getReservation`, `listReservations` and
|
|
105
|
+
`iterateReservations`. `Reservation` carries `status` (`active`, `redeemed`,
|
|
106
|
+
`released`, `expired`; `RESERVATION_STATUSES`), `reference`, `items[]` with
|
|
107
|
+
`quantity`, `redeemed`, `released`, and `expiresAt`. `expiresInMinutes` is
|
|
108
|
+
5 to 43,200.
|
|
109
|
+
- **Idempotency**: every write takes `idempotencyKey` (1 to 120 characters,
|
|
110
|
+
required in the type). The client refuses to send a write without a valid
|
|
111
|
+
key, because a write without one cannot be retried safely; it never trims or
|
|
112
|
+
shortens the key. Retry after a timeout with the **same** key: the call takes
|
|
113
|
+
effect once and returns the stored answer; the same key with other content
|
|
114
|
+
gives `idempotency_conflict`.
|
|
115
|
+
- **Checked before sending**, nothing else: the key, required ids, integer
|
|
116
|
+
quantities, amounts and prices (`1.5` is never 1.5 pieces), the range of
|
|
117
|
+
`expiresInMinutes`, an update without a field, an empty release list,
|
|
118
|
+
`receiveGoods` with `dryRun: true` (use `previewGoodsReceipt`). All of
|
|
119
|
+
these throw `KasseneckValidationError` with `scope: 'request'`.
|
|
120
|
+
- **Models**: `Article` gains `description`, `stockKind` and
|
|
121
|
+
`minStockByLocation` (missing on an older server: `null`, `null`, `{}`).
|
|
122
|
+
`StockMovement` gains `reservedDelta` (0 for every movement that is not a
|
|
123
|
+
reservation) and `stockAfter.reserved` (only on reservation movements,
|
|
124
|
+
otherwise `null`); the movement type `reservation` has `quantityDelta: 0`.
|
|
125
|
+
The minimum stock (`below_minimum`, `listStock({ belowMinimum })`,
|
|
126
|
+
`stock.below_minimum`) is now measured against `available = onHand −
|
|
127
|
+
reserved`, so a reservation alone can trigger it. A fractional value in any
|
|
128
|
+
of these fields throws `KasseneckValidationError` with `scope: 'response'`.
|
|
129
|
+
- **Events**: `reservation.expired`, `reservation.released` and
|
|
130
|
+
`reservation.redeemed` in `INVENTORY_WEBHOOK_EVENTS` and
|
|
131
|
+
`parseInventoryWebhookEvent`, carrying the reservation with its status
|
|
132
|
+
afterwards. `released` and `redeemed` fire on every effective release or
|
|
133
|
+
redemption, also a partial one (status stays `active`).
|
|
134
|
+
- **Errors**: `INVENTORY_ERROR_CODES` keeps its 12 codes in front and appends
|
|
135
|
+
the codes of the write side in contract order (`idempotency_key_required`,
|
|
136
|
+
`idempotency_conflict`, `exceeds_stock`, `code_taken`,
|
|
137
|
+
`external_id_taken`, `stock_kind_locked`, `article_inactive`,
|
|
138
|
+
`insufficient_available`, `reservation_not_found`,
|
|
139
|
+
`reservation_not_active` …).
|
|
140
|
+
- **Invoice API, reservations**: `issueInvoice` items take `reservationId`
|
|
141
|
+
(`IssueInvoiceItemInput`, needs `articleId`; not on credit notes, where the
|
|
142
|
+
server rejects it). New codes at the end of `INVOICE_ERROR_CODES`:
|
|
143
|
+
`reservation_not_found`, `reservation_mismatch`, `reservation_not_active`;
|
|
144
|
+
new notice `reservation_expired` (with `reservationId`): the invoice is
|
|
145
|
+
issued anyway and sells without the reservation. Three new examples in
|
|
146
|
+
`fixtures/invoice-api-examples/`.
|
|
147
|
+
- **Invoice API, VAT ID check**: an invoice without VAT that relies on the
|
|
148
|
+
customer's VAT ID (intra-Community supply, reverse charge) is only issued
|
|
149
|
+
with a result of the VAT ID check (FinanzOnline, otherwise VIES) on the day
|
|
150
|
+
of issue. Reason: the caller has to decide by the code whether to retry
|
|
151
|
+
later or not to issue at all.
|
|
152
|
+
- Codes `vat_id_invalid` and `vat_id_check_pending` in
|
|
153
|
+
`INVOICE_ERROR_CODES`, after `amount_too_large` and before the
|
|
154
|
+
reservation codes (the backend order). `vat_id_check_pending` carries
|
|
155
|
+
`details.retryAfter` (seconds until the next sensible attempt, with the
|
|
156
|
+
same `idempotencyKey`).
|
|
157
|
+
- `IssueInvoiceRequest.acceptVatIdRisk` (optional boolean): issues despite
|
|
158
|
+
a pending check, the issuer bears the risk. An invalid VAT ID blocks even
|
|
159
|
+
with it. It does not count towards the idempotency of the request.
|
|
160
|
+
- `Invoice.vatIdProof` (`InvoiceVatIdProof`: `checkedOn`, `source`
|
|
161
|
+
`finanzonline` | `vies`, `level` 1 | 2, `code`) and `Invoice.vatIdRisk`
|
|
162
|
+
(`InvoiceVatIdRisk`: `acceptedOn`), otherwise `null`.
|
|
163
|
+
- `fixtures/invoice-api.schema.json` regenerated (`acceptVatIdRisk`, the
|
|
164
|
+
two codes), two new examples `issue-vat-id-risk.json` and
|
|
165
|
+
`issue-error-vat-id-risk.json`.
|
|
166
|
+
- **Contract**: `fixtures/v3/` pulled again from the backend (the 13 new
|
|
167
|
+
endpoints, catalogs, `warningCodes`, events and the 5b cases in
|
|
168
|
+
`antworten/lager.json`, the VAT ID cases in `antworten/rechnungen.json`);
|
|
169
|
+
`PUBLIC_CALLS` and `ALL_CALLS` gain the 13 names (87 public calls);
|
|
170
|
+
`surface.json` gains the new lists under `inventory` and the two invoice
|
|
171
|
+
codes.
|
|
172
|
+
|
|
7
173
|
## 1.4.0
|
|
8
174
|
|
|
9
175
|
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
|
|
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
|
|
|
@@ -433,30 +433,42 @@ receipt email, and values outside a settings field's value list throw a
|
|
|
433
433
|
`KasseneckValidationError` before anything is sent, instead of being dropped
|
|
434
434
|
silently. For a layout option of 0.x the message names its English successor.
|
|
435
435
|
|
|
436
|
-
### `outcome: 'unknown'`: never
|
|
436
|
+
### `outcome: 'unknown'`: never resend blindly
|
|
437
437
|
|
|
438
438
|
`KasseneckApiError`, `KasseneckHttpError` and `KasseneckNetworkError` carry
|
|
439
439
|
`outcome`. `'rejected'` means the server turned the request down; whether
|
|
440
440
|
anything else is safe to do depends on the code, and on the money calls it
|
|
441
441
|
is only set for the codes listed below. `'unknown'` means the operation **may have been carried out**, for
|
|
442
|
-
`createReceipt` a signed receipt in the chain. Then never send it again:
|
|
443
|
-
the result back (`getReceipt`, `listMyReceipts`, the original of a
|
|
444
|
-
cancellation) and continue from there. `
|
|
445
|
-
|
|
442
|
+
`createReceipt` a signed receipt in the chain. Then never send it again blindly:
|
|
443
|
+
read the result back (`getReceipt`, `listMyReceipts`, the original of a
|
|
444
|
+
cancellation) and continue from there. A call with an `idempotencyKey` (or,
|
|
445
|
+
for invoice items, the same `source` and `reference`) is the exception: send
|
|
446
|
+
it again unchanged (see below).
|
|
447
|
+
`isOutcomeUnknown(error)` covers all three classes. The outcome is unknown for:
|
|
446
448
|
|
|
447
449
|
- `dialect_mismatch`, `receipt_outcome_unknown`, `cancellation_outcome_unknown`;
|
|
448
450
|
- `response_translation_failed`, unless `details.handled === false`;
|
|
449
451
|
- `response_unreadable`: a signing or money-moving call reported success,
|
|
450
452
|
but the response lacks what the call promises (no receipt, no reference, no
|
|
451
453
|
remaining quantities, no Hobex receipt, no captured payment intent);
|
|
452
|
-
- on
|
|
453
|
-
`
|
|
454
|
-
|
|
455
|
-
`
|
|
456
|
-
|
|
454
|
+
- on every call with an effect (`UNKNOWN_OUTCOME_CALLS`, also in
|
|
455
|
+
`fixtures/surface.json` as `unknownOutcomeCalls`): the signing calls
|
|
456
|
+
`createReceipt`, `cancelReceipt` and `financeWebService`, the money calls
|
|
457
|
+
`hobexPay` (`hobexPayApi`, charges a card), `hobexRefund`
|
|
458
|
+
(`hobexRefundApi`) and `stripeCaptureIntent`, and since 1.5.1 every other
|
|
459
|
+
call that books, issues, creates, changes, deletes or sends something:
|
|
460
|
+
inventory writes and reservations, webhooks, invoices, credit notes,
|
|
461
|
+
recorded payments, customers, invoice items and SEPA mandates, partner
|
|
462
|
+
businesses, register settings, pairing, print jobs and receipt emails.
|
|
463
|
+
There it is a network error or timeout after sending began, HTTP 5xx, and
|
|
464
|
+
HTTP 200 with the `Kasseneck-Api-Version: v3` marker but an empty,
|
|
457
465
|
non-JSON (also `text/html`) or status-less body (`KasseneckHttpError`,
|
|
458
466
|
`reason` `empty-body`, `not-json` or `missing-status`). HTML without the
|
|
459
|
-
marker stays `route_missing` with `'rejected'`: no function saw the call
|
|
467
|
+
marker stays `route_missing` with `'rejected'`: no function saw the call.
|
|
468
|
+
Reading calls and dry runs (`previewGoodsReceipt`, `previewInvoice`) stay
|
|
469
|
+
`'rejected'`, and so do the register sign-in sessions and
|
|
470
|
+
`createPaymentLinkStripe`: a repeat books nothing (a second short-lived
|
|
471
|
+
session, a second link nobody else has seen);
|
|
460
472
|
- on the money calls: every error envelope **without a code**, and every
|
|
461
473
|
code that is not one of the rejection codes below. The Hobex and Stripe
|
|
462
474
|
handlers also answer with a plain error message after the provider was
|
|
@@ -490,17 +502,33 @@ failed `hobexRefund` throws a `KasseneckApiError`; it never returns `false`,
|
|
|
490
502
|
because a `false` on an unclear outcome invites a second refund. Even a
|
|
491
503
|
`'rejected'` is no invitation to resend blindly: fix the cause first.
|
|
492
504
|
|
|
493
|
-
**Never retry
|
|
494
|
-
package.** A `fetch` passed in `options.fetch` (or a proxy or service
|
|
495
|
-
front of it) must not resend a request by itself after a network
|
|
496
|
-
timeout or a 5xx: a silent second send is a second signed receipt, a
|
|
497
|
-
charge or a second
|
|
498
|
-
instead (`getReceipt`, `listMyReceipts`, the Hobex
|
|
499
|
-
`transactionId`, the Stripe session).
|
|
500
|
-
|
|
501
|
-
`
|
|
502
|
-
`
|
|
503
|
-
`
|
|
505
|
+
**Never retry a call with an effect blindly, and never put a retrying layer
|
|
506
|
+
under the package.** A `fetch` passed in `options.fetch` (or a proxy or service
|
|
507
|
+
worker in front of it) must not resend a request by itself after a network
|
|
508
|
+
error, a timeout or a 5xx: a silent second send is a second signed receipt, a
|
|
509
|
+
second charge, a second refund or a second booking, and the package cannot
|
|
510
|
+
see it. Look the result up instead (`getReceipt`, `listMyReceipts`, the Hobex
|
|
511
|
+
transaction by its `transactionId`, the Stripe session).
|
|
512
|
+
|
|
513
|
+
**With an `idempotencyKey`** (inventory writes and reservations, `issueInvoice`,
|
|
514
|
+
`cancelInvoice`, `createCreditNote`, `recordInvoicePayment`, `createCustomer`
|
|
515
|
+
and `createPartnerCustomer` with a key) the safe retry after `'unknown'` is the
|
|
516
|
+
same request with the **same** key: it takes effect exactly once and returns
|
|
517
|
+
the stored answer (invoices and partner customers mark it with
|
|
518
|
+
`replayed: true`; inventory writes return the stored answer unchanged). Never a
|
|
519
|
+
new key: that books a second time. `createInvoiceItem` has no key; the server recognises a repeat by
|
|
520
|
+
`source` + `reference`, so the same request with the same values is safe
|
|
521
|
+
(`replayed: true`; other data gives `item_reference_conflict`). Without a key
|
|
522
|
+
(receipts, cancellations, money calls, settings, webhooks, `updateCustomer`,
|
|
523
|
+
`createCustomer` and `createPartnerCustomer` without one, `updateInvoiceItem`,
|
|
524
|
+
`withdrawInvoiceItem`, `setCustomerMandate`, `revokeCustomerMandate`) read the
|
|
525
|
+
state first (for invoice items `getInvoiceItem`) and only then decide.
|
|
526
|
+
|
|
527
|
+
A call through the open transport that this package does not know counts as
|
|
528
|
+
having no effect; say so with `transport(name, params, undefined, undefined,
|
|
529
|
+
{ hasEffect: true })`. `hasEffect: false` only counts together with
|
|
530
|
+
`params.dryRun === true` (a dry run under the name of the real call); without
|
|
531
|
+
it a call with an effect stays `'unknown'`.
|
|
504
532
|
|
|
505
533
|
```ts
|
|
506
534
|
import { isOutcomeUnknown, paymentsExpectedCents } from '@kreiseck/kasseneck-api';
|
|
@@ -944,7 +972,7 @@ process:
|
|
|
944
972
|
`hobexPay`, `hobexRefund` and `stripeCaptureIntent` move money. They are never
|
|
945
973
|
retried, neither by the app nor by a retrying `fetch` under the package: on
|
|
946
974
|
`isOutcomeUnknown(error)` look the payment up (see
|
|
947
|
-
[`outcome: 'unknown'`](#outcome-unknown-never-
|
|
975
|
+
[`outcome: 'unknown'`](#outcome-unknown-never-resend-blindly)).
|
|
948
976
|
- **Hobex HPS via Kasseneck Connect**, described below.
|
|
949
977
|
|
|
950
978
|
### Hobex HPS via Kasseneck Connect
|
|
@@ -1351,9 +1379,35 @@ was classified.
|
|
|
1351
1379
|
`previewInvoice` and `recordInvoicePayment`. An intra-Community supply carries
|
|
1352
1380
|
`recapitulative_statement_due` (recapitulative statement).
|
|
1353
1381
|
|
|
1382
|
+
**VAT ID check.** An invoice without VAT that relies on the customer's VAT ID
|
|
1383
|
+
(intra-Community supply, reverse charge) is only issued with a result of the
|
|
1384
|
+
VAT ID check (FinanzOnline, otherwise VIES) on the day of issue.
|
|
1385
|
+
`vat_id_invalid` blocks the invoice. `vat_id_check_pending` means there is no
|
|
1386
|
+
result yet: retry after `retryAfter` seconds with the same `idempotencyKey`,
|
|
1387
|
+
or send `acceptVatIdRisk: true` to issue anyway, at the issuer's risk. The
|
|
1388
|
+
invoice then carries `vatIdRisk` (`acceptedOn`), otherwise the frozen proof
|
|
1389
|
+
`vatIdProof` (`checkedOn`, `source`, `level`, `code`); both are `null` where
|
|
1390
|
+
nothing was checked.
|
|
1391
|
+
|
|
1392
|
+
```ts
|
|
1393
|
+
try {
|
|
1394
|
+
await invoices.issueInvoice(request);
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
if (!isInvoiceError(error, 'vat_id_check_pending')) throw error;
|
|
1397
|
+
const retryAfter = error.details['retryAfter']; // seconds
|
|
1398
|
+
// later: same request and key, or with acceptVatIdRisk: true
|
|
1399
|
+
}
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1354
1402
|
**After a timeout, retry with the same `idempotencyKey`**, never with a new one:
|
|
1355
1403
|
you then get the invoice that was already issued (`replayed: true`). The same
|
|
1356
|
-
key with different data gives `idempotency_conflict`.
|
|
1404
|
+
key with different data gives `idempotency_conflict`. Since 1.5.1 every invoice
|
|
1405
|
+
call with an effect (`issueInvoice`, `cancelInvoice`, `createCreditNote`,
|
|
1406
|
+
`recordInvoicePayment`, `createCustomer`, `updateCustomer`) reports
|
|
1407
|
+
`outcome: 'unknown'` after a timeout, a network error, HTTP 5xx or an
|
|
1408
|
+
unreadable answer (before: `'rejected'`); `previewInvoice` and the reading
|
|
1409
|
+
calls stay `'rejected'`. See
|
|
1410
|
+
[`outcome: 'unknown'`](#outcome-unknown-never-resend-blindly).
|
|
1357
1411
|
|
|
1358
1412
|
Validation errors arrive as `validation` with field paths
|
|
1359
1413
|
(`invoiceFieldErrors(error)` → `[{ field: 'items[0].vatRate', message }]`).
|
|
@@ -1425,10 +1479,16 @@ the old formula: `fixtures/invoice-totals.json`.
|
|
|
1425
1479
|
For online shops and other systems that show or mirror the stock of a
|
|
1426
1480
|
Kasseneck account: read articles, locations, stock per location and the stock
|
|
1427
1481
|
ledger, and get every stock change pushed by webhook within seconds, also the
|
|
1428
|
-
ones made at the register in the shop.
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1482
|
+
ones made at the register in the shop. Since 1.5.0 also the write side:
|
|
1483
|
+
create and update articles, book goods receipts, transfers, losses and
|
|
1484
|
+
condition changes, and reserve stock at checkout (see
|
|
1485
|
+
[Shop: reserve and invoice](#shop-reserve-and-invoice)). Uses the `api_key` of
|
|
1486
|
+
the account and belongs on a **server**. Reading needs the module `lager`,
|
|
1487
|
+
writing additionally the account switch *Lager-API schreiben*, which Kasseneck
|
|
1488
|
+
turns on (always on in the test environment, `kr_test_…`; otherwise
|
|
1489
|
+
`inventory_api_not_enabled`). Purchase prices and stock values appear only when
|
|
1490
|
+
the account has the permission `costs` (otherwise the fields are absent, not
|
|
1491
|
+
`null`).
|
|
1432
1492
|
|
|
1433
1493
|
**Integers with a fixed scale:** quantities in thousandths of the base unit
|
|
1434
1494
|
(`1000` = 1 piece, `250` = 0.250 kg), money in cents, purchase prices in
|
|
@@ -1500,16 +1560,22 @@ app.post('/kasseneck-webhook', express.raw({ type: '*/*' }), async (req, res) =>
|
|
|
1500
1560
|
- **Events.** `stock.changed` carries the current state of one article at one
|
|
1501
1561
|
location (`onHand`, `reserved`, `available`, `defective`, `sequence`,
|
|
1502
1562
|
`updatedAt`) plus `cause` (`sale`, `invoice`, `goods_receipt`, `transfer`,
|
|
1503
|
-
`takeover` …) and `movementId`; changes within 10 seconds are
|
|
1504
|
-
one delivery. `stock.below_minimum` fires once when
|
|
1505
|
-
|
|
1506
|
-
|
|
1563
|
+
`takeover`, `reservation` …) and `movementId`; changes within 10 seconds are
|
|
1564
|
+
combined into one delivery. `stock.below_minimum` fires once when
|
|
1565
|
+
`available` (`onHand − reserved`, so a reservation alone can trigger it)
|
|
1566
|
+
drops below the minimum stock set for the location (`minStockByLocation` of
|
|
1567
|
+
the article); `minStock` in the payload is that threshold. The article's own
|
|
1568
|
+
`minStock` is a legacy field and never triggers it, and
|
|
1507
1569
|
`listStock({ belowMinimum: true })` follows the same rule. In movements,
|
|
1508
1570
|
`goods_receipt` is a goods receipt; `receipt` only ever means a sales receipt
|
|
1509
1571
|
(`source.type`).
|
|
1510
1572
|
`article.created`, `article.updated` and `article.deactivated` carry the
|
|
1511
|
-
article as `getArticle` returns it, without purchase prices.
|
|
1512
|
-
`
|
|
1573
|
+
article as `getArticle` returns it, without purchase prices.
|
|
1574
|
+
`reservation.expired`, `reservation.released` and `reservation.redeemed`
|
|
1575
|
+
carry the reservation as `getReservation` returns it, with its status after
|
|
1576
|
+
the change; `released` and `redeemed` also fire for a partial release or
|
|
1577
|
+
redemption (status still `active`). Deduplicate on `event.id`; deliveries
|
|
1578
|
+
are retried after 1 min, 5 min, 30 min, 2 h and 12 h.
|
|
1513
1579
|
- **Safety net without webhooks.** `listStock({ changedSince })` and
|
|
1514
1580
|
`listArticles({ updatedSince })` are sorted by `updatedAt` ascending and
|
|
1515
1581
|
include the boundary, so remembering the last `updatedAt` and asking again
|
|
@@ -1528,6 +1594,159 @@ app.post('/kasseneck-webhook', express.raw({ type: '*/*' }), async (req, res) =>
|
|
|
1528
1594
|
`invalid_webhook_url`, `event_not_subscribed` and `webhook_inactive` are
|
|
1529
1595
|
decided on the code with `isInventoryError(error, code)`.
|
|
1530
1596
|
|
|
1597
|
+
### Writing: articles and bookings
|
|
1598
|
+
|
|
1599
|
+
Every write takes an `idempotencyKey` (1 to 120 characters). The same request
|
|
1600
|
+
with the same key takes effect exactly once; repeating it returns the stored
|
|
1601
|
+
answer of the first call, the same key with different content gives
|
|
1602
|
+
`idempotency_conflict`. After a timeout or a network error, repeat with the
|
|
1603
|
+
**same** key, never with a new one. Since 1.5.1 every write, reservation and
|
|
1604
|
+
webhook call reports `outcome: 'unknown'` in that case (also after HTTP 5xx or
|
|
1605
|
+
an unreadable answer; before: `'rejected'`), so `isOutcomeUnknown(error)` is
|
|
1606
|
+
the signal to resend with the same key; `previewGoodsReceipt` and the reading
|
|
1607
|
+
calls stay `'rejected'`. The client refuses to send a write without
|
|
1608
|
+
a valid key, and checks before sending only what is certainly wrong without the
|
|
1609
|
+
network (required ids, integer quantities, amounts and prices, the range of
|
|
1610
|
+
`expiresInMinutes`); everything else the server decides and reports with its
|
|
1611
|
+
code.
|
|
1612
|
+
|
|
1613
|
+
```ts
|
|
1614
|
+
import { createInventoryClient, isInventoryError, inventoryFieldErrors } from '@kreiseck/kasseneck-api/inventory';
|
|
1615
|
+
|
|
1616
|
+
const inventory = createInventoryClient({ apiKey: process.env.KASSENECK_API_KEY! });
|
|
1617
|
+
|
|
1618
|
+
// A foreign article with its EAN; without ean the server assigns an own code.
|
|
1619
|
+
const roll = await inventory.createArticle({
|
|
1620
|
+
idempotencyKey: 'shop-article-1001',
|
|
1621
|
+
name: 'Kaisersemmel',
|
|
1622
|
+
ean: '9001234567896',
|
|
1623
|
+
unitPriceCents: 65,
|
|
1624
|
+
vatRate: 10,
|
|
1625
|
+
stockTracked: true,
|
|
1626
|
+
minStockByLocation: { haupt: 20000 }, // thousandths: warn below 20 pieces
|
|
1627
|
+
externalIds: { shop: '1001' }, // lookupArticleByCode({ externalSystem: 'shop', externalId: '1001' })
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
// Goods receipt: 60 pieces at 0.38 EUR each, plus freight spread by value.
|
|
1631
|
+
const receipt = await inventory.receiveGoods({
|
|
1632
|
+
idempotencyKey: 'shop-goods-receipt-118',
|
|
1633
|
+
items: [{ articleId: roll.id, quantity: 60000, unitPriceMicros: 380000 }],
|
|
1634
|
+
landedCosts: [{ type: 'freight', amountCents: 450 }],
|
|
1635
|
+
reference: 'LS-2026-118',
|
|
1636
|
+
});
|
|
1637
|
+
// receipt: { operationId, movementIds, lotIds, warnings: [] }
|
|
1638
|
+
|
|
1639
|
+
try {
|
|
1640
|
+
await inventory.recordStockLoss({ idempotencyKey: 'shop-loss-7', reason: 'breakage', items: [{ articleId: roll.id, quantity: 2000 }] });
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
if (isInventoryError(error, 'exceeds_stock')) { /* losses, transfers and condition changes never overdraw */ }
|
|
1643
|
+
else if (isInventoryError(error, 'validation')) console.log(inventoryFieldErrors(error));
|
|
1644
|
+
else throw error;
|
|
1645
|
+
}
|
|
1646
|
+
```
|
|
1647
|
+
|
|
1648
|
+
- **Articles.** `updateArticle({ idempotencyKey, articleId, …fields })` changes
|
|
1649
|
+
only the fields it names; `null` clears an optional one. `externalIds` and
|
|
1650
|
+
`metadata` are replaced as a whole, `minStockByLocation` is merged per
|
|
1651
|
+
location (`{ haupt: null }` removes just that one). `stockKind` is fixed after
|
|
1652
|
+
the first movement (`stock_kind_locked`). `deactivateArticle` frees the code
|
|
1653
|
+
and the external ids. `code_taken` and `external_id_taken` carry `field` and
|
|
1654
|
+
the `articleId` that holds the code in `error.details`. `minStock` is a
|
|
1655
|
+
legacy field without effect; use `minStockByLocation`.
|
|
1656
|
+
- **Bookings.** `receiveGoods`, `transferStock`, `recordStockLoss` (`reason`
|
|
1657
|
+
from `STOCK_LOSS_REASONS`; `other` needs `note`, `withdrawal` needs
|
|
1658
|
+
`withdrawalType`), `changeStockCondition` (`sellable` ↔ `defective`) and
|
|
1659
|
+
`reverseStockMovement({ operationId, reason })` answer with a
|
|
1660
|
+
`StockOperation`. `warnings[]` are notices from `INVENTORY_WARNING_CODES`,
|
|
1661
|
+
never errors: the booking took effect. Prices may be sent without the
|
|
1662
|
+
permission `costs`; values come back only in `previewGoodsReceipt`, which is
|
|
1663
|
+
`receiveGoods` with `dryRun: true` (books nothing, needs no key; a `null`
|
|
1664
|
+
key counts as none and is not sent). `receiveGoods` itself refuses
|
|
1665
|
+
`dryRun: true` before sending (`KasseneckValidationError`, `scope:
|
|
1666
|
+
'request'`): a preview is not a booking, call `previewGoodsReceipt`.
|
|
1667
|
+
|
|
1668
|
+
### Shop: reserve and invoice
|
|
1669
|
+
|
|
1670
|
+
Hold the goods while the customer pays, turn the reservation into a sale with
|
|
1671
|
+
the invoice, and let the server release what was never paid:
|
|
1672
|
+
|
|
1673
|
+
```ts
|
|
1674
|
+
import { createInventoryClient, inventoryShortfalls, isInventoryError, parseInventoryWebhookEvent } from '@kreiseck/kasseneck-api/inventory';
|
|
1675
|
+
import { createInvoiceApi } from '@kreiseck/kasseneck-api/invoice';
|
|
1676
|
+
|
|
1677
|
+
const inventory = createInventoryClient({ apiKey: process.env.KASSENECK_API_KEY! });
|
|
1678
|
+
const invoices = createInvoiceApi({ apiKey: process.env.KASSENECK_API_KEY! });
|
|
1679
|
+
|
|
1680
|
+
// 1. Checkout: reserve everything or nothing, measured against `available`.
|
|
1681
|
+
async function checkout(order: Order) {
|
|
1682
|
+
try {
|
|
1683
|
+
return await inventory.createReservation({
|
|
1684
|
+
idempotencyKey: `checkout-${order.id}`, // the same key on every retry of this checkout
|
|
1685
|
+
reference: `Bestellung ${order.number}`,
|
|
1686
|
+
expiresInMinutes: 30,
|
|
1687
|
+
items: [{ articleId: 'kaisersemmel', quantity: 6000 }], // 6 pieces, at the default location
|
|
1688
|
+
});
|
|
1689
|
+
} catch (error) {
|
|
1690
|
+
if (!isInventoryError(error, 'insufficient_available')) throw error;
|
|
1691
|
+
// Nothing was reserved. Tell the customer what is short:
|
|
1692
|
+
// [{ articleId, locationId, requested: 6000, available: 2000 }]
|
|
1693
|
+
return showShortage(inventoryShortfalls(error));
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// 2. Paid: issue the invoice; the position redeems the reservation.
|
|
1698
|
+
async function paid(order: Order, reservationId: string) {
|
|
1699
|
+
const { invoice, notice } = await invoices.issueInvoice({
|
|
1700
|
+
idempotencyKey: `invoice-${order.id}`,
|
|
1701
|
+
priceMode: 'gross',
|
|
1702
|
+
serviceStart: '2026-10-06',
|
|
1703
|
+
orderReference: `Bestellung ${order.number}`,
|
|
1704
|
+
items: [{
|
|
1705
|
+
description: 'Kaisersemmel', quantity: 6, unitPriceCents: 65, vatRate: 10,
|
|
1706
|
+
articleId: 'kaisersemmel', reservationId,
|
|
1707
|
+
}],
|
|
1708
|
+
payment: { method: 'online' },
|
|
1709
|
+
});
|
|
1710
|
+
// Expired before the invoice: no error, the invoice sells without it.
|
|
1711
|
+
if (notice?.some((n) => n.code === 'reservation_expired')) log('sold without reservation', invoice.number);
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// 3. Abandoned cart: give the goods back at once instead of waiting for expiry.
|
|
1715
|
+
async function abandoned(order: Order, reservationId: string) {
|
|
1716
|
+
await inventory.releaseReservation({ idempotencyKey: `release-${order.id}`, reservationId });
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// 4. Webhooks (subscribe to reservation.* with createWebhook, verify as above).
|
|
1720
|
+
function onDelivery(rawBody: Buffer) {
|
|
1721
|
+
const event = parseInventoryWebhookEvent(rawBody);
|
|
1722
|
+
if (event && !event.test && event.type === 'reservation.expired') {
|
|
1723
|
+
return shop.cancelUnpaidOrder(event.data.reference); // the stock is available again
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
```
|
|
1727
|
+
|
|
1728
|
+
- **Redeeming.** A position with `reservationId` needs `articleId`. The server
|
|
1729
|
+
checks it when the invoice is issued: `reservation_not_found` (unknown or
|
|
1730
|
+
another account), `reservation_not_active` (already redeemed or released),
|
|
1731
|
+
`reservation_mismatch` (no open quantity of this article at the invoice's
|
|
1732
|
+
stock location, `stockLocationId` or the default location); nothing is issued
|
|
1733
|
+
then. An expired reservation is no error but the notice `reservation_expired`
|
|
1734
|
+
(with `reservationId`). The reservation is redeemed when the invoice is
|
|
1735
|
+
booked into stock: selling less than reserved releases the rest, selling
|
|
1736
|
+
more gives the warning `reservation_exceeded`. Credit notes take no
|
|
1737
|
+
`reservationId`.
|
|
1738
|
+
- **Expiry.** Without `expiresInMinutes` the account's default applies (7 days);
|
|
1739
|
+
5 to 43,200 minutes are allowed. `extendReservation` sets a new expiry from
|
|
1740
|
+
now, only for an active reservation that is not yet due
|
|
1741
|
+
(`reservation_not_active`). `releaseReservation` without `items` releases
|
|
1742
|
+
everything, with `items` per position (without `quantity` the whole rest);
|
|
1743
|
+
the status becomes `released` only when nothing is open.
|
|
1744
|
+
- **Events.** `reservation.expired`, `reservation.released` and
|
|
1745
|
+
`reservation.redeemed` carry the reservation with its status afterwards.
|
|
1746
|
+
`listReservations({ status: 'active' })` or `({ reference })` is the safety
|
|
1747
|
+
net without webhooks; the stock ledger shows reservations as movements of
|
|
1748
|
+
type `reservation` with `quantityDelta: 0` and the change in `reservedDelta`.
|
|
1749
|
+
|
|
1531
1750
|
## Migrating from 0.x
|
|
1532
1751
|
|
|
1533
1752
|
1.0 is one breaking step: the wire, the exported names and the contract files
|
|
@@ -1603,7 +1822,7 @@ in machine form what both sides agreed on, among them:
|
|
|
1603
1822
|
|---|---|---|
|
|
1604
1823
|
| `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` |
|
|
1605
1824
|
| `stored/pos-settings-defaults.json` | the same defaults in the stored (internal, German) form (`betrieb`, `geraet`) | `npm run fixtures:kasse` |
|
|
1606
|
-
| `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` |
|
|
1825
|
+
| `surface.json` | base URLs (`baseUrls.public`, `baseUrls.pos`), the calls per path (`calls`, and the backend's `routes`), the calls with an effect whose outcome is `unknown` after a timeout, network error, 5xx or unreadable answer (`unknownOutcomeCalls`, sorted), 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` |
|
|
1607
1826
|
| `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` |
|
|
1608
1827
|
| `pos-texts.json` | the register's message catalogue | `npm run fixtures:texte` |
|
|
1609
1828
|
| `invoice-texts.json` | invoice texts in both languages | `npm run fixtures:rechnungstexte` |
|