diff --git a/custom/Espo/Custom/Hooks/CAkten/UpdateLastSyncFromDocuments.php b/custom/Espo/Custom/Hooks/CAkten/UpdateLastSyncFromDocuments.php index a812585c..e6410835 100644 --- a/custom/Espo/Custom/Hooks/CAkten/UpdateLastSyncFromDocuments.php +++ b/custom/Espo/Custom/Hooks/CAkten/UpdateLastSyncFromDocuments.php @@ -14,97 +14,78 @@ class UpdateLastSyncFromDocuments implements BeforeSave public function __construct( private \Espo\ORM\EntityManager $entityManager ) {} - + public function beforeSave(Entity $entity, SaveOptions $options): void { // Überspringe, wenn skipHooks gesetzt ist (verhindert Loops) if ($options->get('skipHooks')) { return; } - + // Nur wenn Entity bereits existiert (nicht bei Create) if ($entity->isNew()) { return; } - + try { + $pdo = $this->entityManager->getPDO(); + $aktenId = $entity->getId(); + // Hole das neueste lastSyncTimestamp aller verknüpften Dokumente - $query = $this->entityManager->getQueryBuilder() - ->select([ - 'MAX:lastSyncTimestamp' => 'maxLastSync', - 'COUNT:id' => 'dokumentCount' - ]) - ->from('CDokumente') - ->where([ - 'cAktenId' => $entity->getId(), - 'deleted' => false, - 'lastSyncTimestamp!=' => null - ]) - ->build(); - - $pdoStatement = $this->entityManager->getQueryExecutor()->execute($query); - $result = $pdoStatement->fetch(\PDO::FETCH_ASSOC); - + $stmt = $pdo->prepare( + "SELECT MAX(last_sync_timestamp) AS maxLastSync + FROM c_dokumente + WHERE c_akten_id = :aktenId AND deleted = 0 AND last_sync_timestamp IS NOT NULL" + ); + $stmt->execute([':aktenId' => $aktenId]); + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + if ($result && $result['maxLastSync']) { - // Setze lastSync auf den neuesten Sync-Timestamp $entity->set('lastSync', $result['maxLastSync']); } - - // Berechne auch syncStatus basierend auf verknüpften Dokumenten - $this->updateSyncStatus($entity); - + + // Berechne syncStatus basierend auf verknüpften Dokumenten + $this->updateSyncStatus($entity, $pdo, $aktenId); + } catch (\Exception $e) { - // Fehler loggen, aber nicht werfen (um Save nicht zu blockieren) $GLOBALS['log']->error('CAkten UpdateLastSyncFromDocuments Hook Error: ' . $e->getMessage()); } } - - /** - * Aktualisiert syncStatus basierend auf den Status der verknüpften Dokumente - */ - private function updateSyncStatus(Entity $entity): void + + private function updateSyncStatus(Entity $entity, \PDO $pdo, string $aktenId): void { - $query = $this->entityManager->getQueryBuilder() - ->select(['syncStatus']) - ->from('CDokumente') - ->where([ - 'cAktenId' => $entity->getId(), - 'deleted' => false - ]) - ->build(); - - $pdoStatement = $this->entityManager->getQueryExecutor()->execute($query); - $rows = $pdoStatement->fetchAll(\PDO::FETCH_ASSOC); - - // Wenn keine Dokumente verknüpft, setze auf "unclean" + $stmt = $pdo->prepare( + "SELECT sync_status FROM c_dokumente + WHERE c_akten_id = :aktenId AND deleted = 0" + ); + $stmt->execute([':aktenId' => $aktenId]); + $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC); + if (empty($rows)) { $entity->set('syncStatus', 'unclean'); return; } - - // Prüfe, ob irgendein Dokument "new" oder "unclean" ist + $hasUnsynced = false; $hasFailed = false; - + foreach ($rows as $row) { - $status = $row['syncStatus'] ?? null; - + $status = $row['sync_status'] ?? null; + if ($status === 'failed') { $hasFailed = true; } - + if ($status === 'new' || $status === 'unclean' || $status === null || $status === '') { $hasUnsynced = true; } } - - // Setze globalen Status + if ($hasFailed) { $entity->set('syncStatus', 'failed'); } elseif ($hasUnsynced) { $entity->set('syncStatus', 'unclean'); } else { - // Alle Dokumente sind "synced" $entity->set('syncStatus', 'synced'); } } diff --git a/custom/Espo/Custom/Resources/layouts/CDokumente/detail.json b/custom/Espo/Custom/Resources/layouts/CDokumente/detail.json index a4f42b4d..8a941e93 100644 --- a/custom/Espo/Custom/Resources/layouts/CDokumente/detail.json +++ b/custom/Espo/Custom/Resources/layouts/CDokumente/detail.json @@ -99,5 +99,38 @@ "noteText": null, "noteStyle": "info", "customLabel": "Advoware Sync" + }, + { + "rows": [ + [ + { + "name": "aiSyncStatus" + }, + { + "name": "aiLastSync" + } + ], + [ + { + "name": "aiCollectionId" + }, + { + "name": "aiFileId" + } + ], + [ + { + "name": "aiSyncHash" + }, + {"isFull": false} + ] + ], + "dynamicLogicVisible": null, + "style": "default", + "tabBreak": false, + "hidden": false, + "noteText": null, + "noteStyle": "info", + "customLabel": "AI Sync" } ] \ No newline at end of file diff --git a/custom/scripts/check_role_permissions.php b/custom/scripts/check_role_permissions.php new file mode 100644 index 00000000..e87ca3b7 --- /dev/null +++ b/custom/scripts/check_role_permissions.php @@ -0,0 +1,117 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $targetRole = 'api-user-motia'; + + echo "=== Rolle: $targetRole ===\n\n"; + + // 1. Rolle in DB finden + $stmt = $pdo->prepare("SELECT id, name, data FROM role WHERE name = ?"); + $stmt->execute([$targetRole]); + $role = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$role) { + echo "FEHLER: Rolle '$targetRole' nicht gefunden!\n"; + + // Alle Rollen auflisten + echo "\nVorhandene Rollen:\n"; + $stmt = $pdo->query("SELECT id, name FROM role ORDER BY name"); + foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) { + echo " - {$r['name']} (id: {$r['id']})\n"; + } + exit(1); + } + + echo "Rollen-ID: {$role['id']}\n\n"; + + $data = json_decode($role['data'], true); + if (!$data) { + echo "FEHLER: Rollen-Daten konnten nicht geparst werden.\n"; + echo "Raw data: " . substr($role['data'], 0, 500) . "\n"; + exit(1); + } + + // 2. Scope-Permissions anzeigen (table-Abschnitt) + $table = $data['table'] ?? []; + + echo "=== Scope-Permissions (table) ===\n"; + $interesting = ['CAkten', 'CAdvowareAkten', 'CAktenCDokumente', 'CAdvowareAktenCDokumente', 'CDokumente', 'CAIKnowledge', 'CAICollection']; + echo "\nGezielte Suche nach relevanten Entitäten:\n"; + echo str_repeat("-", 60) . "\n"; + printf("%-40s %-10s %-10s\n", "Entität", "create", "read"); + echo str_repeat("-", 60) . "\n"; + + foreach ($interesting as $entity) { + if (isset($table[$entity])) { + $perms = $table[$entity]; + $create = $perms['create'] ?? '(n/a)'; + $read = $perms['read'] ?? '(n/a)'; + printf("%-40s %-10s %-10s\n", $entity, $create, $read); + } else { + printf("%-40s %-10s\n", $entity, '--- FEHLT ---'); + } + } + + echo "\n=== Alle Scope-Einträge (table) ===\n"; + echo str_repeat("-", 60) . "\n"; + ksort($table); + foreach ($table as $entity => $perms) { + $read = $perms['read'] ?? '-'; + $create = $perms['create'] ?? '-'; + $edit = $perms['edit'] ?? '-'; + $delete = $perms['delete'] ?? '-'; + printf("%-40s read=%-8s create=%-8s edit=%-8s delete=%-8s\n", $entity, $read, $create, $edit, $delete); + } + + // 3. Field-Permissions anzeigen + $fieldTable = $data['fieldTable'] ?? []; + echo "\n=== Feld-Permissions (fieldTable) für relevante Entitäten ===\n"; + foreach ($interesting as $entity) { + if (isset($fieldTable[$entity])) { + echo "\n $entity:\n"; + foreach ($fieldTable[$entity] as $field => $perm) { + echo " $field: " . json_encode($perm) . "\n"; + } + } + } + + // 4. API-User → Rolle Zuordnung prüfen + echo "\n=== API-User mit Rolle '$targetRole' ===\n"; + $stmt = $pdo->query(" + SELECT u.id, u.user_name, u.name, u.type, u.is_active + FROM `user` u + JOIN entity_user eu ON eu.user_id = u.id + JOIN role r ON r.id = eu.entity_id AND eu.entity_type = 'Role' + WHERE r.name = " . $pdo->quote($targetRole) + ); + $users = $stmt->fetchAll(PDO::FETCH_ASSOC); + if ($users) { + foreach ($users as $u) { + $active = $u['is_active'] ? 'aktiv' : 'inaktiv'; + echo " - {$u['user_name']} ({$u['name']}) type={$u['type']} [$active]\n"; + } + } else { + echo " Keine User direkt zugeordnet.\n"; + // Alternativ: per teams oder direkte Rollen-ID im User + $stmt2 = $pdo->prepare("SELECT id, user_name, name, type FROM `user` WHERE is_active = 1 AND type = 'api'"); + $stmt2->execute(); + echo "\n Alle API-User:\n"; + foreach ($stmt2->fetchAll(PDO::FETCH_ASSOC) as $u) { + echo " - {$u['user_name']} ({$u['name']}) type={$u['type']}\n"; + } + } + +} catch (Exception $e) { + echo "FEHLER: " . $e->getMessage() . "\n"; +} diff --git a/data/config.php b/data/config.php index be80af34..bcfb9292 100644 --- a/data/config.php +++ b/data/config.php @@ -360,7 +360,7 @@ return [ 0 => 'youtube.com', 1 => 'google.com' ], - 'microtime' => 1774518628.185454, + 'microtime' => 1774526770.942727, '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 1e0b29e2..2003120b 100644 --- a/data/state.php +++ b/data/state.php @@ -1,7 +1,7 @@ 1774518628, - 'microtimeState' => 1774518628.373828, + 'cacheTimestamp' => 1774530224, + 'microtimeState' => 1774530224.017606, 'currencyRates' => [ 'EUR' => 1.0 ],