@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.
Files changed (105) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/README.md +255 -36
  3. package/dist/cjs/client/aufrufe.d.ts +58 -4
  4. package/dist/cjs/client/aufrufe.js +196 -1
  5. package/dist/cjs/client/errors.d.ts +32 -19
  6. package/dist/cjs/client/errors.js +28 -17
  7. package/dist/cjs/client/index.d.ts +2 -2
  8. package/dist/cjs/client/index.js +2 -1
  9. package/dist/cjs/client/transport.d.ts +27 -1
  10. package/dist/cjs/client/transport.js +25 -27
  11. package/dist/cjs/index.d.ts +1 -1
  12. package/dist/cjs/index.js +3 -2
  13. package/dist/cjs/inventory/api.d.ts +19 -3
  14. package/dist/cjs/inventory/api.js +18 -2
  15. package/dist/cjs/inventory/endpunkte.d.ts +22 -2
  16. package/dist/cjs/inventory/endpunkte.js +6 -1
  17. package/dist/cjs/inventory/fehler.d.ts +11 -1
  18. package/dist/cjs/inventory/fehler.js +19 -0
  19. package/dist/cjs/inventory/index.d.ts +6 -4
  20. package/dist/cjs/inventory/index.js +32 -2
  21. package/dist/cjs/inventory/lesen.d.ts +8 -1
  22. package/dist/cjs/inventory/lesen.js +131 -1
  23. package/dist/cjs/inventory/schreiben.d.ts +86 -0
  24. package/dist/cjs/inventory/schreiben.js +320 -0
  25. package/dist/cjs/inventory/typen.d.ts +371 -11
  26. package/dist/cjs/inventory/vertrag.d.ts +76 -16
  27. package/dist/cjs/inventory/vertrag.js +138 -13
  28. package/dist/cjs/inventory/webhook.d.ts +3 -1
  29. package/dist/cjs/inventory/webhook.js +7 -1
  30. package/dist/cjs/invoice/endpunkte.d.ts +6 -0
  31. package/dist/cjs/invoice/endpunkte.js +13 -2
  32. package/dist/cjs/invoice/index.d.ts +2 -2
  33. package/dist/cjs/invoice/index.js +2 -1
  34. package/dist/cjs/invoice/typen.d.ts +46 -2
  35. package/dist/cjs/invoice/vertrag.d.ts +19 -5
  36. package/dist/cjs/invoice/vertrag.js +37 -5
  37. package/dist/cjs/pos/texte.d.ts +11 -6
  38. package/dist/cjs/pos/texte.js +11 -6
  39. package/dist/cjs/stored/vokabular.d.ts +1 -1
  40. package/dist/cjs/stored/vokabular.js +5 -3
  41. package/dist/cjs/version.d.ts +1 -1
  42. package/dist/cjs/version.js +1 -1
  43. package/dist/esm/client/aufrufe.d.ts +58 -4
  44. package/dist/esm/client/aufrufe.js +194 -0
  45. package/dist/esm/client/errors.d.ts +32 -19
  46. package/dist/esm/client/errors.js +28 -17
  47. package/dist/esm/client/index.d.ts +2 -2
  48. package/dist/esm/client/index.js +1 -1
  49. package/dist/esm/client/transport.d.ts +27 -1
  50. package/dist/esm/client/transport.js +26 -28
  51. package/dist/esm/index.d.ts +1 -1
  52. package/dist/esm/index.js +1 -1
  53. package/dist/esm/inventory/api.d.ts +19 -3
  54. package/dist/esm/inventory/api.js +18 -2
  55. package/dist/esm/inventory/endpunkte.d.ts +22 -2
  56. package/dist/esm/inventory/endpunkte.js +6 -5
  57. package/dist/esm/inventory/fehler.d.ts +11 -1
  58. package/dist/esm/inventory/fehler.js +18 -1
  59. package/dist/esm/inventory/index.d.ts +6 -4
  60. package/dist/esm/inventory/index.js +5 -3
  61. package/dist/esm/inventory/lesen.d.ts +8 -1
  62. package/dist/esm/inventory/lesen.js +127 -1
  63. package/dist/esm/inventory/schreiben.d.ts +86 -0
  64. package/dist/esm/inventory/schreiben.js +303 -0
  65. package/dist/esm/inventory/typen.d.ts +371 -11
  66. package/dist/esm/inventory/vertrag.d.ts +76 -16
  67. package/dist/esm/inventory/vertrag.js +137 -12
  68. package/dist/esm/inventory/webhook.d.ts +3 -1
  69. package/dist/esm/inventory/webhook.js +8 -2
  70. package/dist/esm/invoice/endpunkte.d.ts +6 -0
  71. package/dist/esm/invoice/endpunkte.js +13 -2
  72. package/dist/esm/invoice/index.d.ts +2 -2
  73. package/dist/esm/invoice/index.js +1 -1
  74. package/dist/esm/invoice/typen.d.ts +46 -2
  75. package/dist/esm/invoice/vertrag.d.ts +19 -5
  76. package/dist/esm/invoice/vertrag.js +36 -4
  77. package/dist/esm/pos/texte.d.ts +11 -6
  78. package/dist/esm/pos/texte.js +11 -6
  79. package/dist/esm/stored/vokabular.d.ts +1 -1
  80. package/dist/esm/stored/vokabular.js +5 -3
  81. package/dist/esm/version.d.ts +1 -1
  82. package/dist/esm/version.js +1 -1
  83. package/fixtures/hobex-hps-codes.json +1 -1
  84. package/fixtures/invoice-api-examples/credit-error-reservation.json +26 -0
  85. package/fixtures/invoice-api-examples/issue-error-reservation-article.json +25 -0
  86. package/fixtures/invoice-api-examples/issue-error-vat-id-risk.json +26 -0
  87. package/fixtures/invoice-api-examples/issue-reservation.json +24 -0
  88. package/fixtures/invoice-api-examples/issue-vat-id-risk.json +28 -0
  89. package/fixtures/invoice-api.schema.json +15 -2
  90. package/fixtures/invoice-texts.json +1 -1
  91. package/fixtures/pos-texts.json +1 -1
  92. package/fixtures/surface.json +206 -9
  93. package/fixtures/v3/antworten/belege.json +2 -2
  94. package/fixtures/v3/antworten/belegmail.json +2 -2
  95. package/fixtures/v3/antworten/kasse-belege.json +2 -2
  96. package/fixtures/v3/antworten/kasse.json +3 -3
  97. package/fixtures/v3/antworten/lager.json +2491 -130
  98. package/fixtures/v3/antworten/rechnungen.json +267 -44
  99. package/fixtures/v3/antworten/storno.json +2 -2
  100. package/fixtures/v3/stored/belege.json +2 -2
  101. package/fixtures/v3/stored/kasse.json +3 -3
  102. package/fixtures/v3/stored/rechnungen.json +174 -18
  103. package/fixtures/v3/v3-vokabular.json +593 -23
  104. package/fixtures/v3/zahlbetrag-faelle.json +2 -2
  105. package/package.json +1 -1
