@kreiseck/kasseneck-api 1.7.1 → 1.8.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 (113) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +170 -1
  3. package/dist/cjs/client/aufrufe.d.ts +9 -7
  4. package/dist/cjs/client/aufrufe.js +57 -2
  5. package/dist/cjs/client/errors.js +2 -0
  6. package/dist/cjs/client/index.d.ts +1 -1
  7. package/dist/cjs/client/index.js +2 -1
  8. package/dist/cjs/client/transport.d.ts +27 -3
  9. package/dist/cjs/client/transport.js +29 -3
  10. package/dist/cjs/index.d.ts +1 -1
  11. package/dist/cjs/index.js +3 -2
  12. package/dist/cjs/inventory/api.d.ts +21 -2
  13. package/dist/cjs/inventory/api.js +22 -3
  14. package/dist/cjs/inventory/fehler.d.ts +6 -0
  15. package/dist/cjs/inventory/fehler.js +12 -0
  16. package/dist/cjs/inventory/index.d.ts +6 -4
  17. package/dist/cjs/inventory/index.js +33 -3
  18. package/dist/cjs/inventory/inventur.d.ts +104 -0
  19. package/dist/cjs/inventory/inventur.js +245 -0
  20. package/dist/cjs/inventory/lesen.d.ts +20 -1
  21. package/dist/cjs/inventory/lesen.js +307 -0
  22. package/dist/cjs/inventory/typen.d.ts +410 -1
  23. package/dist/cjs/inventory/vertrag.d.ts +94 -8
  24. package/dist/cjs/inventory/vertrag.js +117 -5
  25. package/dist/cjs/invoice/vertrag.d.ts +1 -1
  26. package/dist/cjs/invoice/vertrag.js +2 -0
  27. package/dist/cjs/models/cancellation.d.ts +1 -1
  28. package/dist/cjs/models/cancellation.js +2 -0
  29. package/dist/cjs/models/payment-errors.d.ts +1 -1
  30. package/dist/cjs/models/payment-errors.js +2 -0
  31. package/dist/cjs/models/receipt-email.d.ts +1 -1
  32. package/dist/cjs/models/receipt-email.js +2 -0
  33. package/dist/cjs/models/receipt-errors.d.ts +1 -1
  34. package/dist/cjs/models/receipt-errors.js +2 -0
  35. package/dist/cjs/pos/errors.d.ts +4 -3
  36. package/dist/cjs/pos/errors.js +23 -2
  37. package/dist/cjs/pos/index.d.ts +4 -1
  38. package/dist/cjs/pos/index.js +9 -2
  39. package/dist/cjs/pos/inventur.d.ts +104 -0
  40. package/dist/cjs/pos/inventur.js +117 -0
  41. package/dist/cjs/pos/texte.d.ts +82 -0
  42. package/dist/cjs/pos/texte.js +35 -0
  43. package/dist/cjs/register/errors.d.ts +1 -1
  44. package/dist/cjs/register/errors.js +2 -0
  45. package/dist/cjs/stored/vokabular.d.ts +1 -1
  46. package/dist/cjs/stored/vokabular.js +1 -1
  47. package/dist/cjs/version.d.ts +1 -1
  48. package/dist/cjs/version.js +1 -1
  49. package/dist/esm/client/aufrufe.d.ts +9 -7
  50. package/dist/esm/client/aufrufe.js +57 -2
  51. package/dist/esm/client/errors.js +2 -0
  52. package/dist/esm/client/index.d.ts +1 -1
  53. package/dist/esm/client/index.js +1 -1
  54. package/dist/esm/client/transport.d.ts +27 -3
  55. package/dist/esm/client/transport.js +28 -3
  56. package/dist/esm/index.d.ts +1 -1
  57. package/dist/esm/index.js +1 -1
  58. package/dist/esm/inventory/api.d.ts +21 -2
  59. package/dist/esm/inventory/api.js +23 -4
  60. package/dist/esm/inventory/fehler.d.ts +6 -0
  61. package/dist/esm/inventory/fehler.js +11 -0
  62. package/dist/esm/inventory/index.d.ts +6 -4
  63. package/dist/esm/inventory/index.js +5 -3
  64. package/dist/esm/inventory/inventur.d.ts +104 -0
  65. package/dist/esm/inventory/inventur.js +226 -0
  66. package/dist/esm/inventory/lesen.d.ts +20 -1
  67. package/dist/esm/inventory/lesen.js +301 -0
  68. package/dist/esm/inventory/typen.d.ts +410 -1
  69. package/dist/esm/inventory/vertrag.d.ts +94 -8
  70. package/dist/esm/inventory/vertrag.js +116 -4
  71. package/dist/esm/invoice/vertrag.d.ts +1 -1
  72. package/dist/esm/invoice/vertrag.js +2 -0
  73. package/dist/esm/models/cancellation.d.ts +1 -1
  74. package/dist/esm/models/cancellation.js +2 -0
  75. package/dist/esm/models/payment-errors.d.ts +1 -1
  76. package/dist/esm/models/payment-errors.js +2 -0
  77. package/dist/esm/models/receipt-email.d.ts +1 -1
  78. package/dist/esm/models/receipt-email.js +2 -0
  79. package/dist/esm/models/receipt-errors.d.ts +1 -1
  80. package/dist/esm/models/receipt-errors.js +2 -0
  81. package/dist/esm/pos/errors.d.ts +4 -3
  82. package/dist/esm/pos/errors.js +23 -2
  83. package/dist/esm/pos/index.d.ts +4 -1
  84. package/dist/esm/pos/index.js +2 -1
  85. package/dist/esm/pos/inventur.d.ts +104 -0
  86. package/dist/esm/pos/inventur.js +109 -0
  87. package/dist/esm/pos/texte.d.ts +82 -0
  88. package/dist/esm/pos/texte.js +35 -0
  89. package/dist/esm/register/errors.d.ts +1 -1
  90. package/dist/esm/register/errors.js +2 -0
  91. package/dist/esm/stored/vokabular.d.ts +1 -1
  92. package/dist/esm/stored/vokabular.js +1 -1
  93. package/dist/esm/version.d.ts +1 -1
  94. package/dist/esm/version.js +1 -1
  95. package/fixtures/hobex-hps-codes.json +1 -1
  96. package/fixtures/invoice-api.schema.json +1 -1
  97. package/fixtures/invoice-texts.json +1 -1
  98. package/fixtures/pos-texts.json +94 -1
  99. package/fixtures/stocktake-quantity-cases.json +55 -0
  100. package/fixtures/surface.json +151 -8
  101. package/fixtures/v3/antworten/belege.json +1 -1
  102. package/fixtures/v3/antworten/belegmail.json +1 -1
  103. package/fixtures/v3/antworten/kasse-belege.json +1 -1
  104. package/fixtures/v3/antworten/kasse.json +2958 -3
  105. package/fixtures/v3/antworten/lager.json +1727 -3
  106. package/fixtures/v3/antworten/rechnungen.json +1 -1
  107. package/fixtures/v3/antworten/storno.json +1 -1
  108. package/fixtures/v3/stored/belege.json +1 -1
  109. package/fixtures/v3/stored/kasse.json +414 -3
  110. package/fixtures/v3/stored/rechnungen.json +1 -1
  111. package/fixtures/v3/v3-vokabular.json +475 -8
  112. package/fixtures/v3/zahlbetrag-faelle.json +1 -1
  113. package/package.json +1 -1
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ /**
3
+ * Inventur der Lager-API (Lager-Kern Stufe 3): anlegen, zaehlen, Zaehlungen
4
+ * stornieren, pruefen, nachzaehlen, abschliessen, abbrechen, Inventurprotokoll
5
+ * holen; dazu die Listen der Inventuren, Positionen und Zaehlungen.
6
+ *
7
+ * Ablauf: `createStocktake` → `recordStocktakeCount` (beliebig oft, auch von
8
+ * mehreren Geraeten; je Position addiert) → `reviewStocktake` (erst jetzt Soll
9
+ * und Differenz) → bei Bedarf `recountStocktake` und erneut `reviewStocktake`
10
+ * → `closeStocktake` → `getStocktakePdf`. Pruefen und Abschliessen rechnet der
11
+ * Server im Hintergrund: die Antwort traegt den Zwischenstand
12
+ * (`review.complete: false` bzw. `status: 'closing'`), `getStocktake` den
13
+ * Fortgang.
14
+ *
15
+ * **Blind:** vor `review` traegt keine Antwort ein Soll; `blind: false` zeigt
16
+ * beim Zaehlen nur den heutigen Buchbestand (`bookStockNow`), nie das Soll
17
+ * zur Referenzzeit.
18
+ *
19
+ * Rechte wie in Stufe 5b: Lesen mit jedem Schluessel, alles Schreibende (auch
20
+ * Zaehlen) mit dem Konto-Schalter „Lager-API schreiben“ (Test-Schluessel
21
+ * immer), sonst `inventory_api_not_enabled`; Werte und das Protokoll mit
22
+ * Werten nur mit `lagerApi.kosten`.
23
+ *
24
+ * **Vor dem Senden geprueft wird nur, was ohne Netz sicher falsch ist:**
25
+ * `idempotencyKey` (Pflicht bei jedem Schreiben, 1–120 Zeichen), fehlende
26
+ * Kennungen (`stocktakeId`, `articleId`, `countId`, `locationId`), `scope`
27
+ * kein Objekt, eine Menge, die keine Ganzzahl ist (Tausendstel), ein leerer
28
+ * Grund, `items` beim Nachzaehlen leer oder ohne `articleId`,
29
+ * `serialNumbers` keine Liste, `uncountedAsZero` kein Wahrheitswert. Alles
30
+ * Fachliche (Umfang, Stichtag, Seriennummern, Zustand) prueft der Server.
31
+ *
32
+ * **Wiederholen** wie bei jedem Schreiben: nach `outcome: 'unknown'` denselben
33
+ * Aufruf mit **demselben** `idempotencyKey`. Eine Zaehlung wirkt dann genau
34
+ * einmal (das Zaehldokument ist selbst der Nachweis); ein neuer Schluessel
35
+ * zaehlte die Ware ein zweites Mal.
36
+ */
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.createStocktake = createStocktake;
39
+ exports.listStocktakes = listStocktakes;
40
+ exports.iterateStocktakes = iterateStocktakes;
41
+ exports.getStocktake = getStocktake;
42
+ exports.listStocktakeItems = listStocktakeItems;
43
+ exports.iterateStocktakeItems = iterateStocktakeItems;
44
+ exports.listStocktakeCounts = listStocktakeCounts;
45
+ exports.iterateStocktakeCounts = iterateStocktakeCounts;
46
+ exports.zaehlungPruefen = zaehlungPruefen;
47
+ exports.stornoPruefen = stornoPruefen;
48
+ exports.recordStocktakeCount = recordStocktakeCount;
49
+ exports.voidStocktakeCount = voidStocktakeCount;
50
+ exports.reviewStocktake = reviewStocktake;
51
+ exports.recountStocktake = recountStocktake;
52
+ exports.closeStocktake = closeStocktake;
53
+ exports.cancelStocktake = cancelStocktake;
54
+ exports.getStocktakePdf = getStocktakePdf;
55
+ const endpunkte_js_1 = require("./endpunkte.js");
56
+ const lesen_js_1 = require("./lesen.js");
57
+ const schreiben_js_1 = require("./schreiben.js");
58
+ const istObjekt = (w) => w !== null && typeof w === 'object' && !Array.isArray(w);
59
+ const kopfAus = (name, daten) => (0, lesen_js_1.inventur)({ name, pfad: 'stocktake' }, (0, lesen_js_1.objekt)(daten)?.stocktake);
60
+ /** Ein Grund (Storno, Nachzaehlen, Abbruch): nicht leer. Die Laenge prueft der Server. */
61
+ function grund(name, w) {
62
+ if (typeof w !== 'string' || w.trim() === '')
63
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'reason fehlt (Grund mit 1 bis 500 Zeichen)');
64
+ }
65
+ /** Schreibende Anfrage mit `stocktakeId`: Schluessel und Kennung geprueft, flach wie gesendet. */
66
+ function schreibend(name, request) {
67
+ const p = (0, schreiben_js_1.anfrage)(name, request);
68
+ (0, schreiben_js_1.schluessel)(name, p, { pflicht: true });
69
+ (0, endpunkte_js_1.kennung)(name, 'stocktakeId', p.stocktakeId);
70
+ return p;
71
+ }
72
+ /** Eine Liste unter einer Inventur: `stocktakeId` Pflicht, sonst wie jede Abfrage. */
73
+ function abfrageMitInventur(name, query) {
74
+ if (!istObjekt(query))
75
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'Abfrage mit stocktakeId erwartet');
76
+ const p = (0, endpunkte_js_1.abfrage)(name, query);
77
+ (0, endpunkte_js_1.kennung)(name, 'stocktakeId', p.stocktakeId);
78
+ return p;
79
+ }
80
+ // ---- Anlegen und Lesen -------------------------------------------------------------
81
+ /**
82
+ * Legt eine Inventur an. Je Standort hoechstens eine offene
83
+ * (`stocktake_location_busy`, `data.stocktakeId` nennt sie); Umfang `groups`
84
+ * bzw. `articles` nur mit bestandsgefuehrten Artikeln (`article_not_tracked`).
85
+ * Jede Inventur beginnt in `counting`.
86
+ */
87
+ async function createStocktake(transport, request) {
88
+ const name = 'createStocktake';
89
+ const p = (0, schreiben_js_1.anfrage)(name, request);
90
+ (0, schreiben_js_1.schluessel)(name, p, { pflicht: true });
91
+ (0, endpunkte_js_1.kennung)(name, 'locationId', p.locationId);
92
+ if (!istObjekt(p.scope))
93
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'scope fehlt oder ist kein Objekt { type, groupIds?, articleIds? }');
94
+ return kopfAus(name, await transport(name, p));
95
+ }
96
+ /**
97
+ * Inventuren des Kontos. Ohne `updatedSince` zuletzt geaenderte zuerst; mit
98
+ * `updatedSince` aufsteigend nach `updatedAt` und inklusive (Abgleich: das
99
+ * groesste gesehene `updatedAt` als naechstes `updatedSince`). Filter
100
+ * `status` und `locationId`.
101
+ */
102
+ async function listStocktakes(transport, query) {
103
+ const name = 'listStocktakes';
104
+ const daten = await transport(name, (0, endpunkte_js_1.abfrage)(name, query));
105
+ return { stocktakes: (0, lesen_js_1.liste)(name, daten, 'stocktakes', lesen_js_1.inventur), nextCursor: (0, lesen_js_1.naechsterCursor)(name, daten) };
106
+ }
107
+ /** Alle Inventuren der Abfrage, Seite fuer Seite, in der Reihenfolge von [listStocktakes]. */
108
+ function iterateStocktakes(transport, query) {
109
+ const { cursor, ...rest } = query ?? {};
110
+ return (0, endpunkte_js_1.seitenweise)('listStocktakes', cursor, async (c) => {
111
+ const s = await listStocktakes(transport, { ...rest, cursor: c });
112
+ return { eintraege: s.stocktakes, nextCursor: s.nextCursor };
113
+ });
114
+ }
115
+ /** Eine Inventur samt `progress.counted` (gezaehlte Positionen). */
116
+ async function getStocktake(transport, stocktakeId) {
117
+ const name = 'getStocktake';
118
+ return kopfAus(name, await transport(name, { stocktakeId: (0, endpunkte_js_1.kennung)(name, 'stocktakeId', stocktakeId) }));
119
+ }
120
+ /** Positionen einer Inventur nach Kennung; `openOnly` = ungezaehlt bzw. in `review` zum Nachzaehlen offen. */
121
+ async function listStocktakeItems(transport, query) {
122
+ const name = 'listStocktakeItems';
123
+ const daten = await transport(name, abfrageMitInventur(name, query));
124
+ return { items: (0, lesen_js_1.liste)(name, daten, 'items', lesen_js_1.inventurPosition), nextCursor: (0, lesen_js_1.naechsterCursor)(name, daten) };
125
+ }
126
+ function iterateStocktakeItems(transport, query) {
127
+ const { cursor, ...rest } = query;
128
+ return (0, endpunkte_js_1.seitenweise)('listStocktakeItems', cursor, async (c) => {
129
+ const s = await listStocktakeItems(transport, { ...rest, cursor: c });
130
+ return { eintraege: s.items, nextCursor: s.nextCursor };
131
+ });
132
+ }
133
+ /** Zaehlungen einer Inventur, neueste zuerst, auch stornierte; mit `articleId` nur die des Artikels. */
134
+ async function listStocktakeCounts(transport, query) {
135
+ const name = 'listStocktakeCounts';
136
+ const daten = await transport(name, abfrageMitInventur(name, query));
137
+ return { counts: (0, lesen_js_1.liste)(name, daten, 'counts', lesen_js_1.inventurZaehlung), nextCursor: (0, lesen_js_1.naechsterCursor)(name, daten) };
138
+ }
139
+ function iterateStocktakeCounts(transport, query) {
140
+ const { cursor, ...rest } = query;
141
+ return (0, endpunkte_js_1.seitenweise)('listStocktakeCounts', cursor, async (c) => {
142
+ const s = await listStocktakeCounts(transport, { ...rest, cursor: c });
143
+ return { eintraege: s.counts, nextCursor: s.nextCursor };
144
+ });
145
+ }
146
+ // ---- Zaehlen und Stornieren ------------------------------------------------------
147
+ /** Pruefung einer Zaehlung vor dem Senden, geteilt mit dem Kassenweg. Paketintern. */
148
+ function zaehlungPruefen(name, p) {
149
+ (0, endpunkte_js_1.kennung)(name, 'articleId', p.articleId);
150
+ (0, schreiben_js_1.ganz)(name, 'quantity', p.quantity, { pflicht: true });
151
+ if (p.serialNumbers !== undefined && p.serialNumbers !== null && !Array.isArray(p.serialNumbers)) {
152
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'serialNumbers muss eine Liste sein');
153
+ }
154
+ }
155
+ /** Pruefung eines Stornos vor dem Senden, geteilt mit dem Kassenweg. Paketintern. */
156
+ function stornoPruefen(name, p) {
157
+ (0, endpunkte_js_1.kennung)(name, 'countId', p.countId);
158
+ grund(name, p.reason);
159
+ }
160
+ /**
161
+ * Eine Zaehlung. Antwort: die Zaehlung und ihre Position danach (Summe der
162
+ * Runde in `item.quantity`). Gezaehlt wird in `counting`, in `review` nur an
163
+ * Positionen, die zum Nachzaehlen frei sind (sonst `stocktake_not_open`).
164
+ */
165
+ async function recordStocktakeCount(transport, request) {
166
+ const name = 'recordStocktakeCount';
167
+ const p = schreibend(name, request);
168
+ zaehlungPruefen(name, p);
169
+ return (0, lesen_js_1.zaehlungMitPosition)(name, await transport(name, p));
170
+ }
171
+ /** Storniert eine Zaehlung mit Grund; die Position wird aus den uebrigen Zaehlungen neu summiert. */
172
+ async function voidStocktakeCount(transport, request) {
173
+ const name = 'voidStocktakeCount';
174
+ const p = schreibend(name, request);
175
+ stornoPruefen(name, p);
176
+ return (0, lesen_js_1.zaehlungMitPosition)(name, await transport(name, p));
177
+ }
178
+ // ---- Pruefen bis Abbruch ------------------------------------------------------------
179
+ /**
180
+ * Pruefen: `counting` → `review`, in `review` neu rechnen (nach dem
181
+ * Nachzaehlen). Die Antwort kommt sofort mit `review.complete: false`; Soll
182
+ * und Differenz stehen an den Positionen, sobald `complete` `true` ist.
183
+ */
184
+ async function reviewStocktake(transport, request) {
185
+ const name = 'reviewStocktake';
186
+ return kopfAus(name, await transport(name, schreibend(name, request)));
187
+ }
188
+ /** Nachzaehlen: je genannter Position eine neue Runde; danach zaehlen und erneut [reviewStocktake]. */
189
+ async function recountStocktake(transport, request) {
190
+ const name = 'recountStocktake';
191
+ const p = schreibend(name, request);
192
+ if (!Array.isArray(p.items) || p.items.length === 0)
193
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'items fehlt oder ist leer');
194
+ p.items.forEach((x, i) => {
195
+ if (!istObjekt(x))
196
+ throw (0, endpunkte_js_1.anfragefehler)(name, `items[${i}] muss ein Objekt sein`);
197
+ (0, endpunkte_js_1.kennung)(name, `items[${i}].articleId`, x.articleId);
198
+ });
199
+ grund(name, p.reason);
200
+ return kopfAus(name, await transport(name, p));
201
+ }
202
+ /**
203
+ * Abschliessen (nur aus `review`, sonst `stocktake_not_in_review`; offene
204
+ * Nachzaehlungen ergeben `stocktake_recount_open`). Der Server bucht in
205
+ * Teilen weiter; die Antwort traegt meist `status: 'closing'`. Ein erneuter
206
+ * Aufruf waehrend `closing` stoesst den Abschluss wieder an.
207
+ */
208
+ async function closeStocktake(transport, request) {
209
+ const name = 'closeStocktake';
210
+ const p = schreibend(name, request);
211
+ if (p.uncountedAsZero !== undefined && typeof p.uncountedAsZero !== 'boolean') {
212
+ throw (0, endpunkte_js_1.anfragefehler)(name, 'uncountedAsZero muss true oder false sein');
213
+ }
214
+ const daten = await transport(name, p);
215
+ return { stocktake: kopfAus(name, daten), warnings: (0, lesen_js_1.inventurWarnungen)({ name, pfad: 'warnings' }, (0, lesen_js_1.objekt)(daten)?.warnings) };
216
+ }
217
+ /** Abbrechen mit Grund (aus `counting` oder `review`); der Standort ist danach frei. */
218
+ async function cancelStocktake(transport, request) {
219
+ const name = 'cancelStocktake';
220
+ const p = schreibend(name, request);
221
+ grund(name, p.reason);
222
+ return kopfAus(name, await transport(name, p));
223
+ }
224
+ // ---- Protokoll ------------------------------------------------------------------
225
+ /**
226
+ * Das Inventurprotokoll (PDF), erst nach dem Abschluss (`stocktake_not_closed`,
227
+ * auch fuer eine abgebrochene Inventur). Mit `lagerApi.kosten` die Fassung mit
228
+ * Werten, sonst die nur mit Mengen. Bis 9 MiB kommt die Datei selbst
229
+ * (`kind: 'pdf'`), darueber ein signierter Lese-Link fuer 15 Minuten
230
+ * (`kind: 'download'`); die geladene Datei an `download.sha256` pruefen.
231
+ *
232
+ * Als freie Funktion braucht sie den Transport aus `createPdfOrDataTransport`
233
+ * (Paketwurzel): ein JSON-Transport laese die Datei als Text, ein reiner
234
+ * Binaer-Transport kennt den Link nicht. [createInventoryClient] bindet ihn selbst.
235
+ */
236
+ async function getStocktakePdf(transport, stocktakeId) {
237
+ const name = 'getStocktakePdf';
238
+ const ergebnis = await transport(name, { stocktakeId: (0, endpunkte_js_1.kennung)(name, 'stocktakeId', stocktakeId) });
239
+ if ('pdf' in ergebnis)
240
+ return { kind: 'pdf', pdf: ergebnis.pdf };
241
+ const link = (0, lesen_js_1.objekt)(ergebnis.data)?.download;
242
+ if (link === undefined || link === null)
243
+ throw (0, lesen_js_1.antwortfehler)(name, 'Antwort ist weder ein PDF noch ein Lese-Link (data.download fehlt)');
244
+ return { kind: 'download', download: (0, lesen_js_1.protokollLink)(name, link) };
245
+ }
@@ -12,7 +12,7 @@
12
12
  * wenn der Server sie sendet: „fehlt“ heisst dort „kein Recht“, nicht „leer“.
