feat: Inventar-Export für ERPNext (erp-inventory:read) #1

Merged
Nick.Rosenloecher merged 8 commits from feat/erp-inventar-export into main 2026-10-13 05:39:01 +00:00
Owner

Inhalt

Read-only Inventar-Export für ERPNext (T8 im ERPNext-Ausbau), Vertrag in docs/erp-inventar-export.md (v1).

  • Endpunkte /api/v1/export/inventar/{produkte,chargen,bewegungen,ernten}, nur mit API-Schlüssel und Scope erp-inventory:read
  • Produkte und Chargen: vollständiges Neulesen, remainingQuantity live; Bewegungen mit 5 Minuten Verzögerung (Cursor)
  • Lockout sperrt gültige Schlüssel nie; Cursor-Jahr auf 2000–2100 begrenzt; Warnung bei fehlendem TRUST_PROXY
  • Schlüssel-Oberfläche bietet nur Scopes an, die der Benutzer selbst hat

Prüfung

  • origin/main (Paket 0, 10cbe9b) eingemergt; einziger Konflikt ARCHITECTURE.md (Punkte 11/12 von main, Punkt 14 mit erp-inventory:read)
  • Backend: Typecheck ok, Jest 1165/1165
  • Frontend: Typecheck ok, Vitest 269/269
  • Zwei Sicherheitsreviews, Befunde behoben

Test plan

  • CI auf main grün
  • Nach Rollout: API-Schlüssel mit erp-inventory:read anlegen, in ERPNext als APICORE_ERP_KEY hinterlegen, Abgleich-Job einmal laufen lassen

🤖 Generated with Claude Code

## Inhalt Read-only Inventar-Export für ERPNext (T8 im ERPNext-Ausbau), Vertrag in `docs/erp-inventar-export.md` (v1). - Endpunkte `/api/v1/export/inventar/{produkte,chargen,bewegungen,ernten}`, nur mit API-Schlüssel und Scope `erp-inventory:read` - Produkte und Chargen: vollständiges Neulesen, `remainingQuantity` live; Bewegungen mit 5 Minuten Verzögerung (Cursor) - Lockout sperrt gültige Schlüssel nie; Cursor-Jahr auf 2000–2100 begrenzt; Warnung bei fehlendem `TRUST_PROXY` - Schlüssel-Oberfläche bietet nur Scopes an, die der Benutzer selbst hat ## Prüfung - `origin/main` (Paket 0, 10cbe9b) eingemergt; einziger Konflikt `ARCHITECTURE.md` (Punkte 11/12 von main, Punkt 14 mit `erp-inventory:read`) - Backend: Typecheck ok, Jest 1165/1165 - Frontend: Typecheck ok, Vitest 269/269 - Zwei Sicherheitsreviews, Befunde behoben ## Test plan - [ ] CI auf main grün - [ ] Nach Rollout: API-Schlüssel mit `erp-inventory:read` anlegen, in ERPNext als `APICORE_ERP_KEY` hinterlegen, Abgleich-Job einmal laufen lassen 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Nick.Rosenloecher added 8 commits 2026-10-13 05:21:52 +00:00
Nach 20 abgelehnten API-Schlüsseln (fehlerhaft, unbekannt, widerrufen,
abgelaufen) in 15 Minuten antwortet ApiCore der Absender-IP mit 429, auch
für richtige Schlüssel - vor jeder Datenbankabfrage. Gleiches Budget wie
der ERP-Export des Shops. Die Zählung liegt wie der Throttler im Speicher
des Pods und ist auf 10.000 Adressen begrenzt.

Abgelehnte Schlüssel werden mit Grund, öffentlichem Präfix (ack_xxxxxxxx)
und IP protokolliert, nie mit dem Schlüssel. Die Suche bleibt über den
SHA-256-Hash; neu sind Unit-Tests für authenticate (fehlend, unbekannt,
widerrufen, abgelaufen, Scope-Filter, IP-Allowlist, Sperre).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Datenquelle für den nächtlichen Inventar-Abgleich in ERPNext (ERPNext-
Ausbau T8, Entscheidungen E2/E3). Neues Recht erp-inventory:read, auch als
API-Schlüssel-Scope; es öffnet nur diese vier GET-Endpunkte:

