feat(C.5): Modularer Import/Export — Shared Helpers, Preview/Mapping, Background Jobs, Partial-Failure
C5-BASE: app/services/import_export_helpers.py (NEU, 352 Zeilen)
- parse_csv/json/xlsx, write_csv/json/xlsx, map_fields, suggest_mapping
- validate_row, build_error_report, build_import_result, detect_format
C5-PREVIEW: POST /import/preview + POST /import/validate
- Preview gibt erste 10 Zeilen + Spalten + Mapping-Vorschlag
- Validate gibt Fehler-Report ohne Import
C5-JOB: app/services/import_export_jobs.py (NEU, 165 Zeilen)
- ARQ Background Job für Files >1000 Zeilen
- Job-Status: pending/processing/completed/partial_success/failed
- GET /import/status/{job_id} — Status + Progress + Fehler-Report
- Partial-Failure: try/except pro Zeile, fehlerhafte gesammelt, erfolgreiche committet
C5-CONTACT+C5-COMPANY: Handler auf Shared Helpers umgestellt
C5-UI: ImportWizard.tsx (5 Steps: Upload→Preview→Validation→Review→Result)
C5-TEST: 45 Tests in test_import_export.py — alle grün
C5-DOC: Plugin-Dev-Guide Kapitel 31 (Import/Export Handler)
This commit is contained in:
@@ -2092,4 +2092,113 @@ Plugins deklarieren ihre API-Prefixe im Manifest (`routes.prefix`). Plugin-API-
|
||||
|
||||
---
|
||||
|
||||
## 31. Import/Export Handler auf Shared Helpers
|
||||
|
||||
LeoCRM stellt wiederverwendbare Bausteine für Import/Export-Funktionalität bereit. Plugins können eigene Import/Export-Handler auf dieser Basis aufsetzen.
|
||||
|
||||
### 31.1 Shared Helpers (`app/services/import_export_helpers.py`)
|
||||
|
||||
Die folgenden Funktionen sind verfügbar und können von jedem Plugin importiert werden:
|
||||
|
||||
```python
|
||||
from app.services.import_export_helpers import (
|
||||
parse_csv, # CSV → list[dict] mit Encoding-Detection
|
||||
parse_json, # JSON → list[dict]
|
||||
parse_xlsx, # XLSX → list[dict] (openpyxl)
|
||||
write_csv, # list[dict] → CSV bytes
|
||||
write_json, # list[dict] → JSON bytes
|
||||
write_xlsx, # list[dict] → XLSX bytes
|
||||
map_fields, # Source-Column → Target-Field Mapping
|
||||
suggest_mapping, # Auto-Mapping-Vorschlag
|
||||
validate_row, # Zeilen-Validierung mit required + validators
|
||||
build_error_report, # Strukturierter Fehler-Report
|
||||
build_import_result, # Standardisiertes Import-Ergebnis
|
||||
detect_format, # Format-Erkennung (csv/json/xlsx)
|
||||
parse_file, # Auto-Detect + Parse
|
||||
)
|
||||
```
|
||||
|
||||
### 31.2 Eigener Import-Handler
|
||||
|
||||
Ein Plugin kann einen eigenen Import-Handler erstellen:
|
||||
|
||||
```python
|
||||
from app.services.import_export_helpers import parse_file, map_fields, validate_row, build_import_result
|
||||
|
||||
async def import_my_entity(db, tenant_id, user_id, content: bytes, filename: str, field_mapping: dict | None = None):
|
||||
rows = parse_file(filename, content)
|
||||
total = len(rows)
|
||||
errors = []
|
||||
valid_rows = []
|
||||
|
||||
for idx, row in enumerate(rows, start=1):
|
||||
if field_mapping:
|
||||
row = map_fields(row, field_mapping)
|
||||
row_errors = validate_row(row, required=["name"], validators={"email": {"type": "email"}})
|
||||
if row_errors:
|
||||
for e in row_errors:
|
||||
errors.append({"row": idx, "field": "", "message": e})
|
||||
else:
|
||||
valid_rows.append(row)
|
||||
|
||||
# ... DB-Insert mit Partial-Failure ...
|
||||
for idx, row in enumerate(valid_rows, start=1):
|
||||
try:
|
||||
# Insert entity
|
||||
pass
|
||||
except Exception as exc:
|
||||
errors.append({"row": idx, "field": "", "message": str(exc)})
|
||||
|
||||
failed_row_count = len({e["row"] for e in errors})
|
||||
return build_import_result(
|
||||
total=total,
|
||||
succeeded=len(valid_rows),
|
||||
failed=failed_row_count,
|
||||
errors=errors,
|
||||
)
|
||||
```
|
||||
|
||||
### 31.3 Partial-Failure-Semantik
|
||||
|
||||
Import-Handler müssen Partial-Failure implementieren:
|
||||
|
||||
1. **Validierung pro Zeile**: `validate_row()` prüft required fields und validators
|
||||
2. **Fehlerhafte Zeilen sammeln**: Fehler werden in `errors`-Liste mit `{row, field, message}` gesammelt
|
||||
3. **Erfolgreiche Zeilen committen**: Gültige Zeilen werden in DB geschrieben, pro Zeile try/except
|
||||
4. **Status-Klassifizierung**: `build_import_result()` setzt Status auf `success`, `partial_success`, oder `failed`
|
||||
5. **Fehler-Report**: `build_error_report()` erstellt strukturierten Report mit `total_errors` und `errors`-Liste
|
||||
|
||||
### 31.4 Background Processing für große Imports
|
||||
|
||||
Für Dateien > 1000 Zeilen soll der Import als ARQ-Job laufen:
|
||||
|
||||
```python
|
||||
from app.services.import_export_jobs import create_import_job, get_import_job_status
|
||||
|
||||
# In Route:
|
||||
if len(rows) > 1000:
|
||||
job_id = await create_import_job(
|
||||
entity_type="my_entity",
|
||||
csv_content=content.decode("utf-8"),
|
||||
tenant_id=tenant_id,
|
||||
user_id=user_id,
|
||||
field_mapping=mapping,
|
||||
)
|
||||
return {"status": "pending", "job_id": job_id}
|
||||
```
|
||||
|
||||
Job-Status wird in Redis gespeichert (`leocrm:import_job:{job_id}`) mit TTL 1h.
|
||||
|
||||
### 31.5 Frontend-Integration
|
||||
|
||||
Das Frontend nutzt die API-Client-Funktionen aus `importExport.ts`:
|
||||
|
||||
- `previewImport(file, entityType)` — Vorschau mit Mapping-Vorschlag
|
||||
- `validateImport(file, entityType, fieldMapping)` — Validierung ohne Import
|
||||
- `importCsv(file, entityType, dryRun, fieldMapping)` — Import mit optionalem Mapping
|
||||
- `getImportJobStatus(jobId)` — Polling für Background-Jobs
|
||||
- `exportData(entityType, format)` — Export als CSV/XLSX/JSON
|
||||
|
||||
---
|
||||
|
||||
*This document is authoritative for all plugin development at LeoCRM.*
|
||||
|
||||
Reference in New Issue
Block a user