20 KiB
Umsystem-Integration: generische Connectors + kundenspezifische Workflows + Datenquellen-Seiten
Beschreibung und Kontext
Ein Kunde schreibt seine Rechnungen in einem externen System (SelectLine, REST-API vorhanden) und möchte, dass diese Rechnungen ins RMA (Run My Accounts) geladen werden. Die Frage des Kunden: «Kann PowerOn dafür einfach eine Schnittstelle machen?»
Der konkrete Fall ist ein Spezialfall eines generischen Musters: PowerOn soll proprietäre Umsysteme (ERP, Fakturierung, Lohn, …) anbinden. Wir haben die Logik dafür im Prinzip schon – im Feature Trustee sind Buchhaltungssysteme (ABACUS, Bexio, Banana, RMA) über das AccountingBridge-Connector-Framework angebunden (platform-core/modules/features/trustee/accounting/). RMA ist bereits ein vollwertiger Ziel-Connector (inkl. pushBooking, pushInvoice, Beleg-Upload).
Kern-Architekturvorgabe (Auftrag): Es soll keine kundenspezifische Logik im Code entstehen. Wir trennen sauber in:
- Generische Bausteine (Code, wiederverwendbar, ohne Kundennamen): Connectors, kanonische Datenmodelle, generische Workflow-Nodes.
- Kundenspezifische Logik (als Daten = Workflow-Graph + Config, nicht als Code): die konkrete Verdrahtung «SelectLine → Mapping → RMA» pro Mandant/Instanz im Graphical Editor.
Business-Treiber:
- Konkreter Kundenwunsch (Termin nächste Woche).
- Wiederverwendbares Muster für jedes weitere Umsystem → Sales-Asset und skalierbare Onboarding-Story.
- Vermeidung von Wartungs-Hölle durch
if kunde == X-Code.
Abgrenzung — NICHT als Personal Source einbinden. Personal Sources (UDB-DataSource-Familie: SharePoint, Drive, Infomaniak) sind user-privat und für unstrukturierte RAG-Daten gedacht (wiki/b-reference/platform/unified-data-bar.md). Eine Debitorenrechnung von SelectLine nach RMA ist ein transaktionaler Buchungs-Vorgang auf Mandant-/Feature-Ebene mit Dedup, Sync-Audit und verschlüsselten Credentials pro Mandant. Das gehört in das Trustee-Connector-Framework (featureInstanceId-scoped), nicht in Personal Sources.
Fokus und kritische Details
- Trennung Baustein ↔ Kundenlogik ist das eigentliche Ziel. Jede Versuchung, Kunden-/Systemspezifika fest zu verdrahten (Konten-Mappings, Filter, Belegart-Auswahl), muss in Config/Workflow-Graph wandern, nicht in den Connector.
- Connector = reiner API-Adapter. Auth, Lesen/Schreiben von Ressourcen, Mapping zwischen nativem API-Format und kanonischem Modell. Keine Geschäftsregeln, keine Kundennamen, keine Konto-Zuordnungen.
- Richtung. Bestehende AccountingBridge ist heute push-orientiert (lokale
TrusteePosition→ extern). SelectLine ist eine Quelle (lesen). Wir brauchen einen sauberen Import-/Source-Pfad und eine Rolle (source/target) auf der Config, weil eine Instanz künftig eine Quelle und ein Ziel hat. - On-Prem-Erreichbarkeit (grösstes Risiko, extern abhängig). Die SelectLine-API läuft typischerweise beim Kunden (Doku zeigt
http://localhost/...). Unsere Cloud muss sie erreichen → VPN / Reverse-Tunnel / fester Endpoint / On-Site-Agent. Klärt Aufwand und Sicherheit. - Datenschutz/Neutralisierung. Rechnungen enthalten Personendaten. Pfade, die durch das LLM laufen (z. B. KI-gestütztes Konten-Mapping), müssen über das Neutralisierungs-Gate (
wiki/b-reference/platform/neutralization.md). - Fragile Stellen:
accountingBridge.py(Orchestrierung, Dedup, Sync-Records) ist heute auf Position→Push zugeschnitten; der Source-Pfad muss additiv ergänzt werden, ohne den bestehenden Push-Pfad zu brechen.TrusteeAccountingConfigerlaubt heute nur eine aktive Config pro Instanz (siehegetActiveConfig).
Ziel und Nicht-Ziele
- Ziel: Generisches, plugin-basiertes Umsystem-Connector-Framework (Erweiterung des bestehenden AccountingBridge-Musters), mit dem ein neues System = eine Connector-Datei + Config, ohne Architekturänderung.
- Ziel: SelectLine-Connector (Quelle): Login, Lesen von Ausgangsrechnungen (
Document/DocumentKind), Kunden, Konten, Steuerschlüssel. - Ziel: Source→Target-Import als generische Workflow-Nodes, sodass «SelectLine → RMA» ein Workflow-Graph ist (kundenspezifisch als Daten), kein Code.
- Ziel: UI-Muster «Datenquelle = Seite» im Feature Trustee: die Seite «Buchhaltungssystem» existiert; das Muster wird generisch, sodass weitere Kategorien (z. B. «Saläre», «Fakturierung») als eigene Seiten dazukommen können.
- Explizit NICHT: kundenspezifische Logik im Code (
if kunde == …). - Explizit NICHT: SelectLine als Personal Source.
- Explizit NICHT: Aufbau eines eigenen ERP/Faktura-Systems – wir konsumieren/transferieren nur.
- Explizit NICHT (vorerst): voll generisches «Integrations-Framework für alles». Wir bleiben im Trustee-Kontext und verallgemeinern erst, wenn die zweite Kategorie (Saläre) real wird (YAGNI – siehe «Meine Gedanken»).
Architektur: generische Bausteine vs. kundenspezifische Logik
Vier Schichten. Schicht 1–3 sind Code (generisch), Schicht 4 ist Daten (kundenspezifisch).
┌─ Schicht 4: WORKFLOW-GRAPH + CONFIG (Daten, pro Instanz, im Graphical Editor) ─┐
│ "SelectLine → mappe Konto 1100→1100, Belegart=AR, seit lastSync → RMA (GL)" │
│ Konto-Mappings · Filter · Zeitplan · Quelle/Ziel-Auswahl → KUNDENSPEZIFISCH │
└─────────────────────────────────────────────────────────────────────────────────┘
▲ konsumiert generische Nodes, enthält KEINEN Code
┌─ Schicht 3: WORKFLOW-NODES (generische Typed Actions) ─────────────────────────┐
│ source.fetchInvoices · transform.mapAccounts · target.pushBooking · attachDoc │
└─────────────────────────────────────────────────────────────────────────────────┘
▲ arbeiten ausschliesslich auf kanonischen Modellen
┌─ Schicht 2: KANONISCHE MODELLE ─────────────────────────────────────────────────┐
│ AccountingBooking / AccountingBookingLine / Invoice / Contact / Chart … │
│ (bereits vorhanden in accountingConnectorBase.py) │
└─────────────────────────────────────────────────────────────────────────────────┘
▲ Connectors übersetzen natives API-Format ⇄ kanonisch
┌─ Schicht 1: CONNECTORS (generische API-Adapter, plugin-discovery) ─────────────┐
│ accountingConnectorRma.py · accountingConnectorSelectline.py · …bexio/abacus │
│ nur Auth + Lesen/Schreiben + Mapping. KEINE Kundenlogik. │
└─────────────────────────────────────────────────────────────────────────────────┘
Schicht 1 — Connectors (neu: SelectLine)
- Neue Datei
platform-core/modules/features/trustee/accounting/connectors/accountingConnectorSelectline.py, implementiertBaseAccountingConnector(accountingConnectorBase.py). Auto-Discovery überaccountingRegistry.py(PatternaccountingConnector*.py) → keine Registrierungs-Änderung nötig. - Methoden:
testConnection(Login + GET),getChartOfAccounts(Konten),getCustomers/getVendors, und die Lese-Seite für Belege (siehe «Source-Capability» unten). - Config-Felder (
getRequiredConfigFields):baseUrl,username,password(secret), optionalmandant. Verschlüsselt wie alle Configs (_decryptConfig). - Source-Capability: Da die Base heute primär Push/Read-Journal kennt, ergänzen wir eine schlanke, optionale Lesemethode für Belege, z. B.
getOutgoingInvoices(config, dateFrom, sinceCursor) -> List[Invoice](Default[]in der Base; nur Source-Connectors überschreiben). Alternativ Wiederverwendung vongetJournalEntries– Entscheidung siehe «Offene Fragen».
Schicht 2 — kanonische Modelle (vorhanden)
AccountingBooking, AccountingBookingLine, AccountingChart, SyncResult etc. in accountingConnectorBase.py. Für reine Debitoren-Fakturen ggf. ein kanonisches Invoice-Modell ergänzen (Header + Lines + Kunde + Steuer), falls RMA als AR-Rechnung (offener Posten) statt GL-Buchung beschrieben werden soll.
Schicht 3 — Workflow-Nodes (generisch, Typed Actions)
Analog zu den bestehenden Trustee-Actions (trustee.extractFromFiles → processDocuments → syncToAccounting). Neue, system-agnostische Nodes:
integration.fetchFromSource— zieht Belege/Buchungen aus dem konfigurierten Quell-Connector (Quelle = Config, nicht hartcodiert) → kanonische Liste.integration.mapAccounts— wendet eine Mapping-Tabelle (Config/Daten) auf Konten/Steuercodes an.integration.pushToTarget— schreibt über den konfigurierten Ziel-Connector (pushBooking/pushInvoice+ Beleg-Upload, bereits in RMA vorhanden).
Diese Nodes erscheinen über den Editor-Adapter (graphicalEditor/nodeDefinitions/) als Bausteine im Graphical Editor. Die Pipeline trigger → fetchFromSource(SelectLine) → mapAccounts → pushToTarget(RMA) ersetzt damit für strukturierte Quellen den KI-Extraktionsschritt (günstiger + zuverlässiger).
Schicht 4 — Workflow-Graph + Config (kundenspezifisch, als Daten)
- Der konkrete Kundenfluss ist ein Workflow-Template/Graph pro Feature-Instanz (persistiert), gebaut im Graphical Editor. Konten-Mappings, Filter (nur Ausgangsrechnungen, nur «seit letztem Sync»), Buchungsart, Zeitplan = Config-Datensätze.
- Ergebnis: zwei Kunden mit SelectLine→RMA, aber unterschiedlichem Kontenplan-Mapping = zwei Graphen/Configs, ein Code.
Datenmodell-Änderung
TrusteeAccountingConfig um role (source | target) erweitern, sodass pro Instanz mehrere Connector-Configs koexistieren (eine Quelle + ein Ziel). getActiveConfig / _resolveConnectorAndConfig entsprechend um role filtern. Optional: connectorCategory (accounting | payroll | invoicing) für die UI-Seitenzuordnung.
SelectLine-API – Befund (Kurz)
Geprüft anhand hilfe.selectline.ch und der Demo-API (demo.slmobile.de):
- REST, JSON/XML. Auth:
POST /Loginmit{username,password}→AccessToken/LoginId; danach HeaderAuthorization: LoginId <token>(Session-Token, Re-Login bei Ablauf). - Relevante Ressourcen:
Document/DocumentKind(Belegkette inkl. Ausgangsrechnungen),Customers/Vendors(Geschäftspartner),Konten+Kontensalden,Journale,Offene Posten,Steuerschlüssel,Kostenrechnung,Bankassistent(camt.053-Import). - Betriebsmodell: i. d. R. on-premise beim Kunden → Erreichbarkeit klären (Risiko oben).
- Swagger-/OpenAPI-Beschreibung pro Installation herunterladbar → exaktes Beleg-Schema beim Kunden verifizieren.
UI-Konzept: «Datenquelle = Seite»
Heute ist «Buchhaltungs-Einstellungen» eine eigene Seite im Trustee-Feature:
- Registriert als UI-Object
ui.feature.trustee.settings(meta.area="settings",admin_only) inmainTrustee.py. - Gemappt auf
TrusteeAccountingSettingsViewinui-nyla/src/pages/FeatureView.tsx(VIEW_COMPONENTS.trustee.settings). - Innen bereits Tabs: «Verbindungseinstellungen» + «Buchhaltungsdaten importieren» (Connector wählen → Credentials → Test → Import).
Vorschlag: dieses Muster zu einem generischen «Datenquellen-Seiten»-Pattern verallgemeinern – eine Seite pro Kategorie von Umsystem:
| Seite (UI-Area) | Kategorie | Connectors (Beispiele) | Kanonisches Modell |
|---|---|---|---|
| Buchhaltungssystem (existiert) | accounting |
RMA, Bexio, Abacus, SelectLine (Quelle) | AccountingBooking |
| Saläre (Idee) | payroll |
SwissSalary, Abacus Lohn, Swissdec-konform | PayrollEntry (neu) |
| Fakturierung (Idee) | invoicing |
SelectLine, Bexio | Invoice |
Pro Seite identischer Aufbau (eine generische React-Komponente, parametrisiert über category):
- Verbindungen — Connectors der Kategorie listen, Rolle (Quelle/Ziel) wählen, Credentials, Test (wiederverwendet die bestehende
TrusteeAccountingSettingsView-Mechanik, generalisiert). - Datenfluss/Import — Workflow-Template wählen/starten (= Schicht-4-Graph), Zeitplan, Status/Audit.
- Daten — eingelesene Datensätze (read-only Spiegel).
Konkret:
- Generische
IntegrationSettingsView(ausTrusteeAccountingSettingsViewextrahiert), diecategoryals Prop bekommt. - Neue UI-Areas in
mainTrustee.py(z. B.ui.feature.trustee.payroll) + Mapping inFeatureView.tsx→ gleiche Komponente, anderecategory. - «Saläre» ist bewusst nur ein Platzhalter-Beispiel, um zu zeigen, dass das Muster skaliert; nicht Teil der ersten Umsetzung.
Betroffene Module
- Gateway (platform-core):
features/trustee/accounting/connectors/accountingConnectorSelectline.py(neu).accounting/accountingConnectorBase.py(optionale Source-Lesemethode, Default[]).accounting/accountingBridge.py(additiver Import-/Source-Pfad;role-Auflösung).datamodelFeatureTrustee.py(TrusteeAccountingConfig.role[+ optionalconnectorCategory]) → DB-Migration: ja.workflows/methods/methodTrustee/actions/+graphicalEditor/nodeDefinitions/(generische Nodesintegration.fetchFromSource/mapAccounts/pushToTarget).mainTrustee.py(UI-Areas, RBAC-Objekte, Workflow-Templates).
- Frontend (ui-nyla):
TrusteeAccountingSettingsView→ generischeIntegrationSettingsView(category-Prop).FeatureView.tsxRegistry +mainTrusteeUI-Areas für weitere Seiten.trusteeApi.ts(Connector-Listing nach Kategorie, Rolle source/target).
- DB-Migration: ja (
role, optionalconnectorCategory). - Andere: Neutralisierung (falls KI-Mapping), Audit/Billing (Sync-Volumen).
Entscheidungen
| Datum | Entscheidung | Begründung |
|---|---|---|
| 2026-06-04 | Umsystem als Trustee-Connector, nicht als Personal Source | Transaktional, mandant-scoped, Dedup/Audit/verschlüsselte Credentials bereits in AccountingBridge |
| 2026-06-04 | Kundenlogik als Workflow-Graph + Config, nicht im Code | Auftrag: keine kundenspezifische Logik im Code; nutzt bestehende Typed-Action-/Editor-Architektur |
| 2026-06-04 | UI-Muster «Datenquelle = Seite» pro Kategorie, eine generische Komponente | Skaliert auf Saläre/Fakturierung ohne Copy-Paste |
Umsetzungs-Checkliste
- DB:
TrusteeAccountingConfig.role(+ optionalconnectorCategory) + Migration - Bridge: Source-Auflösung nach
role, Import-Pfad (additiv, Push-Pfad unverändert) - Connector:
accountingConnectorSelectline.py(Login/Test/Konten/Kunden/Belege lesen) - Kanonisch:
Invoice-Modell prüfen/ergänzen (falls AR-Rechnung in RMA) - Workflow-Nodes:
integration.fetchFromSource/mapAccounts/pushToTarget+ Editor-Adapter - Workflow-Template «Source→Target Import» (parametrisierbar)
- UI:
IntegrationSettingsViewgeneralisieren (category), Quelle/Ziel-Auswahl - RBAC / Permissions (neue UI-Areas, admin_only)
- Neutralisierung betroffen? (nur falls KI-Mapping)
- Navigation / Routing (UI-Areas + FeatureView-Registry)
- Billing-Impact? (Sync-Läufe/Volumen)
Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|---|---|
| 1 | Given konfigurierter SelectLine-Source + RMA-Target, When der Import-Workflow läuft, Then erscheinen die SelectLine-Ausgangsrechnungen als Buchungen in RMA (inkl. Beleg-Verlinkung) | must |
| 2 | Given zwei Mandanten mit unterschiedlichem Konten-Mapping, When beide SelectLine→RMA nutzen, Then unterscheiden sie sich nur in Workflow-Graph/Config – kein kundenspezifischer Code | must |
| 3 | Given ein neues Umsystem, When ein Connector-File + Config ergänzt wird, Then ist es ohne Änderung an Registry/Bridge/Editor verfügbar | must |
| 4 | Given erneuter Import, When Belege bereits gebucht sind, Then werden sie über die bestehende Dedup-Logik nicht doppelt gebucht | must |
| 5 | Given das UI-Muster, When eine neue Kategorie-Seite («Saläre») registriert wird, Then nutzt sie dieselbe generische Komponente | should |
Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|---|---|---|---|---|---|
| T1 | 1 | unit | ja | platform-core/tests/unit/features/trustee/test_accountingConnectorSelectline.py | pending |
| T2 | 1,4 | integration | ja | platform-core/tests/integration/trustee/test_source_target_import_e2e.py | pending |
| T3 | 3 | unit | ja | platform-core/tests/unit/.../test_adapter_validator.py (Editor-Node-Drift) | pending |
| T4 | 2 | unit | ja | platform-core/tests/unit/.../test_integration_nodes.py (Mapping aus Config) | pending |
Offene Fragen (für den Kundentermin)
- Erreichbarkeit: Wo läuft die SelectLine-API (on-prem/Cloud)? Welcher Zugriffsweg (VPN/Tunnel/Agent)?
- Zielart in RMA: Debitoren-Rechnung (AR, offener Posten) oder GL-Buchung? → bestimmt
pushInvoice-Ausbau vs.pushBooking. - Mapping: Liefert SelectLine pro Rechnung Konto + Steuerschlüssel, oder Kontenplan-Mapping SelectLine→RMA nötig?
- Beleg-PDF: Rechnungs-PDF mit nach RMA (Beleg-Upload vorhanden)?
- Delta/Frequenz: On-demand, Zeitplan oder Event? Cursor = Belegnummer/Datum?
- Alternative: SelectLine
Bankassistent(camt.053) – relevant oder nur Ausgangsrechnungen?
Meine Gedanken / Empfehlungen
- Saubere Trennung ist erreichbar, weil die Plattform sie schon vorlebt. Trustee nutzt bereits Typed Actions + Graphical Editor + Templates. «Kundenlogik als Graph» ist kein neues Paradigma, sondern Anwendung des Bestehenden. Das ist der grösste Hebel gegen kundenspezifischen Code.
- Nicht zu früh über-generalisieren. Ein einziges «BaseConnector für alles» mit Capability-Mixins (
AccountingCapable,PayrollCapable) ist elegant, aber bevor «Saläre» real ist, würde ich beim bestehendenBaseAccountingConnectorbleiben und nur die Source-Rolle ergänzen. Die Abstraktion ziehen wir, wenn die zweite Kategorie konkret wird – sonst bauen wir ein Framework für hypothetische Fälle (YAGNI). - Namensgebung zur Abgrenzung: «Personal Sources» = user-privat/RAG. Diese hier sind «Umsystem-Anbindungen» / «Integrationen» auf Mandant-Ebene. Klare Begriffe verhindern, dass die zwei Welten wieder vermischt werden.
- Source vs. Target ist die wichtigste neue Achse. Sie macht aus dem heutigen Push-Framework ein bidirektionales Integrations-Framework und ist die Voraussetzung für «System A → PowerOn → System B» generell.
- «Saläre»-Seite ist ein gutes nächstes Beispiel: Lohnsystem (Swissdec-konform) → Lohnbuchungen → Buchhaltung. Gleicher Mechanismus, neues kanonisches Modell
PayrollEntry. Bestätigt, dass das Seiten-Muster trägt. - Risiko bleibt extern: On-Prem-Erreichbarkeit von SelectLine ist der kritische Pfad – das ist eher Infrastruktur/Netzwerk als Code. Im Kundentermin zuerst klären.
Links
- Vorgelagerte Analyse (dieser Chat): SelectLine-API-Prüfung + erste Empfehlung
- Referenz Trustee:
wiki/b-reference/platform-core/features/trustee.md - Referenz UDB/Personal Sources:
wiki/b-reference/platform/unified-data-bar.md - Code:
platform-core/modules/features/trustee/accounting/(Bridge, Base, Registry, Connectors) - PR: —
- Issue: —
Abschluss
- b-reference/ aktualisiert (
features/trustee.mdAbschnitt «Source-Connectors / Integrationen»; ggf. neueplatform/integrations.md) - TOPICS.md aktualisiert (neues Thema «Umsystem-Integrationen»)
- Dieses Dokument → c-work/2-build/ (bei Umsetzungsbeginn), am Ende → z-archive/