KI-basierte Bearbeitung von EspoCRM: Struktur und Funktionsweise

## 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!

### Check & Rebuild Script (Empfohlen)

**Zentrales Tool:** `custom/scripts/check_and_rebuild.sh`

Dieses Script sollte **IMMER** verwendet werden (nicht manueller Rebuild). Es führt automatisch aus:

✅ **Validierungen:**
- JSON-Syntax-Prüfung aller `.json` Dateien im `custom/` Verzeichnis
- Dateirechte-Prüfung (`www-data:www-data` Owner)
- System-Checks (Cache-Verzeichnis, Logs-Verzeichnis)

✅ **Automatische Korrekturen:**
- Setzt fehlerhafte Dateirechte auf `www-data:www-data`
- Korrigiert Verzeichnis-Permissions (775) und Datei-Permissions (664)

✅ **Rebuild:**
- 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
./custom/scripts/check_and_rebuild.sh
```

**Ausgabe:**
- ✓ Grün: Alles in Ordnung, Rebuild erfolgreich
- ⚠ Gelb: Warnungen, Rebuild wird trotzdem ausgeführt
- ✗ Rot: Fehler (z.B. ungültiges JSON), Rebuild wird NICHT ausgeführt

**Bei Fehlern:**
- JSON-Syntax-Fehler werden mit Datei und Zeilennummer angezeigt
- Dateirechte-Probleme werden automatisch korrigiert
- System-Fehler (fehlende Verzeichnisse) müssen manuell behoben werden

### 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:** Unnötige oder falsche Array-Elemente in Layouts, die zu UI-Problemen führen.

**Symptome:**
- Felder werden nicht korrekt angeordnet
- Leere Bereiche in Detail-Views
- Inkonsistente Spaltenbreiten

**❌ FALSCH:**
```json
// layouts/Entity/detail.json
{
  "rows": [
    [
      {"name": "field1"}
    ],
    [
      {"name": "field2"},
      false  // ← Unnötiges false-Element!
    ]
  ]
}
```

**✅ RICHTIG:**
```json
{
  "rows": [
    [
      {"name": "field1"},
      {"name": "field2"}  // Beide in einer Row
    ]
  ]
}
```

**Häufige Layout-Fehler:**
- ❌ Width-Attribute in Detail-Layouts (nur für List-Layouts!)
- ❌ Leere Objekte `{}` oder `false` in Rows
- ❌ Index nicht angepasst nach Entfernung von Panels (bottomPanelsDetail)

**Best Practice:**
- Width nur in `list.json` und `listSmall.json` verwenden
- Rows immer vollständig füllen oder Felder kombinieren
- Nach Panel-Entfernung Indices neu nummerieren

---

#### 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 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**

### 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
```

### Workflow-Entwicklung mit KI

Für KI-gestützte Workflow-Erstellung:
1. Workflow-Definition im `custom/workflows/` Verzeichnis als JSON ablegen
2. Mit `import` Befehl in EspoCRM einspielen
3. Im Admin-Interface testen und bei Bedarf anpassen
4. Mit `export` Befehl aktualisierten Workflow sichern
5. JSON-Datei im Repository committen
## Projektziele und Zukunftsvision: "Vermieterhelden"

Das Projekt "Vermieterhelden" ist ein maßgeschneidertes Backend-System auf Basis von EspoCRM für eine Anwaltskanzlei, spezialisiert auf die Durchführung und Verwaltung von immobilienrechtlichen Klagen (z. B. Räumungsklagen, Mietinkasso). Der aktuelle Fokus liegt auf der strukturierten Verwaltung von Stammdaten (Entitäten wie Mietverhältnisse, Mietobjekte, Beteiligte, Dokumente und Klagen) und der Abbildung von rechtlichen Workflows (z. B. automatisierte Tasks bei Statusänderungen, Fristen-Überwachung).

Zukünftige Ziele:
- **Customer Portal**: Integration eines Mandanten-Portals, damit Klienten (Mieter/Vermieter) selbst auf relevante Daten zugreifen können (z. B. Status von Klagen, Dokumente hochladen). Dies nutzt EspoCRMs eingebaute Portal-Funktionalität für Self-Service.
- **KI-Integration über Middleware**: Automatisierung von Prozessen via Webhooks und externer Middleware. Beispiele:
  - Automatische Analyse von Dokumenten (z. B. Verträge scannen und Felder extrahieren).
  - Intelligente Fristen-Erinnerungen basierend auf rechtlichen Regeln (z. B. Kündigungsfristen berechnen).
  - Workflow-Optimierung (z. B. Vorschläge für nächste Schritte in Klage-Prozessen).
  - Die KI soll über APIs/Webhooks angebunden werden, ohne EspoCRMs Core zu modifizieren, um Stabilität zu wahren.
- **Erweiterte Features**: Mehrsprachigkeit, Mandanten-Isolation für mehrere Kanzlei-Teams, Integration mit externen Systemen (z. B. Gerichts-APIs, Buchhaltung).

Die KI kann diese Ziele unterstützen, indem sie JSON-Strukturen analysiert, Änderungen vorschlägt (z. B. neue Felder für Compliance) und Workflows modelliert. Das System soll skalierbar und benutzerfreundlich sein, um die Effizienz in der Rechtsbranche zu steigern.

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.

### Check & Rebuild Script

Das Script `custom/scripts/check_and_rebuild.sh` automatisiert die Qualitätssicherung und führt folgende Prüfungen durch:

1. **JSON-Syntax-Validierung**: Prüft alle `.json` Dateien im `custom/` Verzeichnis auf gültiges JSON
2. **Dateirechte-Prüfung**: Stellt sicher, dass alle Dateien `www-data:www-data` als Owner haben
3. **System-Checks**: Validiert Existenz von Cache- und Logs-Verzeichnissen
4. **Automatischer Rebuild**: Bei Fehlerfreiheit wird der Rebuild durchgeführt

**Verwendung:**
```bash
# Im EspoCRM-Root-Verzeichnis ausführen
./custom/scripts/check_and_rebuild.sh
```

**Ausgabe:**
- ✓ Grün: Alles in Ordnung
- ⚠ Gelb: Warnungen (Rebuild wird trotzdem ausgeführt)
- ✗ Rot: Fehler (Rebuild wird NICHT ausgeführt)

**Bei Berechtigungsfehlern:**
```bash
sudo chown -R www-data:www-data custom/
sudo find custom/ -type f -name "*.json" -exec chmod 664 {} \;
sudo find custom/ -type d -exec chmod 775 {} \;
```

## 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)