Initial release: Plenty Dojo Wissensbasis
Geteilte Plentymarkets API Learnings (DOJO.md, ANTI-PATTERNS.md), Claude Code Skill (/plenty-dojo), und Install-Script für neue User.
This commit is contained in:
231
ANTI-PATTERNS.md
Normal file
231
ANTI-PATTERNS.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# Plenty Dojo — Anti-Patterns
|
||||
|
||||
> Dinge, die man mit der Plentymarkets API **nicht** tun sollte.
|
||||
> Diese Datei ist allgemeingültig und kann an jeden Plenty-Entwickler weitergegeben werden.
|
||||
>
|
||||
> Shop-spezifische Anti-Patterns stehen in `instances/<PID>.md`.
|
||||
> Ausführliche Erklärungen stehen in `DOJO.md`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Delta-Sync mit exaktem oder zu knappem Timestamp
|
||||
|
||||
```javascript
|
||||
// FALSCH: Exakter Timestamp
|
||||
const syncSince = lastSyncTimestamp;
|
||||
|
||||
// AUCH FALSCH: 3h Overlap reicht nicht
|
||||
const OVERLAP = 3 * 60 * 60 * 1000;
|
||||
```
|
||||
|
||||
**Problem:** Race Condition — wenn ein Sync exakt zur gleichen Sekunde läuft, in der eine Order geändert wird, fällt die Order permanent durch alle folgenden Syncs. 3h Overlap half nicht, weil zwischen Container-Restarts (Deploys) und der Order-Änderung mehr als 3h vergehen können.
|
||||
|
||||
**Fix:** 24h Overlap. Kostet ~200 extra Orders pro Sync, eliminiert aber die Race Condition. **Siehe DOJO.md #4**
|
||||
|
||||
---
|
||||
|
||||
## 2. Statushistorie nach Timestamp sortieren
|
||||
|
||||
```sql
|
||||
ORDER BY createdAt DESC -- oder ORDER BY status_at DESC
|
||||
```
|
||||
|
||||
**Problem:** Mehrere Statuswechsel in derselben Sekunde → zufällige Reihenfolge → falscher "letzter" Status.
|
||||
|
||||
**Fix:** `ORDER BY plenty_id ASC/DESC` — eindeutig, aufsteigend, chronologisch. **Siehe DOJO.md #13**
|
||||
|
||||
---
|
||||
|
||||
## 3. Aufträge im Schnellfeuer-Modus verschieben
|
||||
|
||||
```javascript
|
||||
for (const order of orders) {
|
||||
await api.updateStatus(order.id, 6); // kein Warten, keine Prüfung
|
||||
}
|
||||
```
|
||||
|
||||
**Problem:** Event-Procedures reagieren sofort auf Statuswechsel. Race Conditions zwischen eigenem Write und Plenty-Automationen sind häufig.
|
||||
|
||||
**Fix:** Pause + Verifizieren nach jedem Write. **Siehe DOJO.md #15**
|
||||
|
||||
---
|
||||
|
||||
## 4. Timestamps ohne Timezone-Offset senden
|
||||
|
||||
```javascript
|
||||
"2026-03-30T02:30:00" // kein Offset — Interpretation unklar
|
||||
```
|
||||
|
||||
**Problem:** Plenty's Verhalten bei fehlenden Offsets ist undokumentiert und endpoint-abhängig. Bei DST-Wechseln kritisch.
|
||||
|
||||
**Fix:** Immer `+00:00` für UTC. **Siehe DOJO.md #19**
|
||||
|
||||
---
|
||||
|
||||
## 5. Nur Zielstatus prüfen, nicht die Transition
|
||||
|
||||
```javascript
|
||||
if (order.statusId === 6) { /* OK */ }
|
||||
```
|
||||
|
||||
**Problem:** Auftrag kann im Zielstatus sein, aber zwischendurch durch andere Status gelaufen sein. Oder er war kurz dort und wurde weitergeschoben.
|
||||
|
||||
**Fix:** Statushistorie abrufen und Transition als Sequenz prüfen. **Siehe DOJO.md #15**
|
||||
|
||||
---
|
||||
|
||||
## 6. contactId ohne Relations-Fallback lesen
|
||||
|
||||
```javascript
|
||||
const contactId = order.contactId; // kann undefined sein!
|
||||
```
|
||||
|
||||
**Problem:** `contactId` ist nicht immer direkt am Order-Objekt vorhanden. Ohne `relations` in den `with`-Parametern fehlt er komplett.
|
||||
|
||||
**Fix:**
|
||||
```javascript
|
||||
const contactId = order.contactId
|
||||
|| order.relations?.find(r => r.referenceType === 'contact')?.referenceId;
|
||||
```
|
||||
**Siehe DOJO.md #6, Quirk #1**
|
||||
|
||||
---
|
||||
|
||||
## 7. Status-IDs als Integer behandeln
|
||||
|
||||
```javascript
|
||||
if (parseInt(order.statusId) === 5) { /* FALSCH */ }
|
||||
if (order.statusId === 5) { /* FALSCH */ }
|
||||
```
|
||||
|
||||
**Problem:** Plenty verwendet fraktionale Status-IDs: 5.1, 5.12, 5.32, 6.01 etc. `parseInt(5.12)` ergibt `5`, `5.12 === 5` ist `false`.
|
||||
|
||||
**Fix:** `parseFloat()` und Range-Vergleiche:
|
||||
```javascript
|
||||
const s = parseFloat(order.statusId);
|
||||
if (s >= 5 && s < 6) { /* Pool */ }
|
||||
```
|
||||
**Siehe DOJO.md #16**
|
||||
|
||||
---
|
||||
|
||||
## 8. Status-History-Response als Array erwarten
|
||||
|
||||
```javascript
|
||||
const entries = history; // Manchmal ist es aber { entries: [] }!
|
||||
```
|
||||
|
||||
**Problem:** Der Endpoint gibt manchmal ein Array, manchmal ein Objekt mit `entries`-Feld zurück.
|
||||
|
||||
**Fix:**
|
||||
```javascript
|
||||
const entries = Array.isArray(history) ? history : (history.entries || []);
|
||||
```
|
||||
**Siehe DOJO.md #14**
|
||||
|
||||
---
|
||||
|
||||
## 9. Dokument sofort nach Generierung fetchen
|
||||
|
||||
```javascript
|
||||
await api.post(`/rest/orders/${id}/documents/invoice/generate`);
|
||||
const docs = await api.get(`/rest/orders/${id}/documents`);
|
||||
// → Dokument hat status: 'pending', nicht 'done'!
|
||||
```
|
||||
|
||||
**Problem:** Dokument-Generierung ist asynchron. Das Dokument ist nicht sofort verfügbar.
|
||||
|
||||
**Fix:** Retry-Loop mit steigenden Delays (3s, 5s, 8s, 10s, 15s), immer auf `status === 'done'` filtern. **Siehe DOJO.md #17**
|
||||
|
||||
---
|
||||
|
||||
## 10. Dokument-Download über Order-Endpoint
|
||||
|
||||
```javascript
|
||||
// FALSCH:
|
||||
await api.get(`/rest/orders/${id}/documents/${docId}`);
|
||||
```
|
||||
|
||||
**Problem:** Der Download-Endpoint ist `/rest/documents/{docId}` — OHNE Order-Prefix!
|
||||
|
||||
**Fix:**
|
||||
```javascript
|
||||
const res = await api.getRaw(`/rest/documents/${docId}`);
|
||||
const buffer = Buffer.from(await res.arrayBuffer());
|
||||
```
|
||||
**Siehe DOJO.md #18**
|
||||
|
||||
---
|
||||
|
||||
## 11. amounts-Array ohne Null-Check lesen
|
||||
|
||||
```javascript
|
||||
const total = order.amounts[0].invoiceTotal; // Can crash!
|
||||
```
|
||||
|
||||
**Problem:** `amounts` ist ein Array das leer sein kann. Kein Array-Zugriff ohne Check.
|
||||
|
||||
**Fix:**
|
||||
```javascript
|
||||
const total = order.amounts?.[0]?.invoiceTotal ?? null;
|
||||
```
|
||||
**Siehe DOJO.md #11, Quirk #7**
|
||||
|
||||
---
|
||||
|
||||
## 12. Bundle-Komponenten als eigene Artikel zählen
|
||||
|
||||
```javascript
|
||||
const serverCount = items.filter(i => isServer(i)).reduce((sum, i) => sum + i.quantity, 0);
|
||||
// → Zählt Bundle-Komponenten (typeId 3) doppelt!
|
||||
```
|
||||
|
||||
**Problem:** Bundle-Header (`typeId 2`) enthält die Gesamtmenge. Bundle-Komponenten (`typeId 3`) sind die Einzelteile des Bundles. Beides zählen = Doppelzählung.
|
||||
|
||||
**Fix:** Nur `typeId 1` (Variation), `typeId 2` (Bundle-Header), `typeId 11` (Variations-Bundle) zählen. `typeId 3` (Komponenten) ignorieren. **Siehe DOJO.md #12**
|
||||
|
||||
---
|
||||
|
||||
## 13. Tag-Operationen als Fehler behandeln wenn idempotent
|
||||
|
||||
```javascript
|
||||
// FALSCH: 409 beim Hinzufügen und 404 beim Löschen als Fehler werfen
|
||||
const res = await api.post(`.../variation_tags`, { tagId: 123 });
|
||||
if (res.status !== 200) throw new Error('Tag konnte nicht gesetzt werden');
|
||||
```
|
||||
|
||||
**Problem:** `409 Conflict` beim Hinzufügen = Tag existiert bereits (gewünschter Zustand!). `404 Not Found` beim Löschen = Tag existiert nicht (auch gewünscht!).
|
||||
|
||||
**Fix:** Diese HTTP-Codes als Erfolg behandeln, nicht als Fehler.
|
||||
|
||||
---
|
||||
|
||||
## 14. Neue Eigenschaften über den Merkmale-Endpoint setzen
|
||||
|
||||
```javascript
|
||||
// FALSCH: variation_properties ist für das ALTE Merkmal-System
|
||||
await api.post(`/rest/items/${itemId}/variations/${varId}/variation_properties`, {
|
||||
propertyId: 5, selectionRelationId: 48
|
||||
});
|
||||
// → 500 Foreign Key Constraint (plenty_character_item)
|
||||
```
|
||||
|
||||
**Problem:** `variation_properties` arbeitet mit dem Legacy-Merkmal-System (`plenty_character_*`-Tabellen). Neue Eigenschaften (Property-System) werden über `/rest/properties/relations` verwaltet — komplett andere API.
|
||||
|
||||
**Fix:** `/rest/properties/relations` mit `relationTypeIdentifier: 'item'` verwenden. **Siehe DOJO.md #25**
|
||||
|
||||
---
|
||||
|
||||
## 15. Mehrere Bulk-Scripts parallel gegen die API laufen lassen
|
||||
|
||||
```javascript
|
||||
// Script A: Care Packs setzen (600ms delay)
|
||||
// Script B: Lizenzen setzen (600ms delay)
|
||||
// → Effektiv ~3,3 Requests/s → 429 Rate Limit!
|
||||
```
|
||||
|
||||
**Problem:** Jedes Script hält seinen eigenen Delay ein, aber die API sieht die Summe aller Requests. Andere Crons/Services teilen sich dasselbe Rate-Limit-Budget.
|
||||
|
||||
**Fix:** Bulk-Operationen immer sequenziell (eins nach dem anderen). Delay ≥ 1.500ms wenn andere Services parallel laufen. **Siehe DOJO.md #2**
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user