SPEC W4a: Import/Export Contribution-Architektur — zentraler Dialog per Toolbar-Button, Formate als Plugins, Module als Contributors #359
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Entscheidungen (2026-08-27, User-abgestimmt)
Zugang: Primärer Zugang ist der zentrale Dialog, geöffnet über einen Toolbar-Button in jeder Modulliste, deren Plugin Import/Export anbietet (Modul vorgewählt, aktive Filter übernommen). Die zentrale /import-export-Seite wird zur Übersicht aller Angebote (Variante b) — kein doppelter Arbeitsweg mehr.
Formate = Plugins: CSV/JSON/XLSX als Standard-Formate in einem bundled Plugin; neue Formate (PDF später) als weitere Format-Plugins — Erweiterung ohne Core-Änderung.
Module = Contributors: Jedes Plugin registriert via Contract, welche Formate es anbietet, und liefert die Logik (Daten holen / Zeilen anwenden).
Core = Orchestrator + Sicherheits-Policy-Layer: Sensitive-Data-Filter, Tenant-Scoping, Audit werden unabhängig vom Modul-Beitrag erzwungen (Lektion aus dem heutigen Doppel-Weg mit driftender Security).
Schnittstellen-Skizze
Dialog-Spec
Export-Tab: Format-Auswahl (Schnittmenge), Feldauswahl mit Modul-Defaults, Filter-Vorschau aus der Liste, Download bzw. Background-Job.
Import-Tab: 1. Datei hochladen (Format-Plugin parst) → 2. Mapping (Spalten↔Felder, Heuristik vom Modul, korrigierbar) → 3. Dry-Run (valid/invalid + strukturierte Fehler) → 4. Ausführung + Partial-Failure-Report.
Abgelöst:
app/services/export_service.py(78 Z., CSV-only) und die contact-spezifische Logik inimport_export_service.py(504 Z.) — Duplikat-Weg konsolidiert.Phasen + Gates
Phase 1 (Backend-Kern) umgesetzt in Commit
cd8ef75— deployed SUCCESS (2026-08-27 21:14).Was gebaut wurde
app/core/importexport_registry.py(neu)FormatHandler-Protokoll (parse/serialize),available_for()berechnet die Schnittmenge Module ∩ Formate, Singleton + Testing-Resetapp/plugins/builtins/importexport_formats/(neu)on_activateregistriert,on_deactivateunregistriert (Welle-1-Regeln)app/plugins/builtins/contacts/contracts.pyimportexport_entities()+ie_*-Methoden: Columns, Target-Fields, Validatoren, Normalizer, Required, Row-Valid, Fetch-Rows (visibility-gefiltert), Persist-Row (mit Audit)app/services/import_export_service.py(neu geschrieben)_find_ie_contract()iteriert generisch überregistry.list_discovered()— keine hartcodierten Plugin-Namen mehr; Signaturen identisch zum alten ServiceFunktionserhalt (Live-Messung)
45/45 tests/test_import_export.py passed — inkl. der subtilen Fehler-Multiplizitäts-Semantik: 2 failed rows → 3 total_errors (Zeile mit leerem Name UND invalidem Email = 2 Fehler). Erreicht durch:
ie_required()wird an generischesvalidate_row()durchgereicht (wie alter Code),ie_row_valid()greift nur beim contacts either-or-Sonderfall, companies' required-name läuft über validate_row.Ein Zwischenstand mit 2 Failures wurde live gefangen und korrigiert (ie_row_valid überschattete anfangs die validate_row-Multiplizität) — der Test beweist jetzt die Original-Semantik.
Phase 2 (Frontend-Dialog) folgt
Modal lg/xl (bestehendes ui/Modal-Muster), Export-Tab 1 Schritt, Import-Tab 4 Schritte, Toolbar-Buttons in Modullisten, alte Seite → Übersicht. Gates: Vitest + tsc + Production-Build vor Commit (Kritikpunkt 21).
Phase 2 (Frontend-Dialog) umgesetzt in Commit
38df597— frontend-only deployed (2026-08-27 21:34).Was gebaut wurde
ImportExportDialog.tsx(neu)lg/xlnach bestehendem ui/Modal-Muster (Fokus-Handling, Escape, ARIA); Export-Tab (Format-Auswahl csv/xlsx/json + Download) und Import-Tab (4 Schritte: Datei → Mapping-Editor mit Modul-Heuristik → Dry-Run mit valid/invalid-Zählern und Fehlerliste → Ausführung + Ergebnis inkl. Background-Job-Polling ab 1000 Zeilen)importexport.*-Keys in de.json + en.json (keine hardcoded Strings — AGENTS.md-Konvention)contacts:read-Gate, Upload-Icon) über das bestehende pluginToolbarStore-Muster;entityType="contacts"vorgewähltVerifikation (Live-Messung)
importExportDialog.test.ts: 6/6 passed (Tabs, Formate, 4 Spec-Schritte, Job-Polling, Toolbar-Integration, Modal-Nutzung) + routePermissions 6/6 = 12/12Status: Phase 1 (Backend) + Phase 2 (Frontend) sind produktiv. Phase 3 (PDF als weiteres Format-Plugin) separat.
Phase 2b (W4c): Custom-Fields-Routen aus Core in ContactsPlugin migriert — Commit
c6decf5, deployed SUCCESS (2026-08-28 13:23).Was migriert wurde
app/routes/custom_fields.py(contact-spezifisch: importiert Contact, nutzt contacts:read/write, Route /{contact_id}/custom-fields) wandert inapp/plugins/builtins/contacts/routes.py(gleicher Router-Prefix /api/v1/contacts, bereits via manifest.routes gemounted).app/routes/custom_fields.pygelöscht, main.py bereinigt.Der generische
custom_field_definitions.py-Endpoint bleibt im Core (echtes Core-Entity, nicht contact-spezifisch).Verifikation (Live-Messung)
Damit ist Kritikpunkt 14 (custom_fields Contact-Fixierung) durch Konvention gelöst: Der Contact-spezifische Endpoint lebt jetzt im ContactsPlugin (is_core=True), der generische custom_field_definitions-Endpoint bleibt im Core für zukünftige Entity-Types.