This commit is contained in:
@@ -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
|
||||
|
||||
145
DOJO.md
145
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, '<br>');
|
||||
|
||||
// 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: `<p>${htmlText}</p>`,
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user