2133 lines
65 KiB
Markdown
2133 lines
65 KiB
Markdown
KI-basierte Bearbeitung von EspoCRM: Struktur und Funktionsweise
|
||
|
||
## 🚀 Schnellstart für KI
|
||
|
||
**NEU:** Automatisches KI-Einstiegsscript für vollständigen Projekt-Überblick!
|
||
|
||
```bash
|
||
# 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](custom/scripts/KI_OVERVIEW_README.md) für Details.
|
||
|
||
## Inhaltsverzeichnis
|
||
1. [Überblick](#überblick)
|
||
2. [Custom Directory Struktur](#custom-directory-struktur)
|
||
3. [Rebuild-Prozess](#rebuild-prozess)
|
||
4. [Häufige Fehler vermeiden](#häufige-fehler-vermeiden)
|
||
5. [Dateiformate und JSON-Strukturen](#dateiformate-und-json-strukturen)
|
||
6. [Workflow-Verwaltung](#workflow-verwaltung)
|
||
7. [Internationalisierung (i18n) und Tooltips](#internationalisierung-i18n-und-tooltips)
|
||
8. [Formula-Scripts und Custom PHP-Erweiterungen](#formula-scripts-und-custom-php-erweiterungen)
|
||
9. [Panel-Labels und Übersetzungen](#panel-labels-und-übersetzungen)
|
||
10. [Custom JavaScript & CSS Integration](#custom-javascript--css-integration)
|
||
11. [Reports und Report-Panels](#reports-und-report-panels)
|
||
12. [Portal-Freigabe-System](#portal-freigabe-system)
|
||
13. [Troubleshooting](#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:**
|
||
```bash
|
||
# 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:
|
||
|
||
```bash
|
||
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:**
|
||
```json
|
||
// custom/Espo/Custom/Resources/metadata/entityDefs/CBankverbindungen.json
|
||
{
|
||
"fields": {
|
||
"iban": {"type": "varchar"}
|
||
},
|
||
"formula": {
|
||
"beforeSaveApiScript": "..." // ← Funktioniert NICHT!
|
||
}
|
||
}
|
||
```
|
||
|
||
**✅ RICHTIG:**
|
||
```json
|
||
// 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):**
|
||
```json
|
||
// layouts/Entity/detail.json
|
||
{
|
||
"rows": [
|
||
[
|
||
{"name": "field1"},
|
||
{"name": "field2"},
|
||
false, // ← DEPRECATED! Funktioniert in 7.x NICHT!
|
||
false // ← Verursacht Layout-Fehler!
|
||
]
|
||
]
|
||
}
|
||
```
|
||
|
||
**✅ RICHTIG (EspoCRM 7.x):**
|
||
```json
|
||
{
|
||
"rows": [
|
||
[
|
||
{"name": "field1"},
|
||
{"name": "field2"},
|
||
{}, // ← Leeres Objekt verwenden!
|
||
{} // ← Funktioniert in 7.x korrekt
|
||
]
|
||
]
|
||
}
|
||
```
|
||
|
||
**❌ FALSCH - bottomPanelsDetail.json als Array:**
|
||
```json
|
||
// layouts/Entity/bottomPanelsDetail.json
|
||
[
|
||
{"name": "contacts"},
|
||
{"name": "documents"}
|
||
]
|
||
```
|
||
|
||
**✅ RICHTIG - bottomPanelsDetail.json als Objekt:**
|
||
```json
|
||
{
|
||
"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:**
|
||
```json
|
||
// entityDefs/CMietobjekt.json
|
||
{
|
||
"links": {
|
||
"kontakte": {
|
||
"type": "hasMany",
|
||
"entity": "Contact",
|
||
"foreign": "mietobjekte" // ← Existiert nicht in Contact!
|
||
}
|
||
}
|
||
}
|
||
|
||
// entityDefs/Contact.json - FEHLT!
|
||
```
|
||
|
||
**✅ RICHTIG - Beide Seiten definiert:**
|
||
```json
|
||
// 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:**
|
||
```json
|
||
// i18n/de_DE/Entity.json
|
||
{
|
||
"fields": {
|
||
"mietobjekte": "Mietobjekte"
|
||
},
|
||
"links": {} // ← Label fehlt!
|
||
}
|
||
|
||
// i18n/en_US/Entity.json - FEHLT KOMPLETT!
|
||
```
|
||
|
||
**✅ RICHTIG - Vollständig in beiden Sprachen:**
|
||
```json
|
||
// 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:
|
||
|
||
```bash
|
||
./custom/scripts/check_and_rebuild.sh
|
||
```
|
||
|
||
**Manuelle Korrektur (falls nötig):**
|
||
```bash
|
||
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:**
|
||
- [Troubleshooting](#troubleshooting) - Ausführliche Fehlerdiagnose
|
||
- [Custom Directory Struktur](/custom/CUSTOM_DIRECTORY.md) - 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:**
|
||
```json
|
||
{
|
||
"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
|
||
|
||
```json
|
||
// 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:**
|
||
```json
|
||
// 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:**
|
||
```json
|
||
// 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:**
|
||
```json
|
||
// 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:**
|
||
```bash
|
||
# ❌ 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:**
|
||
```json
|
||
|
||
{
|
||
"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:**
|
||
```json
|
||
|
||
[
|
||
{
|
||
"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**
|
||
```bash
|
||
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**
|
||
```bash
|
||
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**
|
||
```bash
|
||
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**
|
||
```bash
|
||
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**
|
||
```bash
|
||
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
|
||
```json
|
||
{
|
||
"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
|
||
```json
|
||
{
|
||
"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"
|
||
|
||
```json
|
||
{
|
||
"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:**
|
||
```json
|
||
{
|
||
"fields": {
|
||
"iban": {
|
||
"type": "varchar",
|
||
"tooltip": true // Aktiviert Tooltip-Anzeige
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**i18n/de_DE/{Entity}.json:**
|
||
```json
|
||
{
|
||
"fields": {
|
||
"iban": "IBAN"
|
||
},
|
||
"tooltips": {
|
||
"iban": "Internationale Bankkontonummer im Format DE89..."
|
||
}
|
||
}
|
||
```
|
||
|
||
**i18n/en_US/{Entity}.json:**
|
||
```json
|
||
{
|
||
"fields": {
|
||
"iban": "IBAN"
|
||
},
|
||
"tooltips": {
|
||
"iban": "International Bank Account Number in format DE89..."
|
||
}
|
||
}
|
||
```
|
||
|
||
### Häufige Fehler:
|
||
|
||
❌ **FALSCH** - Unvollständige en_US-Datei:
|
||
```json
|
||
{
|
||
"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:
|
||
```json
|
||
// custom/Espo/Custom/Resources/metadata/entityDefs/Entity.json
|
||
{
|
||
"fields": {...},
|
||
"formula": {
|
||
"beforeSaveApiScript": "..." // FUNKTIONIERT NICHT!
|
||
}
|
||
}
|
||
```
|
||
|
||
✅ **RICHTIG** - Separate Formula-Datei:
|
||
```json
|
||
// 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
|
||
<?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`
|
||
|
||
```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`
|
||
|
||
```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:**
|
||
```bash
|
||
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
|
||
- `validationFailure` → `throwBadRequest()` 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
|
||
|
||
3. 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**
|
||
```bash
|
||
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**
|
||
```bash
|
||
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**
|
||
```bash
|
||
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**
|
||
```bash
|
||
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**
|
||
```bash
|
||
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
|
||
```json
|
||
{
|
||
"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
|
||
```json
|
||
{
|
||
"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:**
|
||
```bash
|
||
# 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
|
||
```
|
||
|
||
|
||
6. 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:**
|
||
```json
|
||
// 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
|
||
|
||
### 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.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:
|
||
```json
|
||
// 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:
|
||
```bash
|
||
./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
|
||
```json
|
||
{
|
||
"collection": {
|
||
"filters": {
|
||
"reportFilterXXXXXXXXXX": {
|
||
"isReportFilter": true,
|
||
"id": "reportFilterIdHere"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
2. **selectDefs/{EntityType}.json** - Filter-Klasse
|
||
```json
|
||
{
|
||
"primaryFilterClassNameMap": {
|
||
"reportFilterXXXXXXXXXX": "Espo\\Modules\\Advanced\\Classes\\Select\\Common\\PrimaryFilters\\ReportFilter"
|
||
}
|
||
}
|
||
```
|
||
|
||
3. **clientDefs/{EntityType}.json** - Frontend-Integration
|
||
```json
|
||
{
|
||
"filterList": [
|
||
"__APPEND__",
|
||
{
|
||
"isReportFilter": true,
|
||
"name": "reportFilterXXXXXXXXXX",
|
||
"accessDataList": [
|
||
{
|
||
"teamIdList": ["team-id-here"]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
4. **i18n/{Language}/{EntityType}.json** - Übersetzungen
|
||
```json
|
||
{
|
||
"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
|
||
```json
|
||
{
|
||
"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"
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
1. **entityDefs der Hauptentität** (z.B. `CBeteiligte.json`):
|
||
```json
|
||
"contactsBeteiligte": {
|
||
"type": "hasMany",
|
||
"relationName": "cBeteiligteContact",
|
||
"foreign": "cBeteiligteContact",
|
||
"entity": "Contact",
|
||
"audited": false,
|
||
"isCustom": true
|
||
}
|
||
```
|
||
|
||
2. **entityDefs von Contact** (`Contact.json`):
|
||
```json
|
||
"cBeteiligteContact": {
|
||
"type": "hasMany",
|
||
"relationName": "cBeteiligteContact",
|
||
"foreign": "contactsBeteiligte",
|
||
"entity": "CBeteiligte",
|
||
"audited": false,
|
||
"isCustom": true
|
||
}
|
||
```
|
||
|
||
3. **clientDefs der Hauptentität** (`CBeteiligte.json`):
|
||
```json
|
||
"relationshipPanels": {
|
||
"contactsBeteiligte": {
|
||
"layout": null,
|
||
"selectPrimaryFilterName": "portalUsers"
|
||
}
|
||
}
|
||
```
|
||
|
||
4. **bottomPanelsDetail Layout** (Tab-Ansicht):
|
||
```json
|
||
{
|
||
"_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:**
|
||
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:**
|
||
```json
|
||
// 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:**
|
||
```json
|
||
// 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!
|
||
|
||
```json
|
||
// 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:
|
||
```json
|
||
{
|
||
"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:
|
||
|
||
```bash
|
||
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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
./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`):
|
||
```javascript
|
||
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`):
|
||
```javascript
|
||
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**:
|
||
```json
|
||
{
|
||
"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`):
|
||
```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`):
|
||
```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):**
|
||
```html
|
||
<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) |