- Introduced new entity CAdvowareAkten with fields: name, description, createdAt, modifiedAt, assignedUser, teams, vmhRumungsklage, mietinkasso, aktenzeichen, and aktennummer. - Defined relationships for CAdvowareAkten with existing entities: CVmhRumungsklage and CMietinkasso. - Created client definitions, layouts, and ACL for CAdvowareAkten. - Added internationalization support for CAdvowareAkten in multiple languages. - Updated existing entities and metadata to include references to CAdvowareAkten.
KI-basierte Bearbeitung von EspoCRM: Struktur und Funktionsweise
🚀 Schnellstart für KI
NEU: Automatisches KI-Einstiegsscript für vollständigen Projekt-Überblick!
# Vollständige Projekt-Analyse für KI
./custom/scripts/ki-overview.sh
# Nur Schnellübersicht
./custom/scripts/ki-overview.sh --stats
# In Datei speichern
./custom/scripts/ki-overview.sh --file /tmp/overview.txt
Das Script analysiert automatisch alle Entitäten, Beziehungen, Custom Code, Workflows und Frontend-Anpassungen. Siehe KI_OVERVIEW_README.md für Details.
Inhaltsverzeichnis
- Überblick
- Custom Directory Struktur
- Rebuild-Prozess
- Häufige Fehler vermeiden
- Dateiformate und JSON-Strukturen
- Workflow-Verwaltung
- Internationalisierung (i18n) und Tooltips
- Formula-Scripts und Custom PHP-Erweiterungen
- Panel-Labels und Übersetzungen
- Custom JavaScript & CSS Integration
- Reports und Report-Panels
- Portal-Freigabe-System
- Troubleshooting
Überblick
EspoCRM ist ein modular aufgebautes CRM-System, das auf PHP (Backend) und Backbone.js (Frontend) basiert. Konfigurationen für Entitäten, Felder, Beziehungen, Views und Layouts werden in JSON-basierten Metadata-Dateien gespeichert.
Die Anpassung erfolgt über das custom/-Verzeichnis, um Core-Dateien nicht zu überschreiben und Upgrades zu erleichtern. EspoCRM verwendet eine rekursive Merging-Mechanik: Custom-Dateien überschreiben oder erweitern Core-Definitionen.
Anpassungsprozess:
- JSON-Dateien im
custom/-Verzeichnis erstellen/bearbeiten - Rebuild-Script ausführen (validiert, merged, aktualisiert DB)
- Änderungen sind sofort wirksam
Keine integrierte KI-Schnittstelle existiert, aber mit Dateizugriff können automatisierte Anpassungen vorgenommen werden: Felder hinzufügen, Views anpassen, Beziehungen definieren, Workflows erstellen.
Custom Directory Struktur
Vollständige Übersicht: Siehe /custom/CUSTOM_DIRECTORY.md für detaillierte Dokumentation aller Custom-Verzeichnisse.
Wichtigste Bereiche:
custom/Espo/Custom/Resources/metadata/- Backend-Definitionen (entityDefs, clientDefs, etc.)custom/Espo/Custom/Classes/- Custom PHP-Klassen (Formula-Funktionen, Services)client/custom/src/- Frontend JavaScript (Views, Module)client/custom/css/- Custom Stylesheetscustom/scripts/- Wartungs-Scripts (Rebuild, Workflow-Manager)custom/workflows/- Versionierte Workflow-Definitionen
Rebuild-Prozess
WICHTIG: Nach jeder Änderung an Custom-Dateien muss ein Rebuild durchgeführt werden!
Validate & Rebuild Script (Empfohlen)
Zentrales Tool: custom/scripts/validate_and_rebuild.py
Dieses Script sollte IMMER verwendet werden (niemals manueller Rebuild). Es führt automatisch aus:
✅ Validierungen:
- JSON-Syntax-Prüfung aller
.jsonDateien imcustom/Verzeichnis - Relationship-Konsistenz-Prüfung (bidirektionale Links, foreign-Definitionen)
- Formula-Script Platzierung (korrekt in
/formula/statt/entityDefs/) - i18n-Vollständigkeit (fehlende Übersetzungen für Links)
- Layout-Struktur-Prüfung
- Dateirechte-Prüfung (
www-data:www-dataOwner)
✅ Automatische Korrekturen:
- Setzt fehlerhafte Dateirechte auf
www-data:www-data - Korrigiert Verzeichnis-Permissions (775) und Datei-Permissions (664)
✅ Rebuild:
- Nur wenn keine kritischen Fehler gefunden werden!
- Merged alle Custom-Metadata mit Core-Definitionen
- Aktualisiert Datenbank-Schema (neue Felder, Tabellen, Indizes)
- Leert Cache-Verzeichnis
- Regeneriert Frontend-Assets
Verwendung:
# Im EspoCRM-Root-Verzeichnis ausführen
python3 custom/scripts/validate_and_rebuild.py
# Nur Validierung ohne Rebuild
python3 custom/scripts/validate_and_rebuild.py --dry-run
Ausgabe:
- ✓ Grün: Alles in Ordnung, Rebuild erfolgreich
- ⚠ Gelb: Warnungen (z.B. fehlende i18n), Rebuild wird trotzdem ausgeführt
- ✗ Rot: Kritische Fehler (z.B. ungültiges JSON, fehlende Relationships), Rebuild wird NICHT ausgeführt
Bei Fehlern:
- JSON-Syntax-Fehler werden mit Datei und Zeilennummer angezeigt
- Relationship-Fehler zeigen fehlende Links zwischen Entities
- Formula-Platzierungsfehler werden erkannt und gemeldet
- i18n-Probleme werden als Warnungen angezeigt (kein Abbruch)
- Dateirechte-Probleme werden automatisch korrigiert
Detaillierte Dokumentation: Siehe custom/scripts/VALIDATOR_README.md
Wann Rebuild erforderlich?
Backend-Änderungen:
- ✅ entityDefs, clientDefs, layouts, scopes bearbeitet
- ✅ Formula-Scripts erstellt/geändert
- ✅ i18n-Dateien aktualisiert
- ✅ Custom PHP-Klassen hinzugefügt
- ✅ CSS in app/client.json registriert
Frontend-Änderungen:
- ✅ JavaScript Views erstellt/geändert
- ✅ CSS-Dateien hinzugefügt
- ⚠️ Zusätzlich Browser Hard Refresh (Ctrl+Shift+R) erforderlich!
Workflows:
- ❌ Kein Rebuild nötig (Import über workflow_manager.php)
Was der Rebuild bewirkt
- Metadata-Merging: Kombiniert Custom-Definitionen mit Core-Definitionen
- Datenbank-Schema-Update: Erstellt neue Tabellen/Spalten/Indizes basierend auf entityDefs
- Cache-Bereinigung: Löscht gecachte Metadata, Views, Templates
- Frontend-Build: Regeneriert JavaScript/CSS-Bundles
- ORM-Update: Aktualisiert Entity-Klassen und Repositories
Manuelle Dateirechte-Korrektur
Falls check_and_rebuild.sh Rechte-Probleme nicht beheben kann:
sudo chown -R www-data:www-data custom/ client/custom/ data/
sudo find custom/ -type f -name "*.json" -exec chmod 664 {} \;
sudo find custom/ -type d -exec chmod 775 {} \;
sudo find client/custom/ -type f -exec chmod 664 {} \;
sudo find client/custom/ -type d -exec chmod 775 {} \;
Häufige Fehler vermeiden
Basierend auf der Analyse von Git-Commits und praktischen Erfahrungen treten bestimmte Fehler besonders häufig auf. Diese Sektion hilft, die 5 häufigsten Fehler zu vermeiden.
⚠️ Top 5 Fehlerquellen
1. Formula-Scripts falsch platziert
Problem: Formula-Scripts werden in entityDefs/{Entity}.json statt in separater formula/{Entity}.json abgelegt.
Symptome:
- beforeSaveApiScript wird nicht ausgeführt
- Validierungen greifen nicht
- Keine Fehler in Logs, Script wird einfach ignoriert
❌ FALSCH:
// custom/Espo/Custom/Resources/metadata/entityDefs/CBankverbindungen.json
{
"fields": {
"iban": {"type": "varchar"}
},
"formula": {
"beforeSaveApiScript": "..." // ← Funktioniert NICHT!
}
}
✅ RICHTIG:
// custom/Espo/Custom/Resources/metadata/formula/CBankverbindungen.json
{
"beforeSaveApiScript": "if (iban != null && iban != '') { ... }"
}
Zusätzliche Fallstricke:
- ❌
string\isEmpty()verwenden (existiert nicht!) - ✅ Stattdessen:
field != null && field != ''
Nach Korrektur: ./custom/scripts/check_and_rebuild.sh ausführen
2. Layout-Strukturfehler
Problem: Falsche Platzhalter und Strukturen in Layouts führen zu UI-Problemen.
⚠️ KRITISCH für EspoCRM 7.x:
In EspoCRM 7.x ist false als Platzhalter NICHT MEHR ERLAUBT!
Symptome:
- Layout wird nicht geladen, Fehlermeldung in UI
- Felder werden nicht korrekt angeordnet
- Leere Bereiche in Detail-Views
- bottomPanelsDetail funktioniert nicht
❌ FALSCH (EspoCRM 7.x):
// layouts/Entity/detail.json
{
"rows": [
[
{"name": "field1"},
{"name": "field2"},
false, // ← DEPRECATED! Funktioniert in 7.x NICHT!
false // ← Verursacht Layout-Fehler!
]
]
}
✅ RICHTIG (EspoCRM 7.x):
{
"rows": [
[
{"name": "field1"},
{"name": "field2"},
{}, // ← Leeres Objekt verwenden!
{} // ← Funktioniert in 7.x korrekt
]
]
}
❌ FALSCH - bottomPanelsDetail.json als Array:
// layouts/Entity/bottomPanelsDetail.json
[
{"name": "contacts"},
{"name": "documents"}
]
✅ RICHTIG - bottomPanelsDetail.json als Objekt:
{
"contacts": {
"index": 0,
"sticked": true,
"style": "warning"
},
"_tabBreak_0": {
"index": 1,
"columnBreak": true
},
"documents": {
"index": 2
},
"activities": {
"disabled": true
},
"history": {
"disabled": true
}
}
Häufige Layout-Fehler:
- ❌
falseals Platzhalter (seit EspoCRM 7.x nicht mehr unterstützt!) - ❌ Width-Attribute in Detail-Layouts (nur für List-Layouts!)
- ❌ bottomPanelsDetail.json als Array statt Objekt
- ❌ Index nicht angepasst nach Entfernung von Panels
Best Practice:
- Immer
{}stattfalsefür leere Zellen in detail.json verwenden - Width nur in
list.jsonundlistSmall.jsonverwenden - bottomPanelsDetail.json MUSS Objekt-Format haben
- Rows immer mit 4 Spalten füllen (nutze
{}zum Auffüllen) - Nach Panel-Entfernung Indices neu nummerieren
_tabBreak_{index}verwenden um Panels auf verschiedene Tabs zu verteilen
3. Unvollständige Relationship-Definitionen
Problem: Bei hasMany-Relationships wird nur eine Seite definiert, die Gegenseite fehlt.
Symptome:
- HTTP 404 "Link does not exist"-Fehler in Logs
- Relationship-Panel zeigt keine Daten
- Verknüpfungen können nicht erstellt werden
❌ FALSCH - Nur eine Seite definiert:
// entityDefs/CMietobjekt.json
{
"links": {
"kontakte": {
"type": "hasMany",
"entity": "Contact",
"foreign": "mietobjekte" // ← Existiert nicht in Contact!
}
}
}
// entityDefs/Contact.json - FEHLT!
✅ RICHTIG - Beide Seiten definiert:
// entityDefs/CMietobjekt.json
{
"links": {
"contactsMietobjekt": {
"type": "hasMany",
"relationName": "cMietobjektContactPortal",
"foreign": "cMietobjektContactPortal",
"entity": "Contact"
}
}
}
// entityDefs/Contact.json
{
"links": {
"cMietobjektContactPortal": {
"type": "hasMany",
"relationName": "cMietobjektContactPortal", // ← IDENTISCH!
"foreign": "contactsMietobjekt", // ← Zeigt auf Gegenseite
"entity": "CMietobjekt"
}
}
}
Kritische Punkte:
- ✅
relationNamemuss auf beiden Seiten identisch sein - ✅
foreignzeigt auf den Link-Namen der Gegenseite - ✅ Beide entityDefs-Dateien müssen erstellt werden
Siehe auch: Abschnitt "Troubleshooting → Link does not exist"
4. i18n/Localization-Fehler
Problem: Labels nicht vollständig oder nur in einer Sprache definiert.
Symptome:
- Relationship-Panels zeigen technische Namen statt Labels
- Tooltips zeigen nur Feldnamen
- In manchen Sprachen fehlen Beschriftungen
❌ FALSCH - Unvollständig:
// i18n/de_DE/Entity.json
{
"fields": {
"mietobjekte": "Mietobjekte"
},
"links": {} // ← Label fehlt!
}
// i18n/en_US/Entity.json - FEHLT KOMPLETT!
✅ RICHTIG - Vollständig in beiden Sprachen:
// i18n/de_DE/Entity.json
{
"fields": {
"mietobjekte": "Mietobjekte"
},
"links": {
"mietobjekte": "Mietobjekte" // ← BEIDE Sektionen!
},
"tooltips": {
"mietobjekte": "Verknüpfte Mietobjekte"
}
}
// i18n/en_US/Entity.json ← IMMER erstellen!
{
"fields": {
"mietobjekte": "Rental Properties"
},
"links": {
"mietobjekte": "Rental Properties"
},
"tooltips": {
"mietobjekte": "Linked rental properties"
}
}
Kritische Regeln:
- ✅ Labels IMMER in
fieldsUNDlinksdefinieren - ✅ en_US ist Fallback-Sprache → MUSS vollständig sein
- ✅ Tooltips in ALLEN Sprachen konsistent definieren
- ✅ Bei neuen Relationships beide Sprachen aktualisieren
Warum en_US kritisch ist: EspoCRM nutzt en_US als Fallback. Fehlt eine Definition dort, überschreibt sie möglicherweise die deutschen Labels!
Siehe auch: Abschnitt "Internationalisierung (i18n) und Tooltips"
5. Dateirechte-Probleme
Problem: Custom-Dateien gehören root statt www-data, EspoCRM kann nicht darauf zugreifen.
Symptome:
- "Permission denied"-Fehler in Logs
- Layouts können nicht über Admin-UI bearbeitet werden
- HTTP 500-Fehler beim Speichern
Ursache:
Dateien werden als Root-User erstellt (z.B. via sudo, Git-Checkout) und EspoCRM läuft als www-data.
✅ Lösung:
Das check_and_rebuild.sh Script prüft und korrigiert automatisch:
./custom/scripts/check_and_rebuild.sh
Manuelle Korrektur (falls nötig):
sudo chown -R www-data:www-data custom/ client/custom/ data/
sudo find custom/ -type f -name "*.json" -exec chmod 664 {} \;
sudo find custom/ -type d -exec chmod 775 {} \;
Prävention:
- Änderungen direkt im Docker-Container vornehmen
- Nach Git-Pull immer
check_and_rebuild.shausführen - VSCode mit Remote-Development nutzt automatisch korrekte User
🛡️ Fehler-Prävention: Checkliste
Bei neuer Entity-Erstellung:
- entityDefs in beiden Entities (bei Relationships)
relationNameidentisch auf beiden Seitenforeignzeigt auf korrekten Link-Namen- i18n in de_DE UND en_US vollständig
- Labels in fields UND links
- Formula in
formula/, NICHT inentityDefs/ - Nach Erstellung:
./custom/scripts/check_and_rebuild.sh
Bei Hinzufügen neuer Felder:
- Felder in entityDefs definieren mit korrektem Typ und Optionen
- Felder zu relevanten Layouts hinzufügen (detail.json, list.json, etc.)
- Felder sinnvoll gruppieren (eigenes Panel oder bestehendes Panel)
- Labels in i18n/de_DE vollständig (fields UND links)
- Labels in i18n/en_US vollständig (Fallback-Sprache!)
- Tooltips in i18n hinzufügen falls erforderlich
- Nach Änderung:
./custom/scripts/check_and_rebuild.sh - Browser Hard Refresh (Ctrl+Shift+R) durchführen
Bei Layout-Änderungen:
- Keine
falseoder leere Objekte in Rows - Width nur in List-Layouts verwenden
- Indices nach Panel-Entfernung neu nummerieren
- Nach Änderung:
./custom/scripts/check_and_rebuild.sh
Bei Formula-Scripts:
- Separate Datei
formula/{Entity}.jsonerstellen string\isEmpty()NICHT verwenden →!= null && != ''- Null-Checks vor String-Operationen
- Nach Erstellung:
./custom/scripts/check_and_rebuild.sh
Allgemein:
- Immer
./custom/scripts/check_and_rebuild.shnach Änderungen - Browser Hard Refresh (Ctrl+Shift+R) bei Frontend-Änderungen
- Logs prüfen:
tail -n 100 data/logs/espo-$(date +%Y-%m-%d).log
📊 Warum diese Fehler so häufig sind
Root Cause:
- EspoCRM Admin-UI generiert teilweise unvollständige JSON-Definitionen
- Bidirektionale Relationships sind komplex und nicht intuitiv
- en_US als Fallback ist nicht offensichtlich dokumentiert
- Formula-Platzierung wird im Admin-UI falsch vorgeschlagen
- Layout-Generierung erzeugt manchmal ungültige Array-Strukturen
Lösung:
- Diese Dokumentation VOR der Entwicklung lesen
check_and_rebuild.shScript konsequent nutzen- Bei Unsicherheit: Troubleshooting-Abschnitt konsultieren
Siehe auch:
- Troubleshooting - Ausführliche Fehlerdiagnose
- Custom Directory Struktur - Vollständige Dateiübersicht
Dateiformate und JSON-Strukturen
Alle Metadata-Dateien sind im JSON-Format. Die Strukturen sind hierarchisch: Objekte für Felder/Links, Arrays für Optionen/Listen.
Detaillierte Verzeichnisstruktur: Siehe /custom/CUSTOM_DIRECTORY.md
entityDefs/{EntityType}.json
Format-Beispiel:
{
"fields": {
"name": {
"type": "varchar",
"required": true,
"len": 255
},
"status": {
"type": "enum",
"options": ["Active", "Inactive"],
"default": "Active"
},
"employeeCount": {
"type": "int"
}
},
"links": {
"account": {
"type": "belongsTo",
"entity": "Account",
"foreign": "projects"
},
"teams": {
"type": "hasMany",
"entity": "Team",
"relationName": "EntityTeam"
}
},
"collection": {
"sortBy": "createdAt",
"asc": false,
"boolFilters": ["onlyMy"]
},
"indexes": {
"name": {
"columns": ["name"]
}
}
}
Wichtige Eigenschaften:
fields- Feldtypen (varchar, enum, link, etc.), Validierungen, Optionenlinks- Beziehungen zwischen Entitäten (belongsTo, hasMany, hasOne)collection- Listen-View-Einstellungen (Sortierung, Filter)indexes- Datenbank-Performance-Optimierung
KRITISCH - Bidirektionale Relationships:
Bei hasMany-Relationships müssen BEIDE Seiten definiert werden:
Beispiel: Contact ↔ Mietverhältnis
// custom/Espo/Custom/Resources/metadata/entityDefs/CVmhMietverhltnis.json
{
"links": {
"contactsMietverhltnis": {
"type": "hasMany",
"relationName": "cVmhMietverhltnisContact",
"foreign": "cVmhMietverhltnisContact",
"entity": "Contact"
}
}
}
// custom/Espo/Custom/Resources/metadata/entityDefs/Contact.json
{
"links": {
"cVmhMietverhltnisContact": {
"type": "hasMany",
"relationName": "cVmhMietverhltnisContact",
"foreign": "contactsMietverhltnis",
"entity": "CVmhMietverhltnis"
}
}
}
Wichtig:
relationNamemuss auf beiden Seiten identisch seinforeignzeigt auf den Link-Namen der Gegenseite- Fehlt eine Seite → "404 Link does not exist"-Fehler
Vollständiger Prozess beim Hinzufügen neuer Felder:
Neue Felder in einer Entity erfordern Änderungen in drei Bereichen:
- entityDefs/{Entity}.json - Feld-Definition
- layouts/{Entity}/*.json - Sichtbarkeit in UI
- i18n/{Sprache}/{Entity}.json - Beschriftungen
Schritt-für-Schritt-Anleitung:
1. Feld in entityDefs definieren:
// custom/Espo/Custom/Resources/metadata/entityDefs/CVmhMietverhltnis.json
{
"fields": {
"kaltmiete": {
"type": "currency",
"required": true,
"onlyDefaultCurrency": true,
"min": 1,
"decimal": true,
"tooltip": true,
"isCustom": true
}
}
}
2. Feld zu Layouts hinzufügen:
// custom/Espo/Custom/Resources/layouts/CVmhMietverhltnis/detail.json
[
{
"rows": [
[
{"name": "kaltmiete"},
{"name": "warmmiete"}
]
],
"customLabel": "Miethöhe",
"noteText": "Erfassen Sie die Miethöhe in ihren einzelnen Bestandteilen",
"noteStyle": "info"
}
]
3. Labels in ALLEN Sprachen definieren:
// custom/Espo/Custom/Resources/i18n/de_DE/CVmhMietverhltnis.json
{
"fields": {
"kaltmiete": "Kaltmiete"
},
"tooltips": {
"kaltmiete": "Monatliche Kaltmiete ohne Betriebskosten"
}
}
// custom/Espo/Custom/Resources/i18n/en_US/CVmhMietverhltnis.json
{
"fields": {
"kaltmiete": "Base Rent"
}
}
Wichtig:
- ✅ IMMER de_DE UND en_US pflegen (en_US ist Fallback!)
- ✅ Tooltips nur wenn
"tooltip": trueim entityDef gesetzt - ✅ Bei Link-Feldern Labels in
fieldsUNDlinksdefinieren - ✅ Nach Änderungen:
./custom/scripts/check_and_rebuild.sh
Typische Layout-Typen:
detail.json- Detail-Ansicht (Panels mit Rows)list.json- Listen-Ansicht (Spalten mit Width)listSmall.json- Kompakte Listen-AnsichtdetailSmall.json- Seitenleisten-Ansichtfilters.json- Filter-Felder
⚠️ WICHTIG: Zwei verschiedene Layout-Verzeichnisse!
EspoCRM verwendet zwei verschiedene Layout-Verzeichnisse mit unterschiedlichen Zwecken:
1. Frontend-Layouts (PRIMÄR - meistens benötigt)
Pfad: custom/Espo/Custom/Resources/layouts/{Entity}/{LayoutType}.json
✅ Verwenden für:
- Liste-Ansichten:
list.json,listSmall.json - Detail-Ansichten:
detail.json,detailSmall.json - Bottom-Panels:
bottomPanelsDetail.json - Relationship-Panels (werden über
listSmall.jsondefiniert) - Alle UI-bezogenen Layouts
Beispiel:
custom/Espo/Custom/Resources/layouts/
├── CBeteiligte/
│ ├── list.json
│ ├── listSmall.json ← Wird in Relationship-Panels verwendet!
│ ├── detail.json
│ └── bottomPanelsDetail.json
2. Metadata-Layouts (SEKUNDÄR - selten benötigt)
Pfad: custom/Espo/Custom/Resources/metadata/layouts/{Entity}/{LayoutType}.json
⚠️ Nur verwenden für:
- Backend-spezifische Layout-Definitionen
- Erweiterte Konfigurationen
- In den meisten Fällen NICHT benötigt!
Regel:
Relationship-Panels in Bottom-Views nutzen IMMER
listSmall.jsonaus demlayouts/Verzeichnis (NICHTmetadata/layouts/)!
Häufiger Fehler:
# ❌ FALSCH - wird ignoriert!
custom/Espo/Custom/Resources/metadata/layouts/CBeteiligte/listSmall.json
# ✅ RICHTIG - wird verwendet!
custom/Espo/Custom/Resources/layouts/CBeteiligte/listSmall.json
Best Practice:
- Layouts IMMER in
custom/Espo/Custom/Resources/layouts/erstellen - Das
metadata/layouts/Verzeichnis nur verwenden, wenn explizit dokumentiert - Nach Änderungen:
./custom/scripts/check_and_rebuild.sh - Browser Hard Refresh (Ctrl+Shift+R) durchführen
clientDefs/{EntityType}.json
Format-Beispiel:
{
"controller": "controllers/record",
"collection": "collection",
"model": "model",
"views": {
"list": "views/record/list",
"detail": "views/record/detail",
"edit": "views/record/edit"
},
"recordViews": {
"list": "views/record/list",
"kanban": "custom:views/record/kanban"
},
"viewSetupHandlers": {
"record/detail": ["custom:handlers/my-detail-handler"]
}
}
Wichtige Eigenschaften:
views/recordViews- Pfade zu JavaScript Views (Custom mit Präfixcustom:)viewSetupHandlers- Dynamische View-AnpassungenfilterList- Report-Filter-Integration (mit__APPEND__)sidePanels- Report-Panels für Side-Panel
layouts/{EntityType}/{LayoutType}.json
Format-Beispiel für Detail-View:
[
{
"label": "Overview",
"rows": [
[
{"name": "name"},
{"name": "assignedUser"}
],
[
{"name": "description"}
]
]
},
{
"label": "Details",
"rows": [
[{"name": "createdAt"}]
]
}
]
Struktur:
- Arrays von Panels (Objekte mit
labelundrows) rowssind Arrays von Zellen (Objekte mitnamefür Felder)- Unterstützt Parameter:
width,notSortable,customLabel
Spezielle Features:
__APPEND__- Als erstes Array-Element einfügen, um bestehende Werte zu erweiternlayoutAvailabilityList- Array für Feld-Sichtbarkeit in LayoutslayoutIgnoreList- Zu ignorierende Layouts
Workflow-Verwaltung
EspoCRM bietet zwei Workflow-Typen für Prozessautomatisierung:
1. Simple Workflows (Regel-basiert)
Trigger-basierte Workflows für einfache Automationen.
Trigger-Typen:
afterRecordSaved- Nach Erstellen oder AktualisierenafterRecordCreated- Nur nach ErstellenafterRecordUpdated- Nur nach Aktualisierenmanual- Manuell ausgeführtscheduled- Zeitgesteuert
Bedingungen:
- Vergleiche:
equals,notEquals,greaterThan,lessThan,contains,isEmpty - Änderungen:
changed,notChanged,wasEqual,wasNotEqual
Aktionen:
sendEmail- E-Mail versendencreateEntity- Record erstellenupdateEntity- Record aktualisierenrelateTo/unrelateFrom- VerknüpfungencreateNotification- Benachrichtigung
2. BPM Flowcharts (Komplex)
Visuelle Workflows mit BPMN 2.0-Standard für komplexe, mehrstufige Geschäftsprozesse.
Komponenten:
- Start-Events: Signal, Conditional, Timer
- Gateways: Exclusive, Inclusive, Parallel
- Tasks, End-Events
Verwendung: Über visuellen Designer im Admin-Interface
Workflow-Dateien
Speicherort: custom/workflows/*.json
Workflow-Definitionen werden als JSON versioniert und über Git verwaltet.
Format-Dokumentation: Siehe custom/workflows/README.md
Workflow Manager Script
Tool: custom/scripts/workflow_manager.php
Kommandozeilen-Tool für Workflow-Verwaltung (Simple und BPM).
Verfügbare Aktionen
1. Alle Workflows auflisten
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php list
Zeigt beide Workflow-Typen (BPM Flowcharts und Simple Workflows) mit Status, ID, Name und Entity.
2. Workflow-Details anzeigen
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php read <workflow-id>
Gibt alle Details eines Workflows als JSON aus.
3. Workflow importieren
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php import \
/var/www/html/custom/workflows/workflow.json
Importiert einen Workflow aus JSON-Datei. Unterstützt beide Workflow-Typen. Erstellt automatisch neue ID.
4. Workflow exportieren
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php export \
<workflow-id> /var/www/html/custom/workflows/exported.json
Exportiert einen Workflow in JSON-Datei für Backup oder Migration.
5. Workflow löschen
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php delete <workflow-id>
Löscht einen Workflow (mit Bestätigung). Funktioniert für beide Workflow-Typen.
JSON-Formate
Simple Workflow Format
{
"type": "simple",
"name": "workflow-name",
"entity_type": "EntityName",
"trigger_type": "afterRecordSaved",
"is_active": true,
"description": "Beschreibung der Funktion",
"category": "Kategorie-Name",
"conditions_all": [
{
"comparison": "equals",
"fieldToCompare": "fieldName",
"value": "expectedValue",
"subjectType": "value"
}
],
"conditions_any": [],
"conditions_formula": null,
"actions": [
{
"type": "sendEmail",
"from": "specifiedEmailAddress",
"fromEmailAddress": "sender@example.com",
"to": "targetEntity",
"emailTemplateId": null,
"doNotStore": false
}
]
}
Wichtige Felder:
category- Workflow-Kategorie für bessere Organisationcomparison- VergleichsoperatorfieldToCompare- Feldname für BedingungsubjectType- Typ des Vergleichswerts (value,field, etc.)from/to- E-Mail-Empfänger (targetEntity,specifiedEmailAddress,system)
BPM Flowchart Format
{
"type": "bpm",
"name": "flowchart-name",
"target_type": "EntityName",
"is_active": true,
"description": "Beschreibung",
"data": {
"list": [
{
"type": "eventStartSignal",
"id": "start1",
"signalName": "@signalName"
}
]
},
"elements_data_hash": {},
"event_start_all_id_list": []
}
Workflow-Entwicklung Best Practices
- Versionierung: Workflows als JSON im
custom/workflows/Verzeichnis versionieren - Naming Convention: Beschreibende Namen mit Präfix (z.B.
vmh-erstberatung-abschliessen.json) - Entwicklungsprozess:
- Workflow-JSON in
custom/workflows/erstellen - Mit
workflow_manager.php importeinspielen - Im Admin-Interface testen und bei Bedarf anpassen
- Mit
exportaktualisierten Workflow sichern - JSON-Datei im Repository committen
- Workflow-JSON in
- Backup: Regelmäßig Export wichtiger Workflows durchführen
- Dokumentation: Description-Feld aussagekräftig füllen
Beispiel-Workflow
Szenario: E-Mail bei Status-Wechsel zu "Warte auf Mandatierung"
{
"type": "simple",
"name": "vmh-erstberatung-abschliessen",
"entity_type": "CVmhErstgespraech",
"trigger_type": "afterRecordSaved",
"is_active": true,
"conditions_all": [
{
"comparison": "equals",
"fieldToCompare": "status",
"value": "Warte auf Mandatierung"
},
{
"comparison": "changed",
"fieldToCompare": "status"
}
],
"actions": [
{
"type": "sendEmail",
"to": "targetEntity",
"emailTemplateId": "template-id-here"
}
]
}
Internationalisierung (i18n) und Tooltips
EspoCRM verwendet ein hierarchisches Mehrsprachen-System mit en_US als Basis-Fallback:
- Sprachpriorität: en_US → aktuelle Sprache (z.B. de_DE)
- Problem: Tooltips in en_US überschreiben Tooltips in anderen Sprachen
- Lösung: Tooltips MÜSSEN in ALLEN Sprachen definiert werden
Beispiel für korrektes Tooltip-Setup:
entityDefs/{Entity}.json:
{
"fields": {
"iban": {
"type": "varchar",
"tooltip": true // Aktiviert Tooltip-Anzeige
}
}
}
i18n/de_DE/{Entity}.json:
{
"fields": {
"iban": "IBAN"
},
"tooltips": {
"iban": "Internationale Bankkontonummer im Format DE89..."
}
}
i18n/en_US/{Entity}.json:
{
"fields": {
"iban": "IBAN"
},
"tooltips": {
"iban": "International Bank Account Number in format DE89..."
}
}
Häufige Fehler:
❌ FALSCH - Unvollständige en_US-Datei:
{
"fields": [],
"tooltips": {
"iban": "iban2" // Überschreibt deutschen Tooltip!
}
}
✅ RICHTIG - Vollständige Definitionen in beiden Sprachen:
- Alle Felder in
fieldsdefinieren - Alle Tooltips in
tooltipsdefinieren - Konsistente Struktur über alle Sprachen
Debugging von Tooltip-Problemen:
- Symptom: Tooltip zeigt nur Feldnamen (z.B. "iban" statt vollständiger Beschreibung)
- Ursache: Fehlerhafte oder fehlende Definition in
i18n/en_US/{Entity}.json - Prüfung:
- Existiert
i18n/en_US/{Entity}.json? - Enthält es fehlerhafte Tooltip-Definitionen?
- Sind alle Tooltips konsistent über alle Sprachen?
- Existiert
- Lösung: Vervollständige en_US-Datei mit korrekten englischen Übersetzungen
Best Practices:
- Erstelle immer sowohl de_DE als auch en_US Übersetzungen
- Verwende beschreibende Tooltips mit Beispielen und Format-Hinweisen
- Teste Tooltips nach jedem Rebuild in beiden Sprachen
- Bei neuen Feldern: erst i18n-Dateien vollständig ausfüllen, dann Rebuild
Formula-Scripts und Custom PHP-Erweiterungen
EspoCRM bietet mächtige Erweiterungsmöglichkeiten durch Formula-Scripts und Custom PHP-Funktionen. Diese ermöglichen Validierungen, Berechnungen und Business-Logik direkt beim Speichern von Datensätzen.
Formula-Scripts: Grundlagen
WICHTIG: Dateistruktur
❌ FALSCH - Formula in entityDefs:
// custom/Espo/Custom/Resources/metadata/entityDefs/Entity.json
{
"fields": {...},
"formula": {
"beforeSaveApiScript": "..." // FUNKTIONIERT NICHT!
}
}
✅ RICHTIG - Separate Formula-Datei:
// custom/Espo/Custom/Resources/metadata/formula/Entity.json
{
"beforeSaveApiScript": "if (field != null) { ... }"
}
Verfügbare Formula-Script-Typen
beforeSaveApiScript- Wird vor dem Speichern ausgeführt (UI + API, ab v7.5+)beforeSaveCustomScript- Nur bei internen Saves (ohne API)afterSaveScript- Nach dem Speichern
Verwendung: Validierungen, Berechnungen, Daten-Transformation vor dem Speichern
Verfügbare Formula-Funktionen
String-Funktionen:
string\concatenate(str1, str2)- Strings verbindenstring\replace(text, search, replace)- Ersetzenstring\substring(text, start, length)- Teilstringstring\length(text)- Längestring\test(text, pattern)- Regex-Test- ⚠️ NICHT verfügbar:
string\isEmpty()→ Verwendefield != null && field != ''
Logik:
if (condition) { ... }&&,||,!(AND, OR, NOT)==,!=,>,<,>=,<=
Fehlerbehandlung:
recordService\throwBadRequest('Fehlermeldung')- Speichern abbrechen mit Fehlermeldung
Custom Formula-Funktionen erstellen
Beispiel: IBAN-Validierung mit Modulo-97-Algorithmus
1. PHP-Klasse erstellen:
Pfad: custom/Espo/Custom/Classes/FormulaFunctions/IbanGroup/ValidateType.php
<?php
namespace Espo\Custom\Classes\FormulaFunctions\IbanGroup;
use Espo\Core\Formula\Functions\BaseFunction;
use Espo\Core\Formula\ArgumentList;
class ValidateType extends BaseFunction
{
public function process(ArgumentList $args)
{
if (count($args) < 1) {
return false;
}
$iban = $this->evaluate($args[0]);
if (!$iban || !is_string($iban)) {
return false;
}
// IBAN-Validierungs-Logik hier
// ... (siehe ValidateType.php für vollständige Implementierung)
return $remainder === 1; // Modulo-97-Check
}
}
2. Funktion registrieren:
Pfad: custom/Espo/Custom/Resources/metadata/app/formula.json
{
"functionList": [
"__APPEND__",
{
"name": "iban\\validate",
"insertText": "iban\\validate(IBAN)"
}
],
"functionClassNameMap": {
"iban\\validate": "Espo\\Custom\\Classes\\FormulaFunctions\\IbanGroup\\ValidateType"
}
}
3. Funktion verwenden:
Pfad: custom/Espo/Custom/Resources/metadata/formula/CBankverbindungen.json
{
"beforeSaveApiScript": "if (iban != null && iban != '') {\n $ibanClean = string\\replace(iban, ' ', '');\n if (!iban\\validate($ibanClean)) {\n recordService\\throwBadRequest('Ungültige IBAN!');\n }\n}"
}
Wichtige Hinweise zur Formula-Entwicklung
Namespace-Struktur:
- Verzeichnis:
custom/Espo/Custom/Classes/FormulaFunctions/{GroupName}/ - Namespace:
Espo\Custom\Classes\FormulaFunctions\{GroupName} - Klassenname:
{FunctionName}Type(z.B.ValidateType) - Muss
BaseFunctionerweitern
Funktionsnamen:
- Format:
group\functionName(z.B.iban\validate,string\replace) - Backslash
\wird verwendet (nicht::oder/)
Häufige Fehler:
| Fehler | Symptom | Lösung |
|---|---|---|
| Formula in entityDefs statt formula/ | Script wird nicht ausgeführt | Separate formula/{Entity}.json erstellen |
string\isEmpty() verwendet |
Error: "Unknown function" | Verwende field != null && field != '' |
| Falsche Namespace-Struktur | Funktion nicht gefunden | Prüfe Namespace, Klassenname, Pfad |
| Funktion nicht registriert | "Unknown function" | Eintrag in app/formula.json erstellen |
Keine __APPEND__ in functionList |
Überschreibt Core-Funktionen | Immer "__APPEND__" als erstes Element |
Debugging von Formula-Scripts
Logs prüfen:
tail -n 100 /var/www/html/data/logs/espo-*.log | grep -i "formula\|error"
Häufige Fehlermeldungen:
Unknown function: xxx→ Funktion existiert nicht oder nicht registriertError 500→ Syntax-Fehler im Formula-Script oder PHP-FehlervalidationFailure→throwBadRequest()wurde aufgerufen
Test-Workflow:
- Formula-Script schreiben
- Rebuild ausführen:
bash custom/scripts/check_and_rebuild.sh - Cache leeren (falls nötig):
rm -rf data/cache/* - Testdaten speichern und Logs prüfen
Best Practices
✅ Empfohlen:
- Separate Formula-Dateien pro Entity in
metadata/formula/ - Wiederverwendbare Logik in Custom PHP-Funktionen auslagern
- Aussagekräftige Fehlermeldungen mit
throwBadRequest() - Null-Checks vor Operationen:
field != null && field != '' - Code kommentieren für spätere Wartung
❌ Vermeiden:
- Formula-Scripts in
entityDefsablegen - Nicht-existente String-Funktionen wie
isEmpty() - Komplexe Logik direkt in Formula (besser: PHP-Funktion)
- Fehlende Registrierung in
app/formula.json
Beispiel-Anwendungsfälle
-
Validierung: IBAN-Check, Email-Format, Telefonnummern
-
Berechnung: Gesamtpreise, Datumsberechnungen, Provisionen
-
Daten-Transformation: Großbuchstaben, Formatierungen, Normalisierungen
-
Business Rules: Status-Überprüfungen, Pflichtfeld-Logik, Abhängigkeiten
-
Auslösen von Änderungen und Rebuild-Prozess
Was Änderungen auslösen: Datei-Änderungen: Werden bei Merging berücksichtigt – rekursiv, also überschreiben Customs Core. Datenbank-Effekte: Neue Felder/Links in entityDefs erzeugen Tabellen/Spalten (bei Rebuild). Frontend-Effekte: clientDefs/Layouts ändern UI sofort nach Rebuild (z. B. neue Panels, Views). Fehlerquellen: Ungültiges JSON oder falsche Typen können zu Fehlern führen (z. B. fehlende required-Felder).
Workflow-Verwaltung
EspoCRM bietet zwei Arten von Workflows für Automatisierung:
Simple Workflows (Regel-basiert)
- Trigger-basierte Workflows für einfache Automationen
- Trigger-Typen:
afterRecordSaved- Nach Erstellen oder AktualisierenafterRecordCreated- Nur nach ErstellenafterRecordUpdated- Nur nach Aktualisierenmanual- Manuell ausgeführtscheduled- Zeitgesteuert
- Bedingungen:
- Vergleiche:
equals,notEquals,greaterThan,lessThan,contains,isEmpty - Änderungen:
changed,notChanged,wasEqual
- Vergleiche:
- Aktionen:
sendEmail- E-Mail versendencreateEntity- Record erstellenupdateEntity- Record aktualisierenrelateTo/unrelateFrom- VerknüpfungencreateNotification- Benachrichtigung
BPM Flowcharts (Komplex)
- Visuelle Workflows mit BPMN 2.0-Standard
- Start-Events: Signal, Conditional, Timer
- Gateways (Exclusive, Inclusive, Parallel), Tasks, End-Events
- Für komplexe, mehrstufige Geschäftsprozesse
Workflow-Dateien
Workflow-Definitionen werden im Ordner custom/workflows/ als JSON abgelegt:
custom/workflows/*.json- Workflow-Definitionen (Simple oder BPM)custom/workflows/README.md- Dokumentation zu Formaten und Verwendung
Workflow Manager Script
Zentrale Schnittstelle: custom/scripts/workflow_manager.php
Dieses Script ermöglicht die Verwaltung aller Workflows (Simple und BPM) über die Kommandozeile.
Unterstützte Funktionen:
- ✓ Kategorisierung von Workflows
- ✓ Import/Export mit Kategorie-Namen
- ✓ Übersichtliche Darstellung nach Kategorien
- ✓ Unterstützung für beide Workflow-Typen (Simple & BPM)
Verfügbare Aktionen
1. Alle Workflows auflisten
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php list
Zeigt beide Workflow-Typen (BPM Flowcharts und Simple Workflows) mit Status, ID, Name und Entity.
2. Workflow-Details anzeigen
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php read <workflow-id>
Gibt alle Details eines Workflows als JSON aus.
3. Workflow importieren
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php import /var/www/html/custom/workflows/workflow.json
Importiert einen Workflow aus einer JSON-Datei. Unterstützt sowohl Simple Workflows als auch BPM Flowcharts. Erstellt automatisch eine neue ID.
4. Workflow exportieren
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php export <workflow-id> /var/www/html/custom/workflows/exported.json
Exportiert einen Workflow in eine JSON-Datei für Backup oder Migration.
5. Workflow löschen
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php delete <workflow-id>
Löscht einen Workflow (mit Bestätigung). Funktioniert für beide Workflow-Typen.
JSON-Formate
Simple Workflow Format
{
"type": "simple",
"name": "workflow-name",
"entity_type": "EntityName",
"trigger_type": "afterRecordSaved",
"is_active": true,
"description": "Beschreibung der Funktion",
"category": "Kategorie-Name",
"conditions_all": [
{
"comparison": "equals",
"fieldToCompare": "fieldName",
"value": "expectedValue",
"subjectType": "value"
}
],
"conditions_any": [],
"conditions_formula": null,
"actions": [
{
"type": "sendEmail",
"from": "specifiedEmailAddress",
"fromEmailAddress": "sender@example.com",
"to": "targetEntity",
"emailTemplateId": null,
"doNotStore": false
}
]
}
Wichtige Felder:
category- NEU: Name der Workflow-Kategorie (optional, für bessere Organisation)comparison- Vergleichsoperator (siehe Bedingungen oben)fieldToCompare- Feldname für BedingungsubjectType- Typ des Vergleichswerts (value,field, etc.)from/to- E-Mail-Empfänger (targetEntity,specifiedEmailAddress,system)
BPM Flowchart Format
{
"type": "bpm",
"name": "flowchart-name",
"target_type": "EntityName",
"is_active": true,
"description": "Beschreibung",
"data": {
"list": [
{
"type": "eventStartSignal",
"id": "start1",
"signalName": "@signalName"
}
]
},
"elements_data_hash": {},
"event_start_all_id_list": []
}
Best Practices
- Versionierung: Workflows als JSON-Dateien im
custom/workflows/Verzeichnis versionieren - Naming Convention: Beschreibende Namen mit Präfix (z.B.
vmh-erstberatung-abschliessen.json) - Testen: Nach Import immer über Admin-Interface testen
- Backup: Regelmäßig Export für wichtige Workflows durchführen
- Dokumentation: Description-Feld aussagekräftig füllen
Beispiel-Workflows
custom/workflows/vmh-erstberatung-abschliessen.json
- Sendet E-Mail bei Status-Wechsel zu "Warte auf Mandatierung"
- Trigger: afterRecordSaved
- Bedingungen: Status = "Warte auf Mandatierung" UND Status hat sich geändert
- Aktion: E-Mail an targetEntity senden
Anwendungsbeispiel:
# Alle Workflows exportieren (Backup)
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php list | grep ID | \
awk '{print $3}' | while read id; do
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php export "${id%,}" \
"/var/www/html/custom/workflows/backup-${id%,}.json"
done
# Workflow aus Datei (re-)importieren
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php import \
/var/www/html/custom/workflows/vmh-erstberatung-abschliessen.json
- Bearbeitung von Entitäten und Layouts
Um EspoCRM anzupassen, bearbeite JSON-Dateien im custom/-Verzeichnis. Änderungen bleiben bei Updates erhalten, da sie Core-Dateien nicht überschreiben.
Entitäten bearbeiten:
Pfad: custom/Espo/Custom/Resources/metadata/entityDefs/{EntityType}.json (z. B. CVmhErstgespraech.json).
Struktur: JSON-Objekt mit "fields" (Felder definieren), "links" (Beziehungen), "collection" (Sortierung/Filter), "indexes" (Performance).
Beispiel: Feld hinzufügen – Füge in "fields" ein neues Objekt ein, z. B. {"type": "varchar", "required": true}.
Beispiel: Feld entfernen – Lösche den entsprechenden Schlüssel aus "fields".
Hinweis: Änderungen wirken sich auf die Datenbank aus (z. B. neue Spalten bei Rebuild).
Layouts bearbeiten:
Pfad: custom/Espo/Custom/Resources/layouts/{EntityType}/{LayoutType}.json (z. B. detail.json für Detail-View).
Struktur: Array von Panels, jedes mit "label" und "rows" (Arrays von Zellen mit {"name": "feldname"}).
Beispiel: Feld hinzufügen – Füge {"name": "neuesFeld"} in eine "rows"-Zeile ein.
Beispiel: Feld entfernen – Lösche die entsprechende Zelle aus "rows".
LayoutTypes: detail, list, edit, etc. – Passe Views an, um UI zu optimieren.
Rebuild durchführen:
Nach Änderungen muss ein Rebuild durchgeführt werden.
Verwende: `./custom/scripts/check_and_rebuild.sh` (validiert JSON, prüft Rechte, führt Rebuild durch)
Siehe Abschnitt "Rebuild-Prozess" für Details.
Panel-Labels und Übersetzungen
Um Relationship-Panels und Links korrekt zu beschriften, müssen Labels in den i18n-Sprachdateien definiert werden.
Vollständige i18n-Dokumentation: Siehe Abschnitt "Internationalisierung (i18n) und Tooltips"
Kurzübersicht:
- Labels in allen Sprachen definieren (de_DE UND en_US)
- Labels in zwei Sektionen:
fieldsUNDlinks - Nach Änderungen:
./custom/scripts/check_and_rebuild.sh
Beispiel:
// custom/Espo/Custom/Resources/i18n/de_DE/CBeteiligte.json
{
"fields": {
"vmhvermieterbeteiligte": "Vermieter",
"vmhmieterbeteiligte": "Mieter"
},
"links": {
"vmhvermieterbeteiligte": "Vermieter",
"vmhmieterbeteiligte": "Mieter"
}
}
Tooltips:
- Aktivierung:
"tooltip": truein entityDef setzen - Definition in
tooltips-Sektion der i18n-Dateien - Vollständige Dokumentation: Siehe Abschnitt "Internationalisierung (i18n) und Tooltips"
Custom JavaScript & CSS Integration
404-Fehler "Link does not exist"
- Symptom: HTTP 404-Fehler in Logs: "Link does not exist" beim Versuch, eine Relationship anzuzeigen oder zu verknüpfen.
- Ursache: Bei hasMany-Relationships fehlt die Definition auf einer Seite der Beziehung. EspoCRM benötigt bidirektionale Link-Definitionen.
- Lösung:
- Prüfe beide entityDefs-Dateien (z.B.
CBeteiligte.jsonUNDContact.json). - Stelle sicher, dass beide Seiten den Link mit derselben
relationNamedefinieren. - Das
foreign-Attribut muss jeweils auf den Link-Namen der Gegenseite zeigen. - Beispiel:
// In CBeteiligte.json: "contactsBeteiligte": { "type": "hasMany", "relationName": "cBeteiligteContact", "foreign": "cBeteiligteContact", "entity": "Contact" } // In Contact.json: "cBeteiligteContact": { "type": "hasMany", "relationName": "cBeteiligteContact", "foreign": "contactsBeteiligte", "entity": "CBeteiligte" } - Nach Korrektur:
./custom/scripts/check_and_rebuild.shausführen.
- Prüfe beide entityDefs-Dateien (z.B.
500-Fehler bei Layout-Änderungen
- Symptom: HTTP 500-Fehler beim Versuch, Layouts in der EspoCRM-UI zu bearbeiten (z.B. "Permission denied for custom/Espo/Custom/Resources/layouts/...").
- Ursache: Das
custom/-Verzeichnis gehörtroot:root, aber der EspoCRM-Container läuft alswww-data-User, der keine Schreibrechte hat. - Lösung:
- Führe auf dem Host aus:
chown -R www-data:www-data /var/lib/docker/volumes/vmh-espocrm_espocrm/_data/custom - Dies gibt
www-dataSchreibrechte für Custom-Dateien.
- Führe auf dem Host aus:
- Prävention: Stelle sicher, dass neue Custom-Dateien mit korrekten Berechtigungen erstellt werden (z.B. via Docker-Container als
www-data).
Allgemeine Tipps
-
WICHTIG: Nach jeder Änderung an Custom-Dateien das Check & Rebuild Script ausführen:
./custom/scripts/check_and_rebuild.shDas Script prüft automatisch auf häufige Fehler (JSON-Syntax, Dateirechte) und führt bei Fehlerfreiheit den Rebuild durch. Siehe Abschnitt "Rebuild-Prozess" für Details.
-
Logs prüfen:
tail -n 100 /var/lib/docker/volumes/vmh-espocrm_espocrm/_data/data/logs/espo-YYYY-MM-DD.log -
Bei Relationship-Problemen: Logs nach "404" und "Link does not exist" durchsuchen:
tail -n 500 /var/lib/docker/volumes/vmh-espocrm_espocrm/_data/data/logs/espo-$(date +%Y-%m-%d).log | grep -A 3 "404\|Link does not exist" -
Bei DB-Problemen: Custom-Scripts wie
workflow_manager.phpverwenden.
9. Reports und Report-Panels
EspoCRM bietet über das Advanced Pack zwei Arten von Report-Integrationen: Report-Filter und Report-Panels. Diese ermöglichen die dynamische Anzeige von gefilterten Listen in Entity-Views.
Report-Filter
Report-Filter ermöglichen es, vordefinierte Filter auf List-Views anzuwenden, die in Datenbanktabellen gespeichert sind.
Struktur und Dateien:
- entityDefs/{EntityType}.json - Filter-Definition
{
"collection": {
"filters": {
"reportFilterXXXXXXXXXX": {
"isReportFilter": true,
"id": "reportFilterIdHere"
}
}
}
}
- selectDefs/{EntityType}.json - Filter-Klasse
{
"primaryFilterClassNameMap": {
"reportFilterXXXXXXXXXX": "Espo\\Modules\\Advanced\\Classes\\Select\\Common\\PrimaryFilters\\ReportFilter"
}
}
- clientDefs/{EntityType}.json - Frontend-Integration
{
"filterList": [
"__APPEND__",
{
"isReportFilter": true,
"name": "reportFilterXXXXXXXXXX",
"accessDataList": [
{
"teamIdList": ["team-id-here"]
}
]
}
]
}
- i18n/{Language}/{EntityType}.json - Übersetzungen
{
"presetFilters": {
"reportFilterXXXXXXXXXX": "Filter-Name"
}
}
Report-Panels
Report-Panels zeigen Listen von Entitäten in Side-Panels der Detail-View an. Sie können Team-basierte Zugriffskontrolle haben.
Struktur:
clientDefs/{EntityType}.json - Panel-Definition
{
"sidePanels": {
"detail": [
"__APPEND__",
{
"isReportPanel": true,
"name": "reportPanelXXXXXXXXXX",
"label": "Panel-Titel",
"view": "advanced:views/report-panel/record/panels/report-panel-side",
"reportPanelId": "reportPanelIdHere",
"reportType": "List",
"reportEntityType": "EntityType",
"displayType": "List",
"displayTotal": false,
"displayOnlyTotal": false,
"useSiMultiplier": true,
"accessDataList": [
{
"scope": "EntityType"
},
{
"teamIdList": ["team-id-here"]
}
]
}
]
}
}
Wichtige Eigenschaften:
isReportFilter/isReportPanel: Markiert den Eintrag als Report-ElementaccessDataList: Array von Zugriffsbedingungen (Team-IDs, Scopes)reportType:"List"für Listen-ReportsdisplayType: Anzeige-Typ ("List","Chart", etc.)view: Spezielle Report-Panel-View aus dem Advanced Pack__APPEND__: Erweitert bestehende Arrays statt sie zu überschreiben
Best Practices:
-
Naming Convention:
- Filter:
reportFilter{uniqueId}(z.B.reportFilter6972174b6540731c1) - Panels:
reportPanel{uniqueId}(z.B.reportPanel697216784307d43ad)
- Filter:
-
Team-basierte Zugriffskontrolle:
- Definiere
teamIdListinaccessDataListfür eingeschränkten Zugriff - Mehrere Teams können kombiniert werden
- Definiere
-
Mehrsprachigkeit:
- Labels in allen Sprachen definieren (de_DE, en_US)
- Fehlerhafte Labels können zu UI-Problemen führen
-
Datei-Abhängigkeiten:
- Report-Filter benötigen 4 Dateien: entityDefs, selectDefs, clientDefs, i18n
- Report-Panels benötigen 1 Datei: clientDefs
- Fehlende Dateien führen zu nicht-funktionalen Filtern
-
Placeholder-Dateien:
logicDefs/{EntityType}.jsonkann als leeres Objekt{}angelegt werden- Ermöglicht zukünftige Erweiterungen ohne Struktur-Änderungen
Beispiel-Implementation:
Szenario: UserTask-Filter für Team "vermieterhelden"
# entityDefs/BpmnUserTask.json
{
"collection": {
"filters": {
"reportFilter6972174b6540731c1": {
"isReportFilter": true,
"id": "6972174b6540731c1"
}
}
}
}
# selectDefs/BpmnUserTask.json
{
"primaryFilterClassNameMap": {
"reportFilter6972174b6540731c1": "Espo\\Modules\\Advanced\\Classes\\Select\\Common\\PrimaryFilters\\ReportFilter"
}
}
# clientDefs/BpmnUserTask.json
{
"filterList": [
"__APPEND__",
{
"isReportFilter": true,
"name": "reportFilter6972174b6540731c1",
"accessDataList": [
{
"teamIdList": ["68da9bdd622c9958a"]
}
]
}
]
}
# i18n/en_US/BpmnUserTask.json
{
"presetFilters": {
"reportFilter6972174b6540731c1": "UserTask"
}
}
Troubleshooting:
- Filter erscheint nicht: Prüfe ob alle 4 Dateien existieren und Rebuild durchgeführt wurde
- Zugriffsfehler: Überprüfe
teamIdListund User-Team-Zuordnung - Leere Liste: Report-Definition in DB prüfen (Tabelle:
report) - Falsches Label: i18n-Dateien in allen Sprachen prüfen
Nach Änderungen:
# Rebuild durchführen (validiert JSON, prüft Rechte)
./custom/scripts/check_and_rebuild.sh
Portal-Freigabe-System
Um Entitäten für Portalnutzer (Contact-Entität) freizugeben, wurde ein konsistentes Freigabe-System implementiert:
Implementierte Portal-Relationships:
- CVmhMietverhltnis →
contactsMietverhltnis(relationName:cVmhMietverhltnisContact) - CBeteiligte →
contactsBeteiligte(relationName:cBeteiligteContact) - CMietobjekt →
contactsMietobjekt(relationName:cMietobjektContactPortal) - CAdressen →
contactsAdressen(relationName:cAdressenContact) - CVmhRumungsklage →
contactsRumungsklage(relationName:cVmhRumungsklageContact)
Pattern für neue Portal-Relationships:
- entityDefs der Hauptentität (z.B.
CBeteiligte.json):
"contactsBeteiligte": {
"type": "hasMany",
"relationName": "cBeteiligteContact",
"foreign": "cBeteiligteContact",
"entity": "Contact",
"audited": false,
"isCustom": true
}
- entityDefs von Contact (
Contact.json):
"cBeteiligteContact": {
"type": "hasMany",
"relationName": "cBeteiligteContact",
"foreign": "contactsBeteiligte",
"entity": "CBeteiligte",
"audited": false,
"isCustom": true
}
- clientDefs der Hauptentität (
CBeteiligte.json):
"relationshipPanels": {
"contactsBeteiligte": {
"layout": null,
"selectPrimaryFilterName": "portalUsers"
}
}
- bottomPanelsDetail Layout (Tab-Ansicht):
{
"_tabBreak_0": {
"index": 0,
"tabBreak": true,
"tabLabel": "Freigabe für"
},
"contactsBeteiligte": {
"dynamicLogicVisible": null,
"style": "warning",
"dynamicLogicStyled": null,
"sticked": true,
"index": 1
}
}
Wichtige Hinweise:
selectPrimaryFilterName: "portalUsers"filtert automatisch auf Portal-User- Tab "Freigabe für" sollte immer der erste Tab im Bottom-Panel sein (index: 0)
- Style "warning" hebt das Panel visuell hervor
- Nach Änderungen:
./custom/scripts/check_and_rebuild.sh - Beide Seiten der Relationship müssen in entityDefs definiert sein
Troubleshooting
"Link does not exist" (404-Fehler)
Symptom: HTTP 404-Fehler in Logs beim Versuch, eine Relationship anzuzeigen oder zu verknüpfen.
Ursache: Bei hasMany-Relationships fehlt die Definition auf einer Seite der Beziehung.
Lösung:
- Prüfe beide entityDefs-Dateien (z.B.
CBeteiligte.jsonUNDContact.json) - Stelle sicher, dass beide Seiten den Link mit derselben
relationNamedefinieren - Das
foreign-Attribut muss jeweils auf den Link-Namen der Gegenseite zeigen
Beispiel:
// In CBeteiligte.json:
"contactsBeteiligte": {
"type": "hasMany",
"relationName": "cBeteiligteContact",
"foreign": "cBeteiligteContact",
"entity": "Contact"
}
// In Contact.json:
"cBeteiligteContact": {
"type": "hasMany",
"relationName": "cBeteiligteContact",
"foreign": "contactsBeteiligte",
"entity": "CBeteiligte"
}
Nach Korrektur: ./custom/scripts/check_and_rebuild.sh
Tooltip zeigt nur Feldnamen
Symptom: Tooltip zeigt nur "iban" statt vollständiger Beschreibung.
Ursache: Fehlerhafte oder fehlende en_US i18n-Datei (en_US ist Fallback-Sprache).
Lösung:
- Existiert
custom/Espo/Custom/Resources/i18n/en_US/{Entity}.json? - Enthält es fehlerhafte Tooltip-Definitionen?
- Sind alle Tooltips konsistent über alle Sprachen?
- Vervollständige en_US-Datei mit korrekten englischen Übersetzungen
Korrekt:
// de_DE/{Entity}.json
{
"fields": {"iban": "IBAN"},
"tooltips": {"iban": "Internationale Bankkontonummer im Format DE89..."}
}
// en_US/{Entity}.json
{
"fields": {"iban": "IBAN"},
"tooltips": {"iban": "International Bank Account Number in format DE89..."}
}
Nach Korrektur: ./custom/scripts/check_and_rebuild.sh
Formula-Script wird nicht ausgeführt
Symptom: beforeSaveApiScript triggert nicht, keine Validierung.
Ursache: Script in entityDefs statt formula/ abgelegt.
Lösung:
- Erstelle separate Datei:
custom/Espo/Custom/Resources/metadata/formula/{Entity}.json - NICHT in
entityDefs/{Entity}.jsoneinfügen!
// custom/Espo/Custom/Resources/metadata/formula/Entity.json
{
"beforeSaveApiScript": "if (field != null && field != '') { ... }"
}
Nach Korrektur: ./custom/scripts/check_and_rebuild.sh
CSS-Änderungen nicht sichtbar
Symptom: CSS-Änderungen werden nicht im Browser angezeigt.
Ursache: Browser-Cache oder fehlende Registrierung.
Lösung:
- CSS in
custom/Espo/Custom/Resources/metadata/app/client.jsonregistrieren:{ "cssList": ["__APPEND__", "client/custom/css/my-styles.css"] } ./custom/scripts/check_and_rebuild.shausführen- Browser Hard Refresh (Ctrl+Shift+R / Cmd+Shift+R)
"Permission denied" bei Layout-Bearbeitung
Symptom: HTTP 500-Fehler beim Versuch, Layouts über die UI zu bearbeiten.
Ursache: Falsche Dateirechte - custom/-Verzeichnis gehört root statt www-data.
Lösung:
Das check_and_rebuild.sh Script prüft und korrigiert Dateirechte automatisch. Falls manuelle Korrektur nötig:
sudo chown -R www-data:www-data custom/ client/custom/
sudo find custom/ -type f -name "*.json" -exec chmod 664 {} \;
sudo find custom/ -type d -exec chmod 775 {} \;
JSON-Syntax-Fehler
Symptom: Rebuild schlägt fehl, Script zeigt "Invalid JSON" Fehler.
Ursache: Ungültiges JSON (fehlende Kommas, Anführungszeichen, etc.).
Lösung:
./custom/scripts/check_and_rebuild.shzeigt Datei und Zeilennummer an- JSON-Validator verwenden (z.B. jsonlint.com)
- Häufige Fehler:
- Komma nach letztem Array/Object-Element
- Einfache statt doppelte Anführungszeichen
- Fehlende schließende Klammern
"Unknown function" in Formula
Symptom: Error "Unknown function: xxx" beim Speichern.
Ursache: Funktion existiert nicht oder ist nicht registriert.
Lösung:
- Für Custom-Funktionen: Registrierung in
app/formula.jsonprüfen string\isEmpty()existiert nicht → verwendefield != null && field != ''- Nach Registrierung:
./custom/scripts/check_and_rebuild.sh
Logs prüfen
Wichtige Log-Dateien:
# Aktuelles Log (heutiges Datum)
tail -n 100 data/logs/espo-$(date +%Y-%m-%d).log
# Nach bestimmten Fehlern suchen
tail -n 500 data/logs/espo-*.log | grep -i "404\|500\|error\|exception"
# Formula-Script-Fehler
tail -n 200 data/logs/espo-*.log | grep -i "formula\|script"
# Relationship-Fehler
tail -n 500 data/logs/espo-*.log | grep -i "link does not exist"
Allgemeine Troubleshooting-Schritte
-
Nach jeder Änderung:
./custom/scripts/check_and_rebuild.sh -
Bei Frontend-Problemen:
- Browser Hard Refresh (Ctrl+Shift+R)
- Browser-Cache komplett leeren
- Inkognito-Modus testen
-
Bei Backend-Problemen:
- Logs prüfen (siehe oben)
- Dateirechte prüfen (
www-data:www-data) - JSON-Syntax validieren
-
Bei Relationship-Problemen:
- Beide Seiten der Relationship prüfen
relationNameidentisch?foreignzeigt auf korrekten Link-Namen?- Nach Rebuild: Cache manuell leeren falls nötig
Vollständige Dokumentation zur Custom Directory Struktur: Siehe /custom/CUSTOM_DIRECTORY.md
JavaScript-Module: client/custom/src/modules/ - AMD-Module für wiederverwendbare Logik
Custom Views: client/custom/src/views/ - Backbone.js Views für Entity-spezifisches UI
CSS-Stylesheets: client/custom/css/ - Custom CSS (Registrierung in app/client.json erforderlich)
JavaScript-Module einbinden
EspoCRM verwendet AMD/RequireJS für JavaScript-Module. Custom JavaScript-Dateien werden in client/custom/src/ abgelegt.
Beispiel: RVG-Gebührenrechner für CVmhErstgespraech
1. Modul erstellen (client/custom/src/modules/rvg-calculator.js):
define('custom:modules/rvg-calculator', [], function () {
return {
kalkuliereKosten: function(streitwert, anzahlKlaeger, anzahlBeklagte, ustProzent) {
// Berechnungslogik
return { /* Ergebnisobjekt */ };
}
};
});
2. Custom Field View erstellen (client/custom/src/views/{entity}/fields/{fieldname}.js):
define('custom:views/c-vmh-erstgespraech/fields/rvg-calculated', [
'views/fields/currency',
'custom:modules/rvg-calculator'
], function (Dep, RvgCalculator) {
return Dep.extend({
setup: function () {
Dep.prototype.setup.call(this);
this.listenTo(this.model, 'change:streitwert change:anzahlVermieter', this.calculate);
this.listenTo(this.model, 'sync', this.calculate); // Initial load
},
calculate: function () {
var result = RvgCalculator.kalkuliereKosten(/*...*/);
this.model.set('kostenRaeumungsantrag', result.kostenRaeumungsantrag);
}
});
});
3. In entityDefs registrieren:
{
"fields": {
"vergleich1InstanzGk": {
"type": "currency",
"readOnly": true,
"view": "custom:views/c-vmh-erstgespraech/fields/rvg-calculated"
}
}
}
Wichtige Patterns:
listenTo(model, 'sync', callback)- Für initiale Berechnung beim LadenlistenTo(model, 'change:field1 change:field2', callback)- Für ReaktivitätcalculatingFlag verhindert Rekursion beimodel.set()- Browser-Cache: Hard Refresh (Ctrl+Shift+R) nach JS-Änderungen erforderlich
CSS-Manipulation & Feld-Hervorhebung
EspoCRM erlaubt Custom CSS über Metadata-Registrierung.
1. CSS-Datei erstellen (client/custom/css/erstgespraech-highlight.css):
/* Feld-Selektor über data-name Attribut */
.detail .cell[data-name="vorzusch1Instanz"] {
background-color: #d4edda;
padding: 10px;
border-bottom: 4px solid #28a745;
border-radius: 4px;
}
.detail .cell[data-name="vorzusch1Instanz"] .numeric-text {
font-weight: bold;
color: #155724;
font-size: 1.1em;
}
2. CSS in Metadata registrieren (custom/Espo/Custom/Resources/metadata/app/client.json):
{
"cssList": [
"__APPEND__",
"client/custom/css/erstgespraech-highlight.css"
]
}
Nach CSS-Änderungen:
./custom/scripts/check_and_rebuild.shausführen- Browser Hard Refresh (Ctrl+Shift+R)
CSS-Targeting-Strategien:
- Feld-spezifisch:
.cell[data-name="fieldName"] - Entity-spezifisch:
body[data-controller="CVmhErstgespraech"] - View-spezifisch:
.detail(Detail-View),.edit(Edit-View),.list(List-View) - Label vs. Value:
.label-text- Feldlabel.numeric-text/.text-default- Feldwert.field[data-name="..."]- Field-Container
HTML-Struktur (Referenz):
<div class="cell col-sm-4 form-group" data-name="vorzusch1Instanz">
<label class="control-label">
<span class="label-text">Vorauszuschießende Kosten I. Inst.</span>
</label>
<div class="field" data-name="vorzusch1Instanz">
<span class="numeric-text">3.067,63</span> €
</div>
</div>
Best Practices:
- CSS-Dateien in
client/custom/css/ablegen __APPEND__verwenden um Core-CSS zu erweitern, nicht zu überschreiben- Spezifische Selektoren verwenden um Kollisionen zu vermeiden
- Nach CSS-Änderungen:
./custom/scripts/check_and_rebuild.sh+ Browser Hard Refresh
RVG-Gebührenrechner (CVmhErstgespraech)
Implementierung: Automatische Berechnung von Anwalts- und Gerichtskosten nach RVG 2025 / GKG
Komponenten:
-
Calculator-Modul (
client/custom/src/modules/rvg-calculator.js):getWertgebuehr(): RVG 2025 Tabelle (65 Stufen, €500-€2M)getGerichtsgebuehr(): GKG progressive BerechnunggetZuschlag(): §7 RVG Personenzuschlag (+0.3 pro Person, max +2.0)kalkuliereKosten(): Hauptfunktion für alle Szenarien
-
Custom Field Views:
rvg-calculated.js: Trigger für alle Berechnungenberuecksichtigte-personen.js: Live-Text-Anzeige "X Vermieter, Y Mieter, Z Dritte"warmmiete.js: Kaltmiete + BK-Vorauszahlung + BK-Pauschalestreitwert.js: (Kaltmiete + BK-Pauschale) × 12
-
Berechnete Felder (readOnly currency fields):
- Außergerichtliche Gebühren: 1.3 + Zuschlag + Pauschale 20% (max 20€)
- Kosten Räumungsantrag: 0.3 + 0.3/Person + Pauschale
-
- Instanz: 3.0 GK + RA-Kosten (1.3 Verf + 1.2 Term + Pauschale)
- Säumnisszenario: 3.0 GK + reduzierte RA (0.5 Term statt 1.2)
- Vergleichsszenario: 1.0 GK + RA (1.3 Verf + 1.2 Term + 1.0 Vergl)
-
USt-Satz Handling:
- Enum Field: "0" / "19" (String, nicht Integer!)
- Konvertierung:
parseInt(ustSatz)→ dann/100im Calculator - Wichtig: Expliziter Null-Check nötig,
0ist falsy in|| 19
-
Reaktivität:
- Listener auf: streitwert, anzahlVermieter, anzahlMieter, anzahlSonstigeVolljhrigeBewohner, ustSatz
- Initial berechnen mit
'sync'Event calculatingFlag verhindert Rekursion
Layout-Panels:
- Gebührenberechnung (primary, info-note): Standard 1. Instanz Kosten
- Säumnisszenario I. Inst. (primary, success-note): Beklagte erscheint nicht
- Vergleichsszenario I. Inst. (primary, success-note): Einigung vor Urteil
Hervorhebung: "Vorauszuschießende Kosten I. Inst." wird via CSS hervorgehoben (grüner Hintergrund, fetter Wert)