From 2a18e62528fc2169c04a1a2fc873ea47be59526a Mon Sep 17 00:00:00 2001 From: bsiggel Date: Fri, 27 Mar 2026 09:52:28 +0100 Subject: [PATCH] Enhance document synchronization hooks to handle linking and unlinking; update sync status for related entities; modify state file for cache and microtime values --- .../CDokumente/UpdateJunctionSyncStatus.php | 97 +++++++++++++++---- .../Hooks/CMietinkasso/PropagateDocuments.php | 13 +++ .../CVmhRumungsklage/PropagateDocuments.php | 13 +++ data/state.php | 4 +- 4 files changed, 107 insertions(+), 20 deletions(-) diff --git a/custom/Espo/Custom/Hooks/CDokumente/UpdateJunctionSyncStatus.php b/custom/Espo/Custom/Hooks/CDokumente/UpdateJunctionSyncStatus.php index dfb4c820..941bae3d 100644 --- a/custom/Espo/Custom/Hooks/CDokumente/UpdateJunctionSyncStatus.php +++ b/custom/Espo/Custom/Hooks/CDokumente/UpdateJunctionSyncStatus.php @@ -2,14 +2,22 @@ namespace Espo\Custom\Hooks\CDokumente; use Espo\ORM\Entity; +use Espo\ORM\Repository\Option\RemoveOptions; use Espo\ORM\Repository\Option\SaveOptions; +use Espo\Core\Hook\Hook\AfterRemove; use Espo\Core\Hook\Hook\AfterSave; /** - * Hook: Bei Änderung eines Dokuments syncStatus und aiSyncStatus auf "unclean" setzen - * und die verknüpfte CAkten-Entity aktualisieren. + * Hook: Bei Änderung, Verlinkung oder Löschung eines Dokuments syncStatus und + * aiSyncStatus auf "unclean" setzen und die verknüpfte CAkten-Entity aktualisieren. + * + * Auslöser: + * - afterSave: Feldänderungen (name, description, file, …) + * - afterSave: Neues Dokument mit cAktenId + * - afterSave: cAktenId-Änderung (Verlinkung / Entlinkung über CAkten-Panel) + * - afterRemove: Gelöschtes Dokument → verknüpfte Akte neu berechnen */ -class UpdateJunctionSyncStatus implements AfterSave +class UpdateJunctionSyncStatus implements AfterSave, AfterRemove { public function __construct( private \Espo\ORM\EntityManager $entityManager @@ -17,30 +25,83 @@ class UpdateJunctionSyncStatus implements AfterSave public function afterSave(Entity $entity, SaveOptions $options): void { - if ($entity->isNew()) { - return; - } - - if (!$this->hasRelevantChanges($entity)) { - return; - } - - if (!$entity->get('cAktenId')) { + // Kein Re-Eintritt wenn dieser Hook selbst gespeichert hat + if ($options->get('skipHooks')) { return; } try { + // Fall 1: Neues Dokument → verknüpfte Akte neu berechnen + if ($entity->isNew()) { + if ($entity->get('cAktenId')) { + $this->triggerAkteUpdate($entity->get('cAktenId')); + } + return; + } + + // Fall 2: cAktenId geändert (Dokument verlinkt oder entlinkt) + if ($entity->isAttributeChanged('cAktenId')) { + $oldAktenId = $entity->getFetched('cAktenId'); + $newAktenId = $entity->get('cAktenId'); + + // Dokument der neuen Akte zugewiesen → als unclean markieren + if ($newAktenId) { + $entity->set('syncStatus', 'unclean'); + $entity->set('aiSyncStatus', 'unclean'); + $this->entityManager->saveEntity($entity, ['silent' => true, 'skipHooks' => true]); + $this->triggerAkteUpdate($newAktenId); + } + + // Alte Akte ebenfalls neu berechnen (Dokument entfernt) + if ($oldAktenId && $oldAktenId !== $newAktenId) { + $this->triggerAkteUpdate($oldAktenId); + } + return; + } + + // Fall 3: Relevante Feldänderungen (Datei, Name, Beschreibung, …) + if (!$this->hasRelevantChanges($entity)) { + return; + } + + if (!$entity->get('cAktenId')) { + return; + } + $entity->set('syncStatus', 'unclean'); $entity->set('aiSyncStatus', 'unclean'); $this->entityManager->saveEntity($entity, ['silent' => true, 'skipHooks' => true]); + $this->triggerAkteUpdate($entity->get('cAktenId')); - // Akte triggern → BeforeSave-Hook UpdateLastSyncFromDocuments aggregiert Status - $akte = $this->entityManager->getEntityById('CAkten', $entity->get('cAktenId')); - if ($akte) { - $this->entityManager->saveEntity($akte, ['silent' => true]); - } } catch (\Exception $e) { - $GLOBALS['log']->error('CDokumente UpdateJunctionSyncStatus Hook Error: ' . $e->getMessage()); + $GLOBALS['log']->error('CDokumente UpdateJunctionSyncStatus afterSave Error: ' . $e->getMessage()); + } + } + + public function afterRemove(Entity $entity, RemoveOptions $options): void + { + $akteId = $entity->get('cAktenId'); + if (!$akteId) { + return; + } + + try { + $this->triggerAkteUpdate($akteId); + } catch (\Exception $e) { + $GLOBALS['log']->error('CDokumente UpdateJunctionSyncStatus afterRemove Error: ' . $e->getMessage()); + } + } + + /** + * Speichert die CAkten-Entity neu, damit UpdateLastSyncFromDocuments + * den Sync-Status aller verknüpften Dokumente neu aggregiert. + */ + private function triggerAkteUpdate(string $akteId): void + { + $akte = $this->entityManager->getEntityById('CAkten', $akteId); + if ($akte) { + // silent=true → keine Audit-Einträge; kein skipHooks → UpdateLastSyncFromDocuments läuft + $this->entityManager->saveEntity($akte, ['silent' => true]); } } diff --git a/custom/Espo/Custom/Hooks/CMietinkasso/PropagateDocuments.php b/custom/Espo/Custom/Hooks/CMietinkasso/PropagateDocuments.php index b134214f..f867cb13 100644 --- a/custom/Espo/Custom/Hooks/CMietinkasso/PropagateDocuments.php +++ b/custom/Espo/Custom/Hooks/CMietinkasso/PropagateDocuments.php @@ -50,6 +50,12 @@ class PropagateDocuments implements AfterRelate, AfterUnrelate $foreignEntity->set('cAktenId', $advowareAkten->getId()); $foreignEntity->set('syncStatus', 'new'); // Mark as new for Advoware sync $this->entityManager->saveEntity($foreignEntity, ['silent' => true, 'skipHooks' => true]); + + // Akte über neue Verlinkung informieren → syncStatus auf unclean + $akte = $this->entityManager->getEntityById('CAkten', $advowareAkten->getId()); + if ($akte) { + $this->entityManager->saveEntity($akte, ['silent' => true]); + } } // Hole verbundene AIKnowledge @@ -97,8 +103,15 @@ class PropagateDocuments implements AfterRelate, AfterUnrelate // Remove direct belongsTo relationship from document if ($advowareAkten && $foreignEntity->get('cAktenId') === $advowareAkten->getId()) { + $akteId = $advowareAkten->getId(); // Vor dem Löschen merken $foreignEntity->set('cAktenId', null); $this->entityManager->saveEntity($foreignEntity, ['silent' => true, 'skipHooks' => true]); + + // Akte über Entlinkung informieren → syncStatus neu berechnen + $akte = $this->entityManager->getEntityById('CAkten', $akteId); + if ($akte) { + $this->entityManager->saveEntity($akte, ['silent' => true]); + } } // Hole verbundene AIKnowledge diff --git a/custom/Espo/Custom/Hooks/CVmhRumungsklage/PropagateDocuments.php b/custom/Espo/Custom/Hooks/CVmhRumungsklage/PropagateDocuments.php index 19d8a79c..1731df66 100644 --- a/custom/Espo/Custom/Hooks/CVmhRumungsklage/PropagateDocuments.php +++ b/custom/Espo/Custom/Hooks/CVmhRumungsklage/PropagateDocuments.php @@ -50,6 +50,12 @@ class PropagateDocuments implements AfterRelate, AfterUnrelate $foreignEntity->set('cAktenId', $advowareAkten->getId()); $foreignEntity->set('syncStatus', 'new'); // Mark as new for Advoware sync $this->entityManager->saveEntity($foreignEntity, ['silent' => true, 'skipHooks' => true]); + + // Akte über neue Verlinkung informieren → syncStatus auf unclean + $akte = $this->entityManager->getEntityById('CAkten', $advowareAkten->getId()); + if ($akte) { + $this->entityManager->saveEntity($akte, ['silent' => true]); + } } // Hole verbundene AIKnowledge @@ -97,8 +103,15 @@ class PropagateDocuments implements AfterRelate, AfterUnrelate // Remove direct belongsTo relationship from document if ($advowareAkten && $foreignEntity->get('cAktenId') === $advowareAkten->getId()) { + $akteId = $advowareAkten->getId(); // Vor dem Löschen merken $foreignEntity->set('cAktenId', null); $this->entityManager->saveEntity($foreignEntity, ['silent' => true, 'skipHooks' => true]); + + // Akte über Entlinkung informieren → syncStatus neu berechnen + $akte = $this->entityManager->getEntityById('CAkten', $akteId); + if ($akte) { + $this->entityManager->saveEntity($akte, ['silent' => true]); + } } // Hole verbundene AIKnowledge diff --git a/data/state.php b/data/state.php index 7bb66d9a..3f14d5b8 100644 --- a/data/state.php +++ b/data/state.php @@ -1,7 +1,7 @@ 1774562639, - 'microtimeState' => 1774562639.79198, + 'cacheTimestamp' => 1774566125, + 'microtimeState' => 1774566125.048162, 'currencyRates' => [ 'EUR' => 1.0 ],