# EspoCRM Best Practices & Entwicklungsrichtlinien **Version:** 2.0 **Datum:** 9. März 2026 **Zielgruppe:** AI Code Agents & Entwickler --- ## 📋 Inhaltsverzeichnis 1. [Projekt-Übersicht](#projekt-übersicht) 2. [Architektur-Prinzipien](#architektur-prinzipien) 3. [Entity-Entwicklung](#entity-entwicklung) 4. [Relationship-Patterns](#relationship-patterns) 5. [API-Entwicklung](#api-entwicklung) 6. [Workflow-Management](#workflow-management) 7. [Testing & Validierung](#testing--validierung) 8. [Fehlerbehandlung](#fehlerbehandlung) 9. [Deployment-Prozess](#deployment-prozess) 10. [Troubleshooting](#troubleshooting) --- ## Projekt-Übersicht ### System-Architektur ``` EspoCRM 9.3.2 ├── PHP 8.2.30 ├── MariaDB 12.2.2 ├── Docker Container: espocrm, espocrm-db └── Workspace: /var/lib/docker/volumes/vmh-espocrm_espocrm/_data ``` ### Verzeichnisstruktur ``` custom/ ├── Espo/Custom/ # Backend-Code │ ├── Controllers/ # REST API Endpoints │ ├── Services/ # Business Logic │ ├── Repositories/ # Data Access Layer │ ├── Hooks/ # Entity Lifecycle Hooks │ └── Resources/ │ ├── metadata/ # Entity & Field Definitionen │ │ ├── entityDefs/ # Entity-Konfiguration │ │ ├── clientDefs/ # Frontend-Konfiguration │ │ ├── scopes/ # Entity-Scopes │ │ └── formula/ # Formula Scripts │ ├── layouts/ # UI-Layouts │ └── i18n/ # Übersetzungen (de_DE, en_US) ├── scripts/ # Entwicklungs-Tools │ ├── validate_and_rebuild.py # Haupt-Validierungs-Tool │ ├── e2e_tests.py # End-to-End Tests │ ├── ki_project_overview.py # Projekt-Analyse für AI │ └── junctiontabletests/ # Junction Table Tests ├── docs/ # Dokumentation (NEU) │ ├── ESPOCRM_BEST_PRACTICES.md # Dieses Dokument │ ├── tools/ # Tool-Dokumentation │ └── workflows/ # Workflow-Dokumentation └── workflows/ # Workflow JSON-Definitions client/custom/ # Frontend-Code ├── src/ # JavaScript Modules ├── css/ # Custom Styles └── res/ # Resources ``` --- ## Architektur-Prinzipien ### 1. Separation of Concerns **EspoCRM = Data Layer** - Speichert Entities - Stellt UI bereit - Validiert Daten - Bietet REST API **Middleware = Business Logic** - KI-Analyse - Team-Zuweisung - Komplexe Workflows - Externe Integrationen ### 2. Drei-Schichten-Architektur ``` ┌─────────────────────────────────────────┐ │ FRONTEND (clientDefs, Layouts) │ │ • User Interface │ │ • JavaScript Actions │ └────────────────┬────────────────────────┘ │ AJAX/REST ┌────────────────▼────────────────────────┐ │ CONTROLLER (Controllers/) │ │ • Request Validation │ │ • ACL Checks │ └────────────────┬────────────────────────┘ │ Service Call ┌────────────────▼────────────────────────┐ │ SERVICE (Services/) │ │ • Business Logic │ │ • Entity Manager │ └────────────────┬────────────────────────┘ │ Repository ┌────────────────▼────────────────────────┐ │ REPOSITORY (Repositories/) │ │ • Data Access │ │ • Relationships │ └─────────────────────────────────────────┘ ``` ### 3. Clean Code Principles **DO:** - ✅ Nutze sprechende Variablennamen - ✅ Schreibe kleine, fokussierte Funktionen - ✅ Kommentiere komplexe Business-Logik - ✅ Verwende Type Hints (PHP 8.2+) - ✅ Folge PSR-12 Coding Standard **DON'T:** - ❌ Keine komplexe Logik in Hooks - ❌ Keine direkten SQL-Queries (nutze EntityManager) - ❌ Keine hard-coded Werte (nutze Config) - ❌ Keine redundanten Includes - ❌ Keine ungenutzten Imports --- ## Entity-Entwicklung ### Entity-Naming Convention **Pattern:** `C{EntityName}` für Custom Entities **Beispiele:** - `CMietobjekt` - Mietobjekte - `CVmhMietverhltnis` - Mietverhältnisse (VMH = Vermieter Helden) - `CKuendigung` - Kündigungen - `CAICollections` - AI Collections ### Entity Definition Template **Datei:** `custom/Espo/Custom/Resources/metadata/entityDefs/{EntityName}.json` ```json { "fields": { "name": { "type": "varchar", "required": true, "maxLength": 255, "trim": true, "isCustom": true, "tooltip": true }, "status": { "type": "enum", "options": ["Neu", "In Bearbeitung", "Abgeschlossen"], "default": "Neu", "required": true, "isCustom": true, "style": { "Neu": "primary", "In Bearbeitung": "warning", "Abgeschlossen": "success" } }, "description": { "type": "text", "rows": 10, "isCustom": true, "tooltip": true }, "amount": { "type": "currency", "isCustom": true, "audited": true }, "dueDate": { "type": "date", "isCustom": true, "audited": true } }, "links": { "parent": { "type": "belongsToParent", "entityList": ["CVmhRumungsklage", "CMietinkasso"] }, "createdBy": { "type": "belongsTo", "entity": "User" }, "modifiedBy": { "type": "belongsTo", "entity": "User" } } } ``` ### Scope Definition **Datei:** `custom/Espo/Custom/Resources/metadata/scopes/{EntityName}.json` ```json { "entity": true, "type": "Base", "module": "Custom", "object": true, "isCustom": true, "tab": true, "acl": true, "stream": true, "disabled": false, "customizable": true, "importable": true, "notifications": true, "calendar": false } ``` **Wichtige Flags:** - `tab: true` - Zeigt Entity in Navigation - `acl: true` - ACL-System aktiv - `stream: true` - Stream/Activity Feed - `calendar: true` - Für Entities mit Datum-Feldern ### i18n (Internationalisierung) **KRITISCH:** Immer BEIDE Sprachen pflegen! **Datei:** `custom/Espo/Custom/Resources/i18n/de_DE/{EntityName}.json` ```json { "labels": { "Create {EntityName}": "{EntityName} erstellen", "{EntityName}": "{EntityName}", "name": "Name", "status": "Status", "description": "Beschreibung" }, "fields": { "name": "Name", "status": "Status", "description": "Beschreibung", "amount": "Betrag", "dueDate": "Fälligkeitsdatum" }, "links": { "parent": "Übergeordnet", "relatedEntity": "Verknüpfte Entity" }, "options": { "status": { "Neu": "Neu", "In Bearbeitung": "In Bearbeitung", "Abgeschlossen": "Abgeschlossen" } }, "tooltips": { "name": "Eindeutiger Name des Datensatzes", "description": "Detaillierte Beschreibung" } } ``` **Datei:** `custom/Espo/Custom/Resources/i18n/en_US/{EntityName}.json` ```json { "labels": { "Create {EntityName}": "Create {EntityName}", "{EntityName}": "{EntityName}" }, "fields": { "name": "Name", "status": "Status", "description": "Description", "amount": "Amount", "dueDate": "Due Date" }, "links": { "parent": "Parent", "relatedEntity": "Related Entity" }, "options": { "status": { "Neu": "New", "In Bearbeitung": "In Progress", "Abgeschlossen": "Completed" } } } ``` --- ## Relationship-Patterns ### 1. One-to-Many (hasMany / belongsTo) **Beispiel:** Ein Mietobjekt hat viele Mietverhältnisse **Parent Entity (CMietobjekt):** ```json { "links": { "mietverhltnisse": { "type": "hasMany", "entity": "CVmhMietverhltnis", "foreign": "mietobjekt" } } } ``` **Child Entity (CVmhMietverhltnis):** ```json { "fields": { "mietobjektId": { "type": "varchar", "len": 17 }, "mietobjektName": { "type": "varchar" } }, "links": { "mietobjekt": { "type": "belongsTo", "entity": "CMietobjekt", "foreign": "mietverhltnisse" } } } ``` ### 2. Many-to-Many (hasMany mit relationName) **Beispiel:** Dokumente ↔ AI Collections **Entity 1 (CDokumente):** ```json { "links": { "cAICollections": { "type": "hasMany", "entity": "CAICollections", "foreign": "cDokumente", "relationName": "cAICollectionCDokumente" } } } ``` **Entity 2 (CAICollections):** ```json { "links": { "cDokumente": { "type": "hasMany", "entity": "CDokumente", "foreign": "cAICollections", "relationName": "cAICollectionCDokumente" } } } ``` **Wichtig:** `relationName` muss identisch sein! ### 3. Many-to-Many mit additionalColumns (Junction Entity) **Seit EspoCRM 6.0:** Junction-Tabellen werden automatisch als Entities verfügbar! **Entity Definition:** ```json { "links": { "cDokumente": { "type": "hasMany", "entity": "CDokumente", "foreign": "cAICollections", "relationName": "cAICollectionCDokumente", "additionalColumns": { "syncId": { "type": "varchar", "len": 255 } } } } } ``` **Junction Entity (CAICollectionCDokumente):** **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" } } } ``` **scopes/CAICollectionCDokumente.json:** ```json { "entity": true, "type": "Base", "module": "Custom", "object": true, "isCustom": true, "tab": false, "acl": true, "disabled": false } ``` **Controller & Service:** ```php getParsedBody(); $id = $data->id ?? null; if (!$id) { throw new BadRequest('ID is required'); } $result = $this->getRecordService()->doSomething($id, $data); return [ 'success' => true, 'data' => $result ]; } /** * Custom GET Action: GET /api/v1/CMyEntity/{id}/customData */ public function getActionCustomData(Request $request): array { $id = $request->getRouteParam('id'); $data = $this->getRecordService()->getCustomData($id); return [ 'data' => $data ]; } } ``` **2. Service Logic:** **Datei:** `custom/Espo/Custom/Services/{EntityName}.php` ```php getAcl()->checkEntityEdit($this->entityType)) { throw new Forbidden(); } // Load Entity $entity = $this->getEntityManager()->getEntity($this->entityType, $id); if (!$entity) { throw new NotFound(); } // Business Logic $entity->set('status', 'In Bearbeitung'); $this->getEntityManager()->saveEntity($entity); // Return Result return [ 'id' => $entity->getId(), 'status' => $entity->get('status') ]; } public function getCustomData(string $id): array { $entity = $this->getEntityManager()->getEntity($this->entityType, $id); if (!$entity) { throw new NotFound(); } // Complex data aggregation $relatedData = $this->getRelatedData($entity); return [ 'entity' => $entity->getValueMap(), 'related' => $relatedData ]; } } ``` ### API Authentication **API Key Header:** ```bash curl -X GET "https://crm.example.com/api/v1/CMyEntity" \ -H "X-Api-Key: your-api-key-here" ``` **Test API Keys:** - `marvin`: `e53def10eea27b92a6cd00f40a3e09a4` - `dev-test`: `2b0747ca34d15032aa233ae043cc61bc` --- ## Workflow-Management ### Workflow-Dateien **Verzeichnis:** `custom/workflows/` **Format:** JSON (Simple Workflow oder BPM Flowchart) ### Simple Workflow Beispiel ```json { "type": "simple", "name": "auto-assign-new-entity", "entity_type": "CMyEntity", "trigger_type": "afterRecordCreated", "is_active": true, "description": "Auto-assign new records to team", "conditions_all": [ { "type": "isEmpty", "attribute": "assignedUserId" } ], "actions": [ { "type": "applyAssignmentRule", "targetTeamId": "team-id-here" }, { "type": "sendEmail", "to": "assignedUser", "emailTemplateId": "template-id" } ] } ``` ### Workflow Import/Export ```bash # Alle Workflows exportieren php custom/scripts/workflow_manager.php export # Workflow importieren php custom/scripts/workflow_manager.php import custom/workflows/my-workflow.json # Workflows auflisten php custom/scripts/workflow_manager.php list ``` --- ## Testing & Validierung ### Validierungs-Tool **Haupt-Tool:** `custom/scripts/validate_and_rebuild.py` ```bash # Vollständige Validierung + Rebuild python3 custom/scripts/validate_and_rebuild.py # Nur Validierung (kein Rebuild) python3 custom/scripts/validate_and_rebuild.py --dry-run # Mit E2E Tests überspringen python3 custom/scripts/validate_and_rebuild.py --skip-e2e ``` **Das Tool prüft:** 1. ✅ JSON-Syntax aller Custom-Dateien 2. ✅ Relationship-Konsistenz (bidirektionale Links) 3. ✅ Formula-Script Platzierung 4. ✅ i18n-Vollständigkeit (de_DE + en_US) 5. ✅ Layout-Struktur (bottomPanelsDetail, detail.json) 6. ✅ Dateirechte (www-data:www-data) 7. ✅ CSS-Validierung (csslint) 8. ✅ JavaScript-Validierung (jshint) 9. ✅ PHP-Syntax (php -l) 10. ✅ EspoCRM Rebuild 11. ✅ E2E-Tests (CRUD-Operationen) **Bei Fehlern:** Automatische Fehlerlog-Analyse der letzten 50 Log-Zeilen! ### End-to-End Tests **Tool:** `custom/scripts/e2e_tests.py` ```bash # E2E Tests ausführen python3 custom/scripts/e2e_tests.py ``` **Tests:** - CRUD für alle Custom Entities - Relationship-Verknüpfungen - ACL-Prüfungen ### Manuelle Tests **Checkliste:** - [ ] Entity in UI sichtbar? - [ ] Felder editierbar? - [ ] Relationships funktionieren? - [ ] Formulas triggern korrekt? - [ ] Workflows aktiv? - [ ] API-Endpoints erreichbar? - [ ] ACL-Regeln greifen? --- ## Fehlerbehandlung ### Log-Files **Verzeichnis:** `data/logs/` **Haupt-Logfile:** `espo-{YYYY-MM-DD}.log` ```bash # Letzte Fehler anzeigen tail -50 data/logs/espo-$(date +%Y-%m-%d).log | grep -i error # Live-Monitoring tail -f data/logs/espo-$(date +%Y-%m-%d).log ``` ### Häufige Fehler #### 1. Layout-Fehler: "false" statt "{}" **Problem:** EspoCRM 7.x+ erfordert `{}` statt `false` als Platzhalter **Falsch:** ```json { "rows": [ [ {"name": "field1"}, false ] ] } ``` **Richtig:** ```json { "rows": [ [ {"name": "field1"}, {} ] ] } ``` #### 2. Relationship nicht bidirektional **Problem:** `foreign` zeigt nicht zurück **Falsch:** ```json // Entity A "links": { "entityB": { "type": "hasMany", "entity": "EntityB", "foreign": "wrongName" // ❌ } } // Entity B "links": { "entityA": { "type": "belongsTo", "entity": "EntityA", "foreign": "entityB" } } ``` **Richtig:** ```json // Entity A "links": { "entityB": { "type": "hasMany", "entity": "EntityB", "foreign": "entityA" // ✅ Zeigt auf Link-Namen in B } } // Entity B "links": { "entityA": { "type": "belongsTo", "entity": "EntityA", "foreign": "entityB" // ✅ Zeigt auf Link-Namen in A } } ``` #### 3. i18n fehlt für en_US **Problem:** Nur de_DE vorhanden, en_US fehlt **Lösung:** IMMER beide Sprachen pflegen! en_US ist Fallback. #### 4. Dateirechte falsch **Problem:** Files gehören root statt www-data **Lösung:** Automatisch via validate_and_rebuild.py oder manuell: ```bash sudo chown -R www-data:www-data custom/ sudo find custom/ -type f -exec chmod 664 {} \; sudo find custom/ -type d -exec chmod 775 {} \; ``` #### 5. ACL: 403 Forbidden **Problem:** Role hat keine Rechte auf Entity **Lösung:** ACL in Admin UI oder via SQL: ```sql UPDATE role SET data = JSON_SET(data, '$.table.CMyEntity', JSON_OBJECT('create', 'yes', 'read', 'all', 'edit', 'all', 'delete', 'all') ) WHERE name = 'RoleName'; ``` --- ## Deployment-Prozess ### Standard-Workflow ```bash # 1. Code-Änderungen durchführen vim custom/Espo/Custom/Resources/metadata/entityDefs/CMyEntity.json # 2. Validierung + Rebuild python3 custom/scripts/validate_and_rebuild.py # 3. Bei Erfolg: Commit git add custom/ git commit -m "feat: Add CMyEntity with custom fields" git push ``` ### Quick Rebuild (nach kleinen Änderungen) ```bash docker exec espocrm php command.php clear-cache docker exec espocrm php command.php rebuild ``` ### Nach Änderungen an Relationships **IMMER:** 1. Cache löschen 2. Rebuild ausführen 3. Browser-Cache löschen (Ctrl+F5) --- ## Troubleshooting ### Rebuild schlägt fehl **1. Logs prüfen:** ```bash python3 custom/scripts/validate_and_rebuild.py # → Zeigt automatisch Fehlerlog-Analyse ``` **2. Manuell Logs checken:** ```bash tail -100 data/logs/espo-$(date +%Y-%m-%d).log ``` **3. PHP-Fehler:** ```bash docker exec espocrm php -l custom/Espo/Custom/Controllers/MyController.php ``` ### Entity nicht sichtbar **Checklist:** - [ ] `tab: true` in scopes? - [ ] `disabled: false` in scopes? - [ ] ACL-Rechte für Role? - [ ] Cache gelöscht? - [ ] Rebuild durchgeführt? ### Relationship funktioniert nicht **Checklist:** - [ ] Bidirektional konfiguriert? - [ ] `foreign` zeigt korrekt zurück? - [ ] `relationName` identisch (bei M2M)? - [ ] Rebuild durchgeführt? ### API gibt 404 **Checklist:** - [ ] Controller existiert? - [ ] Service existiert? - [ ] Action-Methode korrekt benannt? (postAction..., getAction...) - [ ] ACL-Rechte? ### Formula triggert nicht **Checklist:** - [ ] In `metadata/formula/` statt entityDefs? - [ ] Syntax korrekt? - [ ] Rebuild durchgeführt? --- ## Projekt-spezifische Entities ### Übersicht 1. **CMietobjekt** - Mietobjekte (Wohnungen/Häuser) 2. **CVmhMietverhltnis** - Mietverhältnisse 3. **CKuendigung** - Kündigungen 4. **CBeteiligte** - Beteiligte Personen 5. **CMietinkasso** - Mietinkasso-Verfahren 6. **CVmhRumungsklage** - Räumungsklagen 7. **CDokumente** - Dokumente 8. **CPuls** - Puls-System (Entwicklungen) 9. **CAICollections** - AI Collections ### Entity-Graph ``` CMietobjekt ├── CVmhMietverhltnis (hasMany) │ ├── CKuendigung (hasMany) │ │ └── CVmhRumungsklage (hasOne) │ ├── CMietinkasso (hasMany) │ └── CBeteiligte (hasMany) └── Contact (hasMany) CDokumente ├── parent → [CVmhRumungsklage, CMietinkasso, CKuendigung] └── CAICollections (hasMany via Junction) └── CPuls (hasMany) ``` --- ## Tools & Scripts ### Übersicht | Tool | Zweck | Ausführung | |------|-------|-----------| | validate_and_rebuild.py | Validierung + Rebuild | `python3 custom/scripts/validate_and_rebuild.py` | | e2e_tests.py | End-to-End Tests | `python3 custom/scripts/e2e_tests.py` | | ki_project_overview.py | Projekt-Analyse für AI | `python3 custom/scripts/ki_project_overview.py` | | workflow_manager.php | Workflow-Verwaltung | `php custom/scripts/workflow_manager.php list` | ### KI-Projekt-Übersicht **Für AI Code Agents:** ```bash python3 custom/scripts/ki_project_overview.py > /tmp/project-overview.txt # → Gibt vollständigen Projekt-Status für AI aus ``` --- ## Ressourcen ### Dokumentation - **EspoCRM Docs:** https://docs.espocrm.com/ - **API Reference:** https://docs.espocrm.com/development/api/ - **Formula Functions:** https://docs.espocrm.com/administration/formula/ ### Projekt-Dokumentation - `custom/docs/ESPOCRM_BEST_PRACTICES.md` - Dieses Dokument - `custom/scripts/QUICKSTART.md` - Quick Start Guide - `custom/scripts/VALIDATION_TOOLS.md` - Validierungs-Tools - `custom/scripts/E2E_TESTS_README.md` - E2E Tests - `custom/README.md` - Custom Actions Blueprint - `custom/TESTERGEBNISSE_JUNCTION_TABLE.md` - Junction Table Implementation --- ## Glossar **ACL** - Access Control List (Zugriffsrechte) **Entity** - Datenmodell (z.B. CMietobjekt) **Link** - Relationship zwischen Entities **Junction Table** - Verbindungstabelle für Many-to-Many **Formula** - Berechnete Felder oder Automation-Scripts **Scope** - Entity-Konfiguration (Tab, ACL, etc.) **Stream** - Activity Feed einer Entity **Hook** - Lifecycle-Event-Handler **Service** - Business Logic Layer **Controller** - API Request Handler **Repository** - Data Access Layer --- **Ende der Best Practices Dokumentation** Für spezifische Fragen oder Updates: Siehe `/custom/docs/` Verzeichnis.