11 KiB
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:
- Core Plugins — unverzichtbare Basis-Plugins die immer aktiv sind
- Dependency Resolution — Plugins können Abhängigkeiten deklarieren und diese werden durchgesetzt
- 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-Systementity_links— Querverweise zwischen Entitätentags— Tagging-Systemreport_generator— Report- & Dokumentgeneratoraudit— 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
- Jedes ERP-Modul ist ein Plugin — kein festcodiertes ERP
- Core-Plugins sind stabil — brechen nie andere Plugins
- Versionierte Dependencies —
dependencies=["report_generator>=1.0.0"] - Event-Driven — Plugins kommunizieren über Events, nicht direkte Aufrufe
- Tenant-Isolated — Jedes Plugin respektiert Tenant-Grenzen
- 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
- Manifest erweitern —
is_coreFeld hinzufügen - Registry erweitern —
resolve_load_order(),validate_dependencies(),get_dependents() - Plugin Model erweitern —
is_coreSpalte - Startup-Sequenz anpassen — Core-First, Topological Sort
- Report Generator Plugin bauen — Templates, PDF/Excel, Scheduling
- ERP-Module starten — Invoicing als erstes Modul