@@ -25,6 +25,10 @@ exports.zustellung = zustellung;
25
25
  exports.probeZustellung = probeZustellung;
26
26
  exports.bestandGeaendert = bestandGeaendert;
27
27
  exports.unterMindestbestand = unterMindestbestand;
28
+ exports.vorgang = vorgang;
29
+ exports.vorschauZeile = vorschauZeile;
30
+ exports.reservierung = reservierung;
31
+ exports.fehlmengen = fehlmengen;
28
32
  exports.liste = liste;
29
33
  exports.naechsterCursor = naechsterCursor;
30
34
  const errors_js_1 = require("../client/errors.js");
@@ -75,6 +79,30 @@ function textAbbildung(w) {
75
79
  raus[k] = v;
76
80
  return raus;
77
81
  }
82
+ /**
83
+ * Mengen je Standort (`minStockByLocation`): jeder Wert eine Ganzzahl. Fehlt
84
+ * das Feld (Server vor Stufe 5b), gilt „keiner“ (`{}`).
85
+ */
86
+ function mengenJeStandort(ort, feld, w) {
87
+ if (w === undefined || w === null)
88
+ return {};
89
+ const o = objekt(w);
90
+ if (!o)
91
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.${feld} ist kein Objekt)`);
92
+ const raus = {};
93
+ for (const [k, v] of Object.entries(o))
94
+ raus[k] = ganzzahl(ort, `${feld}.${k}`, v);
95
+ return raus;
96
+ }
97
+ /** Eine Liste von Kennungen (`movementIds`, `lotIds`); fehlt sie, ist sie leer. */
98
+ function kennungsliste(ort, feld, w) {
99
+ if (w === undefined || w === null)
100
+ return [];
101
+ if (!Array.isArray(w) || !w.every((x) => typeof x === 'string')) {
102
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}${ort.pfad ? '.' : ''}${feld} ist keine Liste von Kennungen)`);
103
+ }
104
+ return [...w];
105
+ }
78
106
  // ---- Artikel ------------------------------------------------------------------
