Files
migrate-ecodms-to-paperless/docs/02-export-aus-ecodms.md
2026-09-04 19:51:02 +02:00

6.8 KiB

2. Export aus ecoDMS

Der Export läuft über das Datenexport-Plugin, das zum normalen Lizenzumfang gehört. Eine API-Lizenz wird nicht gebraucht.


Warum nicht die Datenbank

Naheliegend wäre, die ecoDMS-PostgreSQL direkt auszulesen. Das funktioniert nicht: ecoDMS legt die Dateien in einem eigenen Container-Speichersystem ab, weder als BLOB in der Datenbank noch als normale Dateien im Archivverzeichnis.

Und die Klassifizierungsdaten liegen dort ebenfalls nicht — in der Live-Datenbank gibt es weder eine klassifizierung- noch eine docs-Tabelle. Diese Struktur erzeugt erst der Exporter.

Der Export ist also der einzige Weg an Inhalte und Metadaten.


Vorab: Bestandsaufnahme

Bevor Zeit in Exporte fließt, sollte man die Größenordnung kennen. Am einfachsten über einen kleinen Testexport (siehe unten) und die Auswertung der Tabellen ecosimsversions und docs.

Wichtige Zahlen:

Frage Warum
Wie viele Dokumente haben mehr als eine Version? bestimmt den Aufwand des schwierigen Teils
Wie viele haben keine Version? brauchen einen Rückfall auf ecosimsarchive
Wie viele sind mehrfach klassifiziert? bestimmt die Zusammenführungsregel
Welche Dateitypen kommen vor? nicht alle kann Paperless konsumieren
Welche Rollen und Rechtearten? müssen in der Übersetzung abgebildet sein

ecodms_extract.py beantwortet all das im Prüfbericht — es lohnt sich deshalb, früh einen kleinen Export zu machen und ihn durch Stufe 1 zu schicken.


Testexport zuerst

Nicht mit dem Gesamtbestand anfangen. Ein Export von fünf bis zehn Dokumenten, der bewusst alle Sonderfälle enthält, ist die beste Investition:

  • ein Office-Dokument mit mehreren Versionen
  • ein PDF mit mehreren Versionen
  • ein Dokument ganz ohne Versionseinträge
  • ein mehrfach klassifiziertes Dokument
  • eine E-Mail (.eml)
  • eine ZUGFeRD-Rechnung
  • ein Dokument mit einer Notiz
  • ein Dokument mit besonderen Rechten

Gegen diesen Satz lässt sich die gesamte Migrationslogik entwickeln, ohne je den Produktivbestand anzufassen.


Fallstrick: unerfüllbare Exportabfrage

Bei manueller Mehrfachauswahl kann der Exporter eine Abfrage erzeugen, die niemals zutrifft:

docid = '11072' AND docid = '11094' AND docid = '11143' AND 

Das Ergebnis: ein Export mit vollständigen Stammdaten und null Dokumenten. Ohne Fehlermeldung, ohne Warnung.

Nach jedem Export prüfen:

python3 -c "
import sqlite3, base64
c = sqlite3.connect('file:export.data?mode=ro', uri=True)
print(base64.b64decode(c.execute('SELECT exp_query FROM ecodmsexporter').fetchone()[0]).decode())
"

Steht dort AND zwischen den IDs statt OR, ist der Export leer.

Vermeiden lässt es sich, indem man Exporte über ein Suchkriterium oder einen ID-Bereich definiert statt über einzeln markierte Dokumente.


Tranchen bilden

Bei größeren Beständen den Export in Blöcke teilen. Bewährt haben sich etwa 2.000 Dokumente je Tranche.

Empfohlene Struktur:

export-daten/
├── export1/archive/{export.data, export.xml, *.pdf, …}
├── export2/archive/…
└── …

Nach ID-Bereichen teilen, nicht nach manueller Auswahl. Der Wurzelknoten der XML trägt startid und endid — der Exporter denkt ohnehin in Bereichen.

