Refactor AdvowareAkte ↔ CDokumente relationship from junction table to direct n:1 relationship
- Removed CAdvowareAktenCDokumente junction table and associated service. - Updated CDokumente entity to include foreign key cAdvowareAktenId and related fields. - Changed relationship in CDokumente from hasMany to belongsTo. - Updated CAdvowareAkten to reflect new direct relationship. - Implemented CDokumente service with duplicateDocument method for document duplication. - Refactored hooks to support new relationship and document propagation. - Removed obsolete API routes related to the junction table. - Added i18n translations for new fields and updated tooltips. - Document flow and auto-linking logic enhanced for better integration with Advoware. - Validation checks passed, and no data migration needed.
This commit is contained in:
@@ -52,6 +52,18 @@ class PropagateDocumentsUp implements AfterRelate, AfterUnrelate
|
||||
|
||||
if ($raumungsklage) {
|
||||
$this->relateDocument($raumungsklage, 'dokumentesvmhraumungsklage', $foreignEntity);
|
||||
|
||||
// Also link to AdvowareAkte if Räumungsklage has one
|
||||
$advowareAkte = $this->entityManager
|
||||
->getRDBRepository('CVmhRumungsklage')
|
||||
->getRelation($raumungsklage, 'advowareAkten')
|
||||
->findOne();
|
||||
|
||||
if ($advowareAkte && !$foreignEntity->get('cAdvowareAktenId')) {
|
||||
$foreignEntity->set('cAdvowareAktenId', $advowareAkte->getId());
|
||||
$foreignEntity->set('syncStatus', 'new');
|
||||
$this->entityManager->saveEntity($foreignEntity, ['silent' => true, 'skipHooks' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfe ob Mietinkasso verknüpft ist
|
||||
@@ -62,6 +74,18 @@ class PropagateDocumentsUp implements AfterRelate, AfterUnrelate
|
||||
|
||||
if ($mietinkasso) {
|
||||
$this->relateDocument($mietinkasso, 'dokumentesmietinkasso', $foreignEntity);
|
||||
|
||||
// Also link to AdvowareAkte if Mietinkasso has one
|
||||
$advowareAkte = $this->entityManager
|
||||
->getRDBRepository('CMietinkasso')
|
||||
->getRelation($mietinkasso, 'advowareAkten')
|
||||
->findOne();
|
||||
|
||||
if ($advowareAkte && !$foreignEntity->get('cAdvowareAktenId')) {
|
||||
$foreignEntity->set('cAdvowareAktenId', $advowareAkte->getId());
|
||||
$foreignEntity->set('syncStatus', 'new');
|
||||
$this->entityManager->saveEntity($foreignEntity, ['silent' => true, 'skipHooks' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
@@ -110,6 +134,9 @@ class PropagateDocumentsUp implements AfterRelate, AfterUnrelate
|
||||
$this->unrelateDocument($mietinkasso, 'dokumentesmietinkasso', $foreignEntity);
|
||||
}
|
||||
|
||||
// Note: We don't remove cAdvowareAktenId on unrelate from AIKnowledge
|
||||
// because the document might still be linked to Räumungsklage/Mietinkasso
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('CAIKnowledge PropagateDocumentsUp (unrelate) Error: ' . $e->getMessage());
|
||||
} finally {
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
namespace Espo\Custom\Hooks\CAdvowareAkten;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Core\Hook\Hook\AfterRelate;
|
||||
|
||||
/**
|
||||
* Hook: Setzt Dokument-Sync-Status auf "new" beim Verknüpfen und
|
||||
* globalen syncStatus auf "unclean"
|
||||
*/
|
||||
class DokumenteSyncStatus implements AfterRelate
|
||||
{
|
||||
public function __construct(
|
||||
private \Espo\ORM\EntityManager $entityManager
|
||||
) {}
|
||||
|
||||
public function afterRelate(
|
||||
Entity $entity,
|
||||
string $relationName,
|
||||
Entity $foreignEntity,
|
||||
array $columnData,
|
||||
\Espo\ORM\Repository\Option\RelateOptions $options
|
||||
): void {
|
||||
// Nur für dokumentes-Beziehung
|
||||
if ($relationName !== 'dokumentes') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setze Sync-Status des Dokuments in der Junction-Tabelle auf "new"
|
||||
$repository = $this->entityManager->getRDBRepository('CAdvowareAkten');
|
||||
|
||||
try {
|
||||
$repository->getRelation($entity, 'dokumentes')->updateColumns(
|
||||
$foreignEntity,
|
||||
['syncstatus' => 'new']
|
||||
);
|
||||
|
||||
// Setze globalen syncStatus auf "unclean"
|
||||
$entity->set('syncStatus', 'unclean');
|
||||
$this->entityManager->saveEntity($entity, ['silent' => true, 'skipHooks' => true]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Fehler loggen, aber nicht werfen (um Verknüpfung nicht zu blockieren)
|
||||
$GLOBALS['log']->error('CAdvowareAkten DokumenteSyncStatus Hook Error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,21 +2,19 @@
|
||||
namespace Espo\Custom\Hooks\CAdvowareAkten;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Core\Hook\Hook\AfterRelate;
|
||||
use Espo\Core\Hook\Hook\AfterUnrelate;
|
||||
use Espo\Core\Hook\Hook\AfterSave;
|
||||
|
||||
/**
|
||||
* Hook: Propagiert Dokumenten-Verknüpfungen von AdvowareAkten nach oben zu Räumungsklage/Mietinkasso
|
||||
* Hook: Propagiert Dokumenten-Änderungen von AdvowareAkten nach oben zu Räumungsklage/Mietinkasso
|
||||
* und auch zu AICollection
|
||||
*
|
||||
* Wenn Dokument mit AdvowareAkten verknüpft wird:
|
||||
* Wenn ein Dokument einer AdvowareAkte zugewiesen wird (via cAdvowareAktenId):
|
||||
* → verknüpfe mit verbundener Räumungsklage/Mietinkasso
|
||||
* → von dort propagiert es automatisch zu AIKnowledge (via deren Hooks)
|
||||
* → verknüpfe mit AICollection
|
||||
*
|
||||
* Wenn Dokument von AdvowareAkten entknüpft wird:
|
||||
* → entknüpfe von verbundener Räumungsklage/Mietinkasso
|
||||
* → von dort propagiert es automatisch von AIKnowledge (via deren Hooks)
|
||||
* Improved logic: Works with direct belongsTo relationship (cAdvowareAktenId)
|
||||
*/
|
||||
class PropagateDocumentsUp implements AfterRelate, AfterUnrelate
|
||||
class PropagateDocumentsUp implements AfterSave
|
||||
{
|
||||
private static array $processing = [];
|
||||
|
||||
@@ -24,94 +22,77 @@ class PropagateDocumentsUp implements AfterRelate, AfterUnrelate
|
||||
private \Espo\ORM\EntityManager $entityManager
|
||||
) {}
|
||||
|
||||
public function afterRelate(
|
||||
Entity $entity,
|
||||
string $relationName,
|
||||
Entity $foreignEntity,
|
||||
array $columnData,
|
||||
\Espo\ORM\Repository\Option\RelateOptions $options
|
||||
): void {
|
||||
// Nur für dokumentes-Beziehung
|
||||
if ($relationName !== 'dokumentes') {
|
||||
public function afterSave(Entity $entity, \Espo\ORM\Repository\Option\SaveOptions $options): void
|
||||
{
|
||||
// Only process when cAdvowareAktenId changed
|
||||
if (!$entity->isAttributeChanged('cAdvowareAktenId')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$akteId = $entity->get('cAdvowareAktenId');
|
||||
if (!$akteId) {
|
||||
return; // Document was unlinked from Akte
|
||||
}
|
||||
|
||||
// Vermeide Loops
|
||||
$key = $entity->getId() . '-' . $foreignEntity->getId() . '-relate';
|
||||
$key = $akteId . '-' . $entity->getId() . '-propagate';
|
||||
if (isset(self::$processing[$key])) {
|
||||
return;
|
||||
}
|
||||
self::$processing[$key] = true;
|
||||
|
||||
try {
|
||||
// Load AdvowareAkte
|
||||
$akte = $this->entityManager->getEntity('CAdvowareAkten', $akteId);
|
||||
if (!$akte) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prüfe ob Räumungsklage verknüpft ist
|
||||
$raumungsklage = $this->entityManager
|
||||
->getRDBRepository('CAdvowareAkten')
|
||||
->getRelation($entity, 'vmhRumungsklage')
|
||||
->getRelation($akte, 'vmhRumungsklage')
|
||||
->findOne();
|
||||
|
||||
if ($raumungsklage) {
|
||||
$this->relateDocument($raumungsklage, 'dokumentesvmhraumungsklage', $foreignEntity);
|
||||
$this->relateDocument($raumungsklage, 'dokumentesvmhraumungsklage', $entity);
|
||||
}
|
||||
|
||||
// Prüfe ob Mietinkasso verknüpft ist
|
||||
$mietinkasso = $this->entityManager
|
||||
->getRDBRepository('CAdvowareAkten')
|
||||
->getRelation($entity, 'mietinkasso')
|
||||
->getRelation($akte, 'mietinkasso')
|
||||
->findOne();
|
||||
|
||||
if ($mietinkasso) {
|
||||
$this->relateDocument($mietinkasso, 'dokumentesmietinkasso', $foreignEntity);
|
||||
$this->relateDocument($mietinkasso, 'dokumentesmietinkasso', $entity);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('CAdvowareAkten PropagateDocumentsUp (relate) Error: ' . $e->getMessage());
|
||||
} finally {
|
||||
unset(self::$processing[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
public function afterUnrelate(
|
||||
Entity $entity,
|
||||
string $relationName,
|
||||
Entity $foreignEntity,
|
||||
\Espo\ORM\Repository\Option\UnrelateOptions $options
|
||||
): void {
|
||||
// Nur für dokumentes-Beziehung
|
||||
if ($relationName !== 'dokumentes') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Vermeide Loops
|
||||
$key = $entity->getId() . '-' . $foreignEntity->getId() . '-unrelate';
|
||||
if (isset(self::$processing[$key])) {
|
||||
return;
|
||||
}
|
||||
self::$processing[$key] = true;
|
||||
|
||||
try {
|
||||
// Prüfe ob Räumungsklage verknüpft ist
|
||||
$raumungsklage = $this->entityManager
|
||||
->getRDBRepository('CAdvowareAkten')
|
||||
->getRelation($entity, 'vmhRumungsklage')
|
||||
->findOne();
|
||||
|
||||
// Also propagate to AICollection if Räumungsklage or Mietinkasso has one
|
||||
if ($raumungsklage) {
|
||||
$this->unrelateDocument($raumungsklage, 'dokumentesvmhraumungsklage', $foreignEntity);
|
||||
$aiKnowledge = $this->entityManager
|
||||
->getRDBRepository('CVmhRumungsklage')
|
||||
->getRelation($raumungsklage, 'aIKnowledge')
|
||||
->findOne();
|
||||
|
||||
if ($aiKnowledge) {
|
||||
$this->relateDocument($aiKnowledge, 'dokumentes', $entity);
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfe ob Mietinkasso verknüpft ist
|
||||
$mietinkasso = $this->entityManager
|
||||
->getRDBRepository('CAdvowareAkten')
|
||||
->getRelation($entity, 'mietinkasso')
|
||||
->findOne();
|
||||
|
||||
if ($mietinkasso) {
|
||||
$this->unrelateDocument($mietinkasso, 'dokumentesmietinkasso', $foreignEntity);
|
||||
$aiKnowledge = $this->entityManager
|
||||
->getRDBRepository('CMietinkasso')
|
||||
->getRelation($mietinkasso, 'aIKnowledge')
|
||||
->findOne();
|
||||
|
||||
if ($aiKnowledge) {
|
||||
$this->relateDocument($aiKnowledge, 'dokumentes', $entity);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('CAdvowareAkten PropagateDocumentsUp (unrelate) Error: ' . $e->getMessage());
|
||||
$GLOBALS['log']->error('CAdvowareAkten PropagateDocumentsUp Error: ' . $e->getMessage());
|
||||
} finally {
|
||||
unset(self::$processing[$key]);
|
||||
}
|
||||
@@ -134,22 +115,4 @@ class PropagateDocumentsUp implements AfterRelate, AfterUnrelate
|
||||
$relation->relate($document);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hilfsfunktion: Entknüpfe Dokument
|
||||
*/
|
||||
private function unrelateDocument(Entity $parentEntity, string $relationName, Entity $document): void
|
||||
{
|
||||
$repository = $this->entityManager->getRDBRepository($parentEntity->getEntityType());
|
||||
$relation = $repository->getRelation($parentEntity, $relationName);
|
||||
|
||||
// Prüfe ob verknüpft
|
||||
$isRelated = $relation
|
||||
->where(['id' => $document->getId()])
|
||||
->findOne();
|
||||
|
||||
if ($isRelated) {
|
||||
$relation->unrelate($document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ use Espo\ORM\Repository\Option\SaveOptions;
|
||||
use Espo\Core\Hook\Hook\AfterSave;
|
||||
|
||||
/**
|
||||
* Hook: Bei Änderung eines Dokuments werden alle verknüpften
|
||||
* AdvowareAkten und AIKnowledge Junction-Table-Einträge auf "unclean" gesetzt
|
||||
* Hook: Bei Änderung eines Dokuments wird syncStatus auf "unclean" gesetzt
|
||||
* und alle verknüpften AIKnowledge Junction-Table-Einträge werden aktualisiert
|
||||
*/
|
||||
class UpdateJunctionSyncStatus implements AfterSave
|
||||
{
|
||||
@@ -28,10 +28,21 @@ class UpdateJunctionSyncStatus implements AfterSave
|
||||
}
|
||||
|
||||
try {
|
||||
// Update AdvowareAkten Junction-Tables
|
||||
$this->updateAdvowareAktenJunctions($entity);
|
||||
// Set syncStatus = 'unclean' directly on CDokumente entity
|
||||
// (only if it has an AdvowareAkte linked)
|
||||
if ($entity->get('cAdvowareAktenId')) {
|
||||
$entity->set('syncStatus', 'unclean');
|
||||
$this->entityManager->saveEntity($entity, ['silent' => true, 'skipHooks' => true]);
|
||||
|
||||
// Also update the parent AdvowareAkte
|
||||
$akte = $this->entityManager->getEntity('CAdvowareAkten', $entity->get('cAdvowareAktenId'));
|
||||
if ($akte) {
|
||||
$akte->set('syncStatus', 'unclean');
|
||||
$this->entityManager->saveEntity($akte, ['silent' => true, 'skipHooks' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
// Update AIKnowledge Junction-Tables
|
||||
// Update AIKnowledge Junction-Tables (unchanged)
|
||||
$this->updateAIKnowledgeJunctions($entity);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
@@ -65,50 +76,6 @@ class UpdateJunctionSyncStatus implements AfterSave
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update AdvowareAkten Junction-Tables
|
||||
*/
|
||||
private function updateAdvowareAktenJunctions(Entity $entity): void
|
||||
{
|
||||
$updateQuery = $this->entityManager->getQueryBuilder()
|
||||
->update()
|
||||
->in('CAdvowareAktenDokumente')
|
||||
->set(['syncstatus' => 'unclean'])
|
||||
->where([
|
||||
'cDokumenteId' => $entity->getId(),
|
||||
'deleted' => false
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($updateQuery);
|
||||
|
||||
// Hole alle betroffenen AdvowareAkten IDs
|
||||
$selectQuery = $this->entityManager->getQueryBuilder()
|
||||
->select(['cAdvowareAktenId'])
|
||||
->from('CAdvowareAktenDokumente')
|
||||
->where([
|
||||
'cDokumenteId' => $entity->getId(),
|
||||
'deleted' => false
|
||||
])
|
||||
->build();
|
||||
|
||||
$pdoStatement = $this->entityManager->getQueryExecutor()->execute($selectQuery);
|
||||
$rows = $pdoStatement->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Trigger Update auf jeder AdvowareAkte (um CheckGlobalSyncStatus Hook auszulösen)
|
||||
foreach ($rows as $row) {
|
||||
$aktenId = $row['cAdvowareAktenId'] ?? null;
|
||||
if ($aktenId) {
|
||||
$akte = $this->entityManager->getEntity('CAdvowareAkten', $aktenId);
|
||||
if ($akte) {
|
||||
// Force Update ohne Hook-Loop
|
||||
$akte->set('syncStatus', 'unclean');
|
||||
$this->entityManager->saveEntity($akte);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update AIKnowledge Junction-Tables
|
||||
*/
|
||||
@@ -147,7 +114,7 @@ class UpdateJunctionSyncStatus implements AfterSave
|
||||
if ($knowledge) {
|
||||
// Force Update ohne Hook-Loop
|
||||
$knowledge->set('syncStatus', 'unclean');
|
||||
$this->entityManager->saveEntity($knowledge);
|
||||
$this->entityManager->saveEntity($knowledge, ['silent' => true, 'skipHooks' => true]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,11 @@ class PropagateDocuments implements AfterRelate, AfterUnrelate
|
||||
->getRelation($entity, 'advowareAkten')
|
||||
->findOne();
|
||||
|
||||
// Verknüpfe Dokument mit AdvowareAkten
|
||||
// Set direct belongsTo relationship on document
|
||||
if ($advowareAkten) {
|
||||
$this->relateDocument($advowareAkten, 'dokumentes', $foreignEntity);
|
||||
$foreignEntity->set('cAdvowareAktenId', $advowareAkten->getId());
|
||||
$foreignEntity->set('syncStatus', 'new'); // Mark as new for Advoware sync
|
||||
$this->entityManager->saveEntity($foreignEntity, ['silent' => true, 'skipHooks' => true]);
|
||||
}
|
||||
|
||||
// Hole verbundene AIKnowledge
|
||||
@@ -93,9 +95,10 @@ class PropagateDocuments implements AfterRelate, AfterUnrelate
|
||||
->getRelation($entity, 'advowareAkten')
|
||||
->findOne();
|
||||
|
||||
// Entknüpfe Dokument von AdvowareAkten
|
||||
if ($advowareAkten) {
|
||||
$this->unrelateDocument($advowareAkten, 'dokumentes', $foreignEntity);
|
||||
// Remove direct belongsTo relationship from document
|
||||
if ($advowareAkten && $foreignEntity->get('cAdvowareAktenId') === $advowareAkten->getId()) {
|
||||
$foreignEntity->set('cAdvowareAktenId', null);
|
||||
$this->entityManager->saveEntity($foreignEntity, ['silent' => true, 'skipHooks' => true]);
|
||||
}
|
||||
|
||||
// Hole verbundene AIKnowledge
|
||||
|
||||
@@ -45,9 +45,11 @@ class PropagateDocuments implements AfterRelate, AfterUnrelate
|
||||
->getRelation($entity, 'advowareAkten')
|
||||
->findOne();
|
||||
|
||||
// Verknüpfe Dokument mit AdvowareAkten
|
||||
// Set direct belongsTo relationship on document
|
||||
if ($advowareAkten) {
|
||||
$this->relateDocument($advowareAkten, 'dokumentes', $foreignEntity);
|
||||
$foreignEntity->set('cAdvowareAktenId', $advowareAkten->getId());
|
||||
$foreignEntity->set('syncStatus', 'new'); // Mark as new for Advoware sync
|
||||
$this->entityManager->saveEntity($foreignEntity, ['silent' => true, 'skipHooks' => true]);
|
||||
}
|
||||
|
||||
// Hole verbundene AIKnowledge
|
||||
@@ -93,9 +95,10 @@ class PropagateDocuments implements AfterRelate, AfterUnrelate
|
||||
->getRelation($entity, 'advowareAkten')
|
||||
->findOne();
|
||||
|
||||
// Entknüpfe Dokument von AdvowareAkten
|
||||
if ($advowareAkten) {
|
||||
$this->unrelateDocument($advowareAkten, 'dokumentes', $foreignEntity);
|
||||
// Remove direct belongsTo relationship from document
|
||||
if ($advowareAkten && $foreignEntity->get('cAdvowareAktenId') === $advowareAkten->getId()) {
|
||||
$foreignEntity->set('cAdvowareAktenId', null);
|
||||
$this->entityManager->saveEntity($foreignEntity, ['silent' => true, 'skipHooks' => true]);
|
||||
}
|
||||
|
||||
// Hole verbundene AIKnowledge
|
||||
|
||||
Reference in New Issue
Block a user