- /api/v1/export/inventar/produkte: Artikel (ohne Beschreibung)
- .../chargen: Abfüllungen und Herstellungschargen mit Losnummer, MHD,
  Ausgangsmenge, Rest aus dem Bewegungsbuch, Füllmenge g und Sorte
- .../bewegungen?seit=&nach=&limit=: Shop-Verkauf, Retoure, Entnahmen mit
  Grund (HOFLADEN = Vertrauensschrank, EIGENBEDARF, BRUCH, PROBE,
  SONSTIGES) und Inventur; Cursor über (createdAt, id), Bewegungen erst
  nach 5 Minuten Wartezeit, damit der Cursor keine offene Buchung überholt
- .../ernten?jahr=: Nettogewicht kg je Kalenderjahr, wie die Statistik

Alle Listen stabil sortiert, seitenweise (1-500), Parameter mit
class-validator geprüft, Cursor je Endpunkt streng validiert. Feld-
Allowlists: keine Notizen, Benutzer, Standorte, Rohstoffe. Die Gründe gab
es im Datenmodell schon - keine Migration; das Recht legt der Katalog-
Sync beim Start an und gibt es der Rolle Administrator.

Vertrag mit Feldern, Einheiten, Fehlercodes und Beispielen:
docs/erp-inventar-export.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Bisher vergab die Oberfläche fest shop-sync:read. Jetzt sind alle
freigegebenen Rechte ankreuzbar (Shop lesen, Shop-Buchungen,
Inventar-Spiegel für ERPNext); vorausgewählt bleibt wie bisher nur
Shop lesen, ohne Haken lässt sich nichts anlegen. Die Liste zeigt, was
ein Schlüssel darf, und der Hinweis nach dem Anlegen nennt das Zielsystem.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Wie der Throttler seit 886b4e7: die Sperre nach abgelehnten Schlüsseln
zählt über rateLimitSubject, damit wechselnde Adressen im eigenen
IPv6-/64 das Budget nicht zurücksetzen. Im Log steht weiter die Adresse.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Befunde aus dem Sicherheitsreview des Branches:

- MEDIUM: Die Sperre lief vor der Schlüsselsuche und zählte auch falsch
  aufgebaute Tokens. Wer die gemeinsame Adresse (Cluster-Ausgang, Proxy)
  nutzt oder per X-Forwarded-For vortäuscht, hätte ERPNext und den Shop
  15 Minuten aussperren können. Jetzt: erst Suche über den Hash; ein
  gültiger, nicht widerrufener, nicht abgelaufener Schlüssel kommt immer
  durch; 429 nur für unbekannte, widerrufene und abgelaufene Schlüssel;
  falsch aufgebaute Tokens sofort 401 ohne Zählung. Beim Start warnt
  ApiCore, wenn TRUST_PROXY in Produktion fehlt (im Cluster gesetzt).
- LOW: Bei vollem Speicher verdrängt der Limiter zuerst nicht gesperrte
  Einträge, gesperrte erst zuletzt.
- LOW: Bewegungs-Cursor mit Jahr außerhalb 2000-2100 ergab 500, jetzt 400.
- LOW/INFO: Vertrag legt fest, dass ERPNext Produkte und Chargen bei jedem
  Lauf vollständig neu liest (Voll-Spiegel) und Bewegungen inkrementell;
  remainingQuantity ist live, Bewegungen kommen 5 Minuten verzögert.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Der Server lehnt Rechte ab, die der Anlegende nicht selbst hat. Das UI
bietet jetzt auch nur diese an (z. B. kein shop-sync:write ohne eigenes
Recht) und erklärt, wenn gar keines übrig bleibt.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
# Conflicts:
#	ARCHITECTURE.md
Nick.Rosenloecher merged commit b8abe16849 into main 2026-10-13 05:39:01 +00:00
Sign in to join this conversation.