Files
espocrm/custom/docs/ESPOCRM_BEST_PRACTICES.md

24 KiB

EspoCRM Best Practices & Entwicklungsrichtlinien

Version: 2.0
Datum: 9. März 2026
Zielgruppe: AI Code Agents & Entwickler


📋 Inhaltsverzeichnis

  1. Projekt-Übersicht
  2. Architektur-Prinzipien
  3. Entity-Entwicklung
  4. Relationship-Patterns
  5. API-Entwicklung
  6. Workflow-Management
  7. Testing & Validierung
  8. Fehlerbehandlung
  9. Deployment-Prozess
  10. 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

{
  "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

{
  "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

{
  "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

{
  "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):

{
  "links": {
    "mietverhltnisse": {
      "type": "hasMany",
      "entity": "CVmhMietverhltnis",
      "foreign": "mietobjekt"
    }
  }
}

Child Entity (CVmhMietverhltnis):

{
  "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):

{
  "links": {
    "cAICollections": {
      "type": "hasMany",
      "entity": "CAICollections",
      "foreign": "cDokumente",
      "relationName": "cAICollectionCDokumente"
    }
  }
}

Entity 2 (CAICollections):

{
  "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:

{
  "links": {
    "cDokumente": {
      "type": "hasMany",
      "entity": "CDokumente",
      "foreign": "cAICollections",
      "relationName": "cAICollectionCDokumente",
      "additionalColumns": {
        "syncId": {
          "type": "varchar",
          "len": 255
        }
      }
    }
  }
}

Junction Entity (CAICollectionCDokumente):

entityDefs/CAICollectionCDokumente.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:

{
  "entity": true,
  "type": "Base",
  "module": "Custom",
  "object": true,
  "isCustom": true,
  "tab": false,
  "acl": true,
  "disabled": false
}

Controller & Service:

<?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:

# 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

{
  "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):

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
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
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:

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

{
  "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

# 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

# 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

# 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

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

{
  "rows": [
    [
      {"name": "field1"},
      false
    ]
  ]
}

Richtig:

{
  "rows": [
    [
      {"name": "field1"},
      {}
    ]
  ]
}

2. Relationship nicht bidirektional

Problem: foreign zeigt nicht zurück

Falsch:

// Entity A
"links": {
  "entityB": {
    "type": "hasMany",
    "entity": "EntityB",
    "foreign": "wrongName"  // ❌
  }
}

// Entity B
"links": {
  "entityA": {
    "type": "belongsTo",
    "entity": "EntityA",
    "foreign": "entityB"
  }
}

Richtig:

// 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:

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:

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

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

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:

python3 custom/scripts/validate_and_rebuild.py
# → Zeigt automatisch Fehlerlog-Analyse

2. Manuell Logs checken:

tail -100 data/logs/espo-$(date +%Y-%m-%d).log

3. PHP-Fehler:

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:

python3 custom/scripts/ki_project_overview.py > /tmp/project-overview.txt
# → Gibt vollständigen Projekt-Status für AI aus

Ressourcen

Dokumentation

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.