# LeoCRM — Core Plugin & Dependency Konzept ## 1. Ziel LeoCRM soll modular zu einem ERP ausgebaut werden. Die Plugin-Architektur ist bereits vorhanden (Manifest, BasePlugin, Registry), aber es fehlen: 1. **Core Plugins** — unverzichtbare Basis-Plugins die immer aktiv sind 2. **Dependency Resolution** — Plugins können Abhängigkeiten deklarieren und diese werden durchgesetzt 3. **ERP-Module** — fachliche Erweiterungen die auf Core-Plugins aufbauen ## 2. Plugin-Kategorien ### 2.1 Core Plugins (`is_core: true`) - **Immer aktiv** — können nicht deaktiviert oder deinstalliert werden - **Werden als erste geladen** — vor allen Nicht-Core-Plugins - **Basis-Funktionalität** die andere Plugins voraussetzen - **Beispiele:** - `permissions` — RBAC-System - `entity_links` — Querverweise zwischen Entitäten - `tags` — Tagging-System - `report_generator` — Report- & Dokumentgenerator - `audit` — Audit-Log (bereits im Core, nicht als Plugin) ### 2.2 Builtin Plugins - **Mitgeliefert aber optional** — können deaktiviert werden - **Dürfen Core-Dependencies deklarieren** - **Beispiele:** `dms`, `mail`, `calendar` ### 2.3 Custom Plugins - **Nutzer-/Drittanbieter-Plugins** — zur Laufzeit installierbar - **Müssen Dependencies explizit deklarieren** - **Beispiele:** ERP-Module (Invoicing, Inventory, HR, etc.) ## 3. Manifest-Erweiterung ```python class PluginManifest(BaseModel): # ... bestehende Felder ... # NEU: Core-Plugin Flag is_core: bool = Field( default=False, description="Core plugins cannot be deactivated and load first" ) # NEU: Mindestversion für Dependencies dependencies: list[str] = Field( default_factory=list, description="Plugin names this plugin requires (must be installed and active)" ) # ERWEITERT: Semantic dependency with version # dependency: str = "permissions>=1.0.0" # Format: plugin_name[>=|>|<=|<|==version] ``` ## 4. Dependency Resolution ### 4.1 Topological Sort Die Registry muss Plugins in Abhängigkeits-Reihenfolge laden: ``` 1. permissions (is_core, keine deps) 2. entity_links (is_core, deps: [permissions]) 3. tags (is_core, deps: [permissions]) 4. report_generator (is_core, deps: [permissions, entity_links]) 5. dms (deps: [permissions]) 6. mail (deps: [permissions]) 7. calendar (deps: [permissions]) 8. invoicing (deps: [permissions, report_generator]) ← ERP-Modul ``` ### 4.2 Algorithmus ```python def resolve_load_order(plugins: dict[str, BasePlugin]) -> list[str]: """Topological sort: Core first, then by dependency order.""" # 1. Kahn's Algorithm oder DFS-based topo sort # 2. Core-Plugins bekommen Priorität bei gleichrangigen Abhängigkeiten # 3. Zyklus-Erkennung: RuntimeError bei zirkulären Dependencies # 4. Fehlende Dependency: RuntimeError mit klarer Meldung ``` ### 4.3 Validierung beim Installieren ```python async def validate_dependencies(plugin: BasePlugin, db: AsyncSession) -> None: """Prüft vor Installation ob alle Dependencies erfüllt sind.""" for dep_name in plugin.manifest.dependencies: dep = await get_plugin_record(db, dep_name) if dep is None: raise PluginDependencyError( f"Plugin '{plugin.name}' requires '{dep_name}' which is not installed" ) if not dep.active: raise PluginDependencyError( f"Plugin '{plugin.name}' requires '{dep_name}' to be active" ) ``` ### 4.4 Deaktivierungs-Schutz ```python async def deactivate_plugin(name: str, db: AsyncSession) -> None: """Verhindert Deaktivierung wenn andere Plugins abhängen.""" # 1. Prüfe ob Plugin is_core → Fehler # 2. Prüfe ob andere aktive Plugins dieses Plugin als Dependency haben → Fehler dependents = await get_dependent_plugins(db, name) if dependents: raise PluginDependencyError( f"Cannot deactivate '{name}': {dependents} still depend on it" ) ``` ## 5. Report Generator als Core Plugin ### 5.1 Konzept Der Report Generator ist ein **Core Plugin** das Dokumente und Reports erzeugt. ERP-Module (Invoicing, Inventory, etc.) nutzen ihn als Dependency. ### 5.2 Manifest ```python class ReportGeneratorPlugin(BasePlugin): manifest = PluginManifest( name="report_generator", version="1.0.0", display_name="Report Generator", description="Generates PDF/Excel/CSV reports from templates and data sources", is_core=True, dependencies=["permissions", "entity_links"], routes=[ PluginRouteDef( path="/api/v1/reports", module="app.plugins.builtins.report_generator.routes", router_attr="router", ), ], events=["report.requested", "report.generated"], migrations=["0001_initial.sql"], permissions=["reports.read", "reports.generate", "reports.manage_templates"], ) ``` ### 5.3 Funktionalität - **Template Engine** — Jinja2-basierte Templates für PDF/Excel/CSV - **Data Sources** — SQL-Queries oder Python-Funktionen als Datenquelle - **Scheduling** — Cron-basierte Report-Generierung - **Output** — PDF (WeasyPrint), Excel (openpyxl), CSV, JSON - **Storage** — Reports werden im DMS gespeichert (wenn DMS aktiv) - **Distribution** — E-Mail-Versand, Download, API ### 5.4 API Endpoints ``` GET /api/v1/reports/templates — Liste aller Templates POST /api/v1/reports/templates — Template erstellen GET /api/v1/reports/templates/{id} — Template Details PUT /api/v1/reports/templates/{id} — Template aktualisieren DELETE /api/v1/reports/templates/{id} — Template löschen POST /api/v1/reports/generate — Report generieren (async) GET /api/v1/reports/{id} — Report Status/Download GET /api/v1/reports/{id}/download — Report herunterladen GET /api/v1/reports/scheduled — Geplante Reports POST /api/v1/reports/scheduled — Report planen DELETE /api/v1/reports/scheduled/{id} — Geplanten Report löschen ``` ### 5.5 ERP-Nutzung Ein ERP-Modul "Invoicing" würde den Report Generator nutzen: ```python class InvoicingPlugin(BasePlugin): manifest = PluginManifest( name="invoicing", version="1.0.0", display_name="Invoicing", description="Invoice management with PDF generation", dependencies=["permissions", "report_generator", "currencies", "taxes"], routes=[ PluginRouteDef( path="/api/v1/invoicing", module="app.plugins.builtins.invoicing.routes", router_attr="router", ), ], events=["invoice.created", "invoice.sent", "invoice.paid"], ) async def on_invoice_created(self, payload: dict) -> None: """When an invoice is created, generate PDF via report_generator.""" # Ruft report_generator API auf: POST /api/v1/reports/generate # Template: "invoice_template" # Data: payload (invoice data) # Output: PDF ``` ## 6. ERP-Aufbau-Strategie ### 6.1 Phasen | Phase | Plugins | Funktionalität | |---|---|---| | **Phase 1** | `report_generator` (Core) | Report- & Dokumentgenerator | | **Phase 2** | `invoicing` | Rechnungen mit PDF-Generierung | | **Phase 3** | `inventory` | Lagerverwaltung, Bestandsführung | | **Phase 4** | `purchase_orders` | Bestellungen, Lieferanten | | **Phase 5** | `hr` | Mitarbeiter, Gehalt, Urlaub | | **Phase 6** | `accounting` | Buchhaltung, Buchungen, Bilanz | ### 6.2 Abhängigkeits-Graph ``` permissions (Core) ├── entity_links (Core) ├── tags (Core) ├── report_generator (Core) │ ├── invoicing │ │ └── accounting │ ├── purchase_orders │ │ └── accounting │ └── hr ├── currencies (Core, bereits als Basis-Feature) ├── taxes (Core, bereits als Basis-Feature) ├── dms (Builtin) ├── mail (Builtin) └── calendar (Builtin) ``` ### 6.3 Implementierungs-Prinzipien 1. **Jedes ERP-Modul ist ein Plugin** — kein festcodiertes ERP 2. **Core-Plugins sind stabil** — brechen nie andere Plugins 3. **Versionierte Dependencies** — `dependencies=["report_generator>=1.0.0"]` 4. **Event-Driven** — Plugins kommunizieren über Events, nicht direkte Aufrufe 5. **Tenant-Isolated** — Jedes Plugin respektiert Tenant-Grenzen 6. **Frontend-Modular** — Plugin-Frontends werden dynamisch geladen ## 7. Technische Umsetzung ### 7.1 Registry-Erweiterung ```python # app/plugins/registry.py — neue Methoden class PluginRegistry: # ... bestehend ... def resolve_load_order(self) -> list[str]: """Topological sort of all discovered plugins.""" # 1. Build dependency graph # 2. Core-Plugins first # 3. Topological sort (Kahn's algorithm) # 4. Cycle detection # 5. Missing dependency detection async def validate_dependencies( self, plugin_name: str, db: AsyncSession ) -> list[str]: """Check if all dependencies are installed and active.""" # Returns list of missing/unmet dependencies async def get_dependents( self, plugin_name: str, db: AsyncSession ) -> list[str]: """Find all plugins that depend on this one.""" # For deactivation protection ``` ### 7.2 Plugin Model Erweiterung ```python # app/models/plugin.py — neues Feld class Plugin(BaseModel): # ... bestehende Felder ... is_core: Mapped[bool] = mapped_column(Boolean, default=False) ``` ### 7.3 Startup-Sequenz (main.py) ```python # Aktuell: Alle Builtins werden automatisch installiert+aktiviert # Neu: # 1. Discover all plugins # 2. resolve_load_order() → [permissions, entity_links, tags, report_generator, dms, mail, calendar] # 3. For each in order: # a. Check if in DB → if not, create record (is_core=True for core plugins) # b. Run migrations # c. Activate (skip for non-core if deactivated by admin) # d. Register routes ``` ## 8. Frontend-Anpassung ### 8.1 Sidebar dynamisch Die Sidebar sollte Plugins nicht mehr hardcoded listen, sondern dynamisch aus der Plugin-API laden: ```typescript // GET /api/v1/plugins/active → [{name, display_name, icon, route_prefix, is_core}] // Sidebar rendert nur aktive Plugins ``` ### 8.2 Plugin-Frontend-Laden Jedes Plugin kann ein Frontend-Modul mitbringen: ``` app/plugins/builtins/invoicing/ ├── __init__.py ├── plugin.py # Backend Plugin ├── routes.py # API Routes ├── manifest.py # (in plugin.py) ├── migrations/ # SQL Migrations └── frontend/ # Frontend Module ├── index.tsx # Plugin Entry Point ├── pages/ # Plugin Pages └── components/ # Plugin Components ``` ## 9. Nächste Schritte 1. **Manifest erweitern** — `is_core` Feld hinzufügen 2. **Registry erweitern** — `resolve_load_order()`, `validate_dependencies()`, `get_dependents()` 3. **Plugin Model erweitern** — `is_core` Spalte 4. **Startup-Sequenz anpassen** — Core-First, Topological Sort 5. **Report Generator Plugin bauen** — Templates, PDF/Excel, Scheduling 6. **ERP-Module starten** — Invoicing als erstes Modul