Tranchen dürfen sich überschneiden. Das Journal überspringt bereits importierte Dokumente. Das ist nützlich, wenn Teile des Bestands nur unter einem anderen Benutzerkonto sichtbar sind: einfach eine zusätzliche Tranche unter diesem Konto exportieren.


Prüfung je Tranche

1. Abfrage plausibel?

Siehe oben.

2. Dokumentanzahl?

python3 -c "
import sqlite3
c = sqlite3.connect('file:export.data?mode=ro', uri=True)
for t in ('docs','klassifizierung','ecosimsversions','ecosimsarchive',
          'docrollen_hist','econotice'):
    print(f'{t:20s}', c.execute(f'SELECT COUNT(*) FROM \"{t}\"').fetchone()[0])
"

Sind docs und klassifizierung auf null, während documentenart und status gefüllt sind, hat die Abfrage nicht gegriffen.

3. Referenzen vollständig?

Die XML verweist auf Dateien im Archivordner. Jede fehlende Referenz ist ein Dokument, das beim Import scheitert:

python3 -c "
import xml.etree.ElementTree as ET, pathlib
root = ET.parse('export.xml').getroot()
ref = {e.get('filePath') for e in root.iter() if e.get('filePath')}
have = {p.name for p in pathlib.Path('.').iterdir() if p.is_file()}
print('fehlt:', sorted(ref - have)[:20])
print('unreferenziert:', sorted(have - ref - {'export.xml','export.data'})[:20])
"

Erwartete Fehlmeldungen: Bei Nicht-PDF-Originalen referenziert die XML eine PDF/A-Fassung je Version, die nicht exportiert wird. Das ist normal und für Paperless unkritisch.

4. Dateitypen?

python3 -c "
import sqlite3, collections
c = sqlite3.connect('file:export.data?mode=ro', uri=True)
n = collections.Counter()
for (f,) in c.execute('SELECT realname FROM ecosimsversions WHERE realname IS NOT NULL'):
    n['.' + f.rsplit('.',1)[-1].lower()] += 1
for k, v in n.most_common(): print(f'{v:6d}  {k}')
"

Vergleich mit dem, was die Zielinstanz kann:

docker compose exec webserver python manage.py shell -c \
"from documents.parsers import get_supported_file_extensions as f; print(sorted(f()))"

Typischerweise nicht unterstützt: .html, .htm, .zip, .indd. ecodms_extract.py sortiert solche Dokumente aus und listet sie zur Nacharbeit.

5. Rollen und Rechtearten?

python3 -c "
import sqlite3
c = sqlite3.connect('file:export.data?mode=ro', uri=True)
for r in c.execute('SELECT DISTINCT role, doc_right FROM docrollen_hist ORDER BY role'):
    print(r)
"

Jede Rolle muss in rollen.yml abgebildet oder ausdrücklich verworfen werden. Stufe 2 bricht sonst vor dem ersten Upload ab — bewusst, denn ein stilles Verwerfen würde Dokumente öffnen, nicht schließen.

6. Mehrfachklassifizierung?

python3 -c "
import sqlite3, collections
c = sqlite3.connect('file:export.data?mode=ro', uri=True)
n = collections.Counter(d for (d,) in c.execute('SELECT docid FROM docs'))
h = collections.Counter(n.values())
for k in sorted(h): print(f'{h[k]:6d} Dokumente mit {k} Klassifizierung(en)')
"

Was der Export nicht enthält

Element Alternative
Dokumentverknüpfungen nicht ermittelbar, Nacharbeit von Hand
Klassifizierungshistorie steht in der XML, aber Paperless kann sie nicht abbilden
Anhänge als eigene Dateien reisen eingebettet im Original mit

Der lucene-Ordner enthält den Suchindex des Offline-Clients und ist für die Migration wertlos.


Speicherbedarf

Der Export enthält je Version Original und teils PDF/A. Rechne mit etwa dem Umfang der ecoDMS-Sicherung. Auf der Zielseite kommt Ähnliches hinzu, weil Paperless Original und Archivdatei getrennt ablegt.


Weiter mit Extraktion und Prüfung.