@kreiseck/kasseneck-api 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +176 -0
  2. package/README.md +293 -1
  3. package/dist/cjs/client/aufrufe.d.ts +2 -2
  4. package/dist/cjs/client/aufrufe.js +58 -0
  5. package/dist/cjs/inventory/api.d.ts +61 -0
  6. package/dist/cjs/inventory/api.js +54 -0
  7. package/dist/cjs/inventory/auth.d.ts +17 -0
  8. package/dist/cjs/inventory/auth.js +27 -0
  9. package/dist/cjs/inventory/endpunkte.d.ts +91 -0
  10. package/dist/cjs/inventory/endpunkte.js +290 -0
  11. package/dist/cjs/inventory/fehler.d.ts +43 -0
  12. package/dist/cjs/inventory/fehler.js +66 -0
  13. package/dist/cjs/inventory/index.d.ts +21 -0
  14. package/dist/cjs/inventory/index.js +94 -0
  15. package/dist/cjs/inventory/lesen.d.ts +45 -0
  16. package/dist/cjs/inventory/lesen.js +424 -0
  17. package/dist/cjs/inventory/schreiben.d.ts +83 -0
  18. package/dist/cjs/inventory/schreiben.js +315 -0
  19. package/dist/cjs/inventory/typen.d.ts +679 -0
  20. package/dist/cjs/inventory/typen.js +14 -0
  21. package/dist/cjs/inventory/vertrag.d.ts +136 -0
  22. package/dist/cjs/inventory/vertrag.js +291 -0
  23. package/dist/cjs/inventory/webhook.d.ts +52 -0
  24. package/dist/cjs/inventory/webhook.js +116 -0
  25. package/dist/cjs/invoice/index.d.ts +2 -2
  26. package/dist/cjs/invoice/index.js +2 -1
  27. package/dist/cjs/invoice/typen.d.ts +46 -2
  28. package/dist/cjs/invoice/vertrag.d.ts +19 -5
  29. package/dist/cjs/invoice/vertrag.js +37 -5
  30. package/dist/cjs/stored/vokabular.d.ts +1 -1
  31. package/dist/cjs/stored/vokabular.js +5 -3
  32. package/dist/cjs/version.d.ts +1 -1
  33. package/dist/cjs/version.js +1 -1
  34. package/dist/esm/client/aufrufe.d.ts +2 -2
  35. package/dist/esm/client/aufrufe.js +58 -0
  36. package/dist/esm/inventory/api.d.ts +61 -0
  37. package/dist/esm/inventory/api.js +51 -0
  38. package/dist/esm/inventory/auth.d.ts +17 -0
  39. package/dist/esm/inventory/auth.js +24 -0
  40. package/dist/esm/inventory/endpunkte.d.ts +91 -0
  41. package/dist/esm/inventory/endpunkte.js +267 -0
  42. package/dist/esm/inventory/fehler.d.ts +43 -0
  43. package/dist/esm/inventory/fehler.js +57 -0
  44. package/dist/esm/inventory/index.d.ts +21 -0
  45. package/dist/esm/inventory/index.js +20 -0
  46. package/dist/esm/inventory/lesen.d.ts +45 -0
  47. package/dist/esm/inventory/lesen.js +404 -0
  48. package/dist/esm/inventory/schreiben.d.ts +83 -0
  49. package/dist/esm/inventory/schreiben.js +298 -0
  50. package/dist/esm/inventory/typen.d.ts +679 -0
  51. package/dist/esm/inventory/typen.js +13 -0
  52. package/dist/esm/inventory/vertrag.d.ts +136 -0
  53. package/dist/esm/inventory/vertrag.js +288 -0
  54. package/dist/esm/inventory/webhook.d.ts +52 -0
  55. package/dist/esm/inventory/webhook.js +107 -0
  56. package/dist/esm/invoice/index.d.ts +2 -2
  57. package/dist/esm/invoice/index.js +1 -1
  58. package/dist/esm/invoice/typen.d.ts +46 -2
  59. package/dist/esm/invoice/vertrag.d.ts +19 -5
  60. package/dist/esm/invoice/vertrag.js +36 -4
  61. package/dist/esm/stored/vokabular.d.ts +1 -1
  62. package/dist/esm/stored/vokabular.js +5 -3
  63. package/dist/esm/version.d.ts +1 -1
  64. package/dist/esm/version.js +1 -1
  65. package/fixtures/hobex-hps-codes.json +1 -1
  66. package/fixtures/invoice-api-examples/credit-error-reservation.json +26 -0
  67. package/fixtures/invoice-api-examples/issue-error-reservation-article.json +25 -0
  68. package/fixtures/invoice-api-examples/issue-error-vat-id-risk.json +26 -0
  69. package/fixtures/invoice-api-examples/issue-reservation.json +24 -0
  70. package/fixtures/invoice-api-examples/issue-vat-id-risk.json +28 -0
  71. package/fixtures/invoice-api.schema.json +15 -2
  72. package/fixtures/invoice-texts.json +1 -1
  73. package/fixtures/pos-texts.json +1 -1
  74. package/fixtures/surface.json +306 -5
  75. package/fixtures/v3/README.md +9 -1
  76. package/fixtures/v3/antworten/belege.json +2 -2
  77. package/fixtures/v3/antworten/belegmail.json +2 -2
  78. package/fixtures/v3/antworten/kasse-belege.json +2 -2
  79. package/fixtures/v3/antworten/kasse.json +3 -3
  80. package/fixtures/v3/antworten/lager.json +3881 -0
  81. package/fixtures/v3/antworten/rechnungen.json +267 -44
  82. package/fixtures/v3/antworten/storno.json +2 -2
  83. package/fixtures/v3/stored/belege.json +2 -2
  84. package/fixtures/v3/stored/kasse.json +404 -4
  85. package/fixtures/v3/stored/rechnungen.json +174 -18
  86. package/fixtures/v3/v3-vokabular.json +1007 -20
  87. package/fixtures/v3/zahlbetrag-faelle.json +2 -2
  88. package/package.json +14 -2
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Leser der Lager-Antworten: aus dem Draht `/v3` werden die Modelle in
3
+ * `typen.ts`. Paketintern, nicht Teil der Oberflaeche.
4
+ *
5
+ * Regel wie beim Lager an der Kasse (`pos/lager.ts`): eine Menge, ein Betrag
6
+ * oder eine Folgenummer ist eine Ganzzahl, sonst endet der Aufruf mit
7
+ * `KasseneckValidationError` (`scope: 'response'`). Ein Ersatzwert wie `0`
8
+ * waere eine falsche Aussage („kein Bestand“ statt „Antwort kaputt“). Texte
9
+ * sind nachsichtiger: ein fehlender oder fremder Wert wird `null`.
10
+ *
11
+ * Optionale Felder (Einkaufswerte, `externalIds` …) erscheinen im Modell nur,
12
+ * wenn der Server sie sendet: „fehlt“ heisst dort „kein Recht“, nicht „leer“.
13
+ */
14
+ import { KasseneckValidationError } from '../client/errors.js';
15
+ import { LOCATION_TYPES } from './vertrag.js';
16
+ const hat = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
17
+ export function antwortfehler(name, grund) {
18
+ return new KasseneckValidationError(name, grund, 'response');
19
+ }
20
+ export function objekt(w) {
21
+ return w !== null && typeof w === 'object' && !Array.isArray(w) ? w : null;
22
+ }
23
+ /** Ein Eintrag, der ein Objekt sein muss. */
24
+ function eintrag(ort, w) {
25
+ const o = objekt(w);
26
+ if (!o)
27
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad} ist kein Objekt)`);
28
+ return o;
29
+ }
30
+ const text = (w) => (typeof w === 'string' ? w : null);
31
+ /** Eine Kennung, die da sein muss; ohne sie ist der Eintrag nicht zuzuordnen. */
32
+ function kennung(ort, feld, w) {
33
+ if (typeof w !== 'string' || w === '') {
34
+ throw antwortfehler(ort.name, `Antwort enthaelt keine Kennung (data.${ort.pfad}.${feld} fehlt)`);
35
+ }
36
+ return w;
37
+ }
38
+ /** Eine Ganzzahl, die da sein muss. */
39
+ function ganzzahl(ort, feld, w) {
40
+ if (typeof w !== 'number' || !Number.isSafeInteger(w)) {
41
+ throw antwortfehler(ort.name, `Antwort enthaelt keine ganze Zahl (data.${ort.pfad}.${feld})`);
42
+ }
43
+ return w;
44
+ }
45
+ /** Eine Ganzzahl oder `null`; fehlt der Wert, gilt `null`. */
46
+ function ganzzahlOderNull(ort, feld, w) {
47
+ if (w === undefined || w === null)
48
+ return null;
49
+ return ganzzahl(ort, feld, w);
50
+ }
51
+ /** Eine Textabbildung (`externalIds`, `metadata`, `variantAttributes`): nur Texteintraege. */
52
+ function textAbbildung(w) {
53
+ const o = objekt(w);
54
+ if (!o)
55
+ return undefined;
56
+ const raus = {};
57
+ for (const [k, v] of Object.entries(o))
58
+ if (typeof v === 'string')
59
+ raus[k] = v;
60
+ return raus;
61
+ }
62
+ /**
63
+ * Mengen je Standort (`minStockByLocation`): jeder Wert eine Ganzzahl. Fehlt
64
+ * das Feld (Server vor Stufe 5b), gilt „keiner“ (`{}`).
65
+ */
66
+ function mengenJeStandort(ort, feld, w) {
67
+ if (w === undefined || w === null)
68
+ return {};
69
+ const o = objekt(w);
70
+ if (!o)
71
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.${feld} ist kein Objekt)`);
72
+ const raus = {};
73
+ for (const [k, v] of Object.entries(o))
74
+ raus[k] = ganzzahl(ort, `${feld}.${k}`, v);
75
+ return raus;
76
+ }
77
+ /** Eine Liste von Kennungen (`movementIds`, `lotIds`); fehlt sie, ist sie leer. */
78
+ function kennungsliste(ort, feld, w) {
79
+ if (w === undefined || w === null)
80
+ return [];
81
+ if (!Array.isArray(w) || !w.every((x) => typeof x === 'string')) {
82
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}${ort.pfad ? '.' : ''}${feld} ist keine Liste von Kennungen)`);
83
+ }
84
+ return [...w];
85
+ }
86
+ // ---- Artikel ------------------------------------------------------------------
87
+ export function artikel(ort, w) {
88
+ const a = eintrag(ort, w);
89
+ let standorte = [];
90
+ if (a.stockLocationIds !== undefined && a.stockLocationIds !== null) {
91
+ if (!Array.isArray(a.stockLocationIds) || !a.stockLocationIds.every((s) => typeof s === 'string')) {
92
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.stockLocationIds ist keine Liste von Kennungen)`);
93
+ }
94
+ standorte = [...a.stockLocationIds];
95
+ }
96
+ const vatRate = a.vatRate;
97
+ if (vatRate !== undefined && vatRate !== null && (typeof vatRate !== 'number' || !Number.isFinite(vatRate))) {
98
+ throw antwortfehler(ort.name, `Antwort enthaelt keinen USt-Satz (data.${ort.pfad}.vatRate)`);
99
+ }
100
+ const raus = {
101
+ id: kennung(ort, 'id', a.id),
102
+ name: text(a.name),
103
+ description: text(a.description),
104
+ unitPriceCents: ganzzahlOderNull(ort, 'unitPriceCents', a.unitPriceCents),
105
+ vatRate: typeof vatRate === 'number' ? vatRate : null,
106
+ unit: text(a.unit),
107
+ number: text(a.number),
108
+ ean: text(a.ean),
109
+ internalCode: text(a.internalCode),
110
+ groupId: text(a.groupId),
111
+ revenueGroupId: text(a.revenueGroupId),
112
+ stockTracked: a.stockTracked === true,
113
+ stockKind: textOderNull(a.stockKind),
114
+ stockLocationIds: standorte,
115
+ minStock: ganzzahlOderNull(ort, 'minStock', a.minStock),
116
+ minStockByLocation: mengenJeStandort(ort, 'minStockByLocation', a.minStockByLocation),
117
+ active: a.active !== false,
118
+ createdAt: text(a.createdAt),
119
+ updatedAt: text(a.updatedAt),
120
+ };
121
+ for (const feld of ['externalIds', 'metadata', 'variantAttributes']) {
122
+ const abb = textAbbildung(a[feld]);
123
+ if (abb)
124
+ raus[feld] = abb;
125
+ }
126
+ if (typeof a.variantGroupId === 'string')
127
+ raus.variantGroupId = a.variantGroupId;
128
+ // Nur mit dem Recht `costs`; ohne fehlt das Feld ganz.
129
+ if (hat(a, 'purchasePriceMicros'))
130
+ raus.purchasePriceMicros = ganzzahlOderNull(ort, 'purchasePriceMicros', a.purchasePriceMicros);
131
+ return raus;
132
+ }
133
+ // ---- Standorte ------------------------------------------------------------------
134
+ const TYPEN = new Set(LOCATION_TYPES);
135
+ const textOderNull = (w) => (typeof w === 'string' && w !== '' ? w : null);
136
+ export function standort(ort, w) {
137
+ const s = eintrag(ort, w);
138
+ const a = objekt(s.address);
139
+ const teile = a ? { street: textOderNull(a.street), zip: textOderNull(a.zip), city: textOderNull(a.city), country: textOderNull(a.country) } : null;
140
+ return {
141
+ id: kennung(ort, 'id', s.id),
142
+ name: typeof s.name === 'string' ? s.name : '',
143
+ type: typeof s.type === 'string' && TYPEN.has(s.type) ? s.type : null,
144
+ // Eine Adresse ohne einen einzigen Teil ist keine Adresse.
145
+ address: teile && Object.values(teile).some((t) => t !== null) ? teile : null,
146
+ licensePlate: textOderNull(s.licensePlate),
147
+ active: s.active !== false,
148
+ virtual: s.virtual === true,
149
+ };
150
+ }
151
+ // ---- Bestand ------------------------------------------------------------------
152
+ export function bestand(ort, w) {
153
+ const b = eintrag(ort, w);
154
+ return {
155
+ articleId: kennung(ort, 'articleId', b.articleId),
156
+ locationId: kennung(ort, 'locationId', b.locationId),
157
+ onHand: ganzzahl(ort, 'onHand', b.onHand),
158
+ reserved: ganzzahl(ort, 'reserved', b.reserved),
159
+ available: ganzzahl(ort, 'available', b.available),
160
+ defective: ganzzahl(ort, 'defective', b.defective),
161
+ sequence: ganzzahl(ort, 'sequence', b.sequence),
162
+ updatedAt: text(b.updatedAt),
163
+ };
164
+ }
165
+ export function wert(ort, w) {
166
+ const v = eintrag(ort, w);
167
+ return {
168
+ articleId: kennung(ort, 'articleId', v.articleId),
169
+ stockValueCents: ganzzahl(ort, 'stockValueCents', v.stockValueCents),
170
+ averageCostMicros: ganzzahlOderNull(ort, 'averageCostMicros', v.averageCostMicros),
171
+ };
172
+ }
173
+ // ---- Bewegungen ------------------------------------------------------------------
174
+ function los(ort, w) {
175
+ const l = eintrag(ort, w);
176
+ const raus = {
177
+ lotId: text(l.lotId),
178
+ quantity: ganzzahl(ort, 'quantity', l.quantity),
179
+ expiresOn: text(l.expiresOn),
180
+ batch: text(l.batch),
181
+ serialNumber: text(l.serialNumber),
182
+ receivedAt: text(l.receivedAt),
183
+ };
184
+ if (hat(l, 'valueCents'))
185
+ raus.valueCents = ganzzahlOderNull(ort, 'valueCents', l.valueCents);
186
+ return raus;
187
+ }
188
+ export function bewegung(ort, w) {
189
+ const b = eintrag(ort, w);
190
+ const nachher = objekt(b.stockAfter);
191
+ const quelle = objekt(b.source);
192
+ const lose = b.lots === undefined || b.lots === null ? [] : b.lots;
193
+ if (!Array.isArray(lose))
194
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.lots ist keine Liste)`);
195
+ const nachherOrt = { name: ort.name, pfad: `${ort.pfad}.stockAfter` };
196
+ const raus = {
197
+ id: kennung(ort, 'id', b.id),
198
+ type: text(b.type),
199
+ articleId: text(b.articleId),
200
+ locationId: text(b.locationId),
201
+ condition: text(b.condition),
202
+ quantityDelta: ganzzahl(ort, 'quantityDelta', b.quantityDelta),
203
+ // Server vor Stufe 5b senden das Feld nicht: dort aenderte keine Bewegung `reserved`.
204
+ reservedDelta: b.reservedDelta === undefined ? 0 : ganzzahl(ort, 'reservedDelta', b.reservedDelta),
205
+ stockAfter: nachher
206
+ ? {
207
+ sellable: ganzzahl(nachherOrt, 'sellable', nachher.sellable),
208
+ defective: ganzzahl(nachherOrt, 'defective', nachher.defective),
209
+ reserved: ganzzahlOderNull(nachherOrt, 'reserved', nachher.reserved),
210
+ }
211
+ : null,
212
+ operationId: text(b.operationId),
213
+ source: quelle
214
+ ? {
215
+ type: text(quelle.type),
216
+ id: text(quelle.id),
217
+ register: text(quelle.register),
218
+ position: typeof quelle.position === 'number' && Number.isSafeInteger(quelle.position) ? quelle.position : null,
219
+ }
220
+ : null,
221
+ viennaDay: text(b.viennaDay),
222
+ time: text(b.time),
223
+ lots: lose.map((l, i) => los({ name: ort.name, pfad: `${ort.pfad}.lots[${i}]` }, l)),
224
+ };
225
+ if (hat(b, 'valueDeltaCents'))
226
+ raus.valueDeltaCents = ganzzahlOderNull(ort, 'valueDeltaCents', b.valueDeltaCents);
227
+ if (hat(b, 'consumedValueCents'))
228
+ raus.consumedValueCents = ganzzahlOderNull(ort, 'consumedValueCents', b.consumedValueCents);
229
+ return raus;
230
+ }
231
+ // ---- Webhooks ------------------------------------------------------------------
232
+ const zahlOderNull = (w) => (typeof w === 'number' && Number.isFinite(w) ? w : null);
233
+ export function webhook(ort, w) {
234
+ const h = eintrag(ort, w);
235
+ const z = objekt(h.lastDelivery);
236
+ return {
237
+ id: kennung(ort, 'id', h.id),
238
+ url: typeof h.url === 'string' ? h.url : '',
239
+ events: Array.isArray(h.events) ? h.events.filter((e) => typeof e === 'string') : [],
240
+ active: h.active !== false,
241
+ description: text(h.description),
242
+ createdAt: text(h.createdAt),
243
+ lastDelivery: z ? { at: text(z.at), status: text(z.status), statusCode: zahlOderNull(z.statusCode) } : null,
244
+ consecutiveFailures: typeof h.consecutiveFailures === 'number' && Number.isSafeInteger(h.consecutiveFailures) ? h.consecutiveFailures : 0,
245
+ };
246
+ }
247
+ export function zustellung(ort, w) {
248
+ const z = eintrag(ort, w);
249
+ return {
250
+ deliveryId: kennung(ort, 'deliveryId', z.deliveryId),
251
+ webhookId: text(z.webhookId),
252
+ event: text(z.event),
253
+ eventId: text(z.eventId),
254
+ status: text(z.status),
255
+ attempts: typeof z.attempts === 'number' && Number.isSafeInteger(z.attempts) ? z.attempts : 0,
256
+ statusCode: zahlOderNull(z.statusCode),
257
+ response: text(z.response),
258
+ error: text(z.error),
259
+ createdAt: text(z.createdAt),
260
+ lastAttemptAt: text(z.lastAttemptAt),
261
+ nextAttemptAt: text(z.nextAttemptAt),
262
+ test: z.test === true,
263
+ };
264
+ }
265
+ export function probeZustellung(ort, w, webhookId) {
266
+ const z = eintrag(ort, w);
267
+ return {
268
+ deliveryId: kennung(ort, 'deliveryId', z.deliveryId),
269
+ webhookId: typeof z.webhookId === 'string' && z.webhookId ? z.webhookId : webhookId,
270
+ status: text(z.status),
271
+ statusCode: zahlOderNull(z.statusCode),
272
+ };
273
+ }
274
+ // ---- Ereignisse ------------------------------------------------------------------
275
+ export function bestandGeaendert(ort, w) {
276
+ const d = eintrag(ort, w);
277
+ const ursache = d.cause;
278
+ if (typeof ursache !== 'string' || ursache === '')
279
+ throw antwortfehler(ort.name, `Ereignis ohne Ursache (${ort.pfad}.cause)`);
280
+ return { ...bestand(ort, d), cause: ursache, movementId: textOderNull(d.movementId) };
281
+ }
282
+ export function unterMindestbestand(ort, w) {
283
+ const d = eintrag(ort, w);
284
+ return {
285
+ articleId: kennung(ort, 'articleId', d.articleId),
286
+ locationId: kennung(ort, 'locationId', d.locationId),
287
+ available: ganzzahl(ort, 'available', d.available),
288
+ minStock: ganzzahl(ort, 'minStock', d.minStock),
289
+ };
290
+ }
291
+ // ---- Schreiben (Stufe 5b) ------------------------------------------------------------
292
+ function warnung(ort, w) {
293
+ const h = eintrag(ort, w);
294
+ if (typeof h.code !== 'string' || h.code === '')
295
+ throw antwortfehler(ort.name, `Hinweis ohne Code (data.${ort.pfad}.code)`);
296
+ return {
297
+ code: h.code,
298
+ articleId: textOderNull(h.articleId),
299
+ locationId: textOderNull(h.locationId),
300
+ message: typeof h.message === 'string' ? h.message : '',
301
+ };
302
+ }
303
+ /** Antwort einer Buchung; ohne `operationId` ist nicht belegt, dass gebucht wurde. */
304
+ export function vorgang(name, daten) {
305
+ const d = objekt(daten);
306
+ if (!d)
307
+ throw antwortfehler(name, 'Antwort ist unbrauchbar (data ist kein Objekt)');
308
+ const ort = { name, pfad: '' };
309
+ if (typeof d.operationId !== 'string' || d.operationId === '') {
310
+ throw antwortfehler(name, 'Antwort enthaelt keine Kennung (data.operationId fehlt)');
311
+ }
312
+ const hinweise = d.warnings === undefined || d.warnings === null ? [] : d.warnings;
313
+ if (!Array.isArray(hinweise))
314
+ throw antwortfehler(name, 'Antwort ist unbrauchbar (data.warnings ist keine Liste)');
315
+ return {
316
+ operationId: d.operationId,
317
+ movementIds: kennungsliste(ort, 'movementIds', d.movementIds),
318
+ lotIds: kennungsliste(ort, 'lotIds', d.lotIds),
319
+ warnings: hinweise.map((h, i) => warnung({ name, pfad: `warnings[${i}]` }, h)),
320
+ };
321
+ }
322
+ /** Eine Zeile der Wareneingangs-Vorschau; Werte nur, wenn der Server sie sendet (Recht `costs`). */
323
+ export function vorschauZeile(ort, w) {
324
+ const z = eintrag(ort, w);
325
+ const serien = z.serialNumbers === undefined || z.serialNumbers === null ? [] : z.serialNumbers;
326
+ if (!Array.isArray(serien) || !serien.every((x) => typeof x === 'string')) {
327
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.serialNumbers ist keine Liste)`);
328
+ }
329
+ const raus = {
330
+ articleId: kennung(ort, 'articleId', z.articleId),
331
+ quantity: ganzzahl(ort, 'quantity', z.quantity),
332
+ expiresOn: textOderNull(z.expiresOn),
333
+ batch: textOderNull(z.batch),
334
+ serialNumbers: [...serien],
335
+ priceFromArticle: z.priceFromArticle === true,
336
+ };
337
+ for (const feld of ['baseCents', 'landedCostCents', 'valueCents', 'unitCostMicros']) {
338
+ if (hat(z, feld))
339
+ raus[feld] = ganzzahlOderNull(ort, feld, z[feld]);
340
+ }
341
+ return raus;
342
+ }
343
+ function reservierungsPosition(ort, w) {
344
+ const p = eintrag(ort, w);
345
+ return {
346
+ articleId: kennung(ort, 'articleId', p.articleId),
347
+ locationId: kennung(ort, 'locationId', p.locationId),
348
+ quantity: ganzzahl(ort, 'quantity', p.quantity),
349
+ redeemed: ganzzahl(ort, 'redeemed', p.redeemed),
350
+ released: ganzzahl(ort, 'released', p.released),
351
+ };
352
+ }
353
+ export function reservierung(ort, w) {
354
+ const r = eintrag(ort, w);
355
+ if (!Array.isArray(r.items))
356
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.items ist keine Liste)`);
357
+ return {
358
+ id: kennung(ort, 'id', r.id),
359
+ status: textOderNull(r.status),
360
+ reference: text(r.reference),
361
+ items: r.items.map((p, i) => reservierungsPosition({ name: ort.name, pfad: `${ort.pfad}.items[${i}]` }, p)),
362
+ expiresAt: text(r.expiresAt),
363
+ createdAt: text(r.createdAt),
364
+ };
365
+ }
366
+ /** Die fehlenden Positionen aus `insufficient_available` (`data.details[]`); ein kaputter Eintrag faellt weg. */
367
+ export function fehlmengen(roh) {
368
+ if (!Array.isArray(roh))
369
+ return [];
370
+ const raus = [];
371
+ for (const e of roh) {
372
+ const o = objekt(e);
373
+ if (!o)
374
+ continue;
375
+ const { articleId, locationId, requested, available } = o;
376
+ if (typeof articleId !== 'string' || typeof locationId !== 'string')
377
+ continue;
378
+ if (!Number.isSafeInteger(requested) || !Number.isSafeInteger(available))
379
+ continue;
380
+ raus.push({ articleId, locationId, requested: requested, available: available });
381
+ }
382
+ return raus;
383
+ }
384
+ // ---- Listen ------------------------------------------------------------------
385
+ /** Eine zugesagte Liste `data.<feld>`, Eintrag fuer Eintrag gelesen. */
386
+ export function liste(name, daten, feld, lesen) {
387
+ const roh = objekt(daten)?.[feld];
388
+ if (!Array.isArray(roh)) {
389
+ const grund = roh === undefined || roh === null
390
+ ? `Antwort enthaelt keine Liste (data.${feld} fehlt)`
391
+ : `Antwort ist unbrauchbar (data.${feld} ist keine Liste)`;
392
+ throw antwortfehler(name, grund);
393
+ }
394
+ return roh.map((e, i) => lesen({ name, pfad: `${feld}[${i}]` }, e));
395
+ }
396
+ /** Der Cursor der naechsten Seite; `null` am Ende. Etwas anderes als Text oder `null` ist kaputt. */
397
+ export function naechsterCursor(name, daten) {
398
+ const c = objekt(daten)?.nextCursor;
399
+ if (c === undefined || c === null)
400
+ return null;
401
+ if (typeof c !== 'string' || c === '')
402
+ throw antwortfehler(name, 'Antwort ist unbrauchbar (data.nextCursor)');
403
+ return c;
404
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Die schreibenden Aufrufe der Lager-API (Backend Stufe 5b): Artikel anlegen,
3
+ * aendern und stilllegen, Bestand buchen (Wareneingang, Umbuchung, Abgang,
4
+ * Zustand, Gegenbuchung) und reservieren.
5
+ *
6
+ * Wie in `endpunkte.ts` nimmt jede Funktion den Transport als ersten Parameter
7
+ * und ist einzeln importierbar. Die Anfrage geht flach hinaus, genau mit den
8
+ * Feldern, die der Aufrufer nennt (`undefined` faellt weg, `null` bleibt: es
9
+ * leert ein Feld bzw. heisst bei der Anlage „nicht angegeben“).
10
+ *
11
+ * **Vor dem Senden geprueft wird nur, was ohne Netz sicher falsch ist:**
12
+ * - `idempotencyKey` fehlt, ist leer oder laenger als 120 Zeichen. Ohne
13
+ * Schluessel gaebe es keine sichere Wiederholung, und der Server wiese die
14
+ * Anfrage ohnehin ab (`idempotency_key_required` bzw. `validation`);
15
+ * - eine Pflichtkennung fehlt oder ist leer;
16
+ * - eine Menge, ein Betrag oder ein Preis ist keine Ganzzahl (Tausendstel,
17
+ * Cent, Mikro-Euro): `1.5` waere nie „1,5 Stueck“, sondern ein Fehler;
18
+ * - `expiresInMinutes` liegt ausserhalb 5 … 43 200;
19
+ * - eine Aenderung nennt kein Feld, eine Freigabe eine leere Liste.
20
+ * Alles Fachliche (Pruefziffer der EAN, Kataloge, Bestand, Rechte) prueft der
21
+ * Server und meldet es mit seinem Code.
22
+ *
23
+ * **Wiederholen:** nach einem Zeitlimit oder Netzfehler denselben Aufruf mit
24
+ * **demselben** `idempotencyKey` noch einmal senden. Er wirkt genau einmal und
25
+ * liefert die gespeicherte Antwort; ein neuer Schluessel buchte ein zweites Mal.
26
+ */
27
+ import type { InternerTransport } from '../client/aufrufe.js';
28
+ import type { Article, ChangeStockConditionRequest, CreateArticleRequest, CreateReservationRequest, DeactivateArticleRequest, ExtendReservationRequest, GoodsReceiptPreview, GoodsReceiptPreviewRequest, ReceiveGoodsRequest, RecordStockLossRequest, ReleaseReservationRequest, Reservation, ReservationListQuery, ReservationPage, ReverseStockMovementRequest, StockOperation, TransferStockRequest, UpdateArticleRequest } from './typen.js';
29
+ /**
30
+ * Legt einen Artikel an. Mit `ean` ein Fremdartikel mit diesem Code (gueltige
31
+ * Pruefziffer, frei im Konto), sonst vergibt der Server den naechsten eigenen
32
+ * Code. Antwort: der Artikel wie `getArticle`.
33
+ */
34
+ export declare function createArticle(transport: InternerTransport, request: CreateArticleRequest): Promise<Article>;
35
+ /** Aendert nur die genannten Felder eines Artikels (siehe [UpdateArticleRequest]). */
36
+ export declare function updateArticle(transport: InternerTransport, request: UpdateArticleRequest): Promise<Article>;
37
+ /** Legt einen Artikel still (`active: false`); Code und eigene Kennungen werden frei. */
38
+ export declare function deactivateArticle(transport: InternerTransport, request: DeactivateArticleRequest): Promise<Article>;
39
+ /**
40
+ * Bucht einen Wareneingang. Ohne `locationId` am Standard-Standort; die
41
+ * Antwort traegt keine Werte, auch mit dem Recht `costs` nicht. Fuer die
42
+ * Vorschau mit Werten: [previewGoodsReceipt].
43
+ */
44
+ export declare function receiveGoods(transport: InternerTransport, request: ReceiveGoodsRequest): Promise<StockOperation>;
45
+ /**
46
+ * Vorschau eines Wareneingangs (`receiveGoods` mit `dryRun: true`): prueft
47
+ * Positionen, Artikel, Preise und Nebenkosten und rechnet die Verteilung,
48
+ * schreibt aber nichts. Ein `idempotencyKey` ist freigestellt (`null` wie nicht
49
+ * angegeben, geht nicht hinaus) und wird nicht verbraucht; dieselbe Anfrage
50
+ * laesst sich danach mit ihm buchen. Werte
51
+ * (`baseCents` …) nur mit dem Recht `costs`. Den Standort prueft erst die Buchung.
52
+ */
53
+ export declare function previewGoodsReceipt(transport: InternerTransport, request: GoodsReceiptPreviewRequest): Promise<GoodsReceiptPreview>;
54
+ /** Bucht Ware von einem Standort an einen anderen; ueberzieht nie (`exceeds_stock`). */
55
+ export declare function transferStock(transport: InternerTransport, request: TransferStockRequest): Promise<StockOperation>;
56
+ /** Bucht einen Abgang (Bruch, Schwund, Diebstahl, Entnahme …); ueberzieht nie (`exceeds_stock`). */
57
+ export declare function recordStockLoss(transport: InternerTransport, request: RecordStockLossRequest): Promise<StockOperation>;
58
+ /** Bucht Ware zwischen `sellable` und `defective` um; ueberzieht nie (`exceeds_stock`). */
59
+ export declare function changeStockCondition(transport: InternerTransport, request: ChangeStockConditionRequest): Promise<StockOperation>;
60
+ /** Nimmt einen ganzen Vorgang (`operationId`) mit einer Gegenbuchung zurueck. */
61
+ export declare function reverseStockMovement(transport: InternerTransport, request: ReverseStockMovementRequest): Promise<StockOperation>;
62
+ /**
63
+ * Reserviert Ware (Checkout im Shop): ganz oder gar nicht, gemessen am
64
+ * verfuegbaren Bestand (`onHand − reserved`). Fehlt etwas, entsteht nichts:
65
+ * `insufficient_available`, die fehlenden Positionen in [inventoryShortfalls].
66
+ * Eingeloest wird ueber eine Rechnung (`issueInvoice` mit `items[].reservationId`),
67
+ * sonst laeuft die Reservierung ab und gibt die Ware wieder frei.
68
+ */
69
+ export declare function createReservation(transport: InternerTransport, request: CreateReservationRequest): Promise<Reservation>;
70
+ /** Verlaengert eine aktive Reservierung: neuer Ablauf = jetzt + `expiresInMinutes`. */
71
+ export declare function extendReservation(transport: InternerTransport, request: ExtendReservationRequest): Promise<Reservation>;
72
+ /**
73
+ * Gibt reservierte Ware frei: ohne `items` alles, sonst je Position (ohne
74
+ * `quantity` der ganze offene Rest). Die Antwort traegt den Stand danach;
75
+ * `released` wird der Status erst, wenn nichts mehr offen ist.
76
+ */
77
+ export declare function releaseReservation(transport: InternerTransport, request: ReleaseReservationRequest): Promise<Reservation>;
78
+ /** Eine Reservierung mit ihrem aktuellen Stand. */
79
+ export declare function getReservation(transport: InternerTransport, reservationId: string): Promise<Reservation>;
80
+ /** Reservierungen, neueste zuerst; Filter `status` und/oder `reference`. */
81
+ export declare function listReservations(transport: InternerTransport, query?: ReservationListQuery): Promise<ReservationPage>;
82
+ /** Alle Reservierungen der Abfrage, Seite fuer Seite ueber `nextCursor`. */
83
+ export declare function iterateReservations(transport: InternerTransport, query?: ReservationListQuery): AsyncGenerator<Reservation, void, undefined>;