Files
leocrm/docs/core-plugin-concept.md
T

11 KiB

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

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

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

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

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

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:

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 Dependenciesdependencies=["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

# 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

# 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)

# 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:

// 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 erweiternis_core Feld hinzufügen
  2. Registry erweiternresolve_load_order(), validate_dependencies(), get_dependents()
  3. Plugin Model erweiternis_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