#!/usr/bin/env python3 """ KI-Einstiegsscript für EspoCRM Projekt ====================================== Gibt einen vollständigen Überblick über das Projekt aus: - README.md Inhalt - Automatisch ermittelte Projektstruktur - Entitäten und ihre Felder - Beziehungen zwischen Entitäten - Custom PHP Klassen - Workflows - Frontend Anpassungen Ziel: KI erhält aktuellen Informationsstand für die Programmierung """ import json import os from pathlib import Path from collections import defaultdict from typing import Dict, List, Set # Basis-Pfad des Projekts BASE_PATH = Path("/var/lib/docker/volumes/vmh-espocrm_espocrm/_data") CUSTOM_PATH = BASE_PATH / "custom/Espo/Custom" README_PATH = BASE_PATH / "README.md" def print_section(title: str, symbol: str = "="): """Gibt eine formatierte Section-Überschrift aus.""" print(f"\n{symbol * 80}") print(f"{title.center(80)}") print(f"{symbol * 80}\n") def print_subsection(title: str): """Gibt eine Unterüberschrift aus.""" print(f"\n{'─' * 80}") print(f"► {title}") print(f"{'─' * 80}") def read_readme(): """Hinweis auf README.md - wird nicht direkt ausgegeben.""" # README.md wird nicht ausgegeben - zu lang für direkte Ausgabe # Hinweis erfolgt am Ende des Scripts pass def analyze_entities(): """Analysiert alle Custom Entitäten und ihre Definitionen.""" print_section("ENTITÄTEN ANALYSE", "=") entity_defs_path = CUSTOM_PATH / "Resources/metadata/entityDefs" scopes_path = CUSTOM_PATH / "Resources/metadata/scopes" if not entity_defs_path.exists(): print("⚠️ Keine Custom Entitäten gefunden!") return {} entities = {} # Alle entityDefs Dateien durchgehen for entity_file in sorted(entity_defs_path.glob("*.json")): entity_name = entity_file.stem try: with open(entity_file, 'r', encoding='utf-8') as f: entity_def = json.load(f) # Scope-Informationen laden scope_info = {} scope_file = scopes_path / f"{entity_name}.json" if scope_file.exists(): with open(scope_file, 'r', encoding='utf-8') as f: scope_info = json.load(f) entities[entity_name] = { 'def': entity_def, 'scope': scope_info, 'file': entity_file } print_subsection(f"Entität: {entity_name}") # Scope Informationen if scope_info: print("\n📋 Scope:") for key, value in scope_info.items(): if key in ['entity', 'module', 'object', 'tab', 'acl', 'customizable', 'stream', 'disabled', 'type', 'isCustom']: print(f" • {key}: {value}") # Felder if 'fields' in entity_def and entity_def['fields']: print(f"\n🔧 Felder ({len(entity_def['fields'])}):") for field_name, field_def in sorted(entity_def['fields'].items()): field_type = field_def.get('type', 'unknown') required = " [REQUIRED]" if field_def.get('required') else "" disabled = " [DISABLED]" if field_def.get('disabled') else "" isCustom = " [CUSTOM]" if field_def.get('isCustom') else "" extra_info = [] if field_type == 'enum': options = field_def.get('options', []) extra_info.append(f"options: {len(options)}") elif field_type == 'link': entity = field_def.get('entity', 'unknown') extra_info.append(f"→ {entity}") elif field_type in ['varchar', 'text']: if 'maxLength' in field_def: extra_info.append(f"max: {field_def['maxLength']}") elif field_type == 'currency': extra_info.append("€") extra_str = f" ({', '.join(extra_info)})" if extra_info else "" print(f" • {field_name}: {field_type}{extra_str}{required}{disabled}{isCustom}") # Beziehungen (Links) if 'links' in entity_def and entity_def['links']: print(f"\n🔗 Beziehungen ({len(entity_def['links'])}):") for link_name, link_def in sorted(entity_def['links'].items()): link_type = link_def.get('type', 'unknown') foreign_entity = link_def.get('entity', 'unknown') foreign_link = link_def.get('foreign', 'N/A') relation_name = link_def.get('relationName', '') disabled = " [DISABLED]" if link_def.get('disabled') else "" relation_info = f" (relationName: {relation_name})" if relation_name else "" print(f" • {link_name} [{link_type}] → {foreign_entity}.{foreign_link}{relation_info}{disabled}") # Formula Scripts formula_file = CUSTOM_PATH / f"Resources/metadata/formula/{entity_name}.json" if formula_file.exists(): with open(formula_file, 'r', encoding='utf-8') as f: formula_def = json.load(f) print(f"\n⚡ Formula Scripts:") for script_type in ['beforeSaveScript', 'beforeSaveApiScript', 'afterSaveScript']: if script_type in formula_def: script = formula_def[script_type] lines = script.count('\n') + 1 print(f" • {script_type}: {lines} Zeilen") print() except json.JSONDecodeError as e: print(f"❌ Fehler beim Parsen von {entity_file.name}: {e}") except Exception as e: print(f"❌ Fehler bei {entity_file.name}: {e}") return entities def analyze_relationships(entities: Dict): """Analysiert Beziehungen zwischen Entitäten.""" print_section("BEZIEHUNGSGRAPH", "=") # Sammle alle Beziehungen relationships = defaultdict(list) for entity_name, entity_data in entities.items(): entity_def = entity_data['def'] if 'links' not in entity_def: continue for link_name, link_def in entity_def['links'].items(): if link_def.get('disabled'): continue target_entity = link_def.get('entity') link_type = link_def.get('type', 'unknown') if target_entity: relationships[entity_name].append({ 'link_name': link_name, 'type': link_type, 'target': target_entity, 'foreign': link_def.get('foreign', 'N/A') }) # Gib Beziehungsgraph aus for entity_name in sorted(relationships.keys()): links = relationships[entity_name] if links: print(f"\n{entity_name}:") for link in links: arrow = "→" if link['type'] in ['belongsTo', 'hasOne'] else "⇄" print(f" {arrow} {link['link_name']} [{link['type']}] → {link['target']}") def analyze_custom_classes(): """Analysiert Custom PHP Klassen.""" print_section("CUSTOM PHP KLASSEN", "=") classes_path = CUSTOM_PATH / "Classes" if not classes_path.exists(): print("ℹ️ Keine Custom PHP Klassen gefunden.") return php_files = list(classes_path.rglob("*.php")) if not php_files: print("ℹ️ Keine Custom PHP Klassen gefunden.") return # Gruppiere nach Typ by_type = defaultdict(list) for php_file in php_files: relative_path = php_file.relative_to(classes_path) parts = relative_path.parts if len(parts) > 0: class_type = parts[0] by_type[class_type].append(relative_path) for class_type in sorted(by_type.keys()): print(f"\n📦 {class_type}:") for file_path in sorted(by_type[class_type]): print(f" • {file_path}") def analyze_workflows(): """Analysiert Workflows.""" print_section("WORKFLOWS", "=") workflows_path = BASE_PATH / "custom/workflows" if not workflows_path.exists(): print("ℹ️ Keine Workflows gefunden.") return workflow_files = list(workflows_path.glob("*.json")) if not workflow_files: print("ℹ️ Keine Workflows gefunden.") return for workflow_file in sorted(workflow_files): try: with open(workflow_file, 'r', encoding='utf-8') as f: workflow = json.load(f) name = workflow.get('name', workflow_file.stem) entity = workflow.get('entityType', 'N/A') is_active = workflow.get('isActive', False) status = "✓ AKTIV" if is_active else "✗ INAKTIV" print(f"\n📋 {name} ({workflow_file.name})") print(f" Entität: {entity}") print(f" Status: {status}") # Trigger-Typ if 'type' in workflow: print(f" Trigger: {workflow['type']}") # Aktionen if 'actions' in workflow: actions = workflow['actions'] print(f" Aktionen ({len(actions)}):") for action in actions[:5]: # Erste 5 Aktionen action_type = action.get('type', 'unknown') print(f" • {action_type}") if len(actions) > 5: print(f" ... und {len(actions) - 5} weitere") except Exception as e: print(f"❌ Fehler beim Lesen von {workflow_file.name}: {e}") def analyze_frontend(): """Analysiert Frontend Anpassungen.""" print_section("FRONTEND ANPASSUNGEN", "=") # JavaScript js_path = BASE_PATH / "client/custom/src" if js_path.exists(): js_files = list(js_path.rglob("*.js")) if js_files: print_subsection("JavaScript Files") for js_file in sorted(js_files): relative = js_file.relative_to(js_path) print(f" • {relative}") # CSS css_path = BASE_PATH / "client/custom/css" if css_path.exists(): css_files = list(css_path.glob("*.css")) if css_files: print_subsection("CSS Files") for css_file in sorted(css_files): print(f" • {css_file.name}") # App Client Config client_config = CUSTOM_PATH / "Resources/metadata/app/client.json" if client_config.exists(): print_subsection("App Client Config") try: with open(client_config, 'r', encoding='utf-8') as f: config = json.load(f) if 'cssList' in config: print(" CSS List:") for css in config['cssList']: if css != "__APPEND__": print(f" • {css}") if 'scriptList' in config: print(" Script List:") for script in config['scriptList']: if script != "__APPEND__": print(f" • {script}") except Exception as e: print(f" ❌ Fehler: {e}") def analyze_layouts(): """Analysiert Custom Layouts.""" print_section("CUSTOM LAYOUTS", "=") layouts_path = CUSTOM_PATH / "Resources/metadata/layouts" if not layouts_path.exists(): print("ℹ️ Keine Custom Layouts gefunden.") return # Gruppiere nach Entität entities = defaultdict(list) for layout_file in layouts_path.rglob("*.json"): relative = layout_file.relative_to(layouts_path) entity = relative.parts[0] if len(relative.parts) > 1 else "unknown" layout_type = relative.stem entities[entity].append(layout_type) for entity in sorted(entities.keys()): layouts = sorted(entities[entity]) print(f"\n{entity}:") print(f" Layouts: {', '.join(layouts)}") def analyze_i18n(): """Analysiert Internationalisierung.""" print_section("INTERNATIONALISIERUNG (i18n)", "=") i18n_path = CUSTOM_PATH / "Resources/i18n" if not i18n_path.exists(): print("ℹ️ Keine i18n Dateien gefunden.") return languages = [d.name for d in i18n_path.iterdir() if d.is_dir()] print(f"Unterstützte Sprachen: {', '.join(sorted(languages))}") for lang in sorted(languages): lang_path = i18n_path / lang json_files = list(lang_path.glob("*.json")) if json_files: print(f"\n{lang}:") print(f" Übersetzungsdateien: {len(json_files)}") # Zähle Labels total_labels = 0 for json_file in json_files: try: with open(json_file, 'r', encoding='utf-8') as f: data = json.load(f) # Rekursiv Labels zählen def count_labels(obj): if isinstance(obj, dict): return sum(count_labels(v) for v in obj.values()) elif isinstance(obj, str): return 1 return 0 total_labels += count_labels(data) except: pass print(f" Geschätzte Labels: ~{total_labels}") def print_quick_stats(): """Gibt eine Schnellübersicht aus.""" print_section("SCHNELLÜBERSICHT", "=") stats = {} # Entitäten entity_defs_path = CUSTOM_PATH / "Resources/metadata/entityDefs" if entity_defs_path.exists(): stats['Entities'] = len(list(entity_defs_path.glob("*.json"))) # PHP Klassen classes_path = CUSTOM_PATH / "Classes" if classes_path.exists(): stats['PHP Classes'] = len(list(classes_path.rglob("*.php"))) # Workflows workflows_path = BASE_PATH / "custom/workflows" if workflows_path.exists(): stats['Workflows'] = len(list(workflows_path.glob("*.json"))) # JS Files js_path = BASE_PATH / "client/custom/src" if js_path.exists(): stats['JavaScript Files'] = len(list(js_path.rglob("*.js"))) # CSS Files css_path = BASE_PATH / "client/custom/css" if css_path.exists(): stats['CSS Files'] = len(list(css_path.glob("*.css"))) # Layouts layouts_path = CUSTOM_PATH / "Resources/metadata/layouts" if layouts_path.exists(): stats['Custom Layouts'] = len(list(layouts_path.rglob("*.json"))) # i18n i18n_path = CUSTOM_PATH / "Resources/i18n" if i18n_path.exists(): languages = [d.name for d in i18n_path.iterdir() if d.is_dir()] stats['Languages'] = len(languages) print("📊 Projekt-Statistiken:\n") for key, value in stats.items(): print(f" • {key:<20} {value:>5}") def main(): """Hauptfunktion - führt alle Analysen aus.""" print("\n" + "=" * 80) print("KI-EINSTIEGSSCRIPT FÜR ESPOCRM PROJEKT".center(80)) print("Automatische Projekt-Analyse für KI-basierte Programmierung".center(80)) print("=" * 80) # 1. Schnellübersicht print_quick_stats() # 2. README.md read_readme() # 3. Entitäten analysieren entities = analyze_entities() # 4. Beziehungsgraph if entities: analyze_relationships(entities) # 5. Custom Layouts analyze_layouts() # 6. Custom PHP Klassen analyze_custom_classes() # 7. Workflows analyze_workflows() # 8. Frontend analyze_frontend() # 9. i18n analyze_i18n() # Abschluss print_section("ANALYSE ABGESCHLOSSEN", "=") print("\n✅ Die KI hat jetzt einen vollständigen Überblick über das Projekt!") print(" Alle Entitäten, Beziehungen, Custom Klassen und Frontend-Anpassungen wurden erfasst.\n") # Hinweis auf README.md print("=" * 80) print("📖 WICHTIG: Bitte jetzt die README.md lesen!") print("=" * 80) print(f"\n Pfad: {README_PATH}") print(f"\n Die README.md enthält {README_PATH.stat().st_size // 1024}KB detaillierte Projektdokumentation:") print(" • Struktur und Funktionsweise") print(" • Rebuild-Prozess") print(" • Workflow-Verwaltung") print(" • Formula-Scripts und Custom PHP") print(" • Internationalisierung") print(" • Troubleshooting") print("\n 📌 UNBEDINGT LESEN für vollständiges Projektverständnis!\n") if __name__ == "__main__": main()