Enhance documentation and workflow management in EspoCRM

- Update README.md to include a table of contents and detailed sections on workflow management and custom scripts.
- Remove deprecated 'runWorkflow' field from CVmhErstgespraech localization files.
- Add 'runWorkflow' field to detail layout for CVmhErstgespraech.
- Update workflow_manager.php to improve workflow listing and management functionality.
- Adjust cache timestamps in config.php.
- Create CUSTOM_DIRECTORY.md to outline the custom directory structure and best practices.
- Add README.md for workflow definitions with usage examples and JSON format specifications.
- Introduce new workflow definitions for 'vmh-erstberatung-abschließen' and its backup.
This commit is contained in:
2026-01-20 23:58:29 +01:00
parent e878125489
commit 8c83e54650
12 changed files with 637 additions and 70 deletions

122
custom/CUSTOM_DIRECTORY.md Normal file
View File

@@ -0,0 +1,122 @@
# EspoCRM Custom Directory - Übersicht
## Verzeichnisstruktur
```
custom/
├── Espo/
│ ├── Custom/ # Custom Entitäten, Services, Controller etc.
│ └── Modules/ # Custom Module
├── scripts/ # Custom PHP Scripts für Wartung/Verwaltung
│ ├── check_and_rebuild.sh
│ └── workflow_manager.php
└── workflows/ # Workflow-Definitionen als JSON
├── README.md
├── vmh-erstberatung-abschliessen.json
└── vmh-erstberatung-abschliessen-backup.json
```
## Zweck der Verzeichnisse
### custom/Espo/Custom/
Hauptverzeichnis für EspoCRM-Customizations:
- `Resources/metadata/` - Entity-, Field-, Client-Definitionen
- `Resources/layouts/` - UI-Layout-Definitionen
- `Resources/i18n/` - Übersetzungen
- `Classes/` - Custom PHP-Klassen
- `Controllers/` - Custom Controller
- `Services/` - Custom Services
- `Repositories/` - Custom Repositories
### custom/Espo/Modules/
Zusätzliche Module (z.B. von Extensions):
- Jedes Modul hat eigene `Resources/metadata/` Struktur
- Module sind gekapselt und wiederverwendbar
### custom/scripts/
**PHP Scripts für Wartung und Verwaltung:**
#### workflow_manager.php
Zentrale Schnittstelle für Workflow-Verwaltung:
- **list** - Alle Workflows (BPM + Simple) auflisten
- **read** - Workflow-Details anzeigen
- **import** - Workflow aus JSON importieren
- **export** - Workflow nach JSON exportieren
- **delete** - Workflow löschen
**Verwendung:**
```bash
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php <action> [args]
```
#### check_and_rebuild.sh
Validierung und Rebuild:
- JSON-Syntax-Prüfung
- EspoCRM Rebuild
- Cache-Bereinigung
### custom/workflows/
**Workflow-Definitionen als JSON:**
- Versionskontrolle für Workflows
- Import/Export über workflow_manager.php
- Sowohl Simple Workflows als auch BPM Flowcharts
- Format-Dokumentation in `README.md`
**Workflow-Typen:**
1. **Simple Workflows** - Regel-basierte Automationen
- Trigger: afterRecordSaved, afterRecordCreated, scheduled, manual
- Bedingungen: Feld-Vergleiche (equals, changed, contains, etc.)
- Aktionen: sendEmail, createEntity, updateEntity, etc.
2. **BPM Flowcharts** - Komplexe BPMN 2.0-Workflows
- Visuelle Designer im Admin-Interface
- Start-Events, Gateways, Tasks, End-Events
- Für mehrstufige Geschäftsprozesse
## Best Practices
### Workflow-Entwicklung
1. Workflow als JSON in `custom/workflows/` erstellen
2. Mit `workflow_manager.php import` einspielen
3. Im Admin-Interface testen
4. Bei Änderungen: Export → Anpassung → Import
### Versionskontrolle
- Alle JSON-Dateien in Git committen
- Beschreibende Dateinamen (z.B. `vmh-<entity>-<funktion>.json`)
- Regelmäßig Backups per `export` erstellen
### Änderungen anwenden
Nach Metadata-Änderungen immer:
```bash
docker exec espocrm php /var/www/html/command.php rebuild
# oder
./custom/scripts/check_and_rebuild.sh
```
## Nützliche Befehle
```bash
# Alle Workflows auflisten
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php list
# Workflow importieren
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php import \
/var/www/html/custom/workflows/my-workflow.json
# Workflow exportieren (Backup)
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php export \
<workflow-id> /var/www/html/custom/workflows/backup-workflow.json
# System rebuilden
docker exec espocrm php /var/www/html/command.php rebuild
# Cache löschen
docker exec espocrm rm -rf /var/www/html/data/cache/*
```
## Dokumentation
- **Hauptdokumentation:** `/README.md` - Vollständige KI-Dokumentation für EspoCRM
- **Workflow-Formate:** `/custom/workflows/README.md` - JSON-Format-Spezifikationen
- **EspoCRM Docs:** https://docs.espocrm.com

