Files
espocrm/custom/docs/ESPOCRM_BEST_PRACTICES.md

1088 lines
24 KiB
Markdown

# EspoCRM Best Practices & Entwicklungsrichtlinien
**Version:** 2.0
**Datum:** 9. März 2026
**Zielgruppe:** AI Code Agents & Entwickler
---
## 📋 Inhaltsverzeichnis
1. [Projekt-Übersicht](#projekt-übersicht)
2. [Architektur-Prinzipien](#architektur-prinzipien)
3. [Entity-Entwicklung](#entity-entwicklung)
4. [Relationship-Patterns](#relationship-patterns)
5. [API-Entwicklung](#api-entwicklung)
6. [Workflow-Management](#workflow-management)
7. [Testing & Validierung](#testing--validierung)
8. [Fehlerbehandlung](#fehlerbehandlung)
9. [Deployment-Prozess](#deployment-prozess)
10. [Troubleshooting](#troubleshooting)
---
## Projekt-Übersicht
### System-Architektur
```
EspoCRM 9.3.2
├── PHP 8.2.30
├── MariaDB 12.2.2
├── Docker Container: espocrm, espocrm-db
└── Workspace: /var/lib/docker/volumes/vmh-espocrm_espocrm/_data
```
### Verzeichnisstruktur
```
custom/
├── Espo/Custom/ # Backend-Code
│ ├── Controllers/ # REST API Endpoints
│ ├── Services/ # Business Logic
│ ├── Repositories/ # Data Access Layer
│ ├── Hooks/ # Entity Lifecycle Hooks
│ └── Resources/
│ ├── metadata/ # Entity & Field Definitionen
│ │ ├── entityDefs/ # Entity-Konfiguration
│ │ ├── clientDefs/ # Frontend-Konfiguration
│ │ ├── scopes/ # Entity-Scopes
│ │ └── formula/ # Formula Scripts
│ ├── layouts/ # UI-Layouts
│ └── i18n/ # Übersetzungen (de_DE, en_US)
├── scripts/ # Entwicklungs-Tools
│ ├── validate_and_rebuild.py # Haupt-Validierungs-Tool
│ ├── e2e_tests.py # End-to-End Tests
│ ├── ki_project_overview.py # Projekt-Analyse für AI
│ └── junctiontabletests/ # Junction Table Tests
├── docs/ # Dokumentation (NEU)
│ ├── ESPOCRM_BEST_PRACTICES.md # Dieses Dokument
│ ├── tools/ # Tool-Dokumentation
│ └── workflows/ # Workflow-Dokumentation
└── workflows/ # Workflow JSON-Definitions
client/custom/ # Frontend-Code
├── src/ # JavaScript Modules
├── css/ # Custom Styles
└── res/ # Resources
```
---
## Architektur-Prinzipien
### 1. Separation of Concerns
**EspoCRM = Data Layer**
- Speichert Entities
- Stellt UI bereit
- Validiert Daten
- Bietet REST API
**Middleware = Business Logic**
- KI-Analyse
- Team-Zuweisung
- Komplexe Workflows
- Externe Integrationen
### 2. Drei-Schichten-Architektur
```
┌─────────────────────────────────────────┐
│ FRONTEND (clientDefs, Layouts) │
│ • User Interface │
│ • JavaScript Actions │
└────────────────┬────────────────────────┘
│ AJAX/REST
┌────────────────▼────────────────────────┐
│ CONTROLLER (Controllers/) │
│ • Request Validation │
│ • ACL Checks │
└────────────────┬────────────────────────┘
│ Service Call
┌────────────────▼────────────────────────┐
│ SERVICE (Services/) │
│ • Business Logic │
│ • Entity Manager │
└────────────────┬────────────────────────┘
│ Repository
┌────────────────▼────────────────────────┐
│ REPOSITORY (Repositories/) │
│ • Data Access │
│ • Relationships │
└─────────────────────────────────────────┘
```
### 3. Clean Code Principles
**DO:**
- ✅ Nutze sprechende Variablennamen
- ✅ Schreibe kleine, fokussierte Funktionen
- ✅ Kommentiere komplexe Business-Logik
- ✅ Verwende Type Hints (PHP 8.2+)
- ✅ Folge PSR-12 Coding Standard
**DON'T:**
- ❌ Keine komplexe Logik in Hooks
- ❌ Keine direkten SQL-Queries (nutze EntityManager)
- ❌ Keine hard-coded Werte (nutze Config)
- ❌ Keine redundanten Includes
- ❌ Keine ungenutzten Imports
---
## Entity-Entwicklung
### Entity-Naming Convention
**Pattern:** `C{EntityName}` für Custom Entities
**Beispiele:**
- `CMietobjekt` - Mietobjekte
- `CVmhMietverhltnis` - Mietverhältnisse (VMH = Vermieter Helden)
- `CKuendigung` - Kündigungen
- `CAICollections` - AI Collections
### Entity Definition Template
**Datei:** `custom/Espo/Custom/Resources/metadata/entityDefs/{EntityName}.json`
```json
{
"fields": {
"name": {
"type": "varchar",
"required": true,
"maxLength": 255,
"trim": true,
"isCustom": true,
"tooltip": true
},
"status": {
"type": "enum",
"options": ["Neu", "In Bearbeitung", "Abgeschlossen"],
"default": "Neu",
"required": true,
"isCustom": true,
"style": {
"Neu": "primary",
"In Bearbeitung": "warning",
"Abgeschlossen": "success"
}
},
"description": {
"type": "text",
"rows": 10,
"isCustom": true,
"tooltip": true
},
"amount": {
"type": "currency",
"isCustom": true,
"audited": true
},
"dueDate": {
"type": "date",
"isCustom": true,
"audited": true
}
},
"links": {
"parent": {
"type": "belongsToParent",
"entityList": ["CVmhRumungsklage", "CMietinkasso"]
},
"createdBy": {
"type": "belongsTo",
"entity": "User"
},
"modifiedBy": {
"type": "belongsTo",
"entity": "User"
}
}
}
```
### Scope Definition
**Datei:** `custom/Espo/Custom/Resources/metadata/scopes/{EntityName}.json`
```json
{
"entity": true,
"type": "Base",
"module": "Custom",
"object": true,
"isCustom": true,
"tab": true,
"acl": true,
"stream": true,
"disabled": false,
"customizable": true,
"importable": true,
"notifications": true,
"calendar": false
}
```
**Wichtige Flags:**
- `tab: true` - Zeigt Entity in Navigation
- `acl: true` - ACL-System aktiv
- `stream: true` - Stream/Activity Feed
- `calendar: true` - Für Entities mit Datum-Feldern
### i18n (Internationalisierung)
**KRITISCH:** Immer BEIDE Sprachen pflegen!
**Datei:** `custom/Espo/Custom/Resources/i18n/de_DE/{EntityName}.json`
```json
{
"labels": {
"Create {EntityName}": "{EntityName} erstellen",
"{EntityName}": "{EntityName}",
"name": "Name",
"status": "Status",
"description": "Beschreibung"
},
"fields": {
"name": "Name",
"status": "Status",
"description": "Beschreibung",
"amount": "Betrag",
"dueDate": "Fälligkeitsdatum"
},
"links": {
"parent": "Übergeordnet",
"relatedEntity": "Verknüpfte Entity"
},
"options": {
"status": {
"Neu": "Neu",
"In Bearbeitung": "In Bearbeitung",
"Abgeschlossen": "Abgeschlossen"
}
},
"tooltips": {
"name": "Eindeutiger Name des Datensatzes",
"description": "Detaillierte Beschreibung"
}
}
```
**Datei:** `custom/Espo/Custom/Resources/i18n/en_US/{EntityName}.json`
```json
{
"labels": {
"Create {EntityName}": "Create {EntityName}",
"{EntityName}": "{EntityName}"
},
"fields": {
"name": "Name",
"status": "Status",
"description": "Description",
"amount": "Amount",
"dueDate": "Due Date"
},
"links": {
"parent": "Parent",
"relatedEntity": "Related Entity"
},
"options": {
"status": {
"Neu": "New",
"In Bearbeitung": "In Progress",
"Abgeschlossen": "Completed"
}
}
}
```
---
## Relationship-Patterns
### 1. One-to-Many (hasMany / belongsTo)
**Beispiel:** Ein Mietobjekt hat viele Mietverhältnisse
**Parent Entity (CMietobjekt):**
```json
{
"links": {
"mietverhltnisse": {
"type": "hasMany",
"entity": "CVmhMietverhltnis",
"foreign": "mietobjekt"
}
}
}
```
**Child Entity (CVmhMietverhltnis):**
```json
{
"fields": {
"mietobjektId": {
"type": "varchar",
"len": 17
},
"mietobjektName": {
"type": "varchar"
}
},
"links": {
"mietobjekt": {
"type": "belongsTo",
"entity": "CMietobjekt",
"foreign": "mietverhltnisse"
}
}
}
```
### 2. Many-to-Many (hasMany mit relationName)
**Beispiel:** Dokumente ↔ AI Collections
**Entity 1 (CDokumente):**
```json
{
"links": {
"cAICollections": {
"type": "hasMany",
"entity": "CAICollections",
"foreign": "cDokumente",
"relationName": "cAICollectionCDokumente"
}
}
}
```
**Entity 2 (CAICollections):**
```json
{
"links": {
"cDokumente": {
"type": "hasMany",
"entity": "CDokumente",
"foreign": "cAICollections",
"relationName": "cAICollectionCDokumente"
}
}
}
```
**Wichtig:** `relationName` muss identisch sein!
### 3. Many-to-Many mit additionalColumns (Junction Entity)
**Seit EspoCRM 6.0:** Junction-Tabellen werden automatisch als Entities verfügbar!
**Entity Definition:**
```json
{
"links": {
"cDokumente": {
"type": "hasMany",
"entity": "CDokumente",
"foreign": "cAICollections",
"relationName": "cAICollectionCDokumente",
"additionalColumns": {
"syncId": {
"type": "varchar",
"len": 255
}
}
}
}
}
```
**Junction Entity (CAICollectionCDokumente):**
**entityDefs/CAICollectionCDokumente.json:**
```json
{
"fields": {
"id": {
"type": "id",
"dbType": "bigint",
"autoincrement": true
},
"cAICollections": {
"type": "link"
},
"cAICollectionsId": {
"type": "varchar",
"len": 17,
"index": true
},
"cDokumente": {
"type": "link"
},
"cDokumenteId": {
"type": "varchar",
"len": 17,
"index": true
},
"syncId": {
"type": "varchar",
"len": 255,
"isCustom": true
},
"deleted": {
"type": "bool",
"default": false
}
},
"links": {
"cAICollections": {
"type": "belongsTo",
"entity": "CAICollections"
},
"cDokumente": {
"type": "belongsTo",
"entity": "CDokumente"
}
}
}
```
**scopes/CAICollectionCDokumente.json:**
```json
{
"entity": true,
"type": "Base",
"module": "Custom",
"object": true,
"isCustom": true,
"tab": false,
"acl": true,
"disabled": false
}
```
**Controller & Service:**
```php
<?php
// Controllers/CAICollectionCDokumente.php
namespace Espo\Custom\Controllers;
use Espo\Core\Controllers\Record;
class CAICollectionCDokumente extends Record
{
// Erbt alle CRUD-Operationen
}
// Services/CAICollectionCDokumente.php
namespace Espo\Custom\Services;
use Espo\Services\Record;
class CAICollectionCDokumente extends Record
{
// Standard-Logik
}
```
**API-Zugriff:**
```bash
# Alle Junction-Einträge
GET /api/v1/CAICollectionCDokumente
# Filtern nach Dokument
GET /api/v1/CAICollectionCDokumente?where[0][type]=equals&where[0][attribute]=cDokumenteId&where[0][value]=doc123
# Neuen Eintrag erstellen
POST /api/v1/CAICollectionCDokumente
{
"cDokumenteId": "doc123",
"cAICollectionsId": "col456",
"syncId": "SYNC-2026-001"
}
```
**WICHTIG:** additionalColumns funktionieren NICHT über Standard-Relationship-Endpoints! Nur über Junction-Entity-API!
### 4. Parent Relationship (belongsToParent)
**Beispiel:** Dokument kann zu Räumungsklage ODER Mietinkasso gehören
```json
{
"fields": {
"parentType": {
"type": "varchar"
},
"parentId": {
"type": "varchar"
},
"parentName": {
"type": "varchar"
}
},
"links": {
"parent": {
"type": "belongsToParent",
"entityList": ["CVmhRumungsklage", "CMietinkasso", "CKuendigung"]
}
}
}
```
---
## API-Entwicklung
### REST API Endpoints
**Standard CRUD (automatisch verfügbar):**
```bash
GET /api/v1/{EntityName} # List
GET /api/v1/{EntityName}/{id} # Read
POST /api/v1/{EntityName} # Create
PUT /api/v1/{EntityName}/{id} # Update
DELETE /api/v1/{EntityName}/{id} # Delete
```
### Custom API Endpoint erstellen
**1. Controller Action:**
**Datei:** `custom/Espo/Custom/Controllers/{EntityName}.php`
```php
<?php
namespace Espo\Custom\Controllers;
use Espo\Core\Controllers\Record;
use Espo\Core\Api\Request;
class CMyEntity extends Record
{
/**
* Custom Action: POST /api/v1/CMyEntity/action/doSomething
*/
public function postActionDoSomething(Request $request): array
{
$data = $request->getParsedBody();
$id = $data->id ?? null;
if (!$id) {
throw new BadRequest('ID is required');
}
$result = $this->getRecordService()->doSomething($id, $data);
return [
'success' => true,
'data' => $result
];
}
/**
* Custom GET Action: GET /api/v1/CMyEntity/{id}/customData
*/
public function getActionCustomData(Request $request): array
{
$id = $request->getRouteParam('id');
$data = $this->getRecordService()->getCustomData($id);
return [
'data' => $data
];
}
}
```
**2. Service Logic:**
**Datei:** `custom/Espo/Custom/Services/{EntityName}.php`
```php
<?php
namespace Espo\Custom\Services;
use Espo\Services\Record;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\NotFound;
class CMyEntity extends Record
{
public function doSomething(string $id, \stdClass $data): array
{
// ACL Check
if (!$this->getAcl()->checkEntityEdit($this->entityType)) {
throw new Forbidden();
}
// Load Entity
$entity = $this->getEntityManager()->getEntity($this->entityType, $id);
if (!$entity) {
throw new NotFound();
}
// Business Logic
$entity->set('status', 'In Bearbeitung');
$this->getEntityManager()->saveEntity($entity);
// Return Result
return [
'id' => $entity->getId(),
'status' => $entity->get('status')
];
}
public function getCustomData(string $id): array
{
$entity = $this->getEntityManager()->getEntity($this->entityType, $id);
if (!$entity) {
throw new NotFound();
}
// Complex data aggregation
$relatedData = $this->getRelatedData($entity);
return [
'entity' => $entity->getValueMap(),
'related' => $relatedData
];
}
}
```
### API Authentication
**API Key Header:**
```bash
curl -X GET "https://crm.example.com/api/v1/CMyEntity" \
-H "X-Api-Key: your-api-key-here"
```
**Test API Keys:**
- `marvin`: `e53def10eea27b92a6cd00f40a3e09a4`
- `dev-test`: `2b0747ca34d15032aa233ae043cc61bc`
---
## Workflow-Management
### Workflow-Dateien
**Verzeichnis:** `custom/workflows/`
**Format:** JSON (Simple Workflow oder BPM Flowchart)
### Simple Workflow Beispiel
```json
{
"type": "simple",
"name": "auto-assign-new-entity",
"entity_type": "CMyEntity",
"trigger_type": "afterRecordCreated",
"is_active": true,
"description": "Auto-assign new records to team",
"conditions_all": [
{
"type": "isEmpty",
"attribute": "assignedUserId"
}
],
"actions": [
{
"type": "applyAssignmentRule",
"targetTeamId": "team-id-here"
},
{
"type": "sendEmail",
"to": "assignedUser",
"emailTemplateId": "template-id"
}
]
}
```
### Workflow Import/Export
```bash
# Alle Workflows exportieren
php custom/scripts/workflow_manager.php export
# Workflow importieren
php custom/scripts/workflow_manager.php import custom/workflows/my-workflow.json
# Workflows auflisten
php custom/scripts/workflow_manager.php list
```
---
## Testing & Validierung
### Validierungs-Tool
**Haupt-Tool:** `custom/scripts/validate_and_rebuild.py`
```bash
# Vollständige Validierung + Rebuild
python3 custom/scripts/validate_and_rebuild.py
# Nur Validierung (kein Rebuild)
python3 custom/scripts/validate_and_rebuild.py --dry-run
# Mit E2E Tests überspringen
python3 custom/scripts/validate_and_rebuild.py --skip-e2e
```
**Das Tool prüft:**
1. ✅ JSON-Syntax aller Custom-Dateien
2. ✅ Relationship-Konsistenz (bidirektionale Links)
3. ✅ Formula-Script Platzierung
4. ✅ i18n-Vollständigkeit (de_DE + en_US)
5. ✅ Layout-Struktur (bottomPanelsDetail, detail.json)
6. ✅ Dateirechte (www-data:www-data)
7. ✅ CSS-Validierung (csslint)
8. ✅ JavaScript-Validierung (jshint)
9. ✅ PHP-Syntax (php -l)
10. ✅ EspoCRM Rebuild
11. ✅ E2E-Tests (CRUD-Operationen)
**Bei Fehlern:** Automatische Fehlerlog-Analyse der letzten 50 Log-Zeilen!
### End-to-End Tests
**Tool:** `custom/scripts/e2e_tests.py`
```bash
# E2E Tests ausführen
python3 custom/scripts/e2e_tests.py
```
**Tests:**
- CRUD für alle Custom Entities
- Relationship-Verknüpfungen
- ACL-Prüfungen
### Manuelle Tests
**Checkliste:**
- [ ] Entity in UI sichtbar?
- [ ] Felder editierbar?
- [ ] Relationships funktionieren?
- [ ] Formulas triggern korrekt?
- [ ] Workflows aktiv?
- [ ] API-Endpoints erreichbar?
- [ ] ACL-Regeln greifen?
---
## Fehlerbehandlung
### Log-Files
**Verzeichnis:** `data/logs/`
**Haupt-Logfile:** `espo-{YYYY-MM-DD}.log`
```bash
# Letzte Fehler anzeigen
tail -50 data/logs/espo-$(date +%Y-%m-%d).log | grep -i error
# Live-Monitoring
tail -f data/logs/espo-$(date +%Y-%m-%d).log
```
### Häufige Fehler
#### 1. Layout-Fehler: "false" statt "{}"
**Problem:** EspoCRM 7.x+ erfordert `{}` statt `false` als Platzhalter
**Falsch:**
```json
{
"rows": [
[
{"name": "field1"},
false
]
]
}
```
**Richtig:**
```json
{
"rows": [
[
{"name": "field1"},
{}
]
]
}
```
#### 2. Relationship nicht bidirektional
**Problem:** `foreign` zeigt nicht zurück
**Falsch:**
```json
// Entity A
"links": {
"entityB": {
"type": "hasMany",
"entity": "EntityB",
"foreign": "wrongName" // ❌
}
}
// Entity B
"links": {
"entityA": {
"type": "belongsTo",
"entity": "EntityA",
"foreign": "entityB"
}
}
```
**Richtig:**
```json
// Entity A
"links": {
"entityB": {
"type": "hasMany",
"entity": "EntityB",
"foreign": "entityA" // ✅ Zeigt auf Link-Namen in B
}
}
// Entity B
"links": {
"entityA": {
"type": "belongsTo",
"entity": "EntityA",
"foreign": "entityB" // ✅ Zeigt auf Link-Namen in A
}
}
```
#### 3. i18n fehlt für en_US
**Problem:** Nur de_DE vorhanden, en_US fehlt
**Lösung:** IMMER beide Sprachen pflegen! en_US ist Fallback.
#### 4. Dateirechte falsch
**Problem:** Files gehören root statt www-data
**Lösung:** Automatisch via validate_and_rebuild.py oder manuell:
```bash
sudo chown -R www-data:www-data custom/
sudo find custom/ -type f -exec chmod 664 {} \;
sudo find custom/ -type d -exec chmod 775 {} \;
```
#### 5. ACL: 403 Forbidden
**Problem:** Role hat keine Rechte auf Entity
**Lösung:** ACL in Admin UI oder via SQL:
```sql
UPDATE role
SET data = JSON_SET(data,
'$.table.CMyEntity',
JSON_OBJECT('create', 'yes', 'read', 'all', 'edit', 'all', 'delete', 'all')
)
WHERE name = 'RoleName';
```
---
## Deployment-Prozess
### Standard-Workflow
```bash
# 1. Code-Änderungen durchführen
vim custom/Espo/Custom/Resources/metadata/entityDefs/CMyEntity.json
# 2. Validierung + Rebuild
python3 custom/scripts/validate_and_rebuild.py
# 3. Bei Erfolg: Commit
git add custom/
git commit -m "feat: Add CMyEntity with custom fields"
git push
```
### Quick Rebuild (nach kleinen Änderungen)
```bash
docker exec espocrm php command.php clear-cache
docker exec espocrm php command.php rebuild
```
### Nach Änderungen an Relationships
**IMMER:**
1. Cache löschen
2. Rebuild ausführen
3. Browser-Cache löschen (Ctrl+F5)
---
## Troubleshooting
### Rebuild schlägt fehl
**1. Logs prüfen:**
```bash
python3 custom/scripts/validate_and_rebuild.py
# → Zeigt automatisch Fehlerlog-Analyse
```
**2. Manuell Logs checken:**
```bash
tail -100 data/logs/espo-$(date +%Y-%m-%d).log
```
**3. PHP-Fehler:**
```bash
docker exec espocrm php -l custom/Espo/Custom/Controllers/MyController.php
```
### Entity nicht sichtbar
**Checklist:**
- [ ] `tab: true` in scopes?
- [ ] `disabled: false` in scopes?
- [ ] ACL-Rechte für Role?
- [ ] Cache gelöscht?
- [ ] Rebuild durchgeführt?
### Relationship funktioniert nicht
**Checklist:**
- [ ] Bidirektional konfiguriert?
- [ ] `foreign` zeigt korrekt zurück?
- [ ] `relationName` identisch (bei M2M)?
- [ ] Rebuild durchgeführt?
### API gibt 404
**Checklist:**
- [ ] Controller existiert?
- [ ] Service existiert?
- [ ] Action-Methode korrekt benannt? (postAction..., getAction...)
- [ ] ACL-Rechte?
### Formula triggert nicht
**Checklist:**
- [ ] In `metadata/formula/` statt entityDefs?
- [ ] Syntax korrekt?
- [ ] Rebuild durchgeführt?
---
## Projekt-spezifische Entities
### Übersicht
1. **CMietobjekt** - Mietobjekte (Wohnungen/Häuser)
2. **CVmhMietverhltnis** - Mietverhältnisse
3. **CKuendigung** - Kündigungen
4. **CBeteiligte** - Beteiligte Personen
5. **CMietinkasso** - Mietinkasso-Verfahren
6. **CVmhRumungsklage** - Räumungsklagen
7. **CDokumente** - Dokumente
8. **CPuls** - Puls-System (Entwicklungen)
9. **CAICollections** - AI Collections
### Entity-Graph
```
CMietobjekt
├── CVmhMietverhltnis (hasMany)
│ ├── CKuendigung (hasMany)
│ │ └── CVmhRumungsklage (hasOne)
│ ├── CMietinkasso (hasMany)
│ └── CBeteiligte (hasMany)
└── Contact (hasMany)
CDokumente
├── parent → [CVmhRumungsklage, CMietinkasso, CKuendigung]
└── CAICollections (hasMany via Junction)
└── CPuls (hasMany)
```
---
## Tools & Scripts
### Übersicht
| Tool | Zweck | Ausführung |
|------|-------|-----------|
| validate_and_rebuild.py | Validierung + Rebuild | `python3 custom/scripts/validate_and_rebuild.py` |
| e2e_tests.py | End-to-End Tests | `python3 custom/scripts/e2e_tests.py` |
| ki_project_overview.py | Projekt-Analyse für AI | `python3 custom/scripts/ki_project_overview.py` |
| workflow_manager.php | Workflow-Verwaltung | `php custom/scripts/workflow_manager.php list` |
### KI-Projekt-Übersicht
**Für AI Code Agents:**
```bash
python3 custom/scripts/ki_project_overview.py > /tmp/project-overview.txt
# → Gibt vollständigen Projekt-Status für AI aus
```
---
## Ressourcen
### Dokumentation
- **EspoCRM Docs:** https://docs.espocrm.com/
- **API Reference:** https://docs.espocrm.com/development/api/
- **Formula Functions:** https://docs.espocrm.com/administration/formula/
### Projekt-Dokumentation
- `custom/docs/ESPOCRM_BEST_PRACTICES.md` - Dieses Dokument
- `custom/scripts/QUICKSTART.md` - Quick Start Guide
- `custom/scripts/VALIDATION_TOOLS.md` - Validierungs-Tools
- `custom/scripts/E2E_TESTS_README.md` - E2E Tests
- `custom/README.md` - Custom Actions Blueprint
- `custom/TESTERGEBNISSE_JUNCTION_TABLE.md` - Junction Table Implementation
---
## Glossar
**ACL** - Access Control List (Zugriffsrechte)
**Entity** - Datenmodell (z.B. CMietobjekt)
**Link** - Relationship zwischen Entities
**Junction Table** - Verbindungstabelle für Many-to-Many
**Formula** - Berechnete Felder oder Automation-Scripts
**Scope** - Entity-Konfiguration (Tab, ACL, etc.)
**Stream** - Activity Feed einer Entity
**Hook** - Lifecycle-Event-Handler
**Service** - Business Logic Layer
**Controller** - API Request Handler
**Repository** - Data Access Layer
---
**Ende der Best Practices Dokumentation**
Für spezifische Fragen oder Updates: Siehe `/custom/docs/` Verzeichnis.