@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.
- package/CHANGELOG.md +176 -0
- package/README.md +293 -1
- package/dist/cjs/client/aufrufe.d.ts +2 -2
- package/dist/cjs/client/aufrufe.js +58 -0
- package/dist/cjs/inventory/api.d.ts +61 -0
- package/dist/cjs/inventory/api.js +54 -0
- package/dist/cjs/inventory/auth.d.ts +17 -0
- package/dist/cjs/inventory/auth.js +27 -0
- package/dist/cjs/inventory/endpunkte.d.ts +91 -0
- package/dist/cjs/inventory/endpunkte.js +290 -0
- package/dist/cjs/inventory/fehler.d.ts +43 -0
- package/dist/cjs/inventory/fehler.js +66 -0
- package/dist/cjs/inventory/index.d.ts +21 -0
- package/dist/cjs/inventory/index.js +94 -0
- package/dist/cjs/inventory/lesen.d.ts +45 -0
- package/dist/cjs/inventory/lesen.js +424 -0
- package/dist/cjs/inventory/schreiben.d.ts +83 -0
- package/dist/cjs/inventory/schreiben.js +315 -0
- package/dist/cjs/inventory/typen.d.ts +679 -0
- package/dist/cjs/inventory/typen.js +14 -0
- package/dist/cjs/inventory/vertrag.d.ts +136 -0
- package/dist/cjs/inventory/vertrag.js +291 -0
- package/dist/cjs/inventory/webhook.d.ts +52 -0
- package/dist/cjs/inventory/webhook.js +116 -0
- package/dist/cjs/invoice/index.d.ts +2 -2
- package/dist/cjs/invoice/index.js +2 -1
- package/dist/cjs/invoice/typen.d.ts +46 -2
- package/dist/cjs/invoice/vertrag.d.ts +19 -5
- package/dist/cjs/invoice/vertrag.js +37 -5
- package/dist/cjs/stored/vokabular.d.ts +1 -1
- package/dist/cjs/stored/vokabular.js +5 -3
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client/aufrufe.d.ts +2 -2
- package/dist/esm/client/aufrufe.js +58 -0
- package/dist/esm/inventory/api.d.ts +61 -0
- package/dist/esm/inventory/api.js +51 -0
- package/dist/esm/inventory/auth.d.ts +17 -0
- package/dist/esm/inventory/auth.js +24 -0
- package/dist/esm/inventory/endpunkte.d.ts +91 -0
- package/dist/esm/inventory/endpunkte.js +267 -0
- package/dist/esm/inventory/fehler.d.ts +43 -0
- package/dist/esm/inventory/fehler.js +57 -0
- package/dist/esm/inventory/index.d.ts +21 -0
- package/dist/esm/inventory/index.js +20 -0
- package/dist/esm/inventory/lesen.d.ts +45 -0
- package/dist/esm/inventory/lesen.js +404 -0
- package/dist/esm/inventory/schreiben.d.ts +83 -0
- package/dist/esm/inventory/schreiben.js +298 -0
- package/dist/esm/inventory/typen.d.ts +679 -0
- package/dist/esm/inventory/typen.js +13 -0
- package/dist/esm/inventory/vertrag.d.ts +136 -0
- package/dist/esm/inventory/vertrag.js +288 -0
- package/dist/esm/inventory/webhook.d.ts +52 -0
- package/dist/esm/inventory/webhook.js +107 -0
- package/dist/esm/invoice/index.d.ts +2 -2
- package/dist/esm/invoice/index.js +1 -1
- package/dist/esm/invoice/typen.d.ts +46 -2
- package/dist/esm/invoice/vertrag.d.ts +19 -5
- package/dist/esm/invoice/vertrag.js +36 -4
- package/dist/esm/stored/vokabular.d.ts +1 -1
- package/dist/esm/stored/vokabular.js +5 -3
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/fixtures/hobex-hps-codes.json +1 -1
- package/fixtures/invoice-api-examples/credit-error-reservation.json +26 -0
- package/fixtures/invoice-api-examples/issue-error-reservation-article.json +25 -0
- package/fixtures/invoice-api-examples/issue-error-vat-id-risk.json +26 -0
- package/fixtures/invoice-api-examples/issue-reservation.json +24 -0
- package/fixtures/invoice-api-examples/issue-vat-id-risk.json +28 -0
- package/fixtures/invoice-api.schema.json +15 -2
- package/fixtures/invoice-texts.json +1 -1
- package/fixtures/pos-texts.json +1 -1
- package/fixtures/surface.json +306 -5
- package/fixtures/v3/README.md +9 -1
- package/fixtures/v3/antworten/belege.json +2 -2
- package/fixtures/v3/antworten/belegmail.json +2 -2
- package/fixtures/v3/antworten/kasse-belege.json +2 -2
- package/fixtures/v3/antworten/kasse.json +3 -3
- package/fixtures/v3/antworten/lager.json +3881 -0
- package/fixtures/v3/antworten/rechnungen.json +267 -44
- package/fixtures/v3/antworten/storno.json +2 -2
- package/fixtures/v3/stored/belege.json +2 -2
- package/fixtures/v3/stored/kasse.json +404 -4
- package/fixtures/v3/stored/rechnungen.json +174 -18
- package/fixtures/v3/v3-vokabular.json +1007 -20
- package/fixtures/v3/zahlbetrag-faelle.json +2 -2
- package/package.json +14 -2
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Die Aufrufe der Lager-API: Artikel, Standorte, Bestand und Bewegungen
|
|
3
|
+
* lesen, Konto-Webhooks verwalten (Backend Stufe 5a). Die schreibenden
|
|
4
|
+
* Aufrufe (Stufe 5b) stehen in `schreiben.ts` und teilen die Helfer hier.
|
|
5
|
+
*
|
|
6
|
+
* Jede Funktion nimmt den Transport als ersten Parameter und ist einzeln
|
|
7
|
+
* importierbar; die Fassade [createInventoryClient] bindet ihn nur einmal.
|
|
8
|
+
*
|
|
9
|
+
* **Geprueft wird hier nur, was ohne Netz sicher falsch ist** (leere Kennung,
|
|
10
|
+
* `limit` ausserhalb 1–200, eine Aenderung ohne Feld). Alles Fachliche prueft
|
|
11
|
+
* der Server und meldet es als `validation` mit `errors[]`; zwei Pruefungen
|
|
12
|
+
* hiessen zwei Wahrheiten.
|
|
13
|
+
*
|
|
14
|
+
* Lesen hat keine Wirkung: nach einem Zeitlimit darf derselbe Aufruf
|
|
15
|
+
* wiederholt werden. Bei `rate_limited` vorher `retryAfterSec` warten
|
|
16
|
+
* ([inventoryRetryAfterSec]).
|
|
17
|
+
*/
|
|
18
|
+
import { KasseneckValidationError } from '../client/errors.js';
|
|
19
|
+
import { antwortfehler, artikel, bestand, bewegung, liste, naechsterCursor, objekt, probeZustellung, standort, webhook, wert, zustellung, } from './lesen.js';
|
|
20
|
+
import { INVENTORY_LIST_LIMIT_MAX } from './vertrag.js';
|
|
21
|
+
export function anfragefehler(name, grund) {
|
|
22
|
+
return new KasseneckValidationError(name, grund, 'request');
|
|
23
|
+
}
|
|
24
|
+
/** Eine Kennung, die gesendet werden muss: Text mit mindestens einem Zeichen ausser Leerraum. */
|
|
25
|
+
export function kennung(name, feld, wert) {
|
|
26
|
+
if (typeof wert !== 'string' || wert.trim() === '')
|
|
27
|
+
throw anfragefehler(name, `${feld} fehlt`);
|
|
28
|
+
return wert;
|
|
29
|
+
}
|
|
30
|
+
/** Felder, die einen Zeitpunkt tragen: ein `Date` geht als ISO 8601 UTC hinaus. */
|
|
31
|
+
const ZEITFELDER = new Set(['updatedSince', 'changedSince', 'from', 'to']);
|
|
32
|
+
/**
|
|
33
|
+
* Die Abfrage einer Liste als Parameter. Unbekannte Felder gehen unveraendert
|
|
34
|
+
* hinaus (der Server weist sie mit `validation` ab), `undefined` faellt weg.
|
|
35
|
+
*/
|
|
36
|
+
export function abfrage(name, query) {
|
|
37
|
+
if (query === undefined || query === null)
|
|
38
|
+
return {};
|
|
39
|
+
if (typeof query !== 'object' || Array.isArray(query))
|
|
40
|
+
throw anfragefehler(name, 'Abfrage muss ein Objekt sein');
|
|
41
|
+
const raus = {};
|
|
42
|
+
for (const [feld, w] of Object.entries(query)) {
|
|
43
|
+
if (w === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
if (ZEITFELDER.has(feld) && w instanceof Date) {
|
|
46
|
+
if (Number.isNaN(w.getTime()))
|
|
47
|
+
throw anfragefehler(name, `${feld} ist kein gueltiger Zeitpunkt`);
|
|
48
|
+
raus[feld] = w.toISOString();
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
raus[feld] = w;
|
|
52
|
+
}
|
|
53
|
+
if (raus.limit !== undefined) {
|
|
54
|
+
const limit = raus.limit;
|
|
55
|
+
if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 1 || limit > INVENTORY_LIST_LIMIT_MAX) {
|
|
56
|
+
throw anfragefehler(name, `limit muss eine ganze Zahl von 1 bis ${INVENTORY_LIST_LIMIT_MAX} sein`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (raus.cursor !== undefined)
|
|
60
|
+
kennung(name, 'cursor', raus.cursor);
|
|
61
|
+
return raus;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Blaettert ueber alle Seiten, Eintrag fuer Eintrag. Derselbe Cursor zweimal
|
|
65
|
+
* waere eine Endlosschleife: dann endet die Schleife mit einem Antwortfehler.
|
|
66
|
+
*/
|
|
67
|
+
export async function* seitenweise(name, start, seite) {
|
|
68
|
+
const gesehen = new Set();
|
|
69
|
+
// Auch der Startcursor zaehlt: nennt die erste Antwort ihn wieder, waere es dieselbe Seite.
|
|
70
|
+
if (start)
|
|
71
|
+
gesehen.add(start);
|
|
72
|
+
let cursor = start;
|
|
73
|
+
for (;;) {
|
|
74
|
+
const { eintraege, nextCursor } = await seite(cursor);
|
|
75
|
+
yield* eintraege;
|
|
76
|
+
if (nextCursor === null)
|
|
77
|
+
return;
|
|
78
|
+
if (gesehen.has(nextCursor))
|
|
79
|
+
throw antwortfehler(name, 'Antwort nennt denselben nextCursor zweimal');
|
|
80
|
+
gesehen.add(nextCursor);
|
|
81
|
+
cursor = nextCursor;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// ---- Artikel ------------------------------------------------------------------
|
|
85
|
+
export async function getArticle(transport, articleId) {
|
|
86
|
+
const name = 'getArticle';
|
|
87
|
+
const daten = await transport(name, { articleId: kennung(name, 'articleId', articleId) });
|
|
88
|
+
return artikel({ name, pfad: 'article' }, objekt(daten)?.article);
|
|
89
|
+
}
|
|
90
|
+
export async function listArticles(transport, query) {
|
|
91
|
+
const name = 'listArticles';
|
|
92
|
+
const daten = await transport(name, abfrage(name, query));
|
|
93
|
+
return { articles: liste(name, daten, 'articles', artikel), nextCursor: naechsterCursor(name, daten) };
|
|
94
|
+
}
|
|
95
|
+
/** Alle Artikel der Abfrage, Seite fuer Seite ueber `nextCursor`. */
|
|
96
|
+
export function iterateArticles(transport, query) {
|
|
97
|
+
const { cursor, ...rest } = query ?? {};
|
|
98
|
+
return seitenweise('listArticles', cursor, async (c) => {
|
|
99
|
+
const s = await listArticles(transport, { ...rest, cursor: c });
|
|
100
|
+
return { eintraege: s.articles, nextCursor: s.nextCursor };
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Ein aktiver Artikel per Code (`number`, `ean` oder `internalCode`) oder per
|
|
105
|
+
* eigener Kennung aus `externalIds` (`{ externalSystem: 'shop', externalId: '4711' }`).
|
|
106
|
+
* Ein stillgelegter Artikel gilt als nicht gefunden (`article_not_found`).
|
|
107
|
+
*/
|
|
108
|
+
export async function lookupArticleByCode(transport, lookup) {
|
|
109
|
+
const name = 'lookupArticleByCode';
|
|
110
|
+
let params;
|
|
111
|
+
if (typeof lookup === 'string') {
|
|
112
|
+
params = { code: kennung(name, 'code', lookup) };
|
|
113
|
+
}
|
|
114
|
+
else if (lookup !== null && typeof lookup === 'object' && !Array.isArray(lookup)) {
|
|
115
|
+
const l = lookup;
|
|
116
|
+
const extern = l.externalSystem !== undefined || l.externalId !== undefined;
|
|
117
|
+
if (l.code !== undefined && extern)
|
|
118
|
+
throw anfragefehler(name, 'entweder code oder externalSystem mit externalId');
|
|
119
|
+
params = extern
|
|
120
|
+
? { externalSystem: kennung(name, 'externalSystem', l.externalSystem), externalId: kennung(name, 'externalId', l.externalId) }
|
|
121
|
+
: { code: kennung(name, 'code', l.code) };
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
throw anfragefehler(name, 'Code als Text oder { code } bzw. { externalSystem, externalId } erwartet');
|
|
125
|
+
}
|
|
126
|
+
const daten = await transport(name, params);
|
|
127
|
+
return artikel({ name, pfad: 'article' }, objekt(daten)?.article);
|
|
128
|
+
}
|
|
129
|
+
// ---- Standorte und Bestand -----------------------------------------------------------
|
|
130
|
+
/** Alle Standorte des Kontos, samt aufgeloesten (`active: false`) und dem Hauptstandort. */
|
|
131
|
+
export async function listLocations(transport) {
|
|
132
|
+
const name = 'listLocations';
|
|
133
|
+
return liste(name, await transport(name, {}), 'locations', standort);
|
|
134
|
+
}
|
|
135
|
+
/** Die Werte (`values`): fehlt das Feld, fehlt das Recht `costs` -> `null`, nie `[]`. */
|
|
136
|
+
function werte(name, daten) {
|
|
137
|
+
const roh = objekt(daten)?.values;
|
|
138
|
+
if (roh === undefined || roh === null)
|
|
139
|
+
return null;
|
|
140
|
+
return liste(name, daten, 'values', wert);
|
|
141
|
+
}
|
|
142
|
+
/** Bestand eines Artikels je Standort. */
|
|
143
|
+
export async function getStock(transport, articleId) {
|
|
144
|
+
const name = 'getStock';
|
|
145
|
+
const daten = await transport(name, { articleId: kennung(name, 'articleId', articleId) });
|
|
146
|
+
return { stock: liste(name, daten, 'stock', bestand), values: werte(name, daten) };
|
|
147
|
+
}
|
|
148
|
+
export async function listStock(transport, query) {
|
|
149
|
+
const name = 'listStock';
|
|
150
|
+
const daten = await transport(name, abfrage(name, query));
|
|
151
|
+
return { stock: liste(name, daten, 'stock', bestand), values: werte(name, daten), nextCursor: naechsterCursor(name, daten) };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Alle Bestandszeilen der Abfrage, Seite fuer Seite. Die Werte (`values`)
|
|
155
|
+
* stehen je Seite in [listStock]; der Iterator liefert nur die Zeilen.
|
|
156
|
+
*/
|
|
157
|
+
export function iterateStock(transport, query) {
|
|
158
|
+
const { cursor, ...rest } = query ?? {};
|
|
159
|
+
return seitenweise('listStock', cursor, async (c) => {
|
|
160
|
+
const s = await listStock(transport, { ...rest, cursor: c });
|
|
161
|
+
return { eintraege: s.stock, nextCursor: s.nextCursor };
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/** Das Lagerprotokoll, neueste zuerst. */
|
|
165
|
+
export async function listStockMovements(transport, query) {
|
|
166
|
+
const name = 'listStockMovements';
|
|
167
|
+
const daten = await transport(name, abfrage(name, query));
|
|
168
|
+
return { movements: liste(name, daten, 'movements', bewegung), nextCursor: naechsterCursor(name, daten) };
|
|
169
|
+
}
|
|
170
|
+
export function iterateStockMovements(transport, query) {
|
|
171
|
+
const { cursor, ...rest } = query ?? {};
|
|
172
|
+
return seitenweise('listStockMovements', cursor, async (c) => {
|
|
173
|
+
const s = await listStockMovements(transport, { ...rest, cursor: c });
|
|
174
|
+
return { eintraege: s.movements, nextCursor: s.nextCursor };
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// ---- Webhooks ------------------------------------------------------------------
|
|
178
|
+
function mitSecret(name, daten) {
|
|
179
|
+
const o = objekt(daten);
|
|
180
|
+
const secret = o?.secret;
|
|
181
|
+
if (typeof secret !== 'string' || secret === '') {
|
|
182
|
+
throw antwortfehler(name, 'Antwort enthaelt kein secret; ohne es laesst sich keine Zustellung pruefen');
|
|
183
|
+
}
|
|
184
|
+
return { webhook: webhook({ name, pfad: 'webhook' }, o?.webhook), secret };
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Legt einen Webhook an (hoechstens 5 je Konto, `webhook_limit`).
|
|
188
|
+
* **Das Secret in der Antwort kommt nur dieses eine Mal**; sofort dorthin
|
|
189
|
+
* schreiben, wo der Empfaenger es liest, nicht in ein Protokoll.
|
|
190
|
+
*/
|
|
191
|
+
export async function createWebhook(transport, options) {
|
|
192
|
+
const name = 'createWebhook';
|
|
193
|
+
const url = kennung(name, 'url', options?.url);
|
|
194
|
+
if (!Array.isArray(options.events) || options.events.length === 0) {
|
|
195
|
+
throw anfragefehler(name, 'events ist leer; ein Webhook ohne Ereignis bekaeme nie etwas');
|
|
196
|
+
}
|
|
197
|
+
const params = { url, events: options.events };
|
|
198
|
+
if (options.description !== undefined)
|
|
199
|
+
params.description = options.description;
|
|
200
|
+
return mitSecret(name, await transport(name, params));
|
|
201
|
+
}
|
|
202
|
+
/** Aendert nur die genannten Felder; `description: null` loescht die Beschreibung. */
|
|
203
|
+
export async function updateWebhook(transport, webhookId, patch) {
|
|
204
|
+
const name = 'updateWebhook';
|
|
205
|
+
const id = kennung(name, 'webhookId', webhookId);
|
|
206
|
+
if (patch === null || typeof patch !== 'object' || Array.isArray(patch))
|
|
207
|
+
throw anfragefehler(name, 'patch muss ein Objekt sein');
|
|
208
|
+
const params = { webhookId: id };
|
|
209
|
+
for (const [feld, w] of Object.entries(patch))
|
|
210
|
+
if (w !== undefined)
|
|
211
|
+
params[feld] = w;
|
|
212
|
+
if (Object.keys(params).length === 1)
|
|
213
|
+
throw anfragefehler(name, 'patch nennt keine Aenderung');
|
|
214
|
+
const daten = await transport(name, params);
|
|
215
|
+
return webhook({ name, pfad: 'webhook' }, objekt(daten)?.webhook);
|
|
216
|
+
}
|
|
217
|
+
export async function deleteWebhook(transport, webhookId) {
|
|
218
|
+
const name = 'deleteWebhook';
|
|
219
|
+
const id = kennung(name, 'webhookId', webhookId);
|
|
220
|
+
const o = objekt(await transport(name, { webhookId: id }));
|
|
221
|
+
return { webhookId: typeof o?.webhookId === 'string' && o.webhookId ? o.webhookId : id, deleted: o?.deleted === true };
|
|
222
|
+
}
|
|
223
|
+
/** Die Webhooks des Kontos und die Ereignisse, die es abonnieren kann. */
|
|
224
|
+
export async function listWebhooks(transport) {
|
|
225
|
+
const name = 'listWebhooks';
|
|
226
|
+
const daten = await transport(name, {});
|
|
227
|
+
const ereignisse = objekt(daten)?.events;
|
|
228
|
+
return {
|
|
229
|
+
webhooks: liste(name, daten, 'webhooks', webhook),
|
|
230
|
+
events: Array.isArray(ereignisse) ? ereignisse.filter((e) => typeof e === 'string') : [],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Ein neues Secret fuer denselben Webhook. Ab der Antwort gilt nur noch das
|
|
235
|
+
* neue (keine Uebergangsfrist): erst speichern, dann weiterarbeiten.
|
|
236
|
+
*/
|
|
237
|
+
export async function rotateWebhookSecret(transport, webhookId) {
|
|
238
|
+
const name = 'rotateWebhookSecret';
|
|
239
|
+
return mitSecret(name, await transport(name, { webhookId: kennung(name, 'webhookId', webhookId) }));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Eine Probe genau dieses Ereignisses an genau diesen Webhook, mit erfundener
|
|
243
|
+
* Nutzlast und `test: true` in der Huelle. Der Webhook muss das Ereignis
|
|
244
|
+
* abonnieren (`event_not_subscribed`) und aktiv sein (`webhook_inactive`).
|
|
245
|
+
*/
|
|
246
|
+
export async function sendWebhookTest(transport, webhookId, event) {
|
|
247
|
+
const name = 'sendWebhookTest';
|
|
248
|
+
const id = kennung(name, 'webhookId', webhookId);
|
|
249
|
+
const ereignis = kennung(name, 'event', event);
|
|
250
|
+
const daten = await transport(name, { webhookId: id, event: ereignis });
|
|
251
|
+
const o = objekt(daten);
|
|
252
|
+
return {
|
|
253
|
+
eventId: typeof o?.eventId === 'string' ? o.eventId : '',
|
|
254
|
+
event: typeof o?.event === 'string' ? o.event : ereignis,
|
|
255
|
+
deliveries: liste(name, daten, 'deliveries', (ort, e) => probeZustellung(ort, e, id)),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/** Die letzten Zustellungen, neueste zuerst; mit `webhookId` nur die eines Webhooks. */
|
|
259
|
+
export async function listWebhookDeliveries(transport, options = {}) {
|
|
260
|
+
const name = 'listWebhookDeliveries';
|
|
261
|
+
const params = {};
|
|
262
|
+
if (options.webhookId !== undefined)
|
|
263
|
+
params.webhookId = kennung(name, 'webhookId', options.webhookId);
|
|
264
|
+
if (options.limit !== undefined)
|
|
265
|
+
Object.assign(params, abfrage(name, { limit: options.limit }));
|
|
266
|
+
return liste(name, await transport(name, params), 'deliveries', zustellung);
|
|
267
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fehler der Lager-API auswerten: am Code, nie am Text.
|
|
3
|
+
*
|
|
4
|
+
* Es zaehlen die Codes der Lager-Endpunkte ([INVENTORY_ERROR_CODES]) und die,
|
|
5
|
+
* die Anmeldung und Rand auf jedem Aufruf erzeugen koennen
|
|
6
|
+
* ([INVENTORY_REQUEST_ERROR_CODES]). Ein fremder Code ist fuer diese Helfer
|
|
7
|
+
* kein Lager-Fehler: wer darauf verzweigt, tut es bewusst ueber
|
|
8
|
+
* `KasseneckApiError.code`.
|
|
9
|
+
*/
|
|
10
|
+
import type { KasseneckApiError } from '../client/errors.js';
|
|
11
|
+
import type { InventoryShortfall } from './typen.js';
|
|
12
|
+
import { type InventoryErrorCode, type InventoryRequestErrorCode, type InventoryWarningCode } from './vertrag.js';
|
|
13
|
+
/** Ein Code, den ein Lager-Aufruf liefern kann. */
|
|
14
|
+
export type InventoryApiErrorCode = InventoryErrorCode | InventoryRequestErrorCode;
|
|
15
|
+
export declare function isInventoryErrorCode(value: unknown): value is InventoryApiErrorCode;
|
|
16
|
+
/** Der Fehlercode eines geworfenen Fehlers; `undefined`, wenn es keiner der Lager-API ist. */
|
|
17
|
+
export declare function inventoryErrorCode(error: unknown): InventoryApiErrorCode | undefined;
|
|
18
|
+
/** Kurzform fuer `catch (e) { if (isInventoryError(e, 'article_not_found')) … }`. */
|
|
19
|
+
export declare function isInventoryError(error: unknown, code?: InventoryApiErrorCode): error is KasseneckApiError;
|
|
20
|
+
/** Ein Feldfehler aus `data.errors[]` einer `validation`-Antwort. */
|
|
21
|
+
export interface InventoryFieldError {
|
|
22
|
+
/** Feld der gesendeten Anfrage, z. B. `limit` oder `events`. */
|
|
23
|
+
field: string;
|
|
24
|
+
message: string;
|
|
25
|
+
}
|
|
26
|
+
/** Die Feldfehler einer `validation`-Antwort; leer, wenn es keine sind. */
|
|
27
|
+
export declare function inventoryFieldErrors(error: unknown): InventoryFieldError[];
|
|
28
|
+
/**
|
|
29
|
+
* Wie lange `rate_limited` noch gilt, in Sekunden (`data.retryAfterSec`,
|
|
30
|
+
* dasselbe wie die Kopfzeile `Retry-After`). `undefined`, wenn der Fehler kein
|
|
31
|
+
* `rate_limited` ist oder der Server keine Angabe macht. Die Grenze gilt je
|
|
32
|
+
* Konto (etwa 20 Anfragen je Sekunde, kurze Spitzen bis 40).
|
|
33
|
+
*/
|
|
34
|
+
export declare function inventoryRetryAfterSec(error: unknown): number | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Die Positionen, fuer die beim Reservieren der verfuegbare Bestand nicht
|
|
37
|
+
* reicht (`insufficient_available`, `data.details[]`): je Artikel und Standort
|
|
38
|
+
* angefragt und verfuegbar, in Tausendstel. Leer, wenn der Fehler ein anderer
|
|
39
|
+
* ist. Es fehlen nur diese Positionen; reserviert wurde nichts.
|
|
40
|
+
*/
|
|
41
|
+
export declare function inventoryShortfalls(error: unknown): InventoryShortfall[];
|
|
42
|
+
/** `true` fuer einen Hinweis-Code einer Buchung (`warnings[].code`); Hinweise sind nie Fehler. */
|
|
43
|
+
export declare function isInventoryWarningCode(value: unknown): value is InventoryWarningCode;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fehler der Lager-API auswerten: am Code, nie am Text.
|
|
3
|
+
*
|
|
4
|
+
* Es zaehlen die Codes der Lager-Endpunkte ([INVENTORY_ERROR_CODES]) und die,
|
|
5
|
+
* die Anmeldung und Rand auf jedem Aufruf erzeugen koennen
|
|
6
|
+
* ([INVENTORY_REQUEST_ERROR_CODES]). Ein fremder Code ist fuer diese Helfer
|
|
7
|
+
* kein Lager-Fehler: wer darauf verzweigt, tut es bewusst ueber
|
|
8
|
+
* `KasseneckApiError.code`.
|
|
9
|
+
*/
|
|
10
|
+
import { bekannterCode, feldfehlerVon } from '../client/fehlercodes.js';
|
|
11
|
+
import { fehlmengen } from './lesen.js';
|
|
12
|
+
import { INVENTORY_ERROR_CODES, INVENTORY_REQUEST_ERROR_CODES, INVENTORY_WARNING_CODES, } from './vertrag.js';
|
|
13
|
+
const BEKANNT = new Set([...INVENTORY_ERROR_CODES, ...INVENTORY_REQUEST_ERROR_CODES]);
|
|
14
|
+
export function isInventoryErrorCode(value) {
|
|
15
|
+
return typeof value === 'string' && BEKANNT.has(value);
|
|
16
|
+
}
|
|
17
|
+
/** Der Fehlercode eines geworfenen Fehlers; `undefined`, wenn es keiner der Lager-API ist. */
|
|
18
|
+
export function inventoryErrorCode(error) {
|
|
19
|
+
return bekannterCode(error, BEKANNT);
|
|
20
|
+
}
|
|
21
|
+
/** Kurzform fuer `catch (e) { if (isInventoryError(e, 'article_not_found')) … }`. */
|
|
22
|
+
export function isInventoryError(error, code) {
|
|
23
|
+
const gefunden = inventoryErrorCode(error);
|
|
24
|
+
return gefunden !== undefined && (code === undefined || gefunden === code);
|
|
25
|
+
}
|
|
26
|
+
/** Die Feldfehler einer `validation`-Antwort; leer, wenn es keine sind. */
|
|
27
|
+
export function inventoryFieldErrors(error) {
|
|
28
|
+
return feldfehlerVon(error);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Wie lange `rate_limited` noch gilt, in Sekunden (`data.retryAfterSec`,
|
|
32
|
+
* dasselbe wie die Kopfzeile `Retry-After`). `undefined`, wenn der Fehler kein
|
|
33
|
+
* `rate_limited` ist oder der Server keine Angabe macht. Die Grenze gilt je
|
|
34
|
+
* Konto (etwa 20 Anfragen je Sekunde, kurze Spitzen bis 40).
|
|
35
|
+
*/
|
|
36
|
+
export function inventoryRetryAfterSec(error) {
|
|
37
|
+
if (inventoryErrorCode(error) !== 'rate_limited')
|
|
38
|
+
return undefined;
|
|
39
|
+
const wert = error.details['retryAfterSec'];
|
|
40
|
+
return typeof wert === 'number' && Number.isFinite(wert) && wert >= 0 ? wert : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Die Positionen, fuer die beim Reservieren der verfuegbare Bestand nicht
|
|
44
|
+
* reicht (`insufficient_available`, `data.details[]`): je Artikel und Standort
|
|
45
|
+
* angefragt und verfuegbar, in Tausendstel. Leer, wenn der Fehler ein anderer
|
|
46
|
+
* ist. Es fehlen nur diese Positionen; reserviert wurde nichts.
|
|
47
|
+
*/
|
|
48
|
+
export function inventoryShortfalls(error) {
|
|
49
|
+
if (inventoryErrorCode(error) !== 'insufficient_available')
|
|
50
|
+
return [];
|
|
51
|
+
return fehlmengen(error.details['details']);
|
|
52
|
+
}
|
|
53
|
+
const HINWEISE = new Set(INVENTORY_WARNING_CODES);
|
|
54
|
+
/** `true` fuer einen Hinweis-Code einer Buchung (`warnings[].code`); Hinweise sind nie Fehler. */
|
|
55
|
+
export function isInventoryWarningCode(value) {
|
|
56
|
+
return typeof value === 'string' && HINWEISE.has(value);
|
|
57
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@kreiseck/kasseneck-api/inventory` – die Lager-API: Artikel, Standorte,
|
|
3
|
+
* Bestand und Lagerprotokoll lesen, Konto-Webhooks verwalten und eingehende
|
|
4
|
+
* Zustellungen pruefen (Backend Stufe 5a); Artikel anlegen und aendern,
|
|
5
|
+
* Bestand buchen und Ware reservieren (Stufe 5b, seit 1.5.0).
|
|
6
|
+
*
|
|
7
|
+
* Ein eigener Unterpfad wie `invoice`: der `api_key` eines Kontos gehoert auf
|
|
8
|
+
* einen **Server** (etwa das Backend eines Online-Shops) und soll nicht
|
|
9
|
+
* versehentlich in ein Browser-Buendel wandern.
|
|
10
|
+
*
|
|
11
|
+
* Mengen in Tausendstel der Basiseinheit, Geld in Cent, Einkaufspreise in
|
|
12
|
+
* Mikro-Euro; alles Ganzzahlen.
|
|
13
|
+
*/
|
|
14
|
+
export { createInventoryClient, type InventoryClient, type InventoryClientOptions } from './api.js';
|
|
15
|
+
export { inventoryKeyAuth, type InventoryKeyAuthOptions } from './auth.js';
|
|
16
|
+
export { isInventoryError, isInventoryErrorCode, inventoryErrorCode, inventoryFieldErrors, inventoryRetryAfterSec, inventoryShortfalls, isInventoryWarningCode, type InventoryApiErrorCode, type InventoryFieldError, } from './fehler.js';
|
|
17
|
+
export { createWebhook, deleteWebhook, getArticle, getStock, iterateArticles, iterateStock, iterateStockMovements, listArticles, listLocations, listStock, listStockMovements, listWebhookDeliveries, listWebhooks, lookupArticleByCode, rotateWebhookSecret, sendWebhookTest, updateWebhook, } from './endpunkte.js';
|
|
18
|
+
export { changeStockCondition, createArticle, createReservation, deactivateArticle, extendReservation, getReservation, iterateReservations, listReservations, previewGoodsReceipt, receiveGoods, recordStockLoss, releaseReservation, reverseStockMovement, transferStock, updateArticle, } from './schreiben.js';
|
|
19
|
+
export { verifyInventoryWebhookSignature, parseInventoryWebhookEvent, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_EVENT_HEADER, WEBHOOK_DELIVERY_HEADER, WEBHOOK_TOLERANCE_SEC, type VerifyInventoryWebhookOptions, } from './webhook.js';
|
|
20
|
+
export type { Article, ArticleInput, ArticleListQuery, ArticleLookup, ArticlePage, ChangeStockConditionRequest, CreateArticleRequest, CreateInventoryWebhookOptions, CreateReservationRequest, DeactivateArticleRequest, ExtendReservationRequest, GoodsReceiptItem, GoodsReceiptPreview, GoodsReceiptPreviewLine, GoodsReceiptPreviewRequest, InventoryShortfall, InventoryWarning, InventoryWebhook, InventoryWebhookDelivery, InventoryWebhookEnvelope, InventoryWebhookEvent, InventoryWebhookList, InventoryWebhookPatch, InventoryWebhookTestDelivery, InventoryWebhookTestResult, InventoryWebhookWithSecret, LandedCost, Location, LocationAddress, ReceiveGoodsRequest, RecordStockLossRequest, ReleaseReservationItem, ReleaseReservationRequest, Reservation, ReservationItem, ReservationItemInput, ReservationListQuery, ReservationPage, ReverseStockMovementRequest, StockBelowMinimumEventData, StockChangedEventData, StockItem, StockLevel, StockListQuery, StockMovement, StockMovementLot, StockMovementPage, StockMovementQuery, StockMovementSourceRef, StockOperation, StockPage, StockResult, StockValue, TransferStockRequest, UpdateArticleRequest, } from './typen.js';
|
|
21
|
+
export { INVENTORY_ENDPOINTS, INVENTORY_ERROR_CODES, INVENTORY_REQUEST_ERROR_CODES, INVENTORY_WEBHOOK_EVENTS, INVENTORY_WEBHOOK_ENVELOPE_FIELDS, INVENTORY_WEBHOOK_LIMIT, INVENTORY_LIST_LIMIT_MAX, LOCATION_TYPES, STOCK_MOVEMENT_TYPES, STOCK_MOVEMENT_SOURCES, STOCK_CONDITIONS, STOCK_CHANGE_CAUSES, WEBHOOK_DELIVERY_STATUSES, INVENTORY_WARNING_CODES, STOCK_KINDS, STOCK_LOSS_REASONS, WITHDRAWAL_TYPES, LANDED_COST_TYPES, LANDED_COST_ALLOCATIONS, RESERVATION_STATUSES, INVENTORY_IDEMPOTENCY_KEY_MAX, RESERVATION_MINUTES_MIN, RESERVATION_MINUTES_MAX, type InventoryEndpoint, type InventoryErrorCode, type InventoryRequestErrorCode, type InventoryWebhookEventType, type LocationType, type StockMovementType, type StockMovementSource, type StockCondition, type StockChangeCause, type WebhookDeliveryStatus, type InventoryWarningCode, type StockKind, type StockLossReason, type WithdrawalType, type LandedCostType, type LandedCostAllocation, type ReservationStatus, } from './vertrag.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@kreiseck/kasseneck-api/inventory` – die Lager-API: Artikel, Standorte,
|
|
3
|
+
* Bestand und Lagerprotokoll lesen, Konto-Webhooks verwalten und eingehende
|
|
4
|
+
* Zustellungen pruefen (Backend Stufe 5a); Artikel anlegen und aendern,
|
|
5
|
+
* Bestand buchen und Ware reservieren (Stufe 5b, seit 1.5.0).
|
|
6
|
+
*
|
|
7
|
+
* Ein eigener Unterpfad wie `invoice`: der `api_key` eines Kontos gehoert auf
|
|
8
|
+
* einen **Server** (etwa das Backend eines Online-Shops) und soll nicht
|
|
9
|
+
* versehentlich in ein Browser-Buendel wandern.
|
|
10
|
+
*
|
|
11
|
+
* Mengen in Tausendstel der Basiseinheit, Geld in Cent, Einkaufspreise in
|
|
12
|
+
* Mikro-Euro; alles Ganzzahlen.
|
|
13
|
+
*/
|
|
14
|
+
export { createInventoryClient } from './api.js';
|
|
15
|
+
export { inventoryKeyAuth } from './auth.js';
|
|
16
|
+
export { isInventoryError, isInventoryErrorCode, inventoryErrorCode, inventoryFieldErrors, inventoryRetryAfterSec, inventoryShortfalls, isInventoryWarningCode, } from './fehler.js';
|
|
17
|
+
export { createWebhook, deleteWebhook, getArticle, getStock, iterateArticles, iterateStock, iterateStockMovements, listArticles, listLocations, listStock, listStockMovements, listWebhookDeliveries, listWebhooks, lookupArticleByCode, rotateWebhookSecret, sendWebhookTest, updateWebhook, } from './endpunkte.js';
|
|
18
|
+
export { changeStockCondition, createArticle, createReservation, deactivateArticle, extendReservation, getReservation, iterateReservations, listReservations, previewGoodsReceipt, receiveGoods, recordStockLoss, releaseReservation, reverseStockMovement, transferStock, updateArticle, } from './schreiben.js';
|
|
19
|
+
export { verifyInventoryWebhookSignature, parseInventoryWebhookEvent, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_EVENT_HEADER, WEBHOOK_DELIVERY_HEADER, WEBHOOK_TOLERANCE_SEC, } from './webhook.js';
|
|
20
|
+
export { INVENTORY_ENDPOINTS, INVENTORY_ERROR_CODES, INVENTORY_REQUEST_ERROR_CODES, INVENTORY_WEBHOOK_EVENTS, INVENTORY_WEBHOOK_ENVELOPE_FIELDS, INVENTORY_WEBHOOK_LIMIT, INVENTORY_LIST_LIMIT_MAX, LOCATION_TYPES, STOCK_MOVEMENT_TYPES, STOCK_MOVEMENT_SOURCES, STOCK_CONDITIONS, STOCK_CHANGE_CAUSES, WEBHOOK_DELIVERY_STATUSES, INVENTORY_WARNING_CODES, STOCK_KINDS, STOCK_LOSS_REASONS, WITHDRAWAL_TYPES, LANDED_COST_TYPES, LANDED_COST_ALLOCATIONS, RESERVATION_STATUSES, INVENTORY_IDEMPOTENCY_KEY_MAX, RESERVATION_MINUTES_MIN, RESERVATION_MINUTES_MAX, } from './vertrag.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
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 type { Article, GoodsReceiptPreviewLine, InventoryShortfall, InventoryWebhook, InventoryWebhookDelivery, InventoryWebhookTestDelivery, Location, Reservation, StockBelowMinimumEventData, StockChangedEventData, StockLevel, StockMovement, StockOperation, StockValue } from './typen.js';
|
|
16
|
+
type Roh = Record<string, unknown>;
|
|
17
|
+
/** Ein Leser bekommt den Vorgang (fuer die Meldung) und den Pfad im Rumpf. */
|
|
18
|
+
export interface Ort {
|
|
19
|
+
name: string;
|
|
20
|
+
pfad: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function antwortfehler(name: string, grund: string): KasseneckValidationError;
|
|
23
|
+
export declare function objekt(w: unknown): Roh | null;
|
|
24
|
+
export declare function artikel(ort: Ort, w: unknown): Article;
|
|
25
|
+
export declare function standort(ort: Ort, w: unknown): Location;
|
|
26
|
+
export declare function bestand(ort: Ort, w: unknown): StockLevel;
|
|
27
|
+
export declare function wert(ort: Ort, w: unknown): StockValue;
|
|
28
|
+
export declare function bewegung(ort: Ort, w: unknown): StockMovement;
|
|
29
|
+
export declare function webhook(ort: Ort, w: unknown): InventoryWebhook;
|
|
30
|
+
export declare function zustellung(ort: Ort, w: unknown): InventoryWebhookDelivery;
|
|
31
|
+
export declare function probeZustellung(ort: Ort, w: unknown, webhookId: string): InventoryWebhookTestDelivery;
|
|
32
|
+
export declare function bestandGeaendert(ort: Ort, w: unknown): StockChangedEventData;
|
|
33
|
+
export declare function unterMindestbestand(ort: Ort, w: unknown): StockBelowMinimumEventData;
|
|
34
|
+
/** Antwort einer Buchung; ohne `operationId` ist nicht belegt, dass gebucht wurde. */
|
|
35
|
+
export declare function vorgang(name: string, daten: unknown): StockOperation;
|
|
36
|
+
/** Eine Zeile der Wareneingangs-Vorschau; Werte nur, wenn der Server sie sendet (Recht `costs`). */
|
|
37
|
+
export declare function vorschauZeile(ort: Ort, w: unknown): GoodsReceiptPreviewLine;
|
|
38
|
+
export declare function reservierung(ort: Ort, w: unknown): Reservation;
|
|
39
|
+
/** Die fehlenden Positionen aus `insufficient_available` (`data.details[]`); ein kaputter Eintrag faellt weg. */
|
|
40
|
+
export declare function fehlmengen(roh: unknown): InventoryShortfall[];
|
|
41
|
+
/** Eine zugesagte Liste `data.<feld>`, Eintrag fuer Eintrag gelesen. */
|
|
42
|
+
export declare function liste<T>(name: string, daten: unknown, feld: string, lesen: (ort: Ort, e: unknown) => T): T[];
|
|
43
|
+
/** Der Cursor der naechsten Seite; `null` am Ende. Etwas anderes als Text oder `null` ist kaputt. */
|
|
44
|
+
export declare function naechsterCursor(name: string, daten: unknown): string | null;
|
|
45
|
+
export {};
|