# Many-to-Many Junction-Tabelle mit additionalColumns - Testergebnisse **Version:** 2.0 **Datum:** 11. März 2026 **Status:** ✅ VOLLSTÄNDIG ERFOLGREICH mit UI-Integration ## ✅ VOLLSTÄNDIG ERFOLGREICH! **UPDATE (März 2026):** 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. **NEU:** UI-Anzeige von Junction-Spalten via columnAttributeMap + notStorable Pattern! ## Zusammenfassung Die Implementierung einer Many-to-Many-Beziehung mit zusätzlichen Feldern in der Junction-Tabelle wurde erfolgreich getestet und ist: - **vollständig funktionsfähig via REST-API** - **im UI anzeigbar via columnAttributeMap Pattern** - **automatisch aktualisierbar via Hooks** ## ✅ 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: ```sql 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:** ```bash GET /api/v1/CAICollectionCDokumente?maxSize=10 ``` **Response:** ```json { "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:** ```bash GET /api/v1/CAICollectionCDokumente?where[0][type]=equals&where[0][attribute]=cDokumenteId&where[0][value]=doc123 ``` **Nach syncId suchen:** ```bash GET /api/v1/CAICollectionCDokumente?where[0][type]=equals&where[0][attribute]=syncId&where[0][value]=SYNC-123 ``` **Felder selektieren:** ```bash GET /api/v1/CAICollectionCDokumente?select=id,cDokumenteId,cAICollectionsId,syncId ``` ### 4. Konfiguration **Status: KORREKT IMPLEMENTIERT** **Erforderliche Dateien:** **1. Entity-Definition** (`entityDefs/CAICollectionCDokumente.json`): ```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`): ```json { "entity": true, "type": "Base", "module": "Custom", "object": true, "isCustom": true, "tab": false, "acl": true, "disabled": false } ``` **3. Controller** (`Controllers/CAICollectionCDokumente.php`): ```php entityManager->getRDBRepository('CAdvowareAkten'); $repository->getRelation($entity, 'dokumentes')->updateColumns( $foreignEntity, [ 'syncstatus' => 'new', 'lastSync' => null ] ); } } ``` ### Vorteile dieser Lösung ✅ **UI-Anzeige**: Junction-Spalten sichtbar in Relationship-Panels ✅ **Kein 405 Fehler**: Read-only Darstellung vermeidet Inline-Edit-Probleme ✅ **API-Kompatibel**: Funktioniert parallel zur Junction-Entity-API ✅ **Bidirektional**: Funktioniert von beiden Seiten der Beziehung ✅ **Hook-Integration**: Updates via Hooks möglich ### Einschränkungen ⚠️ **notStorable = Read-only in UI**: Keine direkte Bearbeitung im Panel ⚠️ **Updates via Hooks**: Änderungen müssen über Hooks oder API erfolgen ⚠️ **layoutAvailabilityList**: Foreign-Side-Felder nur in Custom Layouts sichtbar ## 🎯 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 - ✅ `syncId` ist 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:** 1. ✅ Junction Entity als API-Endpoint nutzen (`/api/v1/CAICollectionCDokumente`) 2. ✅ Keine UI-Panels für Junction-Relationships mit additionalColumns 3. ✅ API-Integration für externe Systeme (Middleware, KI, etc.) 4. ✅ Bei Bedarf: Separate Management-UI für Junction Entity (ohne Relationship-Panel) **Wichtig:** 1. Controller und Service erstellen 2. Scope-Definition anlegen 3. Entity-Definition mit korrekten Feldtypen 4. ACL-Rechte für die Junction-Entity setzen 5. Cache löschen und rebuild 6. **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: ```sql 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:** ```json "cAICollections": { "type": "hasMany", "entity": "CAICollections", "foreign": "cDokumente", "relationName": "cAICollectionCDokumente", "additionalColumns": { "syncId": { "type": "varchar", "len": 255 } } } ``` **CAICollections.json:** ```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:** 1. ❌ Direct display of syncId in relationship panel layout → 405 Fehler 2. ❌ Custom View mit actionEditLinkData → Blank views, dann weiter 405 Fehler 3. ❌ 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:** 1. ❌ Standard GET-Request: `GET /api/v1/CDokumente/{id}/cAICollections` → keine `syncId` in Response 2. ❌ Mit Query-Parametern (select, additionalColumns, columns, etc.) → keine `syncId` 3. ❌ POST mit columns-Parameter beim Verknüpfen → wird nicht gespeichert **Verifiziert:** ```bash # 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`: ```php $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: ```php // 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: ```bash GET /api/v1/CDokumente/{id}/relatedCollectionsWithSyncId ``` ### Option 3: Direkte Datenbank-Abfrage Für einfache Szenarien kann man die Junction-Tabelle direkt abfragen: ```php $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) ```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 ```sql 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)