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)