From ea4738d9eb7fa5af92aa9eb4ed18b08c7adf5b53 Mon Sep 17 00:00:00 2001 From: bsiggel Date: Mon, 23 Mar 2026 21:29:26 +0100 Subject: [PATCH] =?UTF-8?q?Add=20AdvowareAkte=20and=20AIKnowledge=20creati?= =?UTF-8?q?on=20for=20K=C3=BCndigungen;=20synchronize=20between=20K=C3=BCn?= =?UTF-8?q?digungen=20and=20R=C3=A4umungsklagen;=20update=20localization?= =?UTF-8?q?=20and=20metadata=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Hooks/CKuendigung/CreateAdvowareAkte.php | 148 ++++++++++++++++++ .../Hooks/CKuendigung/SyncAdvowareAkte.php | 92 +++++++++++ .../Resources/i18n/de_DE/CAdvowareAkten.json | 1 + .../Resources/i18n/de_DE/CDokumente.json | 4 +- .../Resources/i18n/de_DE/CKuendigung.json | 2 + .../i18n/de_DE/CVmhRumungsklage.json | 1 + .../Resources/i18n/en_US/CAdvowareAkten.json | 1 + .../Resources/i18n/en_US/CDokumente.json | 4 +- .../Resources/i18n/en_US/CKuendigung.json | 2 + .../i18n/en_US/CVmhRumungsklage.json | 1 + .../metadata/entityDefs/CAdvowareAkten.json | 7 + .../metadata/entityDefs/CDokumente.json | 6 + .../metadata/entityDefs/CKuendigung.json | 18 +++ .../metadata/entityDefs/CVmhRumungsklage.json | 8 + custom/Espo/Custom/Services/CDokumente.php | 23 ++- .../Custom/Services/CVmhMietverhltnis.php | 65 ++++++++ .../Espo/Custom/Services/CVmhRumungsklage.php | 131 +++++++++++++++- data/config.php | 2 +- data/state.php | 4 +- 19 files changed, 502 insertions(+), 18 deletions(-) create mode 100644 custom/Espo/Custom/Hooks/CKuendigung/CreateAdvowareAkte.php create mode 100644 custom/Espo/Custom/Hooks/CKuendigung/SyncAdvowareAkte.php diff --git a/custom/Espo/Custom/Hooks/CKuendigung/CreateAdvowareAkte.php b/custom/Espo/Custom/Hooks/CKuendigung/CreateAdvowareAkte.php new file mode 100644 index 00000000..8696922b --- /dev/null +++ b/custom/Espo/Custom/Hooks/CKuendigung/CreateAdvowareAkte.php @@ -0,0 +1,148 @@ +get('silent') || $options->get('skipHooks')) { + return; + } + + // Vermeide Loops + $key = $entity->getId() . '-create-akte'; + if (isset(self::$processing[$key])) { + return; + } + self::$processing[$key] = true; + + try { + // Prüfe ob Kündigung bereits eine AdvowareAkte hat + $existingAkteId = $entity->get('advowareAktenId'); + + if ($existingAkteId) { + $GLOBALS['log']->info("CKuendigung CreateAdvowareAkte: Kündigung already has AdvowareAkte: {$existingAkteId}"); + unset(self::$processing[$key]); + return; // Bereits vorhanden + } + + // Prüfe ob verknüpfte Räumungsklagen eine Akte haben + $raeumungsklagen = $this->entityManager + ->getRDBRepository('CKuendigung') + ->getRelation($entity, 'vmhRumungsklages') + ->find(); + + foreach ($raeumungsklagen as $rk) { + $rkAkteId = $rk->get('advowareAktenId'); + if ($rkAkteId) { + // Übernehme Akte von Räumungsklage + $entity->set('advowareAktenId', $rkAkteId); + $this->entityManager->saveEntity($entity, ['silent' => true, 'skipHooks' => true]); + + // Synchronisiere Aktennummer + $akte = $this->entityManager->getEntity('CAdvowareAkten', $rkAkteId); + if ($akte) { + $this->syncAktennummer($entity, $akte); + } + + $GLOBALS['log']->info("CKuendigung CreateAdvowareAkte: Using AdvowareAkte from Räumungsklage: {$rkAkteId}"); + unset(self::$processing[$key]); + return; + } + } + + // Keine Akte gefunden -> Erstelle neue + $this->createNewAkte($entity); + + } catch (\Exception $e) { + $GLOBALS['log']->error('CKuendigung CreateAdvowareAkte Error: ' . $e->getMessage()); + } finally { + unset(self::$processing[$key]); + } + } + + private function createNewAkte(Entity $kuendigung): void + { + // Hole oder generiere Aktennummer + $aktennummer = $kuendigung->get('aktennr'); + if (!$aktennummer) { + $aktennummer = time(); + } + + // Hole oder generiere Aktenzeichen + $aktenzeichen = $kuendigung->get('advowareAktenzeichen'); + if (!$aktenzeichen) { + $aktenzeichen = 'AZ-' . date('Y-m-d-His'); + } + + // Erstelle AdvowareAkte + $akteData = [ + 'name' => 'Advoware Akte - ' . $kuendigung->get('name'), + 'aktennummer' => $aktennummer, + 'aktenzeichen' => $aktenzeichen, + 'syncStatus' => 'unclean', + 'assignedUserId' => $kuendigung->get('assignedUserId') + ]; + + // Copy teams + $teamsIds = $kuendigung->getLinkMultipleIdList('teams'); + if (!empty($teamsIds)) { + $akteData['teamsIds'] = $teamsIds; + } + + $akte = $this->entityManager->createEntity('CAdvowareAkten', $akteData); + + if ($akte) { + // Verknüpfe mit Kündigung + $kuendigung->set('advowareAktenId', $akte->getId()); + $this->entityManager->saveEntity($kuendigung, ['silent' => true, 'skipHooks' => true]); + + // Synchronisiere Aktennummer zurück zur Kündigung + $this->syncAktennummer($kuendigung, $akte); + + $GLOBALS['log']->info("CKuendigung CreateAdvowareAkte: Created new AdvowareAkte: {$akte->getId()}"); + } else { + $GLOBALS['log']->error('CKuendigung CreateAdvowareAkte: Failed to create AdvowareAkte'); + } + } + + private function syncAktennummer(Entity $kuendigung, Entity $akte): void + { + $needsUpdate = false; + + if (!$kuendigung->get('aktennr') && $akte->get('aktennummer')) { + $kuendigung->set('aktennr', $akte->get('aktennummer')); + $needsUpdate = true; + } + + if (!$kuendigung->get('advowareAktenzeichen') && $akte->get('aktenzeichen')) { + $kuendigung->set('advowareAktenzeichen', $akte->get('aktenzeichen')); + $needsUpdate = true; + } + + if ($needsUpdate) { + $this->entityManager->saveEntity($kuendigung, ['silent' => true, 'skipHooks' => true]); + $GLOBALS['log']->info("CKuendigung CreateAdvowareAkte: Synchronized Aktennummer/Aktenzeichen"); + } + } +} diff --git a/custom/Espo/Custom/Hooks/CKuendigung/SyncAdvowareAkte.php b/custom/Espo/Custom/Hooks/CKuendigung/SyncAdvowareAkte.php new file mode 100644 index 00000000..72a6cf72 --- /dev/null +++ b/custom/Espo/Custom/Hooks/CKuendigung/SyncAdvowareAkte.php @@ -0,0 +1,92 @@ +getId() . '-' . $foreignEntity->getId() . '-sync-akte'; + if (isset(self::$processing[$key])) { + return; + } + self::$processing[$key] = true; + + try { + // $entity = CKuendigung + // $foreignEntity = CVmhRumungsklage + + // Hole AdvowareAkte von der Räumungsklage (hasOne relationship - get via field) + $advowareAkteId = $foreignEntity->get('advowareAktenId'); + + if ($advowareAkteId) { + $advowareAkte = $this->entityManager->getEntity('CAdvowareAkten', $advowareAkteId); + + if ($advowareAkte) { + $GLOBALS['log']->info("CKuendigung SyncAdvowareAkte: Found AdvowareAkte {$advowareAkte->getId()} on Räumungsklage {$foreignEntity->getId()}"); + + // Prüfe ob Kündigung bereits eine andere Akte hat + $existingAktenId = $entity->get('advowareAktenId'); + + if ($existingAktenId && $existingAktenId !== $advowareAkteId) { + $GLOBALS['log']->warning("CKuendigung SyncAdvowareAkte: Kündigung already has different AdvowareAkte {$existingAktenId}, will replace with {$advowareAkteId}"); + } + + // Verknüpfe AdvowareAkte mit Kündigung (belongsTo relationship - set field directly) + $entity->set('advowareAktenId', $advowareAkteId); + + // Synchronisiere Aktennummer und Aktenzeichen + $needsUpdate = false; + + if (!$entity->get('aktennr') && $advowareAkte->get('aktennummer')) { + $entity->set('aktennr', $advowareAkte->get('aktennummer')); + $needsUpdate = true; + } + + if (!$entity->get('advowareAktenzeichen') && $advowareAkte->get('aktenzeichen')) { + $entity->set('advowareAktenzeichen', $advowareAkte->get('aktenzeichen')); + $needsUpdate = true; + } + + // Save once with all changes + $this->entityManager->saveEntity($entity, ['silent' => true, 'skipHooks' => true]); + $GLOBALS['log']->info("CKuendigung SyncAdvowareAkte: Successfully linked AdvowareAkte and synchronized fields to Kündigung"); + } + } else { + $GLOBALS['log']->info("CKuendigung SyncAdvowareAkte: Räumungsklage {$foreignEntity->getId()} has no AdvowareAkte yet"); + } + + } catch (\Exception $e) { + $GLOBALS['log']->error('CKuendigung SyncAdvowareAkte Error: ' . $e->getMessage()); + } finally { + unset(self::$processing[$key]); + } + } +} diff --git a/custom/Espo/Custom/Resources/i18n/de_DE/CAdvowareAkten.json b/custom/Espo/Custom/Resources/i18n/de_DE/CAdvowareAkten.json index 19f86a81..c92a3599 100644 --- a/custom/Espo/Custom/Resources/i18n/de_DE/CAdvowareAkten.json +++ b/custom/Espo/Custom/Resources/i18n/de_DE/CAdvowareAkten.json @@ -4,6 +4,7 @@ "tasks": "Aufgaben", "vmhRumungsklage": "Räumungsklagen", "mietinkasso": "Mietinkasso", + "kuendigungen": "Kündigungen", "dokumentes": "Dokumente" }, "labels": { diff --git a/custom/Espo/Custom/Resources/i18n/de_DE/CDokumente.json b/custom/Espo/Custom/Resources/i18n/de_DE/CDokumente.json index aa2e1023..abbb141f 100644 --- a/custom/Espo/Custom/Resources/i18n/de_DE/CDokumente.json +++ b/custom/Espo/Custom/Resources/i18n/de_DE/CDokumente.json @@ -9,6 +9,7 @@ "hnr": "HNR (Advoware)", "syncStatus": "Sync-Status", "syncedHash": "Sync-Hash", + "usn": "USN", "contactsvmhdokumente": "Freigegebene Nutzer", "vmhMietverhltnisesDokumente": "Mietverhältnisse", "vmhErstgespraechsdokumente": "Erstgespräche", @@ -43,7 +44,8 @@ "blake3hash": "Kryptografischer Blake3-Hash der Datei (schneller und sicherer als MD5/SHA256)", "hnr": "Hierarchische Referenznummer in Advoware", "syncStatus": "Status der Synchronisation mit Advoware: new=neu, unclean=geändert, synced=synchronisiert, failed=Fehler, unsupported=nicht unterstützt", - "syncedHash": "Hash-Wert bei letzter erfolgreicher Synchronisation" + "syncedHash": "Hash-Wert bei letzter erfolgreicher Synchronisation", + "usn": "Update Sequence Number - Versionsnummer für Synchronisation" }, "options": { "syncStatus": { diff --git a/custom/Espo/Custom/Resources/i18n/de_DE/CKuendigung.json b/custom/Espo/Custom/Resources/i18n/de_DE/CKuendigung.json index e1d67b9a..3496e843 100644 --- a/custom/Espo/Custom/Resources/i18n/de_DE/CKuendigung.json +++ b/custom/Espo/Custom/Resources/i18n/de_DE/CKuendigung.json @@ -9,6 +9,8 @@ "gekuendigte": "Mieter", "dokumenteskuendigung": "Dokumente", "contactsKuendigung": "Portal-Freigaben", + "advowareAkten": "Advoware Akte", + "vmhRumungsklages": "Räumungsklagen", "pulse": "Pulse" }, "labels": { diff --git a/custom/Espo/Custom/Resources/i18n/de_DE/CVmhRumungsklage.json b/custom/Espo/Custom/Resources/i18n/de_DE/CVmhRumungsklage.json index 359841f7..3e3d2b55 100644 --- a/custom/Espo/Custom/Resources/i18n/de_DE/CVmhRumungsklage.json +++ b/custom/Espo/Custom/Resources/i18n/de_DE/CVmhRumungsklage.json @@ -25,6 +25,7 @@ "klaeger": "Kläger", "beklagte": "Beklagte", "vmhMietverhltnises": "Mietverhältnisse", + "kuendigungen": "Kündigungen", "contactsRumungsklage": "Freigegebene Nutzer", "dokumentesvmhraumungsklage": "Dokumente", "freigeschalteteNutzer": "Freigeschaltete Nutzer (veraltet)", diff --git a/custom/Espo/Custom/Resources/i18n/en_US/CAdvowareAkten.json b/custom/Espo/Custom/Resources/i18n/en_US/CAdvowareAkten.json index 328415a6..7eac21c0 100644 --- a/custom/Espo/Custom/Resources/i18n/en_US/CAdvowareAkten.json +++ b/custom/Espo/Custom/Resources/i18n/en_US/CAdvowareAkten.json @@ -20,6 +20,7 @@ "tasks": "Tasks", "vmhRumungsklage": "Räumungsklagen", "mietinkasso": "Mietinkasso", + "kuendigungen": "Terminations", "dokumentes": "Dokumente" }, "labels": { diff --git a/custom/Espo/Custom/Resources/i18n/en_US/CDokumente.json b/custom/Espo/Custom/Resources/i18n/en_US/CDokumente.json index a52ffb20..c3282332 100644 --- a/custom/Espo/Custom/Resources/i18n/en_US/CDokumente.json +++ b/custom/Espo/Custom/Resources/i18n/en_US/CDokumente.json @@ -8,6 +8,7 @@ "hnr": "HNR (Advoware)", "syncStatus": "Sync Status", "syncedHash": "Sync Hash", + "usn": "USN", "contactsvmhdokumente": "Portal Users", "vmhMietverhltnisesDokumente": "Tenancies", "vmhErstgespraechsdokumente": "Initial Consultations", @@ -48,7 +49,8 @@ "blake3hash": "Cryptographic Blake3 hash of the file (faster and more secure than MD5/SHA256)", "hnr": "Hierarchical reference number in Advoware", "syncStatus": "Sync status with Advoware: new=new, unclean=changed, synced=synchronized, failed=error, unsupported=not supported", - "syncedHash": "Hash value at last successful synchronization" + "syncedHash": "Hash value at last successful synchronization", + "usn": "Update Sequence Number - Version number for synchronization" }, "options": { "syncStatus": { diff --git a/custom/Espo/Custom/Resources/i18n/en_US/CKuendigung.json b/custom/Espo/Custom/Resources/i18n/en_US/CKuendigung.json index a4d46dd8..c7e45305 100644 --- a/custom/Espo/Custom/Resources/i18n/en_US/CKuendigung.json +++ b/custom/Espo/Custom/Resources/i18n/en_US/CKuendigung.json @@ -44,6 +44,8 @@ "gekuendigte": "Tenant", "dokumenteskuendigung": "Documents", "contactsKuendigung": "Portal Access", + "advowareAkten": "Advoware Case File", + "vmhRumungsklages": "Eviction Lawsuits", "pulse": "Pulses" }, "labels": { diff --git a/custom/Espo/Custom/Resources/i18n/en_US/CVmhRumungsklage.json b/custom/Espo/Custom/Resources/i18n/en_US/CVmhRumungsklage.json index acc57447..16944e25 100644 --- a/custom/Espo/Custom/Resources/i18n/en_US/CVmhRumungsklage.json +++ b/custom/Espo/Custom/Resources/i18n/en_US/CVmhRumungsklage.json @@ -21,6 +21,7 @@ "calls": "Calls", "tasks": "Tasks", "vmhMietverhltnises": "Tenancies", + "kuendigungen": "Terminations", "freigeschalteteNutzer": "Activated Users", "collaborators": "Collaborators", "vmhVermietersRKL": "Landlord", diff --git a/custom/Espo/Custom/Resources/metadata/entityDefs/CAdvowareAkten.json b/custom/Espo/Custom/Resources/metadata/entityDefs/CAdvowareAkten.json index ecbfe35e..c3ea1262 100644 --- a/custom/Espo/Custom/Resources/metadata/entityDefs/CAdvowareAkten.json +++ b/custom/Espo/Custom/Resources/metadata/entityDefs/CAdvowareAkten.json @@ -195,6 +195,13 @@ "entity": "CMietinkasso", "isCustom": true }, + "kuendigungen": { + "type": "hasMany", + "foreign": "advowareAkten", + "entity": "CKuendigung", + "audited": false, + "isCustom": true + }, "dokumentes": { "type": "hasMany", "foreign": "cAdvowareAkten", diff --git a/custom/Espo/Custom/Resources/metadata/entityDefs/CDokumente.json b/custom/Espo/Custom/Resources/metadata/entityDefs/CDokumente.json index ccc672d0..fbd5ec6b 100644 --- a/custom/Espo/Custom/Resources/metadata/entityDefs/CDokumente.json +++ b/custom/Espo/Custom/Resources/metadata/entityDefs/CDokumente.json @@ -102,6 +102,12 @@ "tooltip": true, "isCustom": true }, + "usn": { + "type": "int", + "min": 0, + "tooltip": true, + "isCustom": true + }, "puls": { "type": "link", "entity": "CPuls", diff --git a/custom/Espo/Custom/Resources/metadata/entityDefs/CKuendigung.json b/custom/Espo/Custom/Resources/metadata/entityDefs/CKuendigung.json index 1894bb6e..bf59395b 100644 --- a/custom/Espo/Custom/Resources/metadata/entityDefs/CKuendigung.json +++ b/custom/Espo/Custom/Resources/metadata/entityDefs/CKuendigung.json @@ -73,6 +73,9 @@ "tooltip": true, "isCustom": true }, + "advowareAkten": { + "type": "link" + }, "syncStatus": { "type": "enum", "required": false, @@ -357,6 +360,21 @@ "audited": false, "isCustom": true }, + "advowareAkten": { + "type": "belongsTo", + "foreign": "kuendigungen", + "entity": "CAdvowareAkten", + "audited": false, + "isCustom": true + }, + "vmhRumungsklages": { + "type": "hasMany", + "relationName": "cKuendigungVmhRumungsklage", + "foreign": "kuendigungen", + "entity": "CVmhRumungsklage", + "audited": false, + "isCustom": true + }, "pulse": { "type": "hasMany", "entity": "CPuls", diff --git a/custom/Espo/Custom/Resources/metadata/entityDefs/CVmhRumungsklage.json b/custom/Espo/Custom/Resources/metadata/entityDefs/CVmhRumungsklage.json index 1499789a..8403a72c 100644 --- a/custom/Espo/Custom/Resources/metadata/entityDefs/CVmhRumungsklage.json +++ b/custom/Espo/Custom/Resources/metadata/entityDefs/CVmhRumungsklage.json @@ -195,6 +195,14 @@ "audited": false, "isCustom": true }, + "kuendigungen": { + "type": "hasMany", + "relationName": "cKuendigungVmhRumungsklage", + "foreign": "vmhRumungsklages", + "entity": "CKuendigung", + "audited": false, + "isCustom": true + }, "pulse": { "type": "hasMany", "entity": "CPuls", diff --git a/custom/Espo/Custom/Services/CDokumente.php b/custom/Espo/Custom/Services/CDokumente.php index 80c8981a..d49f56f3 100644 --- a/custom/Espo/Custom/Services/CDokumente.php +++ b/custom/Espo/Custom/Services/CDokumente.php @@ -12,21 +12,20 @@ use Espo\Core\Utils\File\Manager as FileManager; */ class CDokumente extends Record { - private FileStorageManager $fileStorageManager; - private FileManager $fileManager; - /** - * Inject additional dependencies via setter methods - * EspoCRM DI will automatically call these + * Get FileStorageManager from container on-demand */ - public function injectFileStorageManager(FileStorageManager $fileStorageManager): void + private function getFileStorageManager(): FileStorageManager { - $this->fileStorageManager = $fileStorageManager; + return $this->injectableFactory->create(FileStorageManager::class); } - public function injectFileManager(FileManager $fileManager): void + /** + * Get FileManager from container on-demand + */ + private function getFileManager(): FileManager { - $this->fileManager = $fileManager; + return $this->injectableFactory->create(FileManager::class); } /** @@ -120,14 +119,14 @@ class CDokumente extends Record private function duplicateAttachment(Entity $sourceAttachment): Entity { // 1. Get source file path - $sourceFilePath = $this->fileStorageManager->getLocalFilePath($sourceAttachment); + $sourceFilePath = $this->getFileStorageManager()->getLocalFilePath($sourceAttachment); if (!file_exists($sourceFilePath)) { throw new \RuntimeException('Source file not found: ' . $sourceFilePath); } // 2. Read source file content - $fileContent = $this->fileManager->getContents($sourceFilePath); + $fileContent = $this->getFileManager()->getContents($sourceFilePath); // 3. Create new attachment entity $newAttachment = $this->entityManager->getEntity('Attachment'); @@ -142,7 +141,7 @@ class CDokumente extends Record $this->entityManager->saveEntity($newAttachment); // 4. Write file content to new location - $this->fileStorageManager->putContents($newAttachment, $fileContent); + $this->getFileStorageManager()->putContents($newAttachment, $fileContent); // 5. Return new attachment return $newAttachment; diff --git a/custom/Espo/Custom/Services/CVmhMietverhltnis.php b/custom/Espo/Custom/Services/CVmhMietverhltnis.php index f477957b..15517989 100644 --- a/custom/Espo/Custom/Services/CVmhMietverhltnis.php +++ b/custom/Espo/Custom/Services/CVmhMietverhltnis.php @@ -129,6 +129,9 @@ class CVmhMietverhltnis extends \Espo\Services\Record ->relate($bewohner); } + // 9c. Create AdvowareAkte and AIKnowledge (BEFORE document duplication!) + $this->createAdvowareAkteAndAIKnowledge($mietinkasso, $mietinkassoRepo); + // 10. Copy all documents from Mietverhältnis, Mietobjekt and Beteiligte // Get CDokumente service for duplication $dokumenteService = $this->injectableFactory->create(\Espo\Custom\Services\CDokumente::class); @@ -367,6 +370,68 @@ class CVmhMietverhltnis extends \Espo\Services\Record } } + /** + * Create AdvowareAkte and AIKnowledge for Mietinkasso + * + * @param object $mietinkasso The created Mietinkasso entity + * @param object $mietinkassoRepo Repository for relations + */ + private function createAdvowareAkteAndAIKnowledge($mietinkasso, $mietinkassoRepo): void + { + // 1. Create AdvowareAkte + $aktennummer = time(); // Simple timestamp-based generation + $aktenzeichen = 'AZ-' . date('Y-m-d-His'); + + $advowareAkteData = [ + 'name' => 'Advoware Akte - ' . $mietinkasso->get('name'), + 'aktennummer' => $aktennummer, + 'aktenzeichen' => $aktenzeichen, + 'syncStatus' => 'unclean', + 'assignedUserId' => $mietinkasso->get('assignedUserId') + ]; + + // Copy teams + $teamsIds = $mietinkasso->getLinkMultipleIdList('teams'); + if (!empty($teamsIds)) { + $advowareAkteData['teamsIds'] = $teamsIds; + } + + $advowareAkte = $this->entityManager->createEntity('CAdvowareAkten', $advowareAkteData); + + if ($advowareAkte) { + // Link AdvowareAkte to Mietinkasso (hasOne relationship - set field directly) + $mietinkasso->set('advowareAktenId', $advowareAkte->getId()); + $this->entityManager->saveEntity($mietinkasso); + $GLOBALS['log']->info("CVmhMietverhltnis: Created and linked AdvowareAkte for Mietinkasso: {$advowareAkte->getId()}"); + } else { + $GLOBALS['log']->error('CVmhMietverhltnis: Failed to create AdvowareAkte for Mietinkasso'); + } + + // 2. Create AIKnowledge + $aiKnowledgeData = [ + 'name' => 'AI Knowledge - ' . $mietinkasso->get('name'), + 'aktivierungsstatus' => 'deactivated', + 'syncStatus' => 'unclean', + 'assignedUserId' => $mietinkasso->get('assignedUserId') + ]; + + // Copy teams + if (!empty($teamsIds)) { + $aiKnowledgeData['teamsIds'] = $teamsIds; + } + + $aiKnowledge = $this->entityManager->createEntity('CAIKnowledge', $aiKnowledgeData); + + if ($aiKnowledge) { + // Link AIKnowledge to Mietinkasso (hasOne relationship - set field directly) + $mietinkasso->set('aIKnowledgeId', $aiKnowledge->getId()); + $this->entityManager->saveEntity($mietinkasso); + $GLOBALS['log']->info("CVmhMietverhltnis: Created and linked AIKnowledge for Mietinkasso: {$aiKnowledge->getId()}"); + } else { + $GLOBALS['log']->error('CVmhMietverhltnis: Failed to create AIKnowledge for Mietinkasso'); + } + } + /** * Log action to source entity stream * diff --git a/custom/Espo/Custom/Services/CVmhRumungsklage.php b/custom/Espo/Custom/Services/CVmhRumungsklage.php index 87a1e436..ca6a82df 100644 --- a/custom/Espo/Custom/Services/CVmhRumungsklage.php +++ b/custom/Espo/Custom/Services/CVmhRumungsklage.php @@ -264,7 +264,14 @@ class CVmhRumungsklage extends \Espo\Services\Record ->relate($beklagter); } - // 7. Collect all documents from Mietverhältnisse, Kündigungen, Mietobjekte and Beteiligte + // 7. Create or link AdvowareAkte and AIKnowledge (BEFORE document duplication!) + $this->createOrLinkAdvowareAkteAndAIKnowledge( + $raeumungsklage, + $alleKuendigungen, + $raeumungsklagenRepo + ); + + // 8. Collect all documents from Mietverhältnisse, Kündigungen, Mietobjekte and Beteiligte $alleLinkedDokumente = []; // Get CDokumente service for duplication @@ -391,6 +398,128 @@ class CVmhRumungsklage extends \Espo\Services\Record ]; } + /** + * Create or link AdvowareAkte and create AIKnowledge for Räumungsklage + * + * @param object $raeumungsklage The created Räumungsklage entity + * @param array $alleKuendigungen All related Kündigungen + * @param object $raeumungsklagenRepo Repository for relations + */ + private function createOrLinkAdvowareAkteAndAIKnowledge( + $raeumungsklage, + array $alleKuendigungen, + $raeumungsklagenRepo + ): void { + $advowareAkte = null; + + // 1. Check if any Kündigung has an existing AdvowareAkte (belongsTo relationship - get via field) + foreach ($alleKuendigungen as $kuendigung) { + $existingAkteId = $kuendigung->get('advowareAktenId'); + + if ($existingAkteId) { + $existingAkte = $this->entityManager->getEntity('CAdvowareAkten', $existingAkteId); + if ($existingAkte) { + $advowareAkte = $existingAkte; + $GLOBALS['log']->info("CVmhRumungsklage: Using existing AdvowareAkte from Kündigung: {$existingAkte->getId()}"); + break; // Use first found Akte + } + } + } + + // 2. If no existing Akte found, create new one + if (!$advowareAkte) { + // Collect Aktennummer and Aktenzeichen from Kündigungen + $aktennummer = null; + $aktenzeichen = null; + + foreach ($alleKuendigungen as $kuendigung) { + if (!$aktennummer && $kuendigung->get('aktennr')) { + $aktennummer = $kuendigung->get('aktennr'); + } + if (!$aktenzeichen && $kuendigung->get('advowareAktenzeichen')) { + $aktenzeichen = $kuendigung->get('advowareAktenzeichen'); + } + if ($aktennummer && $aktenzeichen) { + break; // Found both + } + } + + // Generate if not found + if (!$aktennummer) { + $aktennummer = time(); // Simple timestamp-based generation + } + if (!$aktenzeichen) { + $aktenzeichen = 'AZ-' . date('Y-m-d-His'); + } + + // Create new AdvowareAkte + $advowareAkteData = [ + 'name' => 'Advoware Akte - ' . $raeumungsklage->get('name'), + 'aktennummer' => $aktennummer, + 'aktenzeichen' => $aktenzeichen, + 'syncStatus' => 'unclean', + 'assignedUserId' => $raeumungsklage->get('assignedUserId') + ]; + + // Copy teams + $teamsIds = $raeumungsklage->getLinkMultipleIdList('teams'); + if (!empty($teamsIds)) { + $advowareAkteData['teamsIds'] = $teamsIds; + } + + $advowareAkte = $this->entityManager->createEntity('CAdvowareAkten', $advowareAkteData); + + if ($advowareAkte) { + $GLOBALS['log']->info("CVmhRumungsklage: Created new AdvowareAkte: {$advowareAkte->getId()}"); + + // Link new Akte to ALL Kündigungen (belongsTo relationship - set field directly) + foreach ($alleKuendigungen as $kuendigung) { + try { + $kuendigung->set('advowareAktenId', $advowareAkte->getId()); + $this->entityManager->saveEntity($kuendigung); + $GLOBALS['log']->info("CVmhRumungsklage: Linked new AdvowareAkte to Kündigung: {$kuendigung->getId()}"); + } catch (\Exception $e) { + $GLOBALS['log']->warning("CVmhRumungsklage: Could not link AdvowareAkte to Kündigung: " . $e->getMessage()); + } + } + } else { + $GLOBALS['log']->error('CVmhRumungsklage: Failed to create AdvowareAkte'); + } + } + + // 3. Link AdvowareAkte to Räumungsklage (hasOne relationship - set field directly) + if ($advowareAkte) { + $raeumungsklage->set('advowareAktenId', $advowareAkte->getId()); + $this->entityManager->saveEntity($raeumungsklage); + $GLOBALS['log']->info("CVmhRumungsklage: Linked AdvowareAkte to Räumungsklage"); + } + + // 4. Create AIKnowledge + $aiKnowledgeData = [ + 'name' => 'AI Knowledge - ' . $raeumungsklage->get('name'), + 'aktivierungsstatus' => 'deactivated', + 'syncStatus' => 'unclean', + 'assignedUserId' => $raeumungsklage->get('assignedUserId') + ]; + + // Copy teams + $teamsIds = $raeumungsklage->getLinkMultipleIdList('teams'); + if (!empty($teamsIds)) { + $aiKnowledgeData['teamsIds'] = $teamsIds; + } + + $aiKnowledge = $this->entityManager->createEntity('CAIKnowledge', $aiKnowledgeData); + + if ($aiKnowledge) { + // Link AIKnowledge to Räumungsklage (hasOne relationship - set field directly) + $raeumungsklage->set('aIKnowledgeId', $aiKnowledge->getId()); + $this->entityManager->saveEntity($raeumungsklage); + $GLOBALS['log']->info("CVmhRumungsklage: Created and linked AIKnowledge: {$aiKnowledge->getId()}"); + } else { + $GLOBALS['log']->error('CVmhRumungsklage: Failed to create AIKnowledge'); + } + } + /** * Log Räumungsklage creation to source entity stream * diff --git a/data/config.php b/data/config.php index 6c065684..93614c85 100644 --- a/data/config.php +++ b/data/config.php @@ -360,7 +360,7 @@ return [ 0 => 'youtube.com', 1 => 'google.com' ], - 'microtime' => 1774294800.115291, + 'microtime' => 1774297547.393199, 'siteUrl' => 'https://crm.bitbylaw.com', 'fullTextSearchMinLength' => 4, 'webSocketUrl' => 'ws://api.bitbylaw.com:5000/espocrm/ws', diff --git a/data/state.php b/data/state.php index 2c2f80dc..2432817c 100644 --- a/data/state.php +++ b/data/state.php @@ -1,7 +1,7 @@ 1774294800, - 'microtimeState' => 1774294800.291216, + 'cacheTimestamp' => 1774297547, + 'microtimeState' => 1774297547.570899, 'currencyRates' => [ 'EUR' => 1.0 ],