diff --git a/custom/ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md b/custom/ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md new file mode 100644 index 00000000..9f6e2d81 --- /dev/null +++ b/custom/ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md @@ -0,0 +1,1510 @@ +# Entwicklungsplan: Entwicklungen-System (Posteingang mit KI-Analyse) + +**Version:** 1.0 +**Datum:** 25. Januar 2026 +**Status:** Spezifikation finalisiert, bereit fรผr Implementierung + +--- + +## ๐Ÿ“‹ Executive Summary + +### Ziel +Implementierung eines intelligenten Posteingangs-Systems fรผr Dokumente zu Vorgรคngen (Rรคumungsklagen, Mietinkasso, Kรผndigungen). Dokumente werden automatisch zu "Entwicklungen" gruppiert, durch KI analysiert und relevanten Teams zur Review vorgelegt. + +### Kernprinzipien +1. **EspoCRM = Data Layer** - Speichert Entities, stellt UI bereit, validiert Daten +2. **Middleware = Business Logic** - KI-Analyse, Team-Zuweisung, Abwesenheitsvertretung +3. **Clean Separation** - Keine komplexen Hooks/Workflows in EspoCRM +4. **Team-basiert** - Dynamische Zuordnung zu Teams statt fixer Workflows + +### Architektur-รœbersicht + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ MIDDLEWARE โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Dokument- โ”‚ โ”‚ KI-Analyse โ”‚ โ”‚ Abwesenheits-โ”‚ โ”‚ +โ”‚ โ”‚ Polling โ”‚โ†’ โ”‚ & Team- โ”‚โ†’ โ”‚ Management โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ Entscheidung โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ†• API โ†• API โ†• API โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ†• +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ESPOCRM โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ CEntwicklung โ”‚โ†โ†’โ”‚ CEntwicklung โ”‚โ†โ†’โ”‚ CDokumente โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ TeamZuordnungโ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ†‘ โ†‘ โ†‘ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ Parent: Rรคumungsklage / Mietinkasso โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๐ŸŽฏ Phase 1: Entities & Datenmodell (MVP) + +### 1.1 Entity: CEntwicklung + +**Zweck:** Gruppierung von Dokumenten mit KI-Analyse und Status-Tracking + +**Datei:** `custom/Espo/Custom/Resources/metadata/entityDefs/CEntwicklung.json` + +```json +{ + "fields": { + "name": { + "type": "varchar", + "required": true, + "maxLength": 255, + "trim": true, + "isCustom": true + }, + "status": { + "type": "enum", + "options": [ + "Neu", + "In Verarbeitung", + "Bereit", + "In Review", + "Teilweise abgeschlossen", + "Abgeschlossen" + ], + "default": "Neu", + "required": true, + "isCustom": true, + "style": { + "Neu": "default", + "In Verarbeitung": "primary", + "Bereit": "success", + "In Review": "warning", + "Teilweise abgeschlossen": "info", + "Abgeschlossen": "success" + } + }, + "syncStatus": { + "type": "enum", + "options": ["clean", "unclean"], + "default": "unclean", + "required": true, + "isCustom": true, + "tooltip": true + }, + "kiAnalyse": { + "type": "text", + "isCustom": true, + "tooltip": true + }, + "zusammenfassung": { + "type": "varchar", + "maxLength": 500, + "isCustom": true, + "tooltip": true + }, + "anzahlDokumente": { + "type": "int", + "readOnly": true, + "notStorable": false, + "isCustom": true + }, + "anzahlTeamsAktiv": { + "type": "int", + "readOnly": true, + "notStorable": false, + "isCustom": true + }, + "anzahlTeamsAbgeschlossen": { + "type": "int", + "readOnly": true, + "notStorable": false, + "isCustom": true + }, + "createdAt": { + "type": "datetime", + "readOnly": true + }, + "modifiedAt": { + "type": "datetime", + "readOnly": true + }, + "createdBy": { + "type": "link", + "entity": "User", + "readOnly": true + }, + "modifiedBy": { + "type": "link", + "entity": "User", + "readOnly": true + }, + "assignedUser": { + "type": "link", + "entity": "User", + "isCustom": true + }, + "teams": { + "type": "linkMultiple", + "isCustom": true + } + }, + + "links": { + "parent": { + "type": "belongsToParent", + "entityList": [ + "CVmhRumungsklage", + "CMietinkasso", + "CKuendigung" + ] + }, + "dokumente": { + "type": "hasMany", + "entity": "CDokumente", + "foreign": "entwicklung" + }, + "teamZuordnungen": { + "type": "hasMany", + "entity": "CEntwicklungTeamZuordnung", + "foreign": "entwicklung" + }, + "createdBy": { + "type": "belongsTo", + "entity": "User" + }, + "modifiedBy": { + "type": "belongsTo", + "entity": "User" + }, + "assignedUser": { + "type": "belongsTo", + "entity": "User" + }, + "teams": { + "type": "hasMany", + "entity": "Team", + "relationName": "EntityTeam", + "layoutRelationshipsDisabled": true + } + }, + + "collection": { + "orderBy": "createdAt", + "order": "desc", + "textFilterFields": ["name", "zusammenfassung"] + }, + + "indexes": { + "parent": { + "columns": ["parentType", "parentId"] + }, + "status": { + "columns": ["status"] + }, + "syncStatus": { + "columns": ["syncStatus"] + }, + "createdAt": { + "columns": ["createdAt"] + } + } +} +``` + +--- + +### 1.2 Entity: CEntwicklungTeamZuordnung + +**Zweck:** Junction Table fรผr dynamische Team-Zuordnung mit Abschluss-Tracking + +**Datei:** `custom/Espo/Custom/Resources/metadata/entityDefs/CEntwicklungTeamZuordnung.json` + +```json +{ + "fields": { + "name": { + "type": "varchar", + "notStorable": true, + "select": { + "select": "CONCAT:(team.name, ' - ', entwicklung.name)" + }, + "orderBy": { + "order": [ + ["team.name", "{direction}"] + ] + } + }, + "entwicklung": { + "type": "link", + "entity": "CEntwicklung", + "required": true, + "isCustom": true + }, + "team": { + "type": "link", + "entity": "Team", + "required": true, + "isCustom": true + }, + "aktiv": { + "type": "bool", + "default": true, + "isCustom": true, + "tooltip": true + }, + "abgeschlossen": { + "type": "bool", + "default": false, + "isCustom": true + }, + "abgeschlossenAm": { + "type": "datetime", + "readOnly": true, + "isCustom": true + }, + "abgeschlossenVon": { + "type": "link", + "entity": "User", + "readOnly": true, + "isCustom": true + }, + "prioritaet": { + "type": "enum", + "options": ["Niedrig", "Normal", "Hoch"], + "default": "Normal", + "isCustom": true, + "style": { + "Niedrig": "default", + "Normal": "primary", + "Hoch": "danger" + } + }, + "createdAt": { + "type": "datetime", + "readOnly": true + }, + "modifiedAt": { + "type": "datetime", + "readOnly": true + } + }, + + "links": { + "entwicklung": { + "type": "belongsTo", + "entity": "CEntwicklung", + "foreign": "teamZuordnungen" + }, + "team": { + "type": "belongsTo", + "entity": "Team" + }, + "abgeschlossenVon": { + "type": "belongsTo", + "entity": "User" + } + }, + + "collection": { + "orderBy": "createdAt", + "order": "desc" + }, + + "indexes": { + "entwicklungTeam": { + "columns": ["entwicklungId", "teamId"], + "unique": true + }, + "aktiv": { + "columns": ["aktiv"] + }, + "abgeschlossen": { + "columns": ["abgeschlossen"] + } + } +} +``` + +--- + +### 1.3 Team-Entity erweitern + +**Zweck:** Kategorisierung fรผr Filter-Logik (Anwalt vs. Team-Teams) + +**Datei:** `custom/Espo/Custom/Resources/metadata/entityDefs/Team.json` + +```json +{ + "fields": { + "teamKategorie": { + "type": "enum", + "options": [ + "Anwalt", + "Mandatsbetreuung", + "Zwangsvollstreckung", + "Sonstiges" + ], + "default": "Sonstiges", + "isCustom": true, + "tooltip": true + } + } +} +``` + +**Post-Setup-Aufgabe:** Bestehende Teams kategorisieren +- Team "Anwalt" โ†’ teamKategorie = "Anwalt" +- Team "Mandatsbetreuung" โ†’ teamKategorie = "Mandatsbetreuung" +- Team "Zwangsvollstreckung" โ†’ teamKategorie = "Zwangsvollstreckung" + +--- + +### 1.4 User-Entity erweitern + +**Zweck:** Abwesenheits-Management fรผr automatische Umverteilung + +**Datei:** `custom/Espo/Custom/Resources/metadata/entityDefs/User.json` + +```json +{ + "fields": { + "abwesend": { + "type": "bool", + "default": false, + "isCustom": true, + "tooltip": true + }, + "abwesendBis": { + "type": "date", + "isCustom": true, + "tooltip": true + }, + "vertretung": { + "type": "link", + "entity": "User", + "isCustom": true, + "tooltip": true + } + }, + + "links": { + "vertretung": { + "type": "belongsTo", + "entity": "User", + "isCustom": true + } + } +} +``` + +--- + +### 1.5 CDokumente erweitern + +**Zweck:** Verknรผpfung zu Entwicklungen + +**Datei:** `custom/Espo/Custom/Resources/metadata/entityDefs/CDokumente.json` + +```json +{ + "fields": { + "entwicklung": { + "type": "link", + "entity": "CEntwicklung", + "isCustom": true + } + }, + + "links": { + "entwicklung": { + "type": "belongsTo", + "entity": "CEntwicklung", + "foreign": "dokumente", + "isCustom": true + } + } +} +``` + +--- + +### 1.6 Scopes definieren + +**Datei:** `custom/Espo/Custom/Resources/metadata/scopes/CEntwicklung.json` + +```json +{ + "entity": true, + "tab": true, + "acl": "recordAllTeamOwnNo", + "aclPortal": false, + "customizable": true, + "stream": true, + "disabled": false, + "type": "Base", + "module": "Custom", + "object": true, + "isCustom": true, + "importable": false, + "notifications": true, + "calendar": false +} +``` + +**Datei:** `custom/Espo/Custom/Resources/metadata/scopes/CEntwicklungTeamZuordnung.json` + +```json +{ + "entity": true, + "tab": false, + "acl": "recordAllTeamNo", + "aclPortal": false, + "customizable": true, + "stream": false, + "disabled": false, + "type": "Base", + "module": "Custom", + "object": true, + "isCustom": true +} +``` + +--- + +### 1.7 Internationalisierung (i18n) + +**Datei:** `custom/Espo/Custom/Resources/i18n/de_DE/CEntwicklung.json` + +```json +{ + "labels": { + "Create CEntwicklung": "Entwicklung erstellen", + "CEntwicklung": "Entwicklung", + "cEntwicklungs": "Entwicklungen" + }, + "fields": { + "name": "Bezeichnung", + "status": "Status", + "syncStatus": "Synchronisations-Status", + "kiAnalyse": "KI-Analyse", + "zusammenfassung": "Zusammenfassung", + "anzahlDokumente": "Anzahl Dokumente", + "anzahlTeamsAktiv": "Teams (aktiv)", + "anzahlTeamsAbgeschlossen": "Teams (abgeschlossen)", + "parent": "Vorgang", + "dokumente": "Dokumente", + "teamZuordnungen": "Team-Zuordnungen" + }, + "links": { + "parent": "Vorgang", + "dokumente": "Dokumente", + "teamZuordnungen": "Team-Zuordnungen" + }, + "tooltips": { + "syncStatus": "clean = KI-Analyse aktuell | unclean = Neue Dokumente, Analyse ausstehend", + "kiAnalyse": "Automatisch generierte Zusammenfassung durch KI-Middleware", + "zusammenfassung": "Kurze Zusammenfassung fรผr Listen-Ansicht" + }, + "options": { + "status": { + "Neu": "Neu", + "In Verarbeitung": "In Verarbeitung", + "Bereit": "Bereit", + "In Review": "In Review", + "Teilweise abgeschlossen": "Teilweise abgeschlossen", + "Abgeschlossen": "Abgeschlossen" + }, + "syncStatus": { + "clean": "Aktuell", + "unclean": "Ausstehend" + } + } +} +``` + +**Datei:** `custom/Espo/Custom/Resources/i18n/en_US/CEntwicklung.json` + +```json +{ + "labels": { + "Create CEntwicklung": "Create Development", + "CEntwicklung": "Development", + "cEntwicklungs": "Developments" + }, + "fields": { + "name": "Name", + "status": "Status", + "syncStatus": "Sync Status", + "kiAnalyse": "AI Analysis", + "zusammenfassung": "Summary", + "anzahlDokumente": "Number of Documents", + "anzahlTeamsAktiv": "Teams (active)", + "anzahlTeamsAbgeschlossen": "Teams (completed)", + "parent": "Parent Record", + "dokumente": "Documents", + "teamZuordnungen": "Team Assignments" + }, + "links": { + "parent": "Parent Record", + "dokumente": "Documents", + "teamZuordnungen": "Team Assignments" + }, + "tooltips": { + "syncStatus": "clean = AI analysis up-to-date | unclean = New documents, analysis pending", + "kiAnalyse": "Automatically generated summary by AI middleware", + "zusammenfassung": "Short summary for list views" + }, + "options": { + "status": { + "Neu": "New", + "In Verarbeitung": "Processing", + "Bereit": "Ready", + "In Review": "In Review", + "Teilweise abgeschlossen": "Partially Completed", + "Abgeschlossen": "Completed" + }, + "syncStatus": { + "clean": "Up-to-date", + "unclean": "Pending" + } + } +} +``` + +**Analog fรผr CEntwicklungTeamZuordnung, Team.teamKategorie, User.abwesend** + +--- + +## ๐Ÿ”ง Phase 2: Validierung & Business Rules + +### 2.1 Formula-Script: Abschluss nur bei clean + +**Datei:** `custom/Espo/Custom/Resources/metadata/formula/CEntwicklung.json` + +```json +{ + "beforeSaveApiScript": "// Verhindere Abschluss bei unclean Status\nif (\n (status == 'Abgeschlossen' || entity\\isAttributeChanged('status'))\n && syncStatus == 'unclean'\n) {\n recordService\\throwBadRequest('Entwicklung kann nicht abgeschlossen werden: Neue Dokumente vorhanden (Status: unclean). Bitte warten Sie auf die KI-Analyse.');\n}" +} +``` + +**Test-Szenario:** +1. Entwicklung mit syncStatus = "unclean" +2. User versucht manuell status = "Abgeschlossen" zu setzen +3. Erwartung: Error-Message, Speichern verhindert + +--- + +### 2.2 Hook: Berechnete Felder aktualisieren + +**Datei:** `custom/Espo/Custom/Hooks/CEntwicklung/UpdateTeamStats.php` + +```php +isNew() || $entity->isAttributeChanged('id')) { + $dokumenteCount = $this->entityManager + ->getRDBRepository('CDokumente') + ->where(['entwicklungId' => $entity->getId()]) + ->count(); + + $entity->set('anzahlDokumente', $dokumenteCount); + } + + // Zรคhle Team-Zuordnungen + $zuordnungen = $this->entityManager + ->getRDBRepository('CEntwicklungTeamZuordnung') + ->where(['entwicklungId' => $entity->getId()]) + ->find(); + + $aktiv = 0; + $abgeschlossen = 0; + + foreach ($zuordnungen as $z) { + if ($z->get('aktiv')) { + $aktiv++; + if ($z->get('abgeschlossen')) { + $abgeschlossen++; + } + } + } + + $entity->set('anzahlTeamsAktiv', $aktiv); + $entity->set('anzahlTeamsAbgeschlossen', $abgeschlossen); + } +} +``` + +--- + +## ๐ŸŽจ Phase 3: Layouts & UI + +### 3.1 Detail-Layout + +**Datei:** `custom/Espo/Custom/Resources/layouts/CEntwicklung/detail.json` + +```json +[ + { + "label": "รœbersicht", + "rows": [ + [ + {"name": "name"}, + {"name": "status"} + ], + [ + {"name": "syncStatus"}, + {"name": "parent"} + ], + [ + {"name": "anzahlDokumente"}, + {"name": "anzahlTeamsAktiv"} + ], + [ + {"name": "zusammenfassung", "span": 2} + ] + ] + }, + { + "label": "KI-Analyse", + "rows": [ + [ + {"name": "kiAnalyse", "span": 2} + ] + ] + }, + { + "label": "System", + "rows": [ + [ + {"name": "createdAt"}, + {"name": "modifiedAt"} + ], + [ + {"name": "createdBy"}, + {"name": "modifiedBy"} + ] + ] + } +] +``` + +--- + +### 3.2 List-Layout + +**Datei:** `custom/Espo/Custom/Resources/layouts/CEntwicklung/list.json` + +```json +[ + {"name": "name", "width": 30}, + {"name": "status", "width": 15}, + {"name": "syncStatus", "width": 10}, + {"name": "parent", "width": 20}, + {"name": "anzahlDokumente", "width": 10}, + {"name": "createdAt", "width": 15} +] +``` + +--- + +### 3.3 Bottom-Panels + +**Datei:** `custom/Espo/Custom/Resources/layouts/CEntwicklung/bottomPanelsDetail.json` + +```json +{ + "teamZuordnungen": { + "index": 0, + "sticked": true, + "style": "info", + "label": "Team-Zuordnungen" + }, + "dokumente": { + "index": 1, + "sticked": false, + "label": "Dokumente" + }, + "stream": { + "index": 2, + "sticked": false + } +} +``` + +--- + +### 3.4 ClientDefs + +**Datei:** `custom/Espo/Custom/Resources/metadata/clientDefs/CEntwicklung.json` + +```json +{ + "controller": "controllers/record", + "iconClass": "fas fa-inbox", + "color": "#3498db", + "filterList": [ + "meineOffenen", + { + "name": "bereit" + }, + { + "name": "inReview" + } + ], + "boolFilterList": [ + "onlyMy" + ], + "defaultFilterPreset": "meineOffenen" +} +``` + +--- + +## ๐Ÿ”Œ Phase 4: Custom API Endpoints + +### 4.1 API: Team-Aktivierung + +**Datei:** `custom/Espo/Custom/Api/CEntwicklung/AktiviereTeams.php` + +```php +getRouteParam('id'); + + if (!$id) { + throw new BadRequest('ID fehlt'); + } + + $entwicklung = $this->entityManager->getEntity('CEntwicklung', $id); + + if (!$entwicklung) { + throw new NotFound('Entwicklung nicht gefunden'); + } + + $data = $request->getParsedBody(); + + // 1. Update Entwicklung + $entwicklung->set([ + 'kiAnalyse' => $data->kiAnalyse ?? null, + 'zusammenfassung' => $data->zusammenfassung ?? null, + 'status' => $data->status ?? 'Bereit', + 'syncStatus' => $data->syncStatus ?? 'clean' + ]); + + $this->entityManager->saveEntity($entwicklung); + + // 2. Lรถsche alte Zuordnungen (soft delete - setze inaktiv) + $this->entityManager + ->getQueryBuilder() + ->update() + ->in('CEntwicklungTeamZuordnung') + ->set(['aktiv' => false]) + ->where(['entwicklungId' => $id]) + ->execute(); + + // 3. Erstelle neue Zuordnungen + if (isset($data->teams) && is_array($data->teams)) { + foreach ($data->teams as $teamData) { + $teamId = $teamData->teamId ?? null; + + if (!$teamId) { + $this->log->warning("Team-ID fehlt in teams-Array"); + continue; + } + + // Prรผfe ob bereits existiert + $existing = $this->entityManager + ->getRDBRepository('CEntwicklungTeamZuordnung') + ->where([ + 'entwicklungId' => $id, + 'teamId' => $teamId + ]) + ->findOne(); + + if ($existing) { + // Reaktiviere + $existing->set([ + 'aktiv' => true, + 'abgeschlossen' => false, + 'prioritaet' => $teamData->prioritaet ?? 'Normal' + ]); + $this->entityManager->saveEntity($existing); + } else { + // Erstelle neu + $zuordnung = $this->entityManager->createEntity('CEntwicklungTeamZuordnung', [ + 'entwicklungId' => $id, + 'teamId' => $teamId, + 'aktiv' => true, + 'abgeschlossen' => false, + 'prioritaet' => $teamData->prioritaet ?? 'Normal' + ]); + } + } + } + + $this->log->info("Teams aktiviert fรผr Entwicklung {$id}"); + + return Response::json([ + 'success' => true, + 'entwicklungId' => $id + ]); + } +} +``` + +**Route registrieren:** + +**Datei:** `custom/Espo/Custom/Resources/metadata/app/api.json` + +```json +{ + "routes": [ + { + "route": "/CEntwicklung/:id/aktiviere-teams", + "method": "put", + "actionClassName": "Espo\\Custom\\Api\\CEntwicklung\\AktiviereTeams" + } + ] +} +``` + +--- + +### 4.2 API: Abschluss fรผr Team + +**Datei:** `custom/Espo/Custom/Api/CEntwicklung/AbschliessenFuerTeam.php` + +```php +getRouteParam('id'); + $data = $request->getParsedBody(); + $teamId = $data->teamId ?? null; + + if (!$entwicklungId || !$teamId) { + throw new BadRequest('entwicklungId oder teamId fehlt'); + } + + // 1. Validierung: Ist User in diesem Team? + $userTeams = $this->user->getLinkMultipleIdList('teams'); + + if (!in_array($teamId, $userTeams)) { + throw new Forbidden('User nicht in angegebenem Team'); + } + + // 2. Lade Entwicklung + $entwicklung = $this->entityManager->getEntity('CEntwicklung', $entwicklungId); + + if (!$entwicklung) { + throw new NotFound('Entwicklung nicht gefunden'); + } + + // 3. Validierung: syncStatus = clean? + if ($entwicklung->get('syncStatus') !== 'clean') { + throw new BadRequest('Entwicklung hat neue Dokumente (unclean) - bitte warten Sie auf die KI-Analyse'); + } + + // 4. Finde Zuordnung + $zuordnung = $this->entityManager + ->getRDBRepository('CEntwicklungTeamZuordnung') + ->where([ + 'entwicklungId' => $entwicklungId, + 'teamId' => $teamId, + 'aktiv' => true + ]) + ->findOne(); + + if (!$zuordnung) { + throw new NotFound('Team-Zuordnung nicht gefunden oder nicht aktiv'); + } + + // 5. Bereits abgeschlossen? + if ($zuordnung->get('abgeschlossen')) { + return Response::json([ + 'success' => true, + 'message' => 'Bereits abgeschlossen', + 'alreadyCompleted' => true + ]); + } + + // 6. Abschluss setzen + $zuordnung->set([ + 'abgeschlossen' => true, + 'abgeschlossenAm' => date('Y-m-d H:i:s'), + 'abgeschlossenVonId' => $this->user->getId() + ]); + + $this->entityManager->saveEntity($zuordnung); + + // 7. Prรผfe: Alle Teams abgeschlossen? + $offeneTeams = $this->entityManager + ->getRDBRepository('CEntwicklungTeamZuordnung') + ->where([ + 'entwicklungId' => $entwicklungId, + 'aktiv' => true, + 'abgeschlossen' => false + ]) + ->count(); + + // 8. Update Entwicklung-Status + if ($offeneTeams === 0) { + $entwicklung->set('status', 'Abgeschlossen'); + } else { + $entwicklung->set('status', 'Teilweise abgeschlossen'); + } + + $this->entityManager->saveEntity($entwicklung); + + $this->log->info("Team {$teamId} hat Entwicklung {$entwicklungId} abgeschlossen"); + + return Response::json([ + 'success' => true, + 'status' => $entwicklung->get('status'), + 'offeneTeams' => $offeneTeams + ]); + } +} +``` + +**Route registrieren:** + +```json +{ + "routes": [ + { + "route": "/CEntwicklung/:id/abschliessen-fuer-team", + "method": "post", + "actionClassName": "Espo\\Custom\\Api\\CEntwicklung\\AbschliessenFuerTeam" + } + ] +} +``` + +--- + +## ๐Ÿ” Phase 5: Custom Primary Filter + +### 5.1 Filter: Meine offenen Entwicklungen + +**Datei:** `custom/Espo/Custom/Classes/Select/CEntwicklung/PrimaryFilters/MeineOffenen.php` + +```php +user->getId(); + $userTeams = $this->user->getLinkMultipleIdList('teams'); + + if (empty($userTeams)) { + // User hat keine Teams -> zeige nichts + $queryBuilder->where(['id' => null]); + return; + } + + // Prรผfe ob User in "Anwalt"-Team ist + $anwaltTeams = $this->entityManager + ->getRDBRepository('Team') + ->where(['teamKategorie' => 'Anwalt']) + ->select(['id']) + ->find(); + + $anwaltTeamIds = []; + foreach ($anwaltTeams as $team) { + $anwaltTeamIds[] = $team->getId(); + } + + $isAnwalt = !empty(array_intersect($userTeams, $anwaltTeamIds)); + + // Join zu TeamZuordnungen + $queryBuilder->distinct(); + $queryBuilder->leftJoin('teamZuordnungen', 'tz'); + + $conditions = []; + + // Bedingung 1: Standard-Teams (Mandatsbetreuung, ZV) + $standardTeamIds = array_diff($userTeams, $anwaltTeamIds); + + if (!empty($standardTeamIds)) { + $conditions[] = [ + 'tz.teamId' => $standardTeamIds, + 'tz.aktiv' => true, + 'tz.abgeschlossen' => false + ]; + } + + // Bedingung 2: Anwalt-Teams (nur eigene Vorgรคnge) + if ($isAnwalt && !empty($anwaltTeamIds)) { + // Subquery fรผr jede Parent-Entitรคt + $parentConditions = []; + + foreach (['CVmhRumungsklage', 'CMietinkasso', 'CKuendigung'] as $parentType) { + $alias = strtolower(str_replace('C', '', $parentType)); + + $queryBuilder->leftJoin( + 'parent', + $alias, + [ + "{$alias}.id:" => 'parentId', + 'parentType' => $parentType + ] + ); + + $parentConditions[] = [ + 'parentType' => $parentType, + "{$alias}.assignedUserId" => $userId + ]; + } + + $conditions[] = [ + 'tz.teamId' => $anwaltTeamIds, + 'tz.aktiv' => true, + 'tz.abgeschlossen' => false, + 'OR' => $parentConditions + ]; + } + + if (!empty($conditions)) { + $queryBuilder->where([ + 'OR' => $conditions + ]); + } else { + // Keine passenden Bedingungen -> zeige nichts + $queryBuilder->where(['id' => null]); + } + } +} +``` + +**Filter registrieren:** + +**Datei:** `custom/Espo/Custom/Resources/metadata/selectDefs/CEntwicklung.json` + +```json +{ + "primaryFilterClassNameMap": { + "meineOffenen": "Espo\\Custom\\Classes\\Select\\CEntwicklung\\PrimaryFilters\\MeineOffenen" + }, + "boolFilterDefs": { + "meineOffenen": {} + } +} +``` + +**i18n:** + +```json +{ + "presetFilters": { + "meineOffenen": "Meine offenen Entwicklungen" + } +} +``` + +--- + +## ๐Ÿš€ Phase 6: Middleware-Integration (Spezifikation) + +### 6.1 Polling-Endpoints + +**Middleware nutzt Standard-EspoCRM-API:** + +#### Neue Dokumente ohne Entwicklung finden: +```http +GET /api/v1/CDokumente?where[0][type]=isNull&where[0][attribute]=entwicklungId&maxSize=50&orderBy=createdAt&order=asc +``` + +#### Entwicklungen mit unclean Status: +```http +GET /api/v1/CEntwicklung?where[0][type]=equals&where[0][attribute]=syncStatus&where[0][value]=unclean&maxSize=10 +``` + +#### Abwesende User: +```http +GET /api/v1/User?where[0][type]=equals&where[0][attribute]=abwesend&where[0][value]=true +``` + +--- + +### 6.2 Middleware-Workflow: Dokument-Verarbeitung + +**Pseudocode:** +``` +POLLING JOB (alle 60 Sekunden): + +1. Query neue Dokumente ohne Entwicklung + +2. Fรผr jedes Dokument: + a) Hat es einen Parent? + NEIN โ†’ Skip (keine Zuordnung mรถglich) + + b) Existiert offene Entwicklung fรผr diesen Parent? + Query: /api/v1/CEntwicklung?where[0][parentId]={id}&where[1][syncStatus]=unclean + + JA โ†’ + - Dokument verknรผpfen: PUT /api/v1/CDokumente/{id} {"entwicklungId": X} + - Entwicklung-Status: PUT /api/v1/CEntwicklung/{id} {"status": "In Verarbeitung"} + + NEIN โ†’ + - Neue Entwicklung erstellen: + POST /api/v1/CEntwicklung { + "name": "Entwicklung #N - [Datum]", + "parentType": "...", + "parentId": "...", + "status": "Neu", + "syncStatus": "unclean" + } + - Dokument verknรผpfen + +3. Queue fรผr KI-Analyse fรผllen + +ANALYSE JOB (async Worker): + +1. Hole Entwicklung aus Queue + +2. Download alle Dokumente: + GET /api/v1/CEntwicklung/{id}/dokumente + Fรผr jedes: GET /api/v1/Attachment/{attachmentId} + +3. KI-Verarbeitung: + - OCR falls nรถtig + - Inhaltsanalyse + - Team-Entscheidung: + * Regex/Keywords fรผr Zwangsvollstreckung + * Sentiment-Analyse fรผr Dringlichkeit + * Named-Entity-Recognition fรผr Beteiligte + - Prioritรคt ableiten + +4. Update via Custom API: + PUT /api/v1/CEntwicklung/{id}/aktiviere-teams { + "kiAnalyse": "Lange Zusammenfassung...", + "zusammenfassung": "Kurz...", + "status": "Bereit", + "syncStatus": "clean", + "teams": [ + {"teamId": "66ab...", "prioritaet": "Hoch"} + ] + } + +5. Optional: Benachrichtigungen triggern +``` + +--- + +### 6.3 Middleware-Workflow: Abwesenheitsvertretung + +**Pseudocode:** +``` +POLLING JOB (alle 5 Minuten): + +1. Query abwesende User + +2. Fรผr jeden User: + a) Prรผfe abwesendBis: + Wenn abwesendBis <= heute: + - PUT /api/v1/User/{id} {"abwesend": false} + - Skip (User ist zurรผck) + + b) Ermittle Vertreter: + - Prio 1: User.vertretung (falls gesetzt) + - Prio 2: Team-Leader (Query: Team mit User als Member) + - Prio 3: User mit wenigsten offenen Entwicklungen + + c) Query offene Entwicklungen fรผr Anwalt-Teams: + GET /api/v1/CEntwicklung + ?where[0][type]=in + &where[0][attribute]=parentType + &where[0][value][]=CVmhRumungsklage + &... + Filtere lokal nach: Parent.assignedUserId = abwesenderUser + + d) Fรผr jede Entwicklung: + - Update Parent-Vorgang: + PUT /api/v1/{ParentType}/{parentId} { + "assignedUserId": "vertreterUserId" + } + - Stream-Eintrag erstellen: + POST /api/v1/Note { + "parentType": "CEntwicklung", + "parentId": "{entwicklungId}", + "type": "Post", + "post": "Umverteilt von [Abwesender] zu [Vertreter] (Abwesenheit)" + } + + e) Analog fรผr Tasks, Workflows, etc. +``` + +--- + +## ๐Ÿ“Š Phase 7: Testing & Qualitรคtssicherung + +### 7.1 Unit-Tests (Custom PHP-Klassen) + +**Datei:** `tests/unit/Espo/Custom/Api/CEntwicklung/AktiviereTeamsTest.php` + +**Test-Cases:** +- โœ… Teams werden korrekt aktiviert +- โœ… Alte Zuordnungen werden deaktiviert +- โœ… Entwicklung-Status wird aktualisiert +- โœ… Fehlerbehandlung bei fehlender ID +- โœ… Fehlerbehandlung bei ungรผltigem Team + +--- + +### 7.2 Integration-Tests + +**Szenario 1: Dokument โ†’ Entwicklung โ†’ Analyse โ†’ Abschluss** +1. Upload Dokument via UI +2. Middleware erkennt Dokument (manuell triggern) +3. Middleware erstellt Entwicklung +4. Middleware analysiert & aktiviert Teams +5. User reviewed & schlieรŸt ab +6. Validierung: Status = "Abgeschlossen" + +**Szenario 2: Mehrere Teams parallel** +1. Entwicklung mit 2 Teams (Mandatsbetreuung + Anwalt) +2. Mandatsbetreuung schlieรŸt ab โ†’ Status = "Teilweise abgeschlossen" +3. Anwalt schlieรŸt ab โ†’ Status = "Abgeschlossen" + +**Szenario 3: Neues Dokument wรคhrend Review** +1. Entwicklung im Status "Bereit" +2. Neues Dokument uploaded +3. Middleware setzt syncStatus = "unclean" +4. Abschluss-Button disabled +5. Middleware re-analysiert +6. syncStatus = "clean", Abschluss wieder mรถglich + +**Szenario 4: Abwesenheitsvertretung** +1. User A setzt abwesend = true, vertretung = User B +2. Middleware pollt +3. Rรคumungsklagen von User A werden umverteilt +4. Entwicklungen erscheinen in User B's Liste +5. User A setzt abwesend = false +6. Keine weitere Umverteilung + +--- + +### 7.3 Performance-Tests + +**Metriken:** +- Query-Zeit fรผr "Meine offenen" Filter < 500ms (bei 1000 Entwicklungen) +- Middleware Polling-Overhead < 1 CPU-Sekunde pro Cycle +- Abschluss-API < 200ms Response-Time + +--- + +## ๐Ÿ“‹ Deployment-Checkliste + +### Pre-Deployment +- [ ] Alle JSON-Dateien validiert (Syntax) +- [ ] Relationships bidirektional definiert +- [ ] i18n vollstรคndig (de_DE + en_US) +- [ ] Custom API-Routes registriert +- [ ] PHP-Klassen Namespace korrekt + +### Deployment +- [ ] Files via Git committen +- [ ] `python3 custom/scripts/validate_and_rebuild.py` ausfรผhren +- [ ] Keine Errors in Validation +- [ ] Rebuild erfolgreich +- [ ] Browser Hard Refresh (Ctrl+Shift+R) + +### Post-Deployment +- [ ] Teams kategorisieren (teamKategorie setzen) +- [ ] Test-Entwicklung manuell erstellen +- [ ] Filter "Meine offenen" testen +- [ ] API-Endpoints mit curl/Postman testen +- [ ] Middleware konfigurieren & starten +- [ ] End-to-End-Test durchfรผhren + +--- + +## ๐Ÿ”„ Rollback-Plan + +**Bei Problemen:** +1. Git: `git revert HEAD` (letzten Commit rรผckgรคngig) +2. Rebuild: `python3 custom/scripts/validate_and_rebuild.py` +3. Cache leeren: `rm -rf data/cache/*` +4. Middleware stoppen +5. DB-Rollback falls nรถtig: `DROP TABLE c_entwicklung, c_entwicklung_team_zuordnung;` + +--- + +## ๐Ÿ“š Dokumentation & Wissenstransfer + +### Fรผr Entwickler +- Dieser Plan (`ENTWICKLUNGSPLAN_ENTWICKLUNGEN.md`) +- Code-Kommentare in PHP-Klassen +- API-Dokumentation (Swagger/Postman Collection) + +### Fรผr User +- User-Guide: "Wie nutze ich Entwicklungen?" +- Video-Tutorial: Entwicklung reviewen & abschlieรŸen +- FAQ: Hรคufige Fragen + +### Fรผr Admins +- Team-Setup-Guide (teamKategorie konfigurieren) +- Middleware-Setup-Guide +- Troubleshooting-Guide + +--- + +## ๐ŸŽฏ Success Metrics + +**Funktional:** +- โœ… Dokumente werden automatisch zu Entwicklungen gruppiert +- โœ… KI-Analyse wird angezeigt +- โœ… Teams sehen nur relevante Entwicklungen +- โœ… Abschluss-Workflow funktioniert +- โœ… Abwesenheitsvertretung funktioniert + +**Performance:** +- โœ… Filter < 500ms +- โœ… API-Calls < 200ms +- โœ… Middleware-Polling ohne Fehler + +**Usability:** +- โœ… User finden Entwicklungen intuitiv +- โœ… Abschluss-Prozess klar +- โœ… Fehler-Messages verstรคndlich + +--- + +## ๐Ÿ”ฎ Zukรผnftige Erweiterungen (Roadmap) + +### v2.0: Erweiterte Features +- [ ] Kommentare zu Entwicklungen (Stream) +- [ ] E-Mail-Benachrichtigungen bei neuen Entwicklungen +- [ ] Dashboard-Widget: "Meine offenen Entwicklungen" +- [ ] Bulk-Actions: Mehrere Entwicklungen gleichzeitig abschlieรŸen + +### v2.5: Analytics +- [ ] Report: Durchschnittliche Bearbeitungszeit pro Team +- [ ] Report: Anzahl Entwicklungen pro Vorgang +- [ ] Dashboard: Entwicklungen-Pipeline (Kanban-View) + +### v3.0: Advanced +- [ ] Workflow-Integration: Auto-Task bei neuer Entwicklung +- [ ] Custom Notification-Channels (Slack, Teams) +- [ ] Mobile-App-Integration +- [ ] KI-gestรผtzte Prioritรคts-Vorhersage + +--- + +## ๐Ÿ‘ฅ Rollen & Verantwortlichkeiten + +**Backend-Entwickler:** +- Entity-Definitionen +- Custom API-Endpoints +- Hooks & Formula-Scripts +- Performance-Optimierung + +**Frontend-Entwickler:** +- Layouts (Detail, List, Panels) +- Custom Views (falls nรถtig) +- CSS-Anpassungen +- UI/UX-Testing + +**Middleware-Entwickler:** +- Polling-Jobs +- KI-Integration +- Abwesenheits-Logik +- Error-Handling + +**QA-Engineer:** +- Test-Cases erstellen +- Integration-Tests +- Performance-Tests +- Bug-Tracking + +**Product Owner:** +- Requirements validieren +- User-Feedback einholen +- Prioritรคten setzen +- Acceptance-Tests + +--- + +## ๐Ÿ“ž Support & Kontakt + +**Bei Fragen zur Implementierung:** +- EspoCRM-Dokumentation: https://docs.espocrm.com +- Custom Development Guide: `README.md` +- KI-Overview-Script: `bash custom/scripts/ki-overview.sh` + +**Bei Problemen:** +- Logs prรผfen: `tail -f data/logs/espo-*.log` +- Validator: `python3 custom/scripts/validate_and_rebuild.py --dry-run` +- Git-History: `git log --oneline custom/Espo/Custom/` + +--- + +**Status:** โœ… Spezifikation vollstรคndig +**Nรคchster Schritt:** Phase 1 Implementierung starten +**Geschรคtzte Dauer:** 2-3 Wochen (alle Phasen) + +--- + +*Ende des Entwicklungsplans*