View File

@@ -59,7 +59,6 @@
"beendigungstatbestand": "Beendigungstatbestand",
"contact": "Kontakt",
"nchsterAnruf": "Nächster Anruf",
"runWorkflow": "Workflow ausführen",
"dokumentesvmherstgespraech": "Dokumente"
},
"links": {

View File

@@ -60,7 +60,6 @@
"contact": "Contact",
"nchsterAnruf": "Next Call",
"dokumentesvmherstgespraech": "Documents",
"runWorkflow": "Run Workflow",
"testArray": "Test Array"
},
"links": {

View File

@@ -1,7 +1,4 @@
[
{
"name": "runWorkflow"
},
{
"name": ":assignedUser"
},

View File

@@ -7,6 +7,9 @@
},
{
"name": "nchsterAnruf"
},
{
"name": "runWorkflow"
}
]
],

View File

@@ -204,7 +204,7 @@
"required": true,
"optionsReference": "CVmhMietverhltnis.kndigungsgrundWohnraum",
"default": "Mietrückstand",
"style": {},
"style": [],
"maxLength": 100,
"isCustom": true
},
@@ -416,21 +416,6 @@
"minuteStep": 5,
"tooltip": true,
"isCustom": true
},
"runWorkflow": {
"type": "link-button",
"mode": "runEspoWorkflow",
"popupHeight": 800,
"popupWidth": 600,
"style": "primary",
"buttonSize": "btn-md",
"confirmationDialog": true,
"confirmationText": "Soll der Vorgang abgeschlossen und die Ersteinschätzung versandt werden?",
"title": "runWorkflow Title",
"isCustom": true,
"default": null,
"buttonLabel": "Erstberatung versenden",
"readOnly": true
}
},
"links": {

View File

@@ -1,8 +1,17 @@
<?php
/**
* Workflow Manager Script for EspoCRM
*
* Manages both BPM Flowcharts and Simple Workflows
*
* Usage: php workflow_manager.php <action> [options]
* Actions: list, read <id>, delete <id>, edit <id> <json_data>, execute <id> <record_id>, test <id>
*
* Actions:
* list - List all workflows (BPM and Simple)
* read <id> - Read workflow details
* delete <id> - Delete a workflow
* import <file> - Import workflow from JSON file
* export <id> <file> - Export workflow to JSON file
*/
$config = require '/var/www/html/data/config-internal.php';
@@ -22,69 +31,196 @@ function connectDB() {
}
function listWorkflows($pdo) {
$stmt = $pdo->query("SELECT id, name FROM bpmn_flowchart WHERE deleted = 0");
echo "=== BPM Flowcharts ===\n";
$stmt = $pdo->query("SELECT id, name, target_type, is_active FROM bpmn_flowchart WHERE deleted = 0");
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "Verfügbare Workflows:\n";
foreach ($results as $row) {
echo "- ID: {$row['id']}, Name: {$row['name']}\n";
$active = $row['is_active'] ? '[AKTIV]' : '[INAKTIV]';
echo " {$active} ID: {$row['id']}, Name: {$row['name']}, Entity: {$row['target_type']}\n";
}
echo "\n=== Simple Workflows ===\n";
$stmt = $pdo->query("SELECT id, name, entity_type, type, is_active FROM workflow WHERE deleted = 0");
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($results as $row) {
$active = $row['is_active'] ? '[AKTIV]' : '[INAKTIV]';
$name = $row['name'] ?: '(unnamed)';
echo " {$active} ID: {$row['id']}, Name: {$name}, Entity: {$row['entity_type']}, Type: {$row['type']}\n";
}
}
function readWorkflow($pdo, $id) {
// Try BPM first
$stmt = $pdo->prepare("SELECT * FROM bpmn_flowchart WHERE id = ? AND deleted = 0");
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row) {
echo "Workflow Details:\n";
echo "=== BPM Flowchart ===\n";
echo json_encode($row, JSON_PRETTY_PRINT) . "\n";
} else {
echo "Workflow nicht gefunden.\n";
return;
}
// Try Simple Workflow
$stmt = $pdo->prepare("SELECT * FROM workflow WHERE id = ? AND deleted = 0");
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row) {
echo "=== Simple Workflow ===\n";
echo json_encode($row, JSON_PRETTY_PRINT) . "\n";
return;
}
echo "Workflow nicht gefunden.\n";
}
function deleteWorkflow($pdo, $id) {
echo "Bist du sicher, dass du den Workflow $id löschen möchtest? (ja/nein): ";
$input = trim(fgets(STDIN));
if (strtolower($input) === 'ja') {
$stmt = $pdo->prepare("UPDATE bpmn_flowchart SET deleted = 1 WHERE id = ?");
$stmt->execute([$id]);
echo "Workflow gelöscht.\n";
} else {
if (strtolower($input) !== 'ja') {
echo "Abgebrochen.\n";
return;
}
// Try BPM first
$stmt = $pdo->prepare("UPDATE bpmn_flowchart SET deleted = 1 WHERE id = ?");
$stmt->execute([$id]);
if ($stmt->rowCount() > 0) {
echo "BPM Flowchart gelöscht.\n";
return;
}
// Try Simple Workflow
$stmt = $pdo->prepare("UPDATE workflow SET deleted = 1 WHERE id = ?");
$stmt->execute([$id]);
if ($stmt->rowCount() > 0) {
echo "Simple Workflow gelöscht.\n";
return;
}
echo "Workflow nicht gefunden.\n";
}
function editWorkflow($pdo, $id, $jsonData) {
$data = json_decode($jsonData, true);
function importWorkflow($pdo, $file) {
if (!file_exists($file)) {
die("Datei nicht gefunden: $file\n");
}
$json = file_get_contents($file);
$data = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
die("Ungültiges JSON.\n");
die("Ungültiges JSON in Datei.\n");
}
$type = $data['type'] ?? 'unknown';
$now = date('Y-m-d H:i:s');
if ($type === 'simple') {
// Import Simple Workflow
$id = substr(bin2hex(random_bytes(12)), 0, 17);
$stmt = $pdo->prepare("
INSERT INTO workflow (
id, name, entity_type, type, is_active, is_internal,
description, conditions_all, conditions_any, conditions_formula,
actions, portal_only, scheduling, scheduling_apply_timezone,
process_order, created_at, modified_at, deleted
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
");
$stmt->execute([
$id,
$data['name'],
$data['entity_type'],
$data['trigger_type'],
$data['is_active'] ?? 1,
0,
$data['description'] ?? null,
json_encode($data['conditions_all'] ?? []),
json_encode($data['conditions_any'] ?? []),
$data['conditions_formula'] ?? null,
json_encode($data['actions'] ?? []),
0,
'0 0 * * *',
1,
10,
$now,
$now
]);
echo "✓ Simple Workflow importiert: $id - {$data['name']}\n";
} elseif ($type === 'bpm') {
// Import BPM Flowchart
$id = substr(bin2hex(random_bytes(12)), 0, 17);
$stmt = $pdo->prepare("
INSERT INTO bpmn_flowchart (
id, name, target_type, is_active,
created_at, modified_at, data, elements_data_hash,
deleted, has_none_start_event, event_start_id_list, event_start_all_id_list
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, 0, '[]', ?)
");
$stmt->execute([
$id,
$data['name'],
$data['target_type'],
$data['is_active'] ?? 1,
$now,
$now,
json_encode($data['data']),
json_encode($data['elements_data_hash']),
json_encode($data['event_start_all_id_list'] ?? [])
]);
echo "✓ BPM Flowchart importiert: $id - {$data['name']}\n";
} else {
die("Unbekannter Workflow-Typ: $type\n");
}
$stmt = $pdo->prepare("UPDATE bpmn_flowchart SET data = ?, modified_at = NOW() WHERE id = ?");
$stmt->execute([json_encode($data), $id]);
echo "Workflow aktualisiert.\n";
}
function executeWorkflow($pdo, $id, $recordId) {
// Einfache Simulation: Setze Status oder triggere Event
// In Realität würde man EspoCRM-API verwenden
echo "Workflow $id für Record $recordId ausführen...\n";
// Beispiel: Status setzen
$stmt = $pdo->prepare("UPDATE c_vmh_erstgespraech SET status = 'Warte auf Mandatierung' WHERE id = ?");
$stmt->execute([$recordId]);
echo "Status gesetzt. Workflow sollte ausgelöst werden.\n";
}
function testWorkflow($pdo, $id) {
// Simuliere Bedingungen
$stmt = $pdo->prepare("SELECT data FROM bpmn_flowchart WHERE id = ?");
function exportWorkflow($pdo, $id, $file) {
// Try BPM first
$stmt = $pdo->prepare("SELECT * FROM bpmn_flowchart WHERE id = ? AND deleted = 0");
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row) {
$data = json_decode($row['data'], true);
echo "Teste Workflow: " . (isset($data['list'][0]['text']) ? $data['list'][0]['text'] : 'Unbekannt') . "\n";
// Prüfe Bedingungen (vereinfacht)
echo "Bedingungen: " . json_encode(isset($data['list'][0]['conditionsAll']) ? $data['list'][0]['conditionsAll'] : []) . "\n";
$export = [
'type' => 'bpm',
'name' => $row['name'],
'target_type' => $row['target_type'],
'is_active' => (bool)$row['is_active'],
'description' => $row['description'],
'data' => json_decode($row['data'], true),
'elements_data_hash' => json_decode($row['elements_data_hash'], true),
'event_start_all_id_list' => json_decode($row['event_start_all_id_list'], true)
];
file_put_contents($file, json_encode($export, JSON_PRETTY_PRINT));
echo "✓ BPM Flowchart exportiert nach: $file\n";
return;
}
// Try Simple Workflow
$stmt = $pdo->prepare("SELECT * FROM workflow WHERE id = ? AND deleted = 0");
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row) {
$export = [
'type' => 'simple',
'name' => $row['name'],
'entity_type' => $row['entity_type'],
'trigger_type' => $row['type'],
'is_active' => (bool)$row['is_active'],
'description' => $row['description'],
'conditions_all' => json_decode($row['conditions_all'], true),
'conditions_any' => json_decode($row['conditions_any'], true),
'conditions_formula' => $row['conditions_formula'],
'actions' => json_decode($row['actions'], true)
];
file_put_contents($file, json_encode($export, JSON_PRETTY_PRINT));
echo "✓ Simple Workflow exportiert nach: $file\n";
return;
}
echo "Workflow nicht gefunden.\n";
}
$pdo = connectDB();
@@ -102,19 +238,16 @@ switch ($action) {
if (!isset($argv[2])) die("Usage: delete <id>\n");
deleteWorkflow($pdo, $argv[2]);
break;
case 'edit':
if (!isset($argv[2]) || !isset($argv[3])) die("Usage: edit <id> <json_data>\n");
editWorkflow($pdo, $argv[2], $argv[3]);
case 'import':
if (!isset($argv[2])) die("Usage: import <file>\n");
importWorkflow($pdo, $argv[2]);
break;
case 'execute':
if (!isset($argv[2]) || !isset($argv[3])) die("Usage: execute <workflow_id> <record_id>\n");
executeWorkflow($pdo, $argv[2], $argv[3]);
break;
case 'test':
if (!isset($argv[2])) die("Usage: test <id>\n");
testWorkflow($pdo, $argv[2]);
case 'export':
if (!isset($argv[2]) || !isset($argv[3])) die("Usage: export <id> <file>\n");
exportWorkflow($pdo, $argv[2], $argv[3]);
break;
default:
echo "Unbekannte Aktion. Verfügbare: list, read, delete, edit, execute, test\n";
echo "Unbekannte Aktion.\n";
echo "Verfügbare Aktionen: list, read, delete, import, export\n";
}
?>
?>

View File

@@ -0,0 +1,70 @@
# Workflow Definitions
This directory contains workflow definitions in JSON format that can be imported into EspoCRM using the workflow manager script.
## File Format
### Simple Workflow
```json
{
"type": "simple",
"name": "workflow-name",
"entity_type": "EntityName",
"trigger_type": "afterRecordSaved",
"is_active": true,
"description": "Description of what this workflow does",
"conditions_all": [],
"conditions_any": [],
"conditions_formula": null,
"actions": []
}
```
**Trigger Types:**
- `afterRecordSaved` - After record is created or updated
- `afterRecordCreated` - Only after record is created
- `scheduled` - Runs on a schedule
**Condition Types:**
- `equals`, `notEquals`, `greaterThan`, `lessThan`, `contains`, `notContains`, `isEmpty`, `isNotEmpty`, `isTrue`, `isFalse`, `wasEqual`, `wasNotEqual`, `changed`, `notChanged`
**Action Types:**
- `sendEmail` - Send email to recipient
- `createEntity` - Create a new record
- `updateEntity` - Update current record
- `relateTo` - Link to another record
- `unrelateFrom` - Unlink from record
- `applyAssignmentRule` - Apply assignment rules
- `createNotification` - Create notification
### BPM Flowchart
```json
{
"type": "bpm",
"name": "flowchart-name",
"target_type": "EntityName",
"is_active": true,
"description": "Description",
"data": {
"list": []
},
"elements_data_hash": {},
"event_start_all_id_list": []
}
```
## Usage
Import a workflow:
```bash
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php import /var/www/html/custom/workflows/your-workflow.json
```
Export a workflow:
```bash
docker exec espocrm php /var/www/html/custom/scripts/workflow_manager.php export <workflow-id> /var/www/html/custom/workflows/exported.json
```
## Examples
- `vmh-erstberatung-abschliessen.json` - Sends email and sets status when consultation is completed

View File

@@ -0,0 +1,43 @@
{
"type": "simple",
"name": "vmh-erstberatung-abschlie\u00dfen",
"entity_type": "CVmhErstgespraech",
"trigger_type": "afterRecordSaved",
"is_active": true,
"description": "Versendet Erstberatungs-E-Mail und setzt Status auf \"Warte auf Mandatierung\"",
"conditions_all": [
{
"comparison": "equals",
"subjectType": "value",
"cid": 0,
"fieldToCompare": "status",
"type": "all",
"value": "Warte auf Mandatierung"
},
{
"comparison": "changed",
"subjectType": null,
"cid": 1,
"fieldToCompare": "status",
"type": "all"
}
],
"conditions_any": [],
"conditions_formula": null,
"actions": [
{
"type": "sendEmail",
"cid": 0,
"id": "423a120400",
"from": "specifiedEmailAddress",
"fromEmailAddress": "anwalt@vermieterhelden.de",
"to": "targetEntity",
"toEmailAddress": null,
"replyTo": null,
"emailTemplateId": null,
"emailTemplateName": null,
"doNotStore": false,
"optOutLink": false
}
]
}

View File

@@ -0,0 +1,27 @@
{
"type": "simple",
"name": "vmh-erstberatung-abschließen",
"entity_type": "CVmhErstgespraech",
"trigger_type": "afterRecordSaved",
"is_active": true,
"description": "Sendet E-Mail und setzt Status wenn Erstberatung abgeschlossen wird",
"conditions_all": [
{
"type": "equals",
"attribute": "status",
"value": "Warte auf Mandatierung"
}
],
"conditions_any": [],
"conditions_formula": null,
"actions": [
{
"type": "sendEmail",
"from": "system",
"to": "contactId",
"emailTemplateId": null,
"subject": "Erstberatung abgeschlossen",
"body": "Ihre Erstberatung wurde erfolgreich abgeschlossen. Wir warten nun auf Ihre Mandatierung."
}
]
}