bsiggel 127fa6503b chore: Update copyright year from 2025 to 2026 across core files
- Updated copyright headers in 3,055 core application files
- Changed 'Copyright (C) 2014-2025' to 'Copyright (C) 2014-2026'
- Added 123 new files from EspoCRM core updates
- Removed 4 deprecated files
- Total changes: 61,637 insertions, 54,283 deletions

This is a routine maintenance update for the new year 2026.
2026-02-07 16:05:21 +01:00
2026-01-19 17:46:06 +01:00
2026-01-19 17:46:06 +01:00
2026-01-19 17:46:06 +01:00
2026-01-19 17:54:52 +01:00
2026-01-19 17:46:06 +01:00

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

  1. Überblick
  2. Custom Directory Struktur
  3. Rebuild-Prozess
  4. Häufige Fehler vermeiden
  5. Dateiformate und JSON-Strukturen
  6. Workflow-Verwaltung
  7. Internationalisierung (i18n) und Tooltips
  8. Formula-Scripts und Custom PHP-Erweiterungen
  9. Panel-Labels und Übersetzungen
  10. Custom JavaScript & CSS Integration
  11. Reports und Report-Panels
  12. Portal-Freigabe-System
  13. 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:

  1. JSON-Dateien im custom/-Verzeichnis erstellen/bearbeiten
  2. Rebuild-Script ausführen (validiert, merged, aktualisiert DB)
  3. Ä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 Stylesheets
  • custom/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 .json Dateien im custom/ 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-data Owner)

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

  1. Metadata-Merging: Kombiniert Custom-Definitionen mit Core-Definitionen
  2. Datenbank-Schema-Update: Erstellt neue Tabellen/Spalten/Indizes basierend auf entityDefs
  3. Cache-Bereinigung: Löscht gecachte Metadata, Views, Templates
  4. Frontend-Build: Regeneriert JavaScript/CSS-Bundles
  5. 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:

  • false als 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 {} statt false für leere Zellen in detail.json verwenden
  • Width nur in list.json und listSmall.json verwenden
  • 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:

  • relationName muss auf beiden Seiten identisch sein
  • foreign zeigt 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 fields UND links definieren
  • 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.sh ausführen
  • VSCode mit Remote-Development nutzt automatisch korrekte User

🛡️ Fehler-Prävention: Checkliste

Bei neuer Entity-Erstellung:

  • entityDefs in beiden Entities (bei Relationships)
  • relationName identisch auf beiden Seiten
  • foreign zeigt auf korrekten Link-Namen
  • i18n in de_DE UND en_US vollständig
  • Labels in fields UND links
  • Formula in formula/, NICHT in entityDefs/
  • 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 false oder 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}.json erstellen
  • 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.sh nach Ä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:

  1. EspoCRM Admin-UI generiert teilweise unvollständige JSON-Definitionen
  2. Bidirektionale Relationships sind komplex und nicht intuitiv
  3. en_US als Fallback ist nicht offensichtlich dokumentiert
  4. Formula-Platzierung wird im Admin-UI falsch vorgeschlagen
  5. Layout-Generierung erzeugt manchmal ungültige Array-Strukturen

Lösung:

  • Diese Dokumentation VOR der Entwicklung lesen
  • check_and_rebuild.sh Script konsequent nutzen
  • Bei Unsicherheit: Troubleshooting-Abschnitt konsultieren