13
13
  */
14
14
  import { KasseneckValidationError } from '../client/errors.js';
15
- import type { Article, GoodsReceiptPreviewLine, InventoryShortfall, InventoryWebhook, InventoryWebhookDelivery, InventoryWebhookTestDelivery, Location, Reservation, StockBelowMinimumEventData, StockChangedEventData, StockLevel, StockMovement, StockOperation, StockValue, VariantGroup } from './typen.js';
15
+ import type { Article, GoodsReceiptPreviewLine, InventoryShortfall, InventoryWebhook, InventoryWebhookDelivery, InventoryWebhookTestDelivery, Location, Reservation, StockBelowMinimumEventData, StockChangedEventData, StockLevel, StockMovement, StockOperation, StockValue, Stocktake, StocktakeCount, StocktakeCountResult, StocktakeItem, StocktakePdfDownload, StocktakeWarning, VariantGroup } from './typen.js';
16
16
  type Roh = Record<string, unknown>;
17
17
  /** Ein Leser bekommt den Vorgang (fuer die Meldung) und den Pfad im Rumpf. */
18
18
  export interface Ort {
@@ -44,6 +44,25 @@ export declare function fehlmengen(roh: unknown): InventoryShortfall[];
44
44
  * bzw. „keine Varianten“).
45
45
  */