79
107
  function artikel(ort, w) {
80
108
  const a = eintrag(ort, w);
@@ -92,6 +120,7 @@ function artikel(ort, w) {
92
120
  const raus = {
93
121
  id: kennung(ort, 'id', a.id),
94
122
  name: text(a.name),
123
+ description: text(a.description),
95
124
  unitPriceCents: ganzzahlOderNull(ort, 'unitPriceCents', a.unitPriceCents),
96
125
  vatRate: typeof vatRate === 'number' ? vatRate : null,
97
126
  unit: text(a.unit),
@@ -101,8 +130,10 @@ function artikel(ort, w) {
101
130
  groupId: text(a.groupId),
102
131
  revenueGroupId: text(a.revenueGroupId),
103
132
  stockTracked: a.stockTracked === true,
133
+ stockKind: textOderNull(a.stockKind),
104
134
  stockLocationIds: standorte,
105
135
  minStock: ganzzahlOderNull(ort, 'minStock', a.minStock),
136
+ minStockByLocation: mengenJeStandort(ort, 'minStockByLocation', a.minStockByLocation),
106
137
  active: a.active !== false,
107
138
  createdAt: text(a.createdAt),
108
139
  updatedAt: text(a.updatedAt),
@@ -189,8 +220,14 @@ function bewegung(ort, w) {
189
220
  locationId: text(b.locationId),
190
221
  condition: text(b.condition),
191
222
  quantityDelta: ganzzahl(ort, 'quantityDelta', b.quantityDelta),
223
+ // Server vor Stufe 5b senden das Feld nicht: dort aenderte keine Bewegung `reserved`.
224
+ reservedDelta: b.reservedDelta === undefined ? 0 : ganzzahl(ort, 'reservedDelta', b.reservedDelta),
192
225
  stockAfter: nachher
193
- ? { sellable: ganzzahl(nachherOrt, 'sellable', nachher.sellable), defective: ganzzahl(nachherOrt, 'defective', nachher.defective) }
226
+ ? {
227
+ sellable: ganzzahl(nachherOrt, 'sellable', nachher.sellable),
228
+ defective: ganzzahl(nachherOrt, 'defective', nachher.defective),
229
+ reserved: ganzzahlOderNull(nachherOrt, 'reserved', nachher.reserved),
230
+ }
194
231
  : null,
195
232
  operationId: text(b.operationId),
196
233
  source: quelle
@@ -271,6 +308,99 @@ function unterMindestbestand(ort, w) {
271
308
  minStock: ganzzahl(ort, 'minStock', d.minStock),
272
309
  };
273
310
  }
311
+ // ---- Schreiben (Stufe 5b) ------------------------------------------------------------
312
+ function warnung(ort, w) {
313
+ const h = eintrag(ort, w);
314
+ if (typeof h.code !== 'string' || h.code === '')
315
+ throw antwortfehler(ort.name, `Hinweis ohne Code (data.${ort.pfad}.code)`);
316
+ return {
317
+ code: h.code,
318
+ articleId: textOderNull(h.articleId),
319
+ locationId: textOderNull(h.locationId),
320
+ message: typeof h.message === 'string' ? h.message : '',
321
+ };
322
+ }
323
+ /** Antwort einer Buchung; ohne `operationId` ist nicht belegt, dass gebucht wurde. */
324
+ function vorgang(name, daten) {
325
+ const d = objekt(daten);
326
+ if (!d)
327
+ throw antwortfehler(name, 'Antwort ist unbrauchbar (data ist kein Objekt)');
328
+ const ort = { name, pfad: '' };
329
+ if (typeof d.operationId !== 'string' || d.operationId === '') {
330
+ throw antwortfehler(name, 'Antwort enthaelt keine Kennung (data.operationId fehlt)');
331
+ }
332
+ const hinweise = d.warnings === undefined || d.warnings === null ? [] : d.warnings;
333
+ if (!Array.isArray(hinweise))
334
+ throw antwortfehler(name, 'Antwort ist unbrauchbar (data.warnings ist keine Liste)');
335
+ return {
336
+ operationId: d.operationId,
337
+ movementIds: kennungsliste(ort, 'movementIds', d.movementIds),
338
+ lotIds: kennungsliste(ort, 'lotIds', d.lotIds),
339
+ warnings: hinweise.map((h, i) => warnung({ name, pfad: `warnings[${i}]` }, h)),
340
+ };
341
+ }
342
+ /** Eine Zeile der Wareneingangs-Vorschau; Werte nur, wenn der Server sie sendet (Recht `costs`). */
343
+ function vorschauZeile(ort, w) {
344
+ const z = eintrag(ort, w);
345
+ const serien = z.serialNumbers === undefined || z.serialNumbers === null ? [] : z.serialNumbers;
346
+ if (!Array.isArray(serien) || !serien.every((x) => typeof x === 'string')) {
347
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.serialNumbers ist keine Liste)`);
348
+ }
349
+ const raus = {
350
+ articleId: kennung(ort, 'articleId', z.articleId),
351
+ quantity: ganzzahl(ort, 'quantity', z.quantity),
352
+ expiresOn: textOderNull(z.expiresOn),
353
+ batch: textOderNull(z.batch),
354
+ serialNumbers: [...serien],
355
+ priceFromArticle: z.priceFromArticle === true,
356
+ };
357
+ for (const feld of ['baseCents', 'landedCostCents', 'valueCents', 'unitCostMicros']) {
358
+ if (hat(z, feld))
359
+ raus[feld] = ganzzahlOderNull(ort, feld, z[feld]);
360
+ }
361
+ return raus;
362
+ }
363
+ function reservierungsPosition(ort, w) {
364
+ const p = eintrag(ort, w);
365
+ return {
366
+ articleId: kennung(ort, 'articleId', p.articleId),
367
+ locationId: kennung(ort, 'locationId', p.locationId),
368
+ quantity: ganzzahl(ort, 'quantity', p.quantity),
369
+ redeemed: ganzzahl(ort, 'redeemed', p.redeemed),
370
+ released: ganzzahl(ort, 'released', p.released),
371
+ };
372
+ }
373
+ function reservierung(ort, w) {
374
+ const r = eintrag(ort, w);
375
+ if (!Array.isArray(r.items))
376
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.items ist keine Liste)`);
377
+ return {
378
+ id: kennung(ort, 'id', r.id),
379
+ status: textOderNull(r.status),
380
+ reference: text(r.reference),
381
+ items: r.items.map((p, i) => reservierungsPosition({ name: ort.name, pfad: `${ort.pfad}.items[${i}]` }, p)),
382
+ expiresAt: text(r.expiresAt),
383
+ createdAt: text(r.createdAt),
384
+ };
385
+ }
386
+ /** Die fehlenden Positionen aus `insufficient_available` (`data.details[]`); ein kaputter Eintrag faellt weg. */
387
+ function fehlmengen(roh) {
388
+ if (!Array.isArray(roh))
389
+ return [];
390
+ const raus = [];
391
+ for (const e of roh) {
392
+ const o = objekt(e);
393
+ if (!o)
394
+ continue;
395
+ const { articleId, locationId, requested, available } = o;
396
+ if (typeof articleId !== 'string' || typeof locationId !== 'string')
397
+ continue;
398
+ if (!Number.isSafeInteger(requested) || !Number.isSafeInteger(available))
399
+ continue;
400
+ raus.push({ articleId, locationId, requested: requested, available: available });
401
+ }
402
+ return raus;
403
+ }
274
404
  // ---- Listen ------------------------------------------------------------------
275
405
  /** Eine zugesagte Liste `data.<feld>`, Eintrag fuer Eintrag gelesen. */
276
406
  function liste(name, daten, feld, lesen) {
@@ -0,0 +1,86 @@
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
+ * Darum meldet jeder schreibende Aufruf in diesen Faellen (auch HTTP 5xx und
27
+ * unlesbare Antwort) `outcome: 'unknown'`, nie `'rejected'`. Lesen und der
28
+ * Probelauf (`previewGoodsReceipt`) bleiben `'rejected'`.
29
+ */
30
+ import type { InternerTransport } from '../client/aufrufe.js';
31
+ import type { Article, ChangeStockConditionRequest, CreateArticleRequest, CreateReservationRequest, DeactivateArticleRequest, ExtendReservationRequest, GoodsReceiptPreview, GoodsReceiptPreviewRequest, ReceiveGoodsRequest, RecordStockLossRequest, ReleaseReservationRequest, Reservation, ReservationListQuery, ReservationPage, ReverseStockMovementRequest, StockOperation, TransferStockRequest, UpdateArticleRequest } from './typen.js';
32
+ /**
33
+ * Legt einen Artikel an. Mit `ean` ein Fremdartikel mit diesem Code (gueltige
34
+ * Pruefziffer, frei im Konto), sonst vergibt der Server den naechsten eigenen
35
+ * Code. Antwort: der Artikel wie `getArticle`.
36
+ */
37
+ export declare function createArticle(transport: InternerTransport, request: CreateArticleRequest): Promise<Article>;
38
+ /** Aendert nur die genannten Felder eines Artikels (siehe [UpdateArticleRequest]). */
39
+ export declare function updateArticle(transport: InternerTransport, request: UpdateArticleRequest): Promise<Article>;
40
+ /** Legt einen Artikel still (`active: false`); Code und eigene Kennungen werden frei. */
41
+ export declare function deactivateArticle(transport: InternerTransport, request: DeactivateArticleRequest): Promise<Article>;
42
+ /**
43
+ * Bucht einen Wareneingang. Ohne `locationId` am Standard-Standort; die
44
+ * Antwort traegt keine Werte, auch mit dem Recht `costs` nicht. Fuer die
45
+ * Vorschau mit Werten: [previewGoodsReceipt].
46
+ */
47
+ export declare function receiveGoods(transport: InternerTransport, request: ReceiveGoodsRequest): Promise<StockOperation>;
48
+ /**
49
+ * Vorschau eines Wareneingangs (`receiveGoods` mit `dryRun: true`): prueft
50
+ * Positionen, Artikel, Preise und Nebenkosten und rechnet die Verteilung,
51
+ * schreibt aber nichts. Ein `idempotencyKey` ist freigestellt (`null` wie nicht
52
+ * angegeben, geht nicht hinaus) und wird nicht verbraucht; dieselbe Anfrage
53
+ * laesst sich danach mit ihm buchen. Werte
54
+ * (`baseCents` …) nur mit dem Recht `costs`. Den Standort prueft erst die Buchung.
55
+ */
56
+ export declare function previewGoodsReceipt(transport: InternerTransport, request: GoodsReceiptPreviewRequest): Promise<GoodsReceiptPreview>;
57
+ /** Bucht Ware von einem Standort an einen anderen; ueberzieht nie (`exceeds_stock`). */
58
+ export declare function transferStock(transport: InternerTransport, request: TransferStockRequest): Promise<StockOperation>;
59
+ /** Bucht einen Abgang (Bruch, Schwund, Diebstahl, Entnahme …); ueberzieht nie (`exceeds_stock`). */
60
+ export declare function recordStockLoss(transport: InternerTransport, request: RecordStockLossRequest): Promise<StockOperation>;
61
+ /** Bucht Ware zwischen `sellable` und `defective` um; ueberzieht nie (`exceeds_stock`). */
62
+ export declare function changeStockCondition(transport: InternerTransport, request: ChangeStockConditionRequest): Promise<StockOperation>;
63
+ /** Nimmt einen ganzen Vorgang (`operationId`) mit einer Gegenbuchung zurueck. */
64
+ export declare function reverseStockMovement(transport: InternerTransport, request: ReverseStockMovementRequest): Promise<StockOperation>;
65
+ /**
66
+ * Reserviert Ware (Checkout im Shop): ganz oder gar nicht, gemessen am
67
+ * verfuegbaren Bestand (`onHand − reserved`). Fehlt etwas, entsteht nichts:
68
+ * `insufficient_available`, die fehlenden Positionen in [inventoryShortfalls].
69
+ * Eingeloest wird ueber eine Rechnung (`issueInvoice` mit `items[].reservationId`),
70
+ * sonst laeuft die Reservierung ab und gibt die Ware wieder frei.
71
+ */
72
+ export declare function createReservation(transport: InternerTransport, request: CreateReservationRequest): Promise<Reservation>;
73
+ /** Verlaengert eine aktive Reservierung: neuer Ablauf = jetzt + `expiresInMinutes`. */
74
+ export declare function extendReservation(transport: InternerTransport, request: ExtendReservationRequest): Promise<Reservation>;
75
+ /**
76
+ * Gibt reservierte Ware frei: ohne `items` alles, sonst je Position (ohne
77
+ * `quantity` der ganze offene Rest). Die Antwort traegt den Stand danach;
78
+ * `released` wird der Status erst, wenn nichts mehr offen ist.
79
+ */
80
+ export declare function releaseReservation(transport: InternerTransport, request: ReleaseReservationRequest): Promise<Reservation>;
81
+ /** Eine Reservierung mit ihrem aktuellen Stand. */
82
+ export declare function getReservation(transport: InternerTransport, reservationId: string): Promise<Reservation>;
83
+ /** Reservierungen, neueste zuerst; Filter `status` und/oder `reference`. */
84
+ export declare function listReservations(transport: InternerTransport, query?: ReservationListQuery): Promise<ReservationPage>;
85
+ /** Alle Reservierungen der Abfrage, Seite fuer Seite ueber `nextCursor`. */
86
+ export declare function iterateReservations(transport: InternerTransport, query?: ReservationListQuery): AsyncGenerator<Reservation, void, undefined>;
@@ -0,0 +1,320 @@
1
+ "use strict";
2
+ /**
3
+ * Die schreibenden Aufrufe der Lager-API (Backend Stufe 5b): Artikel anlegen,
4
+ * aendern und stilllegen, Bestand buchen (Wareneingang, Umbuchung, Abgang,
5
+ * Zustand, Gegenbuchung) und reservieren.
6
+ *
7
+ * Wie in `endpunkte.ts` nimmt jede Funktion den Transport als ersten Parameter
8
+ * und ist einzeln importierbar. Die Anfrage geht flach hinaus, genau mit den
9
+ * Feldern, die der Aufrufer nennt (`undefined` faellt weg, `null` bleibt: es
10
+ * leert ein Feld bzw. heisst bei der Anlage „nicht angegeben“).
11
+ *
12
+ * **Vor dem Senden geprueft wird nur, was ohne Netz sicher falsch ist:**
13
+ * - `idempotencyKey` fehlt, ist leer oder laenger als 120 Zeichen. Ohne
14
+ * Schluessel gaebe es keine sichere Wiederholung, und der Server wiese die
15
+ * Anfrage ohnehin ab (`idempotency_key_required` bzw. `validation`);
16
+ * - eine Pflichtkennung fehlt oder ist leer;
17
+ * - eine Menge, ein Betrag oder ein Preis ist keine Ganzzahl (Tausendstel,
18
+ * Cent, Mikro-Euro): `1.5` waere nie „1,5 Stueck“, sondern ein Fehler;
19
+ * - `expiresInMinutes` liegt ausserhalb 5 … 43 200;
20
+ * - eine Aenderung nennt kein Feld, eine Freigabe eine leere Liste.
21
+ * Alles Fachliche (Pruefziffer der EAN, Kataloge, Bestand, Rechte) prueft der
22
+ * Server und meldet es mit seinem Code.
23
+ *
24
+ * **Wiederholen:** nach einem Zeitlimit oder Netzfehler denselben Aufruf mit
25
+ * **demselben** `idempotencyKey` noch einmal senden. Er wirkt genau einmal und
26
+ * liefert die gespeicherte Antwort; ein neuer Schluessel buchte ein zweites Mal.
27
+ * Darum meldet jeder schreibende Aufruf in diesen Faellen (auch HTTP 5xx und
28
+ * unlesbare Antwort) `outcome: 'unknown'`, nie `'rejected'`. Lesen und der
29
+ * Probelauf (`previewGoodsReceipt`) bleiben `'rejected'`.
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.createArticle = createArticle;
33
+ exports.updateArticle = updateArticle;
34
+ exports.deactivateArticle = deactivateArticle;
35
+ exports.receiveGoods = receiveGoods;
36
+ exports.previewGoodsReceipt = previewGoodsReceipt;
37
+ exports.transferStock = transferStock;
38
+ exports.recordStockLoss = recordStockLoss;
39
+ exports.changeStockCondition = changeStockCondition;
40
+ exports.reverseStockMovement = reverseStockMovement;
41
+ exports.createReservation = createReservation;
42
+ exports.extendReservation = extendReservation;
43
+ exports.releaseReservation = releaseReservation;
44
+ exports.getReservation = getReservation;
45
+ exports.listReservations = listReservations;
46
+ exports.iterateReservations = iterateReservations;
47
+ const endpunkte_js_1 = require("./endpunkte.js");
48
+ const lesen_js_1 = require("./lesen.js");
49
+ const vertrag_js_1 = require("./vertrag.js");
50
+ // ---- Pruefung vor dem Senden ------------------------------------------------------
51
+ /** Die Anfrage als flache Kopie ohne `undefined`; der Aufrufer behaelt sein Objekt. */
52
+ function anfrage(name, request) {
53
+ if (request === null || typeof request !== 'object' || Array.isArray(request)) {
54
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'Anfrage muss ein Objekt sein');
55
+ }
56
+ const raus = {};
57
+ for (const [feld, w] of Object.entries(request))
58
+ if (w !== undefined)
59
+ raus[feld] = w;
60
+ return raus;
61
+ }
62
+ /**
63
+ * `idempotencyKey`: Text mit 1–120 Zeichen, nicht nur Leerraum. Er wird nie
64
+ * getrimmt oder gekuerzt: ein veraenderter Schluessel waere ein anderer.
65
+ */
66
+ function schluessel(name, p, { pflicht }) {
67
+ const w = p.idempotencyKey;
68
+ if (w === undefined || w === null) {
69
+ if (pflicht) {
70
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'idempotencyKey fehlt (Pflicht bei jedem Schreiben); bei einer Wiederholung denselben Schluessel senden');
71
+ }
72
+ return;
73
+ }
74
+ if (typeof w !== 'string' || w.trim() === '')
75
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'idempotencyKey muss ein nicht leerer Text sein');
76
+ if (w.length > vertrag_js_1.INVENTORY_IDEMPOTENCY_KEY_MAX) {
77
+ throw (0, endpunkte_js_1.anfragefehler)(name, `idempotencyKey ist laenger als ${vertrag_js_1.INVENTORY_IDEMPOTENCY_KEY_MAX} Zeichen`);
78
+ }
79
+ }
80
+ /** Eine Ganzzahl (Tausendstel, Cent, Mikro-Euro); fehlt sie, nur wenn sie darf. `null` nur, wo es leert. */
81
+ function ganz(name, feld, w, { pflicht = false, leerbar = false } = {}) {
82
+ if (w === undefined || (w === null && leerbar)) {
83
+ if (pflicht)
84
+ throw (0, endpunkte_js_1.anfragefehler)(name, `${feld} fehlt`);
85
+ return;
86
+ }
87
+ if (typeof w !== 'number' || !Number.isSafeInteger(w)) {
88
+ throw (0, endpunkte_js_1.anfragefehler)(name, `${feld} muss eine ganze Zahl sein (Mengen in Tausendstel, Geld in Cent, Preise in Mikro-Euro)`);
89
+ }
90
+ }
91
+ /** Haltedauer einer Reservierung in ganzen Minuten. */
92
+ function minuten(name, w, { pflicht }) {
93
+ if (w === undefined || (w === null && !pflicht)) {
94
+ if (pflicht)
95
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'expiresInMinutes fehlt');
96
+ return;
97
+ }
98
+ if (typeof w !== 'number' || !Number.isSafeInteger(w) || w < vertrag_js_1.RESERVATION_MINUTES_MIN || w > vertrag_js_1.RESERVATION_MINUTES_MAX) {
99
+ throw (0, endpunkte_js_1.anfragefehler)(name, `expiresInMinutes muss eine ganze Zahl von ${vertrag_js_1.RESERVATION_MINUTES_MIN} bis ${vertrag_js_1.RESERVATION_MINUTES_MAX} sein`);
100
+ }
101
+ }
102
+ /**
103
+ * Positionen: eine Liste (Pflicht, wo der Aufruf sie verlangt), jede ein
104
+ * Objekt mit `articleId` und ganzzahliger `quantity`. `weiter` prueft die
105
+ * uebrigen Zahlenfelder einer Position. Leer und zu lang entscheidet der Server
106
+ * (`no_positions`, `too_many_positions`).
107
+ */
108
+ function positionen(name, w, { pflicht, menge }, weiter = () => { }) {
109
+ if (w === undefined) {
110
+ if (pflicht)
111
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'items fehlt');
112
+ return;
113
+ }
114
+ if (!Array.isArray(w))
115
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'items muss eine Liste sein');
116
+ w.forEach((x, i) => {
117
+ const f = `items[${i}]`;
118
+ if (x === null || typeof x !== 'object' || Array.isArray(x))
119
+ throw (0, endpunkte_js_1.anfragefehler)(name, `${f} muss ein Objekt sein`);
120
+ const o = x;
121
+ (0, endpunkte_js_1.kennung)(name, `${f}.articleId`, o.articleId);
122
+ ganz(name, `${f}.quantity`, o.quantity, { pflicht: menge === 'pflicht' });
123
+ weiter(o, f);
124
+ });
125
+ }
126
+ // ---- Artikel ------------------------------------------------------------------
127
+ /** Zahlenfelder eines Artikels; `null` leert sie (bzw. heisst bei der Anlage „nicht angegeben“). */
128
+ function artikelZahlen(name, p) {
129
+ for (const feld of ['unitPriceCents', 'minStock', 'purchasePriceMicros'])
130
+ ganz(name, feld, p[feld], { leerbar: true });
131
+ const jeStandort = p.minStockByLocation;
132
+ if (jeStandort === undefined || jeStandort === null)
133
+ return;
134
+ if (typeof jeStandort !== 'object' || Array.isArray(jeStandort))
135
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'minStockByLocation muss ein Objekt { standort: Tausendstel } sein');
136
+ for (const [standort, w] of Object.entries(jeStandort))
137
+ ganz(name, `minStockByLocation.${standort}`, w, { leerbar: true });
138
+ }
139
+ const artikelAus = (name, daten) => (0, lesen_js_1.artikel)({ name, pfad: 'article' }, (0, lesen_js_1.objekt)(daten)?.article);
140
+ /**
141
+ * Legt einen Artikel an. Mit `ean` ein Fremdartikel mit diesem Code (gueltige
142
+ * Pruefziffer, frei im Konto), sonst vergibt der Server den naechsten eigenen
143
+ * Code. Antwort: der Artikel wie `getArticle`.
144
+ */
145
+ async function createArticle(transport, request) {
146
+ const name = 'createArticle';
147
+ const p = anfrage(name, request);
148
+ schluessel(name, p, { pflicht: true });
149
+ artikelZahlen(name, p);
150
+ return artikelAus(name, await transport(name, p));
151
+ }
152
+ /** Aendert nur die genannten Felder eines Artikels (siehe [UpdateArticleRequest]). */
153
+ async function updateArticle(transport, request) {
154
+ const name = 'updateArticle';
155
+ const p = anfrage(name, request);
156
+ schluessel(name, p, { pflicht: true });
157
+ (0, endpunkte_js_1.kennung)(name, 'articleId', p.articleId);
158
+ if (Object.keys(p).every((k) => k === 'idempotencyKey' || k === 'articleId')) {
159
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'die Aenderung nennt kein Feld');
160
+ }
161
+ artikelZahlen(name, p);
162
+ return artikelAus(name, await transport(name, p));
163
+ }
164
+ /** Legt einen Artikel still (`active: false`); Code und eigene Kennungen werden frei. */
165
+ async function deactivateArticle(transport, request) {
166
+ const name = 'deactivateArticle';
167
+ const p = anfrage(name, request);
168
+ schluessel(name, p, { pflicht: true });
169
+ (0, endpunkte_js_1.kennung)(name, 'articleId', p.articleId);
170
+ return artikelAus(name, await transport(name, p));
171
+ }
172
+ // ---- Buchen ------------------------------------------------------------------
173
+ function eingangZahlen(name, p) {
174
+ positionen(name, p.items, { pflicht: true, menge: 'pflicht' }, (x, f) => {
175
+ for (const feld of ['totalCents', 'unitPriceMicros', 'landedCostCents'])
176
+ ganz(name, `${f}.${feld}`, x[feld]);
177
+ });
178
+ const nebenkosten = p.landedCosts;
179
+ if (nebenkosten === undefined)
180
+ return;
181
+ if (!Array.isArray(nebenkosten))
182
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'landedCosts muss eine Liste sein');
183
+ nebenkosten.forEach((n, i) => {
184
+ if (n === null || typeof n !== 'object' || Array.isArray(n))
185
+ throw (0, endpunkte_js_1.anfragefehler)(name, `landedCosts[${i}] muss ein Objekt sein`);
186
+ ganz(name, `landedCosts[${i}].amountCents`, n.amountCents, { pflicht: true });
187
+ });
188
+ }
189
+ /**
190
+ * Bucht einen Wareneingang. Ohne `locationId` am Standard-Standort; die
191
+ * Antwort traegt keine Werte, auch mit dem Recht `costs` nicht. Fuer die
192
+ * Vorschau mit Werten: [previewGoodsReceipt].
193
+ */
194
+ async function receiveGoods(transport, request) {
195
+ const name = 'receiveGoods';
196
+ const p = anfrage(name, request);
197
+ if (p.dryRun === true) {
198
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'dryRun: true bucht nichts und liefert eine Vorschau statt eines Vorgangs; dafuer previewGoodsReceipt aufrufen');
199
+ }
200
+ schluessel(name, p, { pflicht: true });
201
+ eingangZahlen(name, p);
202
+ return (0, lesen_js_1.vorgang)(name, await transport(name, p));
203
+ }
204
+ /**
205
+ * Vorschau eines Wareneingangs (`receiveGoods` mit `dryRun: true`): prueft
206
+ * Positionen, Artikel, Preise und Nebenkosten und rechnet die Verteilung,
207
+ * schreibt aber nichts. Ein `idempotencyKey` ist freigestellt (`null` wie nicht
208
+ * angegeben, geht nicht hinaus) und wird nicht verbraucht; dieselbe Anfrage
209
+ * laesst sich danach mit ihm buchen. Werte
210
+ * (`baseCents` …) nur mit dem Recht `costs`. Den Standort prueft erst die Buchung.
211
+ */
212
+ async function previewGoodsReceipt(transport, request) {
213
+ const name = 'receiveGoods';
214
+ const p = anfrage(name, request);
215
+ // null heisst hier „kein Schluessel“ und geht wie undefined nicht hinaus.
216
+ if (p.idempotencyKey === null)
217
+ delete p.idempotencyKey;
218
+ schluessel(name, p, { pflicht: false });
219
+ eingangZahlen(name, p);
220
+ // Ein Probelauf schreibt nichts: nach einem Zeitlimit bleibt er `rejected`,
221
+ // obwohl `receiveGoods` sonst Wirkung hat.
222
+ const daten = await transport(name, { ...p, dryRun: true }, undefined, undefined, { hasEffect: false });
223
+ return { preview: (0, lesen_js_1.liste)(name, daten, 'preview', lesen_js_1.vorschauZeile) };
224
+ }
225
+ const stueckPositionen = (name, p) => positionen(name, p.items, { pflicht: true, menge: 'pflicht' });
226
+ /** Bucht Ware von einem Standort an einen anderen; ueberzieht nie (`exceeds_stock`). */
227
+ async function transferStock(transport, request) {
228
+ const name = 'transferStock';
229
+ const p = anfrage(name, request);
230
+ schluessel(name, p, { pflicht: true });
231
+ (0, endpunkte_js_1.kennung)(name, 'fromLocationId', p.fromLocationId);
232
+ (0, endpunkte_js_1.kennung)(name, 'toLocationId', p.toLocationId);
233
+ stueckPositionen(name, p);
234
+ return (0, lesen_js_1.vorgang)(name, await transport(name, p));
235
+ }
236
+ /** Bucht einen Abgang (Bruch, Schwund, Diebstahl, Entnahme …); ueberzieht nie (`exceeds_stock`). */
237
+ async function recordStockLoss(transport, request) {
238
+ const name = 'recordStockLoss';
239
+ const p = anfrage(name, request);
240
+ schluessel(name, p, { pflicht: true });
241
+ stueckPositionen(name, p);
242
+ return (0, lesen_js_1.vorgang)(name, await transport(name, p));
243
+ }
244
+ /** Bucht Ware zwischen `sellable` und `defective` um; ueberzieht nie (`exceeds_stock`). */
245
+ async function changeStockCondition(transport, request) {
246
+ const name = 'changeStockCondition';
247
+ const p = anfrage(name, request);
248
+ schluessel(name, p, { pflicht: true });
249
+ stueckPositionen(name, p);
250
+ return (0, lesen_js_1.vorgang)(name, await transport(name, p));
251
+ }
252
+ /** Nimmt einen ganzen Vorgang (`operationId`) mit einer Gegenbuchung zurueck. */
253
+ async function reverseStockMovement(transport, request) {
254
+ const name = 'reverseStockMovement';
255
+ const p = anfrage(name, request);
256
+ schluessel(name, p, { pflicht: true });
257
+ (0, endpunkte_js_1.kennung)(name, 'operationId', p.operationId);
258
+ return (0, lesen_js_1.vorgang)(name, await transport(name, p));
259
+ }
260
+ // ---- Reservierung ---------------------------------------------------------------
261
+ const reservierungAus = (name, daten) => (0, lesen_js_1.reservierung)({ name, pfad: 'reservation' }, (0, lesen_js_1.objekt)(daten)?.reservation);
262
+ /**
263
+ * Reserviert Ware (Checkout im Shop): ganz oder gar nicht, gemessen am
264
+ * verfuegbaren Bestand (`onHand − reserved`). Fehlt etwas, entsteht nichts:
265
+ * `insufficient_available`, die fehlenden Positionen in [inventoryShortfalls].
266
+ * Eingeloest wird ueber eine Rechnung (`issueInvoice` mit `items[].reservationId`),
267
+ * sonst laeuft die Reservierung ab und gibt die Ware wieder frei.
268
+ */
269
+ async function createReservation(transport, request) {
270
+ const name = 'createReservation';
271
+ const p = anfrage(name, request);
272
+ schluessel(name, p, { pflicht: true });
273
+ positionen(name, p.items, { pflicht: true, menge: 'pflicht' });
274
+ minuten(name, p.expiresInMinutes, { pflicht: false });
275
+ return reservierungAus(name, await transport(name, p));
276
+ }
277
+ /** Verlaengert eine aktive Reservierung: neuer Ablauf = jetzt + `expiresInMinutes`. */
278
+ async function extendReservation(transport, request) {
279
+ const name = 'extendReservation';
280
+ const p = anfrage(name, request);
281
+ schluessel(name, p, { pflicht: true });
282
+ (0, endpunkte_js_1.kennung)(name, 'reservationId', p.reservationId);
283
+ minuten(name, p.expiresInMinutes, { pflicht: true });
284
+ return reservierungAus(name, await transport(name, p));
285
+ }
286
+ /**
287
+ * Gibt reservierte Ware frei: ohne `items` alles, sonst je Position (ohne
288
+ * `quantity` der ganze offene Rest). Die Antwort traegt den Stand danach;
289
+ * `released` wird der Status erst, wenn nichts mehr offen ist.
290
+ */
291
+ async function releaseReservation(transport, request) {
292
+ const name = 'releaseReservation';
293
+ const p = anfrage(name, request);
294
+ schluessel(name, p, { pflicht: true });
295
+ (0, endpunkte_js_1.kennung)(name, 'reservationId', p.reservationId);
296
+ if (Array.isArray(p.items) && p.items.length === 0) {
297
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'items ist leer; um alles freizugeben, items ganz weglassen');
298
+ }
299
+ positionen(name, p.items, { pflicht: false, menge: 'frei' });
300
+ return reservierungAus(name, await transport(name, p));
301
+ }
302
+ /** Eine Reservierung mit ihrem aktuellen Stand. */
303
+ async function getReservation(transport, reservationId) {
304
+ const name = 'getReservation';
305
+ return reservierungAus(name, await transport(name, { reservationId: (0, endpunkte_js_1.kennung)(name, 'reservationId', reservationId) }));
306
+ }
307
+ /** Reservierungen, neueste zuerst; Filter `status` und/oder `reference`. */
308
+ async function listReservations(transport, query) {
309
+ const name = 'listReservations';
310
+ const daten = await transport(name, (0, endpunkte_js_1.abfrage)(name, query));
311
+ return { reservations: (0, lesen_js_1.liste)(name, daten, 'reservations', lesen_js_1.reservierung), nextCursor: (0, lesen_js_1.naechsterCursor)(name, daten) };
312
+ }
313
+ /** Alle Reservierungen der Abfrage, Seite fuer Seite ueber `nextCursor`. */
314
+ function iterateReservations(transport, query) {
315
+ const { cursor, ...rest } = query ?? {};
316
+ return (0, endpunkte_js_1.seitenweise)('listReservations', cursor, async (c) => {
317
+ const s = await listReservations(transport, { ...rest, cursor: c });
318
+ return { eintraege: s.reservations, nextCursor: s.nextCursor };
319
+ });
320
+ }