Siehe auch:

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, Optionen
  • links - 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:

  • relationName muss auf beiden Seiten identisch sein
  • foreign zeigt 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:

  1. entityDefs/{Entity}.json - Feld-Definition
  2. layouts/{Entity}/*.json - Sichtbarkeit in UI
  3. 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": true im entityDef gesetzt
  • Bei Link-Feldern Labels in fields UND links definieren
  • 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-Ansicht
  • detailSmall.json - Seitenleisten-Ansicht
  • filters.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.json definiert)
  • 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.json aus dem layouts/ Verzeichnis (NICHT metadata/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äfix custom:)
  • viewSetupHandlers - Dynamische View-Anpassungen
  • filterList - 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 label und rows)
  • rows sind Arrays von Zellen (Objekte mit name für Felder)
  • Unterstützt Parameter: width, notSortable, customLabel

Spezielle Features:

  • __APPEND__ - Als erstes Array-Element einfügen, um bestehende Werte zu erweitern
  • layoutAvailabilityList - Array für Feld-Sichtbarkeit in Layouts
  • layoutIgnoreList - 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 Aktualisieren
  • afterRecordCreated - Nur nach Erstellen
  • afterRecordUpdated - Nur nach Aktualisieren
  • manual - Manuell ausgeführt
  • scheduled - Zeitgesteuert

Bedingungen:

  • Vergleiche: equals, notEquals, greaterThan, lessThan, contains, isEmpty
  • Änderungen: changed, notChanged, wasEqual, wasNotEqual

Aktionen:

  • sendEmail - E-Mail versenden
  • createEntity - Record erstellen
  • updateEntity - Record aktualisieren
  • relateTo / unrelateFrom - Verknüpfungen
  • createNotification - 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 Organisation
  • comparison - Vergleichsoperator
  • fieldToCompare - Feldname für Bedingung
  • subjectType - 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

  1. Versionierung: Workflows als JSON im custom/workflows/ Verzeichnis versionieren
  2. Naming Convention: Beschreibende Namen mit Präfix (z.B. vmh-erstberatung-abschliessen.json)
  3. Entwicklungsprozess:
    • Workflow-JSON in custom/workflows/ erstellen
    • Mit workflow_manager.php import einspielen
    • Im Admin-Interface testen und bei Bedarf anpassen
    • Mit export aktualisierten Workflow sichern
    • JSON-Datei im Repository committen
  4. Backup: Regelmäßig Export wichtiger Workflows durchführen
  5. 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:

  1. Sprachpriorität: en_US → aktuelle Sprache (z.B. de_DE)
  2. Problem: Tooltips in en_US überschreiben Tooltips in anderen Sprachen
  3. 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 fields definieren
  • Alle Tooltips in tooltips definieren
  • Konsistente Struktur über alle Sprachen

Debugging von Tooltip-Problemen:

  1. Symptom: Tooltip zeigt nur Feldnamen (z.B. "iban" statt vollständiger Beschreibung)
  2. Ursache: Fehlerhafte oder fehlende Definition in i18n/en_US/{Entity}.json
  3. Prüfung:
    • Existiert i18n/en_US/{Entity}.json?
    • Enthält es fehlerhafte Tooltip-Definitionen?
    • Sind alle Tooltips konsistent über alle Sprachen?
  4. 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

  1. beforeSaveApiScript - Wird vor dem Speichern ausgeführt (UI + API, ab v7.5+)
  2. beforeSaveCustomScript - Nur bei internen Saves (ohne API)
  3. afterSaveScript - Nach dem Speichern

Verwendung: Validierungen, Berechnungen, Daten-Transformation vor dem Speichern

Verfügbare Formula-Funktionen

String-Funktionen:

  • string\concatenate(str1, str2) - Strings verbinden
  • string\replace(text, search, replace) - Ersetzen
  • string\substring(text, start, length) - Teilstring
  • string\length(text) - Länge
  • string\test(text, pattern) - Regex-Test
  • ⚠️ NICHT verfügbar: string\isEmpty() → Verwende field != 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 BaseFunction erweitern

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 registriert
  • Error 500 → Syntax-Fehler im Formula-Script oder PHP-Fehler
  • validationFailurethrowBadRequest() wurde aufgerufen

Test-Workflow:

  1. Formula-Script schreiben
  2. Rebuild ausführen: bash custom/scripts/check_and_rebuild.sh
  3. Cache leeren (falls nötig): rm -rf data/cache/*
  4. 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 entityDefs ablegen
  • Nicht-existente String-Funktionen wie isEmpty()
  • Komplexe Logik direkt in Formula (besser: PHP-Funktion)
  • Fehlende Registrierung in app/formula.json

Beispiel-Anwendungsfälle

  1. Validierung: IBAN-Check, Email-Format, Telefonnummern

  2. Berechnung: Gesamtpreise, Datumsberechnungen, Provisionen

  3. Daten-Transformation: Großbuchstaben, Formatierungen, Normalisierungen

  4. Business Rules: Status-Überprüfungen, Pflichtfeld-Logik, Abhängigkeiten

  5. 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 Aktualisieren
    • afterRecordCreated - Nur nach Erstellen
    • afterRecordUpdated - Nur nach Aktualisieren
    • manual - Manuell ausgeführt
    • scheduled - Zeitgesteuert
  • Bedingungen:
    • Vergleiche: equals, notEquals, greaterThan, lessThan, contains, isEmpty
    • Änderungen: changed, notChanged, wasEqual
  • Aktionen:
    • sendEmail - E-Mail versenden
    • createEntity - Record erstellen
    • updateEntity - Record aktualisieren
    • relateTo / unrelateFrom - Verknüpfungen
    • createNotification - 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 Bedingung
  • subjectType - 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

  1. Versionierung: Workflows als JSON-Dateien im custom/workflows/ Verzeichnis versionieren
  2. Naming Convention: Beschreibende Namen mit Präfix (z.B. vmh-erstberatung-abschliessen.json)
  3. Testen: Nach Import immer über Admin-Interface testen
  4. Backup: Regelmäßig Export für wichtige Workflows durchführen
  5. 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
  1. 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: fields UND links
  • 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": true in entityDef setzen
  • Definition in tooltips-Sektion der i18n-Dateien
  • Vollständige Dokumentation: Siehe Abschnitt "Internationalisierung (i18n) und Tooltips"

Custom JavaScript & CSS Integration

  • 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.json UND Contact.json).
    • Stelle sicher, dass beide Seiten den Link mit derselben relationName definieren.
    • 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 ausführen.

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ört root:root, aber der EspoCRM-Container läuft als www-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-data Schreibrechte für Custom-Dateien.
  • 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.sh
    

    Das 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.php verwenden.

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:

  1. entityDefs/{EntityType}.json - Filter-Definition
{
  "collection": {
    "filters": {
      "reportFilterXXXXXXXXXX": {
        "isReportFilter": true,
        "id": "reportFilterIdHere"
      }
    }
  }
}
  1. selectDefs/{EntityType}.json - Filter-Klasse
{
  "primaryFilterClassNameMap": {
    "reportFilterXXXXXXXXXX": "Espo\\Modules\\Advanced\\Classes\\Select\\Common\\PrimaryFilters\\ReportFilter"
  }
}
  1. clientDefs/{EntityType}.json - Frontend-Integration
{
  "filterList": [
    "__APPEND__",
    {
      "isReportFilter": true,
      "name": "reportFilterXXXXXXXXXX",
      "accessDataList": [
        {
          "teamIdList": ["team-id-here"]
        }
      ]
    }
  ]
}
  1. 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-Element
  • accessDataList: Array von Zugriffsbedingungen (Team-IDs, Scopes)
  • reportType: "List" für Listen-Reports
  • displayType: Anzeige-Typ ("List", "Chart", etc.)
  • view: Spezielle Report-Panel-View aus dem Advanced Pack
  • __APPEND__: Erweitert bestehende Arrays statt sie zu überschreiben

Best Practices:

  1. Naming Convention:

    • Filter: reportFilter{uniqueId} (z.B. reportFilter6972174b6540731c1)
    • Panels: reportPanel{uniqueId} (z.B. reportPanel697216784307d43ad)
  2. Team-basierte Zugriffskontrolle:

    • Definiere teamIdList in accessDataList für eingeschränkten Zugriff
    • Mehrere Teams können kombiniert werden
  3. Mehrsprachigkeit:

    • Labels in allen Sprachen definieren (de_DE, en_US)
    • Fehlerhafte Labels können zu UI-Problemen führen
  4. 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
  5. Placeholder-Dateien:

    • logicDefs/{EntityType}.json kann 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 teamIdList und 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:

  • CVmhMietverhltniscontactsMietverhltnis (relationName: cVmhMietverhltnisContact)
  • CBeteiligtecontactsBeteiligte (relationName: cBeteiligteContact)
  • CMietobjektcontactsMietobjekt (relationName: cMietobjektContactPortal)
  • CAdressencontactsAdressen (relationName: cAdressenContact)
  • CVmhRumungsklagecontactsRumungsklage (relationName: cVmhRumungsklageContact)

Pattern für neue Portal-Relationships:

  1. entityDefs der Hauptentität (z.B. CBeteiligte.json):
"contactsBeteiligte": {
  "type": "hasMany",
  "relationName": "cBeteiligteContact",
  "foreign": "cBeteiligteContact",
  "entity": "Contact",
  "audited": false,
  "isCustom": true
}
  1. entityDefs von Contact (Contact.json):
"cBeteiligteContact": {
  "type": "hasMany",
  "relationName": "cBeteiligteContact",
  "foreign": "contactsBeteiligte",
  "entity": "CBeteiligte",
  "audited": false,
  "isCustom": true
}
  1. clientDefs der Hauptentität (CBeteiligte.json):
"relationshipPanels": {
  "contactsBeteiligte": {
    "layout": null,
    "selectPrimaryFilterName": "portalUsers"
  }
}
  1. 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

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:

  1. Prüfe beide entityDefs-Dateien (z.B. CBeteiligte.json UND Contact.json)
  2. Stelle sicher, dass beide Seiten den Link mit derselben relationName definieren
  3. 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:

  1. Existiert custom/Espo/Custom/Resources/i18n/en_US/{Entity}.json?
  2. Enthält es fehlerhafte Tooltip-Definitionen?
  3. Sind alle Tooltips konsistent über alle Sprachen?
  4. 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}.json einfü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:

  1. CSS in custom/Espo/Custom/Resources/metadata/app/client.json registrieren:
    {
      "cssList": ["__APPEND__", "client/custom/css/my-styles.css"]
    }
    
  2. ./custom/scripts/check_and_rebuild.sh ausführen
  3. 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:

  1. ./custom/scripts/check_and_rebuild.sh zeigt Datei und Zeilennummer an
  2. JSON-Validator verwenden (z.B. jsonlint.com)
  3. 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:

  1. Für Custom-Funktionen: Registrierung in app/formula.json prüfen
  2. string\isEmpty() existiert nicht → verwende field != null && field != ''
  3. 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

  1. Nach jeder Änderung:

    ./custom/scripts/check_and_rebuild.sh
    
  2. Bei Frontend-Problemen:

    • Browser Hard Refresh (Ctrl+Shift+R)
    • Browser-Cache komplett leeren
    • Inkognito-Modus testen
  3. Bei Backend-Problemen:

    • Logs prüfen (siehe oben)
    • Dateirechte prüfen (www-data:www-data)
    • JSON-Syntax validieren
  4. Bei Relationship-Problemen:

    • Beide Seiten der Relationship prüfen
    • relationName identisch?
    • foreign zeigt 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 Laden
  • listenTo(model, 'change:field1 change:field2', callback) - Für Reaktivität
  • calculating Flag verhindert Rekursion bei model.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:

  1. ./custom/scripts/check_and_rebuild.sh ausführen
  2. 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:

  1. Calculator-Modul (client/custom/src/modules/rvg-calculator.js):

    • getWertgebuehr(): RVG 2025 Tabelle (65 Stufen, €500-€2M)
    • getGerichtsgebuehr(): GKG progressive Berechnung
    • getZuschlag(): §7 RVG Personenzuschlag (+0.3 pro Person, max +2.0)
    • kalkuliereKosten(): Hauptfunktion für alle Szenarien
  2. Custom Field Views:

    • rvg-calculated.js: Trigger für alle Berechnungen
    • beruecksichtigte-personen.js: Live-Text-Anzeige "X Vermieter, Y Mieter, Z Dritte"
    • warmmiete.js: Kaltmiete + BK-Vorauszahlung + BK-Pauschale
    • streitwert.js: (Kaltmiete + BK-Pauschale) × 12
  3. 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
      1. 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)
  4. USt-Satz Handling:

    • Enum Field: "0" / "19" (String, nicht Integer!)
    • Konvertierung: parseInt(ustSatz) → dann /100 im Calculator
    • Wichtig: Expliziter Null-Check nötig, 0 ist falsy in || 19
  5. Reaktivität:

    • Listener auf: streitwert, anzahlVermieter, anzahlMieter, anzahlSonstigeVolljhrigeBewohner, ustSatz
    • Initial berechnen mit 'sync' Event
    • calculating Flag 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)

Description
No description provided
Readme AGPL-3.0 32 MiB
Languages
JavaScript 71.2%
PHP 25.1%
Smarty 3.4%
Python 0.2%