46
46
  export declare function variantengruppe(ort: Ort, w: unknown): VariantGroup;
47
+ /** Hinweise einer Inventur; fehlt die Liste, gibt es keine. */
48
+ export declare function inventurWarnungen(ort: Ort, w: unknown): StocktakeWarning[];
49
+ /**
50
+ * Kopf einer Inventur. Was erst nach dem Abschluss kommt (Summen, Hinweise,
51
+ * Siegel, Pruefsumme, Protokoll), steht im Modell nur, wenn der Server es
52
+ * sendet; Werte nur mit dem Kosten-Recht.
53
+ */
54
+ export declare function inventur(ort: Ort, w: unknown): Stocktake;
55
+ /**
56
+ * Eine Position. Soll, Differenz und alles aus Pruefung und Abschluss stehen
57
+ * im Modell nur, wenn der Server sie sendet (blind: vor `review` nie).
58
+ */
59
+ export declare function inventurPosition(ort: Ort, w: unknown): StocktakeItem;
60
+ /** Eine Zaehlung: Menge, Runde und Kennungen muessen da sein. */
61
+ export declare function inventurZaehlung(ort: Ort, w: unknown): StocktakeCount;
62
+ /** Antwort von Zaehlen und Stornieren: beide Teile sind zugesagt. */
63
+ export declare function zaehlungMitPosition(name: string, daten: unknown): StocktakeCountResult;
64
+ /** Lese-Link auf ein grosses Inventurprotokoll; ohne Adresse, Ablauf, Groesse oder Pruefsumme ist er unbrauchbar. */
65
+ export declare function protokollLink(name: string, w: unknown): StocktakePdfDownload;
47
66
  /** Eine zugesagte Liste `data.<feld>`, Eintrag fuer Eintrag gelesen. */
