# 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/.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** --- ## 16. `/rest/batch` als Write-Limit-Umgehung erwarten ```javascript // FALSCH: "20 Writes in einem Request = nur 1 Write fürs Budget" const payloads = items.slice(0, 20).map(i => ({ resource: '...', method: 'POST', body: {...} })); await fetch('/rest/batch', { body: JSON.stringify({ payloads }) }); // → Zählt intern als 20 einzelne Writes! ``` **Problem:** `/rest/batch` reduziert HTTP-Roundtrips, aber das Write-Budget von Plenty zählt jede Operation einzeln. 20 Writes pro Batch = 20 Writes fürs Rate Limit. Kein Vorteil beim "long period write limit". **Fix:** Batch nur für HTTP-Overhead-Reduktion nutzen, nicht als Rate-Limit-Umgehung. Für Writes AIMD-basiertes Rate Limiting verwenden. **Siehe DOJO.md #29, #30** --- ## 18. Property-Relation aktualisieren ohne valueId ```javascript // FALSCH: PUT ohne id in relationValues → HTTP 200, aber kein Update! await client.put(`/rest/properties/relations/${existing.id}`, { relationValues: [{ value: 'Ja', lang: 'de' }], // id fehlt → silent noop }); ``` **Problem:** Plenty akzeptiert den PUT mit HTTP 200, aber der Wert wird nicht gespeichert, wenn `relationValues[0].id` fehlt. Kein Fehler, kein Hinweis — es passiert einfach nichts. **Fix:** Prüfen ob `valueId` vorhanden. Wenn nicht: Relation löschen und neu erstellen (DELETE + POST). **Siehe DOJO.md #32** --- ## 17. Fester Delay für lang laufende Bulk-Operationen ```javascript // FALSCH: Fester Delay für tausende Requests const DELAY = 1000; // Tagsüber zu schnell, nachts zu langsam for (const item of items) { await sleep(DELAY); await api.post(...); } ``` **Problem:** Tagsüber konkurrieren andere Services um dasselbe Rate-Limit-Budget → 429er. Nachts ist die API frei → unnötig langsam. Fester Delay kann beides nicht optimal bedienen. **Fix:** AIMD Rate Limiting: bei Erfolg schneller werden, bei 429 verdoppeln. Konvergiert automatisch zum Optimum. **Siehe DOJO.md #30** ---