From b4604877f30f0ca88d50569f4b22e73a9f086647 Mon Sep 17 00:00:00 2001 From: Liborius Date: Fri, 10 Apr 2026 14:43:33 +0000 Subject: [PATCH] dojo: Kontaktnotizen (REST messages), Contact Properties, PUT-silent-noop (#31, #32) --- ANTI-PATTERNS.md | 15 +++++ DOJO.md | 145 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 143 insertions(+), 17 deletions(-) diff --git a/ANTI-PATTERNS.md b/ANTI-PATTERNS.md index 26c5e8f..e5c5d74 100644 --- a/ANTI-PATTERNS.md +++ b/ANTI-PATTERNS.md @@ -245,6 +245,21 @@ await fetch('/rest/batch', { body: JSON.stringify({ payloads }) }); --- +## 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 diff --git a/DOJO.md b/DOJO.md index 732fdf8..3500fee 100644 --- a/DOJO.md +++ b/DOJO.md @@ -573,30 +573,46 @@ db.function('business_hours', (startIso, endIso) => { --- -## 25. Property-Relation erstellen (Selection-Eigenschaft) +## 25. Property-Relation erstellen (Item und Kontakt) -**Lektion:** Um eine Auswahl-Eigenschaft an einer Variation zu setzen, einen POST auf `/rest/properties/relations` senden. +**Lektion:** `/rest/properties/relations` funktioniert für Items **und** Kontakte — nur `relationTypeIdentifier` ändert sich. -**Pattern:** +**Pattern (Item-Eigenschaft, Selection):** ```javascript -const res = await fetch(`${BASE}/rest/properties/relations`, { - method: 'POST', - headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, - body: JSON.stringify({ - propertyId: 5, // ID der Eigenschaft - relationTypeIdentifier: 'item', // Verknüpfungstyp - relationTargetId: variationId, // mainVariationId des Artikels - selectionRelationId: 48 // ID des Auswahlwerts - }) +await client.post('/rest/properties/relations', { + propertyId: 5, + relationTypeIdentifier: 'item', // für Variationen + relationTargetId: variationId, // mainVariationId + selectionRelationId: 48 // Auswahlwert-ID }); ``` -**Besonderheiten:** -- `selectionRelationId` wird NICHT im gleichnamigen Response-Feld gespeichert (bleibt `null`), sondern als `relationValues[0].value` mit `lang: "0"`. Das Plenty-UI interpretiert es trotzdem korrekt. -- **Unique Constraint** auf `propertyId + targetId + type`: Duplikat-POST → HTTP 500 (Integrity Constraint). Sicher als "bereits gesetzt" behandeln. -- Auslesen: `GET /rest/items/{itemId}/variations/{varId}?with=properties` → `properties.find(p => p.propertyId === X)` +**Pattern (Kontakt-Eigenschaft, Textwert):** +```javascript +await client.post('/rest/properties/relations', { + propertyId: 52, + relationTypeIdentifier: 'contact', // für Kontakte + relationTargetId: contactId, + relationValues: [{ value: 'Ja', lang: 'de' }], // 'lang' ist Pflichtfeld! +}); +``` -**Entdeckt:** 2026-04-08. Artikeltyp-Eigenschaft (Property 5) auf Variationen setzen. +**Lesen (Kontakt):** +```javascript +const resp = await client.get('/rest/properties/relations', { + params: { propertyId, relationTypeIdentifier: 'contact', relationTargetId: contactId }, +}); +const entries = Array.isArray(resp.data) ? resp.data : (resp.data.entries || []); +const existing = entries.find(r => r.relationTargetId === contactId); +``` + +**Besonderheiten:** +- `selectionRelationId` bei Items wird als `relationValues[0].value` gespeichert, nicht im gleichnamigen Feld. +- **Unique Constraint** auf `propertyId + targetId + type`: Duplikat-POST → HTTP 500. +- `lang` ist bei `relationValues` ein Pflichtfeld — ohne `lang` wird der Wert nicht gespeichert. +- Response-Format inkonsistent: mal Array, mal `{ entries: [] }` — immer beide Fälle behandeln. + +**Entdeckt:** 2026-04-08 (Items), 2026-04-10 (Kontakte). --- @@ -772,6 +788,11 @@ await sleep(delay * 2); // Extra-Pause vor Retry | `/rest/properties/relations/{relId}` | PUT | Eigenschafts-Verknüpfung aktualisieren | | `/rest/properties/relations/{relId}` | DELETE | Eigenschafts-Verknüpfung löschen | | `/rest/batch` | POST | Bis zu 20 API-Calls in einem Request bündeln | +| `/rest/user` | GET | Eigene User-ID und Name des API-Users abrufen | +| `/rest/messages` | POST | Kontaktnotiz erstellen (categoryId: 3 = Kontaktnotizen-Tab) | +| `/rest/accounts/contacts` | GET | Kontakte laden (filter: classId, with: options) | +| `/rest/accounts/contacts/{id}` | PUT | Kontakt aktualisieren (z.B. classId ändern) | +| `/rest/accounts/contacts/classes` | GET | Alle Kundenklassen laden (flaches Objekt, kein Array!) | --- @@ -791,4 +812,94 @@ await sleep(delay * 2); // Extra-Pause vor Retry 7. **Fraktionale Status-IDs:** 5.1, 5.12, 5.32, 6.01 etc. — `parseFloat()` verwenden, nie `parseInt()`. +8. **Property-Relation PUT ohne valueId wird silent ignoriert:** Wenn `relationValues[0].id` fehlt, akzeptiert Plenty das PUT mit HTTP 200, speichert aber nichts. Immer DELETE + POST als Fallback. **Siehe #32** + +--- + +# XII. Kontakt-API + +## 31. Kontaktnotizen via Messenger-API erstellen + +**Lektion:** Kontaktnotizen im "Kontaktnotizen"-Tab erscheinen **nicht** über einen dedizierten Notiz-Endpoint, sondern über `POST /rest/messages` mit `categoryId: 3`. + +**Warum:** Der alte Notiz-Endpoint (`/rest/contacts/{id}/notes` o.ä.) erzeugt Einträge, die im falschen Tab landen oder nicht sichtbar sind. Die Messenger-API mit `categoryId: 3` ist der korrekte Weg. + +**Pattern:** +```javascript +// 1. Erst API-User-ID ermitteln (Pflichtfeld für 'from'!) +const userResp = await client.get('/rest/user'); +const userId = userResp.data?.id; +const userName = userResp.data?.real_name || userResp.data?.user || 'System'; + +// 2. Text HTML-escapen und Zeilenumbrüche konvertieren +const htmlText = escapeHtml(text).replace(/\n/g, '
'); + +// 3. Notiz erstellen +await client.post('/rest/messages', { + from: { type: 'user', value: userId, name: userName }, + to: { user: [], role: [], allUsers: false }, + whispered: true, + title: 'Customer notes', + message: `

${htmlText}

`, + referrer: { type: 'backend', value: userId, name: userName }, + linkedTo: [{ type: 'contact', value: contactId }], + categoryId: 3, // 3 = Kontaktnotizen-Tab +}); +``` + +**Wichtig:** +- `from.value` muss eine gültige User-ID sein — nicht weglassen oder erfinden +- `message` muss HTML sein (kein Plaintext), Text vorher escapen +- `linkedTo` verknüpft die Notiz mit dem Kontakt +- `whispered: true` macht die Notiz intern (nicht an Kunden sichtbar) + +**Entdeckt:** 2026-04-10. Atradius-LimitMgmt-Projekt, Kontaktnotizen nach Limit-Schließung. + +--- + +## 32. Property-Relation aktualisieren: PUT ohne valueId ist ein silent Noop + +**Lektion:** Wenn eine bestehende Property-Relation in `relationValues[0]` keine `id` hat, wird ein `PUT` auf die Relation mit HTTP 200 beantwortet, aber der Wert **nicht** gespeichert. + +**Warum:** Plenty erwartet beim PUT die interne Value-ID aus `relationValues[0].id`. Fehlt sie, akzeptiert die API den Request, tut aber nichts. + +**Pattern (sicher):** +```javascript +const existing = entries.find(r => r.relationTargetId === contactId); + +if (existing) { + const valueId = existing.relationValues?.[0]?.id; + + if (valueId) { + // Value-ID bekannt → normaler PUT + await client.put(`/rest/properties/relations/${existing.id}`, { + relationValues: [{ id: valueId, value: newValue, lang: 'de' }], + }); + } else { + // Keine Value-ID → DELETE + POST (PUT würde silent ignoriert) + await client.delete(`/rest/properties/relations/${existing.id}`); + await client.post('/rest/properties/relations', { + propertyId, relationTypeIdentifier: 'contact', relationTargetId: contactId, + relationValues: [{ value: newValue, lang: 'de' }], + }); + } +} else { + // Noch keine Relation → POST + await client.post('/rest/properties/relations', { + propertyId, relationTypeIdentifier: 'contact', relationTargetId: contactId, + relationValues: [{ value: newValue, lang: 'de' }], + }); +} +``` + +**Anti-Pattern:** +```javascript +// FALSCH: PUT ohne id in relationValues → HTTP 200, aber kein Update! +await client.put(`/rest/properties/relations/${existing.id}`, { + relationValues: [{ value: newValue, lang: 'de' }], // id fehlt → noop +}); +``` + +**Entdeckt:** 2026-04-10. Atradius-LimitMgmt. Wert nach PUT immer noch der alte. + ---