48
67
  export declare function liste<T>(name: string, daten: unknown, feld: string, lesen: (ort: Ort, e: unknown) => T): T[];
49
68
  /** Der Cursor der naechsten Seite; `null` am Ende. Etwas anderes als Text oder `null` ist kaputt. */
@@ -30,6 +30,12 @@ exports.vorschauZeile = vorschauZeile;
30
30
  exports.reservierung = reservierung;
31
31
  exports.fehlmengen = fehlmengen;
32
32
  exports.variantengruppe = variantengruppe;
33
+ exports.inventurWarnungen = inventurWarnungen;
34
+ exports.inventur = inventur;
35
+ exports.inventurPosition = inventurPosition;
36
+ exports.inventurZaehlung = inventurZaehlung;
37
+ exports.zaehlungMitPosition = zaehlungMitPosition;
38
+ exports.protokollLink = protokollLink;
33
39
  exports.liste = liste;
34
40
  exports.naechsterCursor = naechsterCursor;
35
41
  const errors_js_1 = require("../client/errors.js");
@@ -476,6 +482,307 @@ function variantengruppe(ort, w) {
476
482
  updatedAt: text(g.updatedAt),
477
483
  };
478
484
  }
485
+ // ---- Inventur (Lager-Kern Stufe 3) ---------------------------------------------------
486
+ const unter = (ort, feld) => ({ name: ort.name, pfad: ort.pfad ? `${ort.pfad}.${feld}` : feld });
487
+ /** Ein Unterobjekt, das `null` sein darf (`review`, `closing` …); etwas anderes als Objekt oder `null` ist kaputt. */
488
+ function objektOderNull(ort, feld, w) {
489
+ if (w === undefined || w === null)
490
+ return null;
491
+ const o = objekt(w);
492
+ if (!o)
493
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.${feld} ist kein Objekt)`);
494
+ return o;
495
+ }
496
+ /** Ein Wahrheitswert, der da sein muss: ein fehlendes „gezaehlt“ ist nicht „ungezaehlt“. */
497
+ function wahrheitswert(ort, feld, w) {
498
+ if (typeof w !== 'boolean')
499
+ throw antwortfehler(ort.name, `Antwort enthaelt keinen Wahrheitswert (data.${ort.pfad}.${feld})`);
500
+ return w;
501
+ }
502
+ /** Eine Liste von Texten, die fehlen darf (dann leer); etwas anderes ist kaputt. */
503
+ function textlisteOderLeer(ort, feld, w) {
504
+ if (w === undefined || w === null)
505
+ return [];
506
+ return textliste(ort, feld, w);
507
+ }
508
+ /** Wer etwas tat; fehlt die Angabe, `null`. Etwas anderes als ein Objekt ist kaputt, nie still „niemand“. */
509
+ function akteur(ort, feld, w) {
510
+ if (w === undefined || w === null)
511
+ return null;
512
+ const a = objekt(w);
513
+ if (!a)
514
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.${feld} ist kein Objekt)`);
515
+ return { type: textOderNull(a.type), id: textOderNull(a.id), name: textOderNull(a.name) };
516
+ }
517
+ function inventurWarnung(ort, w) {
518
+ const h = eintrag(ort, w);
519
+ if (typeof h.code !== 'string' || h.code === '')
520
+ throw antwortfehler(ort.name, `Hinweis ohne Code (data.${ort.pfad}.code)`);
521
+ return { code: h.code, items: ganzzahl(ort, 'items', h.items), message: typeof h.message === 'string' ? h.message : null };
522
+ }
523
+ /** Hinweise einer Inventur; fehlt die Liste, gibt es keine. */
524
+ function inventurWarnungen(ort, w) {
525
+ if (w === undefined || w === null)
526
+ return [];
527
+ if (!Array.isArray(w))
528
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad} ist keine Liste)`);
529
+ return w.map((h, i) => inventurWarnung({ name: ort.name, pfad: `${ort.pfad}[${i}]` }, h));
530
+ }
531
+ function siegel(ort, w) {
532
+ const s = eintrag(ort, w);
533
+ const raus = {
534
+ fromDay: textOderNull(s.fromDay),
535
+ toDay: textOderNull(s.toDay),
536
+ daysChecked: ganzzahlOderNull(ort, 'daysChecked', s.daysChecked),
537
+ verified: typeof s.verified === 'boolean' ? s.verified : null,
538
+ firstBreak: textOderNull(s.firstBreak),
539
+ gaps: textlisteOderLeer(ort, 'gaps', s.gaps),
540
+ gapCount: ganzzahlOderNull(ort, 'gapCount', s.gapCount),
541
+ };
542
+ if (typeof s.notChecked === 'string')
543
+ raus.notChecked = s.notChecked;
544
+ if (hat(s, 'checkedUntil'))
545
+ raus.checkedUntil = textOderNull(s.checkedUntil);
546
+ return raus;
547
+ }
548
+ /**
549
+ * Kopf einer Inventur. Was erst nach dem Abschluss kommt (Summen, Hinweise,
550
+ * Siegel, Pruefsumme, Protokoll), steht im Modell nur, wenn der Server es
551
+ * sendet; Werte nur mit dem Kosten-Recht.
552
+ */
553
+ function inventur(ort, w) {
554
+ const k = eintrag(ort, w);
555
+ const umfang = objektOderNull(ort, 'scope', k.scope);
556
+ const umfangOrt = unter(ort, 'scope');
557
+ const fortschritt = objektOderNull(ort, 'progress', k.progress);
558
+ const fOrt = unter(ort, 'progress');
559
+ const pruefung = objektOderNull(ort, 'review', k.review);
560
+ const pOrt = unter(ort, 'review');
561
+ const abschluss = objektOderNull(ort, 'closing', k.closing);
562
+ const aOrt = unter(ort, 'closing');
563
+ const abbruch = objektOderNull(ort, 'cancellation', k.cancellation);
564
+ const raus = {
565
+ id: kennung(ort, 'id', k.id),
566
+ name: text(k.name),
567
+ locationId: textOderNull(k.locationId),
568
+ scope: umfang
569
+ ? {
570
+ type: textOderNull(umfang.type),
571
+ groupIds: textlisteOderLeer(umfangOrt, 'groupIds', umfang.groupIds),
572
+ articleIds: textlisteOderLeer(umfangOrt, 'articleIds', umfang.articleIds),
573
+ }
574
+ : null,
575
+ type: textOderNull(k.type),
576
+ keyDate: textOderNull(k.keyDate),
577
+ // Blind ist die sichere Vorgabe: nur ein ausdrueckliches false zeigt Bestand.
578
+ blind: k.blind !== false,
579
+ status: textOderNull(k.status),
580
+ progress: fortschritt
581
+ ? {
582
+ items: ganzzahl(fOrt, 'items', fortschritt.items),
583
+ counted: ganzzahlOderNull(fOrt, 'counted', fortschritt.counted),
584
+ recountOpen: fortschritt.recountOpen === true,
585
+ }
586
+ : null,
587
+ createdAt: text(k.createdAt),
588
+ createdBy: akteur(ort, 'createdBy', k.createdBy),
589
+ source: textOderNull(k.source),
590
+ updatedAt: text(k.updatedAt),
591
+ review: pruefung
592
+ ? {
593
+ startedAt: text(pruefung.startedAt),
594
+ startedBy: akteur(pOrt, 'startedBy', pruefung.startedBy),
595
+ complete: pruefung.complete === true,
596
+ expectedAsOf: text(pruefung.expectedAsOf),
597
+ recountUncounted: ganzzahlOderNull(pOrt, 'recountUncounted', pruefung.recountUncounted),
598
+ }
599
+ : null,
600
+ closing: abschluss
601
+ ? {
602
+ startedAt: text(abschluss.startedAt),
603
+ startedBy: akteur(aOrt, 'startedBy', abschluss.startedBy),
604
+ uncountedAsZero: abschluss.uncountedAsZero === true,
605
+ parts: ganzzahlOderNull(aOrt, 'parts', abschluss.parts),
606
+ bookedParts: ganzzahlOderNull(aOrt, 'bookedParts', abschluss.bookedParts),
607
+ completedAt: text(abschluss.completedAt),
608
+ }
609
+ : null,
610
+ cancellation: abbruch
611
+ ? { reason: text(abbruch.reason), cancelledAt: text(abbruch.cancelledAt), cancelledBy: akteur(unter(ort, 'cancellation'), 'cancelledBy', abbruch.cancelledBy) }
612
+ : null,
613
+ };
614
+ const summen = objektOderNull(ort, 'totals', k.totals);
615
+ if (summen) {
616
+ const sOrt = unter(ort, 'totals');
617
+ raus.totals = {
618
+ items: ganzzahl(sOrt, 'items', summen.items),
619
+ counted: ganzzahl(sOrt, 'counted', summen.counted),
620
+ uncounted: ganzzahl(sOrt, 'uncounted', summen.uncounted),
621
+ recounted: ganzzahl(sOrt, 'recounted', summen.recounted),
622
+ withDifference: ganzzahl(sOrt, 'withDifference', summen.withDifference),
623
+ needsCheck: ganzzahl(sOrt, 'needsCheck', summen.needsCheck),
624
+ notBooked: ganzzahl(sOrt, 'notBooked', summen.notBooked),
625
+ };
626
+ for (const feld of ['differenceValueCents', 'inventoryValueCents']) {
627
+ if (hat(summen, feld))
628
+ raus.totals[feld] = ganzzahlOderNull(sOrt, feld, summen[feld]);
629
+ }
630
+ }
631
+ if (hat(k, 'warnings'))
632
+ raus.warnings = inventurWarnungen(unter(ort, 'warnings'), k.warnings);
633
+ if (hat(k, 'seal') && k.seal !== null)
634
+ raus.seal = siegel(unter(ort, 'seal'), k.seal);
635
+ if (hat(k, 'checksum'))
636
+ raus.checksum = textOderNull(k.checksum);
637
+ if (hat(k, 'inventoryAsOf'))
638
+ raus.inventoryAsOf = textOderNull(k.inventoryAsOf);
639
+ const pdf = objektOderNull(ort, 'pdf', k.pdf);
640
+ if (pdf) {
641
+ raus.pdf = { available: pdf.available === true };
642
+ if (typeof pdf.valuesSha256 === 'string')
643
+ raus.pdf.valuesSha256 = pdf.valuesSha256;
644
+ if (typeof pdf.quantitiesSha256 === 'string')
645
+ raus.pdf.quantitiesSha256 = pdf.quantitiesSha256;
646
+ }
647
+ return raus;
648
+ }
649
+ function nichtGebucht(ort, w) {
650
+ const n = eintrag(ort, w);
651
+ if (typeof n.code !== 'string' || n.code === '')
652
+ throw antwortfehler(ort.name, `Antwort enthaelt keinen Grund (data.${ort.pfad}.code)`);
653
+ const raus = { code: n.code, quantity: ganzzahlOderNull(ort, 'quantity', n.quantity) };
654
+ if (n.reasons !== undefined && n.reasons !== null) {
655
+ if (!Array.isArray(n.reasons))
656
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.reasons ist keine Liste)`);
657
+ raus.reasons = n.reasons.map((g, i) => {
658
+ const gOrt = { name: ort.name, pfad: `${ort.pfad}.reasons[${i}]` };
659
+ const r = eintrag(gOrt, g);
660
+ if (typeof r.code !== 'string' || r.code === '')
661
+ throw antwortfehler(ort.name, `Antwort enthaelt keinen Grund (data.${gOrt.pfad}.code)`);
662
+ return { code: r.code, quantity: ganzzahlOderNull(gOrt, 'quantity', r.quantity) };
663
+ });
664
+ }
665
+ return raus;
666
+ }
667
+ /**
668
+ * Eine Position. Soll, Differenz und alles aus Pruefung und Abschluss stehen
669
+ * im Modell nur, wenn der Server sie sendet (blind: vor `review` nie).
670
+ */
671
+ function inventurPosition(ort, w) {
672
+ const p = eintrag(ort, w);
673
+ // Wer gezaehlt hat, ist zugesagt: fehlt die Liste, ist die Antwort kaputt (nie „niemand“).
674
+ if (!Array.isArray(p.countedBy))
675
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.countedBy ist keine Liste)`);
676
+ const zaehler = p.countedBy;
677
+ const nachzaehlen = objektOderNull(ort, 'recount', p.recount);
678
+ const nOrt = unter(ort, 'recount');
679
+ const raus = {
680
+ articleId: kennung(ort, 'articleId', p.articleId),
681
+ condition: kennung(ort, 'condition', p.condition),
682
+ name: text(p.name),
683
+ number: text(p.number),
684
+ unit: text(p.unit),
685
+ round: ganzzahl(ort, 'round', p.round),
686
+ counted: wahrheitswert(ort, 'counted', p.counted),
687
+ quantity: ganzzahlOderNull(ort, 'quantity', p.quantity),
688
+ counts: ganzzahl(ort, 'counts', p.counts),
689
+ firstCountedAt: text(p.firstCountedAt),
690
+ referenceTime: text(p.referenceTime),
691
+ countedBy: zaehler.map((a, i) => {
692
+ const wer = akteur(ort, `countedBy[${i}]`, a);
693
+ if (!wer)
694
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.countedBy[${i}] ist kein Objekt)`);
695
+ return wer;
696
+ }),
697
+ recountRequested: p.recountRequested === true,
698
+ recount: nachzaehlen
699
+ ? {
700
+ reason: text(nachzaehlen.reason),
701
+ requestedAt: text(nachzaehlen.requestedAt),
702
+ requestedBy: akteur(nOrt, 'requestedBy', nachzaehlen.requestedBy),
703
+ round: ganzzahlOderNull(nOrt, 'round', nachzaehlen.round),
704
+ }
705
+ : null,
706
+ addedLater: p.addedLater === true,
707
+ };
708
+ // Die Zaehl- und Storno-Antwort sendet die Position ohne Seriennummern: dann fehlt das Feld, nie [].
709
+ if (hat(p, 'serialNumbers'))
710
+ raus.serialNumbers = textlisteOderLeer(ort, 'serialNumbers', p.serialNumbers);
711
+ // Gezaehlt heisst: es gibt eine Menge. Beides zusammen kaputt waere „0“ oder „nichts“ geraten.
712
+ if (raus.counted && raus.quantity === null)
713
+ throw antwortfehler(ort.name, `Antwort ist unbrauchbar (data.${ort.pfad}.quantity fehlt bei counted: true)`);
714
+ for (const feld of ['bookStockNow', 'expectedQuantity', 'differenceQuantity', 'differenceValueCents', 'bookedQuantity']) {
715
+ if (hat(p, feld))
716
+ raus[feld] = ganzzahlOderNull(ort, feld, p[feld]);
717
+ }
718
+ if (hat(p, 'needsCheck'))
719
+ raus.needsCheck = p.needsCheck === true;
720
+ if (hat(p, 'checkReasons'))
721
+ raus.checkReasons = textlisteOderLeer(ort, 'checkReasons', p.checkReasons);
722
+ if (hat(p, 'expectedAsOf'))
723
+ raus.expectedAsOf = text(p.expectedAsOf);
724
+ if (hat(p, 'missingSerialNumbers'))
725
+ raus.missingSerialNumbers = textlisteOderLeer(ort, 'missingSerialNumbers', p.missingSerialNumbers);
726
+ if (hat(p, 'extraSerialNumbers'))
727
+ raus.extraSerialNumbers = textlisteOderLeer(ort, 'extraSerialNumbers', p.extraSerialNumbers);
728
+ if (hat(p, 'notBooked'))
729
+ raus.notBooked = p.notBooked === null ? null : nichtGebucht(unter(ort, 'notBooked'), p.notBooked);
730
+ if (hat(p, 'inventory')) {
731
+ const inv = objektOderNull(ort, 'inventory', p.inventory);
732
+ if (!inv) {
733
+ raus.inventory = null;
734
+ }
735
+ else {
736
+ const iOrt = unter(ort, 'inventory');
737
+ raus.inventory = { quantity: ganzzahl(iOrt, 'quantity', inv.quantity), countedOn: textOderNull(inv.countedOn) };
738
+ for (const feld of ['unitValueMicros', 'valueCents']) {
739
+ if (hat(inv, feld))
740
+ raus.inventory[feld] = ganzzahlOderNull(iOrt, feld, inv[feld]);
741
+ }
742
+ }
743
+ }
744
+ return raus;
745
+ }
746
+ /** Eine Zaehlung: Menge, Runde und Kennungen muessen da sein. */
747
+ function inventurZaehlung(ort, w) {
748
+ const z = eintrag(ort, w);
749
+ const storno = objektOderNull(ort, 'voided', z.voided);
750
+ return {
751
+ id: kennung(ort, 'id', z.id),
752
+ articleId: kennung(ort, 'articleId', z.articleId),
753
+ condition: kennung(ort, 'condition', z.condition),
754
+ quantity: ganzzahl(ort, 'quantity', z.quantity),
755
+ // Die Zaehlung traegt ihre Seriennummern immer (leer bei Mengenartikeln); fehlt die Liste, ist sie kaputt.
756
+ serialNumbers: textliste(ort, 'serialNumbers', z.serialNumbers),
757
+ round: ganzzahl(ort, 'round', z.round),
758
+ countedBy: akteur(ort, 'countedBy', z.countedBy),
759
+ source: textOderNull(z.source),
760
+ cashregisterId: textOderNull(z.cashregisterId),
761
+ countedAt: text(z.countedAt),
762
+ note: text(z.note),
763
+ voided: storno
764
+ ? { reason: text(storno.reason), voidedAt: text(storno.voidedAt), voidedBy: akteur(unter(ort, 'voided'), 'voidedBy', storno.voidedBy) }
765
+ : null,
766
+ };
767
+ }
768
+ /** Antwort von Zaehlen und Stornieren: beide Teile sind zugesagt. */
769
+ function zaehlungMitPosition(name, daten) {
770
+ const d = objekt(daten);
771
+ return { count: inventurZaehlung({ name, pfad: 'count' }, d?.count), item: inventurPosition({ name, pfad: 'item' }, d?.item) };
772
+ }
773
+ /** Lese-Link auf ein grosses Inventurprotokoll; ohne Adresse, Ablauf, Groesse oder Pruefsumme ist er unbrauchbar. */
774
+ function protokollLink(name, w) {
775
+ const ort = { name, pfad: 'download' };
776
+ const d = eintrag(ort, w);
777
+ return {
778
+ url: kennung(ort, 'url', d.url),
779
+ expiresAt: kennung(ort, 'expiresAt', d.expiresAt),
780
+ sizeBytes: ganzzahl(ort, 'sizeBytes', d.sizeBytes),
781
+ sha256: kennung(ort, 'sha256', d.sha256),
782
+ fileName: textOderNull(d.fileName),
783
+ contentType: textOderNull(d.contentType),
784
+ };
785
+ }
479
786
  // ---- Listen ------------------------------------------------------------------
480
787
  /** Eine zugesagte Liste `data.<feld>`, Eintrag fuer Eintrag gelesen. */
481
788
  function liste(name, daten, feld, lesen) {