|
|
|
|
@@ -1,11 +1,35 @@
|
|
|
|
|
# EspoCRM Best Practices & Entwicklungsrichtlinien
|
|
|
|
|
|
|
|
|
|
**Version:** 2.4
|
|
|
|
|
**Datum:** 12. März 2026
|
|
|
|
|
**Version:** 2.5
|
|
|
|
|
**Datum:** 26. März 2026
|
|
|
|
|
**Zielgruppe:** AI Code Agents & Entwickler
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🔄 Letzte Änderungen (v2.5 - 26. März 2026)
|
|
|
|
|
|
|
|
|
|
**Architektur-Refactoring:**
|
|
|
|
|
- ✅ **CAIKnowledge entfernt**: Entity vollständig entfernt (inkl. Hooks, Controller, i18n, Metadata)
|
|
|
|
|
- ✅ **CAICollections/CAICollectionCDokumente entfernt**: Junction-Entity-Architektur aufgegeben
|
|
|
|
|
- ✅ **CAdvowareAkten entfernt**: Durch `CAkten` ersetzt (direkter Link zu CDokumente via `cAktenId`)
|
|
|
|
|
- ✅ **CAkten** ist nun zentrales AI+Advoware-Sync-Entity mit eigenen Status-Feldern
|
|
|
|
|
- ✅ **`aiProvider` Feld**: Neues Enum-Feld in CAkten (`ragflow` / `xai`, Standard: `ragflow`)
|
|
|
|
|
- ✅ **AI-Felder direkt auf CDokumente**: `aiSyncStatus`, `aiLastSync`, `aiFileId`, `aiCollectionId`, `aiSyncHash`
|
|
|
|
|
|
|
|
|
|
**Hook-Architektur neu:**
|
|
|
|
|
- ✅ **CDokumente/UpdateJunctionSyncStatus** (AfterSave): Markiert CDokumente + CAkten als `unclean` bei Änderungen
|
|
|
|
|
- ✅ **CAkten/UpdateLastSyncFromDocuments** (BeforeSave): Aggregiert Status aller CDokumente per PDO-Query
|
|
|
|
|
- ✅ **CAkten/PropagateDocumentsUp** (AfterSave): Propagiert Dokument-Verlinkungen zu Räumungsklage/Mietinkasso
|
|
|
|
|
- ⚠️ **CAIKnowledge-Hooks entfernt**: CheckGlobalSyncStatus, DokumenteSyncStatus, PropagateDocumentsUp
|
|
|
|
|
|
|
|
|
|
**Dokumentierte Patterns:**
|
|
|
|
|
- Direkte n:1 CDokumente → CAkten Beziehung (kein Junction Table)
|
|
|
|
|
- Worst-Case-Status-Aggregation via Raw PDO (BeforeSave-Hook)
|
|
|
|
|
- Globaler Sync-Status (`globalSyncStatus`, `globalLastSync`) auf CAkten
|
|
|
|
|
- AI-Provider-Auswahl als Enum-Feld (ragflow/xai)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🔄 Letzte Änderungen (v2.4 - 12. März 2026)
|
|
|
|
|
|
|
|
|
|
**Neue Features:**
|
|
|
|
|
@@ -124,28 +148,30 @@ client/custom/ # Frontend-Code
|
|
|
|
|
|
|
|
|
|
### Custom Entities Übersicht
|
|
|
|
|
|
|
|
|
|
**19 Custom Entities implementiert (Stand: März 2026):**
|
|
|
|
|
**17 Custom Entities implementiert (Stand: 26. März 2026):**
|
|
|
|
|
|
|
|
|
|
| Entity | Beschreibung | Hooks | Typ |
|
|
|
|
|
|--------|--------------|-------|-----|
|
|
|
|
|
| `CAdressen` | Adressen-Verwaltung | - | Base |
|
|
|
|
|
| `CAICollections` | AI-Dokumenten-Sammlungen | - | Base |
|
|
|
|
|
| `CAICollectionCDokumente` | Junction: Collections ↔ Dokumente | - | Junction |
|
|
|
|
|
| `CAkten` | Akten (Advoware + AI Sync) | ✅ UpdateLastSync, PropagateDocumentsUp | Base |
|
|
|
|
|
| `CBankverbindungen` | Bankdaten (IBAN/BIC) | ✅ Validierung | Base |
|
|
|
|
|
| `CBeteiligte` | Beteiligte Personen | - | Base |
|
|
|
|
|
| `CCallQueues` | Call-Warteschlangen | - | Base |
|
|
|
|
|
| `CDokumente` | Dokumenten-Management | ✅ Hash-Berechnung | Base |
|
|
|
|
|
| `CKuendigung` | Kündigungen | - | Base |
|
|
|
|
|
| `CMietinkasso` | Mietinkasso-Fälle | - | Base |
|
|
|
|
|
| `CDokumente` | Dokumenten-Management | ✅ Hash-Berechnung, UpdateJunctionSyncStatus | Base |
|
|
|
|
|
| `CKuendigung` | Kündigungen | ✅ CreateAdvowareAkte, SyncAdvowareAkte | Base |
|
|
|
|
|
| `CMietinkasso` | Mietinkasso-Fälle | ✅ PropagateDocuments | Base |
|
|
|
|
|
| `CMietobjekt` | Mietobjekte | - | Base |
|
|
|
|
|
| `CPuls` | Posteingangs-System | ✅ Statistik | Base |
|
|
|
|
|
| `CPulsTeamZuordnung` | Puls-Team-Zuordnungen | - | Base |
|
|
|
|
|
| `CVMHBeteiligte` | VMH-spezifische Beteiligte | - | Base |
|
|
|
|
|
| `CVmhErstgespraech` | Erstgespräche | - | Base |
|
|
|
|
|
| `CVmhMietverhltnis` | Mietverhältnisse | - | Base |
|
|
|
|
|
| `CVmhRumungsklage` | Räumungsklagen | - | Base |
|
|
|
|
|
| `CVmhRumungsklage` | Räumungsklagen | ✅ PropagateDocuments | Base |
|
|
|
|
|
| `CVmhVermieter` | Vermieter | - | Base |
|
|
|
|
|
|
|
|
|
|
> ⚠️ **Entfernt (März 2026):** `CAIKnowledge`, `CAICollections`, `CAICollectionCDokumente`, `CAdvowareAkten`
|
|
|
|
|
> — Die AI-Sync-Logik läuft jetzt direkt über `CAkten` ↔ `CDokumente` (n:1).
|
|
|
|
|
|
|
|
|
|
**Standard-Entities erweitert:**
|
|
|
|
|
- `Contact` - Erweiterterte Kontakt-Felder
|
|
|
|
|
- `Call` - Custom Call-Felder
|
|
|
|
|
@@ -157,10 +183,17 @@ client/custom/ # Frontend-Code
|
|
|
|
|
- `Team` - Team-Anpassungen
|
|
|
|
|
- `BpmnUserTask` - Workflow-Task-Erweiterungen
|
|
|
|
|
|
|
|
|
|
**Implementierte Hooks:**
|
|
|
|
|
1. **CBankverbindungen/BankdatenValidation** - IBAN/BIC-Validierung mit Modulo-97
|
|
|
|
|
2. **CDokumente/CDokumente** - MD5/SHA256-Hash-Berechnung für Uploads
|
|
|
|
|
3. **CPuls/UpdateTeamStats** - Automatische Statistik-Berechnung
|
|
|
|
|
**Implementierte Hooks (Stand: 26. März 2026):**
|
|
|
|
|
1. **CBankverbindungen/BankdatenValidation** (BeforeSave) - IBAN/BIC-Validierung mit Modulo-97
|
|
|
|
|
2. **CDokumente/CDokumente** (BeforeSave) - Blake3/MD5-Hash-Berechnung für Uploads
|
|
|
|
|
3. **CDokumente/UpdateJunctionSyncStatus** (AfterSave) - Setzt `syncStatus` + `aiSyncStatus` = `unclean` bei Dokumentänderungen; triggert CAkten-Aggregation
|
|
|
|
|
4. **CAkten/UpdateLastSyncFromDocuments** (BeforeSave) - Aggregiert Advoware- und AI-Sync-Status aller CDokumente per PDO (worst-case)
|
|
|
|
|
5. **CAkten/PropagateDocumentsUp** (AfterSave) - Propagiert `cAktenId`-Änderungen zu Räumungsklage/Mietinkasso
|
|
|
|
|
6. **CKuendigung/CreateAdvowareAkte** (AfterSave) - Erstellt CAkten-Eintrag bei neuer Kündigung
|
|
|
|
|
7. **CKuendigung/SyncAdvowareAkte** (AfterSave) - Synchronisiert Aktennummer mit Kündigung
|
|
|
|
|
8. **CMietinkasso/PropagateDocuments** (AfterRelate/AfterUnrelate) - Propagiert Dokumente zu Räumungsklage
|
|
|
|
|
9. **CVmhRumungsklage/PropagateDocuments** (AfterRelate/AfterUnrelate) - Propagiert Dokumente zu Mietinkasso
|
|
|
|
|
10. **CPuls/UpdateTeamStats** (AfterSave) - Automatische Statistik-Berechnung
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
@@ -236,7 +269,7 @@ client/custom/ # Frontend-Code
|
|
|
|
|
- `CMietobjekt` - Mietobjekte
|
|
|
|
|
- `CVmhMietverhltnis` - Mietverhältnisse (VMH = Vermieter Helden)
|
|
|
|
|
- `CKuendigung` - Kündigungen
|
|
|
|
|
- `CAICollections` - AI Collections
|
|
|
|
|
- `CAkten` - Akten (Advoware + AI Sync)
|
|
|
|
|
|
|
|
|
|
### Entity Definition Template
|
|
|
|
|
|
|
|
|
|
@@ -1538,13 +1571,12 @@ class PropagateDocuments implements AfterRelate, AfterUnrelate
|
|
|
|
|
|
|
|
|
|
**Down-Propagierung (Räumungsklage → unten):**
|
|
|
|
|
- Hook in Räumungsklage/Mietinkasso
|
|
|
|
|
- Bei Dokumenten-Link → propagiere zu AdvowareAkten + AIKnowledge
|
|
|
|
|
- Deren Hooks versuchen zurück zu propagieren → blockiert durch Loop-Schutz
|
|
|
|
|
- Bei Dokumenten-Link → propagiere zu CAkten
|
|
|
|
|
- CAkten-Hook (`UpdateLastSyncFromDocuments`) aggregiert Status
|
|
|
|
|
|
|
|
|
|
**Up-Propagierung (AdvowareAkten → oben):**
|
|
|
|
|
- Hook in AdvowareAkten/AIKnowledge
|
|
|
|
|
- Bei Dokumenten-Link → propagiere zu Räumungsklage/Mietinkasso
|
|
|
|
|
- Deren Hooks propagieren zu anderen Kind-Entities
|
|
|
|
|
**Up-Propagierung (CAkten → oben):**
|
|
|
|
|
- Hook in CAkten (`PropagateDocumentsUp`)
|
|
|
|
|
- Bei `cAktenId`-Änderung → propagiere zu Räumungsklage/Mietinkasso
|
|
|
|
|
- Loop-Schutz verhindert Rück-Propagierung
|
|
|
|
|
|
|
|
|
|
**Loop-Schutz Mechanismus:**
|
|
|
|
|
@@ -1832,6 +1864,56 @@ class UpdateTeamStats implements BeforeSave
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
#### Beispiel 4: Sync-Status-Aggregation (CAkten ↔ CDokumente)
|
|
|
|
|
|
|
|
|
|
**Dateien:**
|
|
|
|
|
- `custom/Espo/Custom/Hooks/CDokumente/UpdateJunctionSyncStatus.php`
|
|
|
|
|
- `custom/Espo/Custom/Hooks/CAkten/UpdateLastSyncFromDocuments.php`
|
|
|
|
|
|
|
|
|
|
**Use Case:** Wenn ein Dokument geändert wird → CDokumente markiert sich als `unclean` → CAkten aggregiert den schlechtesten Status aller Dokumente via Raw PDO.
|
|
|
|
|
|
|
|
|
|
**Hook-Chain:**
|
|
|
|
|
```
|
|
|
|
|
CDokumente (AfterSave)
|
|
|
|
|
UpdateJunctionSyncStatus
|
|
|
|
|
→ setzt dokument.syncStatus = 'unclean'
|
|
|
|
|
→ setzt dokument.aiSyncStatus = 'unclean'
|
|
|
|
|
→ speichert Dokument (skipHooks: true)
|
|
|
|
|
→ löst CAkten.save() aus (silent: true)
|
|
|
|
|
→ CAkten (BeforeSave)
|
|
|
|
|
UpdateLastSyncFromDocuments
|
|
|
|
|
→ PDO-Query: MAX(lastSyncTimestamp), MAX(aiLastSync), worst-case Status
|
|
|
|
|
→ setzt akte.syncStatus, akte.aiSyncStatus, akte.lastSync, akte.aiLastSync
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Trigger-Felder (UpdateJunctionSyncStatus):**
|
|
|
|
|
```php
|
|
|
|
|
private function hasRelevantChanges(Entity $entity): bool
|
|
|
|
|
{
|
|
|
|
|
$relevantFields = ['name', 'description', 'dokument', 'dokumentId',
|
|
|
|
|
'preview', 'previewId', 'fileStatus'];
|
|
|
|
|
foreach ($relevantFields as $field) {
|
|
|
|
|
if ($entity->isAttributeChanged($field)) return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Status-Aggregation (worst-case Logic in CAkten/UpdateLastSyncFromDocuments):**
|
|
|
|
|
```
|
|
|
|
|
failed > unclean/new/null > synced
|
|
|
|
|
```
|
|
|
|
|
Wenn ein einziges Dokument `failed` ist → Akte zeigt `failed`.
|
|
|
|
|
|
|
|
|
|
**Wichtige Punkte:**
|
|
|
|
|
- Skip bei `isNew()` (neue Entities haben noch keine Dokumente)
|
|
|
|
|
- Skip bei `skipHooks` - verhindert Endlos-Rekursion
|
|
|
|
|
- PDO-Query statt EntityManager für Aggregation (Performance)
|
|
|
|
|
- AI-Felder auf CDokumente: `aiSyncStatus`, `aiLastSync`, `aiFileId`, `aiCollectionId`, `aiSyncHash`
|
|
|
|
|
- AI-Provider-Auswahl auf CAkten: `aiProvider` (ragflow/xai, Standard: ragflow)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
### Best Practices für Hooks
|
|
|
|
|
|
|
|
|
|
#### ✅ DO
|
|
|
|
|
@@ -2535,34 +2617,24 @@ docker exec espocrm bash -c 'curl -s -X GET "http://localhost/api/v1/{EntityName
|
|
|
|
|
3. ✅ Prüfe Logs nach Rebuild auf InjectableFactory-Fehler
|
|
|
|
|
4. ✅ Teste Entity-Zugriff nach Erstellung
|
|
|
|
|
|
|
|
|
|
**Real-World-Beispiel (März 2026):**
|
|
|
|
|
**Real-World-Beispiel (März 2026 → historisch):**
|
|
|
|
|
|
|
|
|
|
**Problem:**
|
|
|
|
|
- Entities `CAICollection` und `CAdvowareAkten` nicht aufrufbar
|
|
|
|
|
- Logs zeigten: `Class 'Espo\Custom\Services\CAICollection' does not exist`
|
|
|
|
|
> ⚠️ **VERALTET** — `CAICollection` und `CAdvowareAkten` wurden entfernt.
|
|
|
|
|
> Die AI-Sync-Logik läuft seit 26. März 2026 über `CAkten` direkt.
|
|
|
|
|
>
|
|
|
|
|
> Das Pattern (Service-Klasse erstellen) bleibt gültig für alle neuen Entities:
|
|
|
|
|
|
|
|
|
|
**Lösung:**
|
|
|
|
|
```bash
|
|
|
|
|
# Service-Klassen erstellt
|
|
|
|
|
cat > custom/Espo/Custom/Services/CAICollection.php << 'EOF'
|
|
|
|
|
```php
|
|
|
|
|
// Für jede neue Custom Entity eine leere Service-Klasse anlegen:
|
|
|
|
|
cat > custom/Espo/Custom/Services/CMeineEntity.php << 'EOF'
|
|
|
|
|
<?php
|
|
|
|
|
namespace Espo\Custom\Services;
|
|
|
|
|
use Espo\Services\Record;
|
|
|
|
|
class CAICollection extends Record {}
|
|
|
|
|
class CMeineEntity extends Record {}
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
cat > custom/Espo/Custom/Services/CAdvowareAkten.php << 'EOF'
|
|
|
|
|
<?php
|
|
|
|
|
namespace Espo\Custom\Services;
|
|
|
|
|
use Espo\Services\Record;
|
|
|
|
|
class CAdvowareAkten extends Record {}
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
|
|
# Rebuild
|
|
|
|
|
python3 custom/scripts/validate_and_rebuild.py
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Ergebnis:** ✅ Beide Entities sofort funktionsfähig, keine Fehler mehr in Logs
|
|
|
|
|
**Regelfall:** Rebuild schlägt mit `Class 'Espo\Custom\Services\...' does not exist` fehl → Service-Datei fehlt.
|
|
|
|
|
|
|
|
|
|
### Formula triggert nicht
|
|
|
|
|
|
|
|
|
|
@@ -2573,56 +2645,12 @@ python3 custom/scripts/validate_and_rebuild.py
|
|
|
|
|
|
|
|
|
|
### Bekannte i18n-Warnungen (nicht kritisch)
|
|
|
|
|
|
|
|
|
|
**Stand: März 2026**
|
|
|
|
|
**Stand: 26. März 2026**
|
|
|
|
|
|
|
|
|
|
Die folgenden i18n-Link-Labels fehlen aktuell (funktional keine Auswirkung):
|
|
|
|
|
> ✅ Die früheren Warnungen zu `CAICollections` und `CAdvowareAkten` sind entfallen, da diese Entities entfernt wurden.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
⚠ CDokumente (en_US): Link 'cAICollections' fehlt in i18n
|
|
|
|
|
⚠ CAICollections (de_DE): Link 'meetings' fehlt in i18n
|
|
|
|
|
⚠ CAICollections (de_DE): Link 'cDokumente' fehlt in i18n
|
|
|
|
|
⚠ CAICollections (en_US): Link 'cDokumente' fehlt in i18n
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Behebung (optional):**
|
|
|
|
|
|
|
|
|
|
**Datei:** `custom/Espo/Custom/Resources/i18n/de_DE/CDokumente.json`
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"links": {
|
|
|
|
|
"cAICollections": "AI Collections"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Datei:** `custom/Espo/Custom/Resources/i18n/en_US/CDokumente.json`
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"links": {
|
|
|
|
|
"cAICollections": "AI Collections"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Datei:** `custom/Espo/Custom/Resources/i18n/de_DE/CAICollections.json`
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"links": {
|
|
|
|
|
"cDokumente": "Dokumente",
|
|
|
|
|
"meetings": "Meetings"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Datei:** `custom/Espo/Custom/Resources/i18n/en_US/CAICollections.json`
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"links": {
|
|
|
|
|
"cDokumente": "Documents",
|
|
|
|
|
"meetings": "Meetings"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
Aktuelle Validierung läuft sauber über `validate_and_rebuild.py`.
|
|
|
|
|
Bei neuen Entities auf vollständige i18n (de_DE + en_US) in `fields` und `links` achten.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
@@ -2637,8 +2665,8 @@ Die folgenden i18n-Link-Labels fehlen aktuell (funktional keine Auswirkung):
|
|
|
|
|
5. **CMietinkasso** - Mietinkasso-Verfahren
|
|
|
|
|
6. **CVmhRumungsklage** - Räumungsklagen
|
|
|
|
|
7. **CDokumente** - Dokumente
|
|
|
|
|
8. **CPuls** - Puls-System (Entwicklungen)
|
|
|
|
|
9. **CAICollections** - AI Collections
|
|
|
|
|
8. **CAkten** - Akten (Advoware + AI Sync)
|
|
|
|
|
9. **CPuls** - Puls-System (Entwicklungen)
|
|
|
|
|
|
|
|
|
|
### Entity-Graph
|
|
|
|
|
|
|
|
|
|
@@ -2647,14 +2675,20 @@ CMietobjekt
|
|
|
|
|
├── CVmhMietverhltnis (hasMany)
|
|
|
|
|
│ ├── CKuendigung (hasMany)
|
|
|
|
|
│ │ └── CVmhRumungsklage (hasOne)
|
|
|
|
|
│ │ └── CAkten (belongsTo)
|
|
|
|
|
│ ├── CMietinkasso (hasMany)
|
|
|
|
|
│ │ └── CAkten (belongsTo)
|
|
|
|
|
│ └── CBeteiligte (hasMany)
|
|
|
|
|
└── Contact (hasMany)
|
|
|
|
|
|
|
|
|
|
CDokumente
|
|
|
|
|
├── parent → [CVmhRumungsklage, CMietinkasso, CKuendigung]
|
|
|
|
|
└── CAICollections (hasMany via Junction)
|
|
|
|
|
└── CPuls (hasMany)
|
|
|
|
|
├── cAkten (belongsTo CAkten) ← direkte n:1 Beziehung
|
|
|
|
|
└── parent → [CVmhRumungsklage, CMietinkasso, CKuendigung, ...]
|
|
|
|
|
|
|
|
|
|
CAkten
|
|
|
|
|
├── dokumentes (hasMany CDokumente) ← Aggregations-Quelle
|
|
|
|
|
├── vmhRumungsklage (belongsTo)
|
|
|
|
|
└── mietinkasso (belongsTo)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|