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
|
## 17. Fester Delay für lang laufende Bulk-Operationen
|
||||||
|
|
||||||
```javascript
|
```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
|
```javascript
|
||||||
const res = await fetch(`${BASE}/rest/properties/relations`, {
|
await client.post('/rest/properties/relations', {
|
||||||
method: 'POST',
|
propertyId: 5,
|
||||||
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
relationTypeIdentifier: 'item', // für Variationen
|
||||||
body: JSON.stringify({
|
relationTargetId: variationId, // mainVariationId
|
||||||
propertyId: 5, // ID der Eigenschaft
|
selectionRelationId: 48 // Auswahlwert-ID
|
||||||
relationTypeIdentifier: 'item', // Verknüpfungstyp
|
|
||||||
relationTargetId: variationId, // mainVariationId des Artikels
|
|
||||||
selectionRelationId: 48 // ID des Auswahlwerts
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
**Besonderheiten:**
|
**Pattern (Kontakt-Eigenschaft, Textwert):**
|
||||||
- `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.
|
```javascript
|
||||||
- **Unique Constraint** auf `propertyId + targetId + type`: Duplikat-POST → HTTP 500 (Integrity Constraint). Sicher als "bereits gesetzt" behandeln.
|
await client.post('/rest/properties/relations', {
|
||||||
- Auslesen: `GET /rest/items/{itemId}/variations/{varId}?with=properties` → `properties.find(p => p.propertyId === X)`
|
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}` | PUT | Eigenschafts-Verknüpfung aktualisieren |
|
||||||
| `/rest/properties/relations/{relId}` | DELETE | Eigenschafts-Verknüpfung löschen |
|
| `/rest/properties/relations/{relId}` | DELETE | Eigenschafts-Verknüpfung löschen |
|
||||||
| `/rest/batch` | POST | Bis zu 20 API-Calls in einem Request bündeln |
|
| `/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()`.
|
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