Files
plenty-dojo/ANTI-PATTERNS.md
Sebastian Poll 200d52f917 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.
2026-04-09 21:23:56 +00:00

6.9 KiB

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

// 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

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

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

"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

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

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:

const contactId = order.contactId
  || order.relations?.find(r => r.referenceType === 'contact')?.referenceId;

Siehe DOJO.md #6, Quirk #1


7. Status-IDs als Integer behandeln

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:

const s = parseFloat(order.statusId);
if (s >= 5 && s < 6) { /* Pool */ }

Siehe DOJO.md #16


8. Status-History-Response als Array erwarten

const entries = history; // Manchmal ist es aber { entries: [] }!

Problem: Der Endpoint gibt manchmal ein Array, manchmal ein Objekt mit entries-Feld zurück.

Fix:

const entries = Array.isArray(history) ? history : (history.entries || []);

Siehe DOJO.md #14


9. Dokument sofort nach Generierung fetchen

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

// FALSCH:
await api.get(`/rest/orders/${id}/documents/${docId}`);

Problem: Der Download-Endpoint ist /rest/documents/{docId} — OHNE Order-Prefix!

Fix:

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

const total = order.amounts[0].invoiceTotal; // Can crash!

Problem: amounts ist ein Array das leer sein kann. Kein Array-Zugriff ohne Check.

Fix:

const total = order.amounts?.[0]?.invoiceTotal ?? null;

Siehe DOJO.md #11, Quirk #7


12. Bundle-Komponenten als eigene Artikel zählen

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

// 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

// 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

// 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