235 lines
6.8 KiB
Markdown
235 lines
6.8 KiB
Markdown
# 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:
|
|
|
|
```sql
|
|
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:**
|
|
|
|
```bash
|
|
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?
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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?
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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?
|
|
|
|
```bash
|
|
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?
|
|
|
|
```bash
|
|
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](03-extraktion.md).
|