17 KiB
Many-to-Many Junction-Tabelle mit additionalColumns - Testergebnisse
✅ VOLLSTÄNDIG ERFOLGREICH!
UPDATE: Die Junction-Tabelle kann als eigene Entity via REST-API abgerufen werden! Seit EspoCRM 6.0.0 werden Junction-Tabellen automatisch als Entities verfügbar gemacht.
Zusammenfassung
Die Implementierung einer Many-to-Many-Beziehung mit zusätzlichen Feldern (syncId) in der Junction-Tabelle wurde erfolgreich getestet und ist vollständig funktionsfähig via REST-API.
✅ Was funktioniert
1. Datenbank-Schema
Status: VOLLSTÄNDIG FUNKTIONSFÄHIG
Die Junction-Tabelle c_a_i_collection_c_dokumente wurde automatisch mit der zusätzlichen sync_id-Spalte erstellt:
CREATE TABLE `c_a_i_collection_c_dokumente` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`c_a_i_collections_id` varchar(17),
`c_dokumente_id` varchar(17),
`sync_id` varchar(255), ← Unser custom Feld!
`deleted` tinyint(1) DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_C_A_I_COLLECTIONS_ID_C_DOKUMENTE_ID` (...)
)
2. Junction-Entity via REST-API
Status: ✅ VOLLSTÄNDIG FUNKTIONSFÄHIG
Die Junction-Tabelle ist als eigene Entity CAICollectionCDokumente via REST-API verfügbar!
Beispiel-Abruf:
GET /api/v1/CAICollectionCDokumente?maxSize=10
Response:
{
"total": 5,
"list": [
{
"id": "6",
"deleted": false,
"cAICollectionsId": "testcol999",
"cDokumenteId": "testdoc999",
"syncId": "SYNC-TEST-999",
"cAICollectionsName": null,
"cDokumenteName": null
}
]
}
✅ Die syncId ist direkt in der API-Response enthalten!
3. Filterung und Suche
Status: ✅ FUNKTIONIERT PERFEKT
Alle Standard-API-Features funktionieren:
Nach Dokument-ID filtern:
GET /api/v1/CAICollectionCDokumente?where[0][type]=equals&where[0][attribute]=cDokumenteId&where[0][value]=doc123
Nach syncId suchen:
GET /api/v1/CAICollectionCDokumente?where[0][type]=equals&where[0][attribute]=syncId&where[0][value]=SYNC-123
Felder selektieren:
GET /api/v1/CAICollectionCDokumente?select=id,cDokumenteId,cAICollectionsId,syncId
4. Konfiguration
Status: KORREKT IMPLEMENTIERT
Erforderliche Dateien:
1. Entity-Definition (entityDefs/CAICollectionCDokumente.json):
{
"fields": {
"id": {"type": "id", "dbType": "bigint", "autoincrement": true},
"cAICollections": {"type": "link"},
"cAICollectionsId": {"type": "varchar", "len": 17, "index": true},
"cDokumente": {"type": "link"},
"cDokumenteId": {"type": "varchar", "len": 17, "index": true},
"syncId": {"type": "varchar", "len": 255, "isCustom": true},
"deleted": {"type": "bool", "default": false}
},
"links": {
"cAICollections": {
"type": "belongsTo",
"entity": "CAICollections"
},
"cDokumente": {
"type": "belongsTo",
"entity": "CDokumente"
}
}
}
2. Scope-Definition (scopes/CAICollectionCDokumente.json):
{
"entity": true,
"type": "Base",
"module": "Custom",
"object": true,
"isCustom": true,
"tab": false,
"acl": true,
"disabled": false
}
3. Controller (Controllers/CAICollectionCDokumente.php):
<?php
namespace Espo\Custom\Controllers;
use Espo\Core\Controllers\Record;
class CAICollectionCDokumente extends Record
{
// Erbt alle CRUD-Operationen
}
4. Service (Services/CAICollectionCDokumente.php):
<?php
namespace Espo\Custom\Services;
use Espo\Services\Record;
class CAICollectionCDokumente extends Record
{
// Standard-Logik
}
5. Many-to-Many-Beziehung in CDokumente.json:
"cAICollections": {
"type": "hasMany",
"entity": "CAICollections",
"foreign": "cDokumente",
"relationName": "cAICollectionCDokumente",
"additionalColumns": {
"syncId": {
"type": "varchar",
"len": 255
}
}
}
6. ACL-Berechtigungen: Die Rolle muss Zugriff auf die Junction-Entity haben:
{
"CAICollectionCDokumente": {
"create": "yes",
"read": "all",
"edit": "all",
"delete": "all"
}
}
💡 Verwendung
Beispiel 1: Alle Verknüpfungen eines Dokuments abrufen
Python:
import requests
response = requests.get(
"https://your-crm.com/api/v1/CAICollectionCDokumente",
headers={"X-Api-Key": "your-api-key"},
params={
"where[0][type]": "equals",
"where[0][attribute]": "cDokumenteId",
"where[0][value]": "doc123",
"select": "cAICollectionsId,syncId"
}
)
data = response.json()
for item in data['list']:
print(f"Collection: {item['cAICollectionsId']}, SyncID: {item['syncId']}")
cURL:
curl "https://your-crm.com/api/v1/CAICollectionCDokumente?where[0][type]=equals&where[0][attribute]=cDokumenteId&where[0][value]=doc123" \
-H "X-Api-Key: your-api-key"
Beispiel 2: Dokument in Collection via syncId finden
response = requests.get(
"https://your-crm.com/api/v1/CAICollectionCDokumente",
headers={"X-Api-Key": "your-api-key"},
params={
"where[0][type]": "equals",
"where[0][attribute]": "syncId",
"where[0][value]": "SYNC-external-id-123"
}
)
if response.json()['list']:
match = response.json()['list'][0]
doc_id = match['cDokumenteId']
col_id = match['cAICollectionsId']
print(f"Found: Document {doc_id} in Collection {col_id}")
Beispiel 3: Neue Verknüpfung mit syncId erstellen
Via Standard-API (POST):
# Erstelle Verknüpfung
response = requests.post(
"https://your-crm.com/api/v1/CAICollectionCDokumente",
headers={"X-Api-Key": "your-api-key"},
json={
"cDokumenteId": "doc123",
"cAICollectionsId": "col456",
"syncId": "SYNC-2026-001"
}
)
Beispiel 4: syncId aktualisieren
# Aktualisiere einen bestehenden Eintrag
response = requests.put(
f"https://your-crm.com/api/v1/CAICollectionCDokumente/{junction_id}",
headers={"X-Api-Key": "your-api-key"},
json={
"syncId": "SYNC-UPDATED-002"
}
)
📊 Test-Ergebnisse
| Feature | Status | Notizen |
|---|---|---|
| Junction-Tabelle Erstellung | ✅ | Automatisch mit syncId-Spalte |
| Junction-Entity via API | ✅ | Vollständig funktionsfähig |
| syncId in API-Response | ✅ | Direkt verfügbar |
| Filterung (where) | ✅ | Standard-API-Syntax |
| Sortierung (orderBy) | ✅ | Funktioniert |
| Paginierung (maxSize, offset) | ✅ | Funktioniert |
| CREATE via API | ✅ | POST mit allen Feldern |
| UPDATE via API | ✅ | PUT zum Ändern von syncId |
| DELETE via API | ✅ | Standard-DELETE |
| View-Darstellung | ❌ | Nicht empfohlen - verursacht 405 Fehler |
⚠️ UI-Panel Warnung
WICHTIG: additionalColumns sollten NICHT in Standard-Relationship-Panels angezeigt werden!
Problem:
- Standard relationship panels versuchen inline-editing
- Dies führt zu 405 Method Not Allowed Fehlern
- additionalColumns sind nicht kompatibel mit Standard-Panel-Architektur
Empfehlung:
- ✅ Nutze API-only Access Pattern
- ✅ Vollständige CRUD via
/api/v1/CAICollectionCDokumente - ❌ NICHT in CDokumente detail view als relationship panel anzeigen
🎯 Fazit
Die Junction-Tabelle mit additionalColumns ist vollständig via REST-API nutzbar!
Vorteile:
- ✅ Keine Custom-Endpoints nötig
- ✅ Standard-API-Features (Filter, Sort, Pagination)
- ✅ CRUD-Operationen vollständig unterstützt
- ✅
syncIdist direkt in der Response - ✅ Einfache Integration in externe Systeme
- ✅ API-only Pattern verhindert 405-Fehler
Einschränkungen:
- ⚠️ UI-Darstellung in Standard-Relationship-Panels verursacht 405 Fehler
- ⚠️ additionalColumns nur über Junction-Entity-API zugänglich
- ⚠️ Standard relationship endpoints (z.B. GET /api/v1/CDokumente/{id}/cAICollections) geben additionalColumns NICHT zurück
Best Practice:
- ✅ Junction Entity als API-Endpoint nutzen (
/api/v1/CAICollectionCDokumente) - ✅ Keine UI-Panels für Junction-Relationships mit additionalColumns
- ✅ API-Integration für externe Systeme (Middleware, KI, etc.)
- ✅ Bei Bedarf: Separate Management-UI für Junction Entity (ohne Relationship-Panel)
Wichtig:
- Controller und Service erstellen
- Scope-Definition anlegen
- Entity-Definition mit korrekten Feldtypen
- ACL-Rechte für die Junction-Entity setzen
- Cache löschen und rebuild
- NICHT als Relationship-Panel in UI anzeigen (→ 405 Fehler)
📁 Dateien
Die Implementierung befindet sich in:
/custom/Espo/Custom/Resources/metadata/entityDefs/CAICollectionCDokumente.json/custom/Espo/Custom/Resources/metadata/scopes/CAICollectionCDokumente.json/custom/Espo/Custom/Controllers/CAICollectionCDokumente.php/custom/Espo/Custom/Services/CAICollectionCDokumente.php/custom/Espo/Custom/Resources/metadata/entityDefs/CDokumente.json(mit additionalColumns)/custom/Espo/Custom/Resources/metadata/entityDefs/CAICollections.json
Datenbank-Tabelle:
c_a_i_collection_c_dokumente
Erstellt: 9. März 2026
Getestet mit: EspoCRM 9.3.2 (MariaDB 12.2.2, PHP 8.2.30)
API-User für Tests: marvin (API-Key: e53def10eea27b92a6cd00f40a3e09a4)
Entity-Name: CAICollectionCDokumente
API-Endpoint: /api/v1/CAICollectionCDokumente
1. Datenbank-Schema
Status: VOLLSTÄNDIG FUNKTIONSFÄHIG
Die Junction-Tabelle c_a_i_collection_c_dokumente wurde automatisch mit der zusätzlichen sync_id-Spalte erstellt:
CREATE TABLE `c_a_i_collection_c_dokumente` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`c_a_i_collections_id` varchar(17),
`c_dokumente_id` varchar(17),
`sync_id` varchar(255), ← Unser custom Feld!
`deleted` tinyint(1) DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_C_A_I_COLLECTIONS_ID_C_DOKUMENTE_ID` (...)
)
2. Konfiguration
Status: KORREKT IMPLEMENTIERT
Die Beziehung wurde in beiden Entity-Definitionen konfiguriert:
CDokumente.json:
"cAICollections": {
"type": "hasMany",
"entity": "CAICollections",
"foreign": "cDokumente",
"relationName": "cAICollectionCDokumente",
"additionalColumns": {
"syncId": {
"type": "varchar",
"len": 255
}
}
}
CAICollections.json:
"cDokumente": {
"type": "hasMany",
"entity": "CDokumente",
"foreign": "cAICollections",
"relationName": "cAICollectionCDokumente"
}
3. Datenspeicherung
Status: FUNKTIONIERT
Die syncId kann in der Datenbank gespeichert werden:
- ✅ Via direktes SQL-INSERT/UPDATE
- ✅ Via interne EspoCRM ORM-API (EntityManager)
- ✅ Daten werden korrekt persistiert
4. View-Darstellung
Status: ⚠️ NICHT EMPFOHLEN (API-ONLY PATTERN)
Problem: Standard EspoCRM Relationship-Panels versuchen inline-editing von Feldern. Bei additionalColumns führt dies zu 405 Method Not Allowed Fehlern, da die Standard-Panel-UI nicht mit dem Junction-Entity-Pattern kompatibel ist.
Versucht & Fehlgeschlagen:
- ❌ Direct display of syncId in relationship panel layout → 405 Fehler
- ❌ Custom View mit actionEditLinkData → Blank views, dann weiter 405 Fehler
- ❌ Simplified relationship layout ohne syncId → 405 Fehler blieben bestehen
ROOT CAUSE: Standard relationship panels senden HTTP-Requests die nicht mit Junction-Entity-Architektur übereinstimmen. additionalColumns erfordern spezielle Behandlung die nicht durch Standard-UI bereitgestellt wird.
LÖSUNG: API-ONLY Access Pattern
- ✅ Vollständige CRUD via
/api/v1/CAICollectionCDokumente - ✅ Kein UI-Panel in CDokumente → keine 405 Fehler
- ✅ Alle Funktionen über REST API verfügbar
- ✅ Perfekt für externe Systeme und Middleware
Falls UI Display gewünscht:
- Option: Custom Panel das direkt die Junction Entity list-view lädt (gefiltert nach documentId)
- Option: Separate Tab/Page für Junction Entity-Management
- Nicht empfohlen: Standard relationship panel mit additionalColumns
❌ Was NICHT funktioniert
REST-API gibt keine additionalColumns zurück
Status: LIMITATION DER STANDARD-API
Das Problem:
Die Standard-EspoCRM REST-API gibt die additionalColumns nicht zurück, wenn Beziehungen abgerufen werden.
Getestete Szenarien:
- ❌ Standard GET-Request:
GET /api/v1/CDokumente/{id}/cAICollections→ keinesyncIdin Response - ❌ Mit Query-Parametern (select, additionalColumns, columns, etc.) → keine
syncId - ❌ POST mit columns-Parameter beim Verknüpfen → wird nicht gespeichert
Verifiziert:
# syncId ist in DB:
SELECT * FROM c_a_i_collection_c_dokumente;
# → sync_id = 'SYNC-20260309-220416'
# Aber API-Response enthält sie nicht:
GET /api/v1/CDokumente/{id}/cAICollections
# → {"list": [{"id": "...", "name": "...", ...}]} # Keine syncId!
💡 Lösungen & Workarounds
Option 1: Interne PHP-API verwenden (Empfohlen)
Verwende die interne EspoCRM-API für den Zugriff auf additionalColumns:
$entityManager = $container->get('entityManager');
$doc = $entityManager->getEntity('CDokumente', $docId);
$repository = $entityManager->getRDBRepository('CDokumente');
$relation = $repository->getRelation($doc, 'cAICollections');
// Lade verknüpfte Collections
$collections = $relation->find();
// Hole additionalColumns
foreach ($collections as $col) {
$relationData = $relation->getColumnAttributes($col, ['syncId']);
$syncId = $relationData['syncId'] ?? null;
echo "syncId: $syncId\n";
}
// Setze syncId beim Verknüpfen
$relation->relateById($collectionId, [
'syncId' => 'your-sync-id-value'
]);
Option 2: Custom API-Endpoint erstellen
Erstelle einen eigenen API-Endpoint, der die additionalColumns zurückgibt:
// custom/Espo/Custom/Controllers/CDokumente.php
public function getActionRelatedCollectionsWithSyncId($params, $data, $request)
{
$id = $params['id'];
$em = $this->getEntityManager();
$doc = $em->getEntity('CDokumente', $id);
$repo = $em->getRDBRepository('CDokumente');
$relation = $repo->getRelation($doc, 'cAICollections');
$result = [];
foreach ($relation->find() as $col) {
$relationData = $relation->getColumnAttributes($col, ['syncId']);
$result[] = [
'id' => $col->getId(),
'name' => $col->get('name'),
'syncId' => $relationData['syncId'] ?? null
];
}
return ['list' => $result];
}
Dann abrufen via:
GET /api/v1/CDokumente/{id}/relatedCollectionsWithSyncId
Option 3: Direkte Datenbank-Abfrage
Für einfache Szenarien kann man die Junction-Tabelle direkt abfragen:
$pdo = $entityManager->getPDO();
$stmt = $pdo->prepare("
SELECT c.*, j.sync_id
FROM c_a_i_collections c
JOIN c_a_i_collection_c_dokumente j
ON c.id = j.c_a_i_collections_id
WHERE j.c_dokumente_id = ?
AND j.deleted = 0
AND c.deleted = 0
");
$stmt->execute([$docId]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
Option 4: Formulas für automatische Synchronisation
Nutze EspoCRM-Formulas um syncId zu setzen:
// In CDokumente.json oder als Workflow
entity\setLinkMultipleColumn('cAICollections', collectionId, 'syncId', 'your-value');
📊 Test-Ergebnisse
| Feature | Status | Notizen |
|---|---|---|
| Junction-Tabelle Erstellung | ✅ | Automatisch mit syncId-Spalte |
| additionalColumns in Entity-Defs | ✅ | Korrekt konfiguriert |
| syncId in Datenbank speichern | ✅ | Via SQL oder interne API |
| syncId über REST-API setzen | ❌ | Wird ignoriert |
| syncId über REST-API abrufen | ❌ | Nicht in Response |
| syncId über interne API | ✅ | Vollständig funktionsfähig |
| View-Darstellung | ✅* | Möglich, aber manuell konfigurieren |
*) Benötigt manuelle Layout-Konfiguration
🎯 Fazit
Die technische Implementierung der Many-to-Many-Beziehung mit additionalColumns funktioniert einwandfrei. Die Datenbank-Struktur ist korrekt, Daten können gespeichert und abgerufen werden.
Jedoch: Die Standard-REST-API von EspoCRM gibt diese zusätzlichen Felder nicht zurück. Für den produktiven Einsatz sollte einer der oben beschriebenen Workarounds verwendet werden - am besten Option 1 (interne PHP-API) oder Option 2 (Custom-Endpoint).
📁 Dateien
Die Konfiguration befindet sich in:
/custom/Espo/Custom/Resources/metadata/entityDefs/CDokumente.json/custom/Espo/Custom/Resources/metadata/entityDefs/CAICollections.json
Datenbank-Tabelle:
c_a_i_collection_c_dokumente
🔧 Verwendung
Beispiel: Dokument in Collection mit Sync-ID einfügen (PHP)
$entityManager = $container->get('entityManager');
// Entities laden
$doc = $entityManager->getEntity('CDokumente', $docId);
$collection = $entityManager->getEntity('CAICollections', $collectionId);
// Verknüpfen mit syncId
$repo = $entityManager->getRDBRepository('CDokumente');
$relation = $repo->getRelation($doc, 'cAICollections');
$relation->relateById($collectionId, [
'syncId' => 'my-unique-sync-id-123'
]);
// SyncId auslesen
$relationData = $relation->getColumnAttributes($collection, ['syncId']);
echo $relationData['syncId']; // 'my-unique-sync-id-123'
Beispiel: Dokument in Collection finden via Sync-ID
SELECT c_dokumente_id, c_a_i_collections_id, sync_id
FROM c_a_i_collection_c_dokumente
WHERE sync_id = 'my-unique-sync-id-123'
AND deleted = 0;
Erstellt: 9. März 2026
Getestet mit: EspoCRM (MariaDB 12.2.2, PHP 8.2.30)
API-User für Tests: marvin (API-Key: e53def10eea27b92a6cd00f40a3e09a4)