7.0 KiB
3. Extraktion und Prüfung
Stufe 1 liest eine Exporttranche und erzeugt ein normalisiertes JSON-Manifest, einen Prüfbericht und eine Ausschlussliste. Paperless wird dabei nicht berührt.
Die Trennung ist Absicht: Der Lauf ist offline, beliebig wiederholbar, und das Ergebnis lässt sich ansehen, bevor irgendetwas geschrieben wird.
Aufruf
python3 ecodms_extract.py /pfad/export1/archive \
-t export1 \
-o export1.json \
--skipped-csv export1-nacharbeit.csv
Als Pfad das Verzeichnis angeben, in dem export.data und export.xml
liegen — also archive, nicht das Tranchenverzeichnis darüber.
Der Prüfbericht
Tranche export1
Dokumente 1963
Uebersprungen 34 (.html 20, .zip 11, .indd 2, .dotx 1)
Versionen 2462
Mehrfachklassifiziert 87
Notizen 81
Rollen FFN, r_benutzer_a, r_benutzer_b, ecoSIMSUSER
Rechtearten R, W
Hinweise (12):
[nicht_unterstuetzt] 34
[mehrfachklassifizierung] 87
[ohne_rechte] 1204
[nur_archiv] 143
…
Diesen Bericht lesen, bevor importiert wird. Er beantwortet alle Fragen, die sonst mitten im Import auftauchen.
Was die Kategorien bedeuten
| Kategorie | Bedeutung | Handlung |
|---|---|---|
nicht_unterstuetzt |
Dateityp, den Paperless ablehnt | Nacharbeit, Liste in der CSV |
mehrfachklassifizierung |
Dokument in mehreren Ordnern | wird zusammengeführt, Konflikte protokolliert |
ohne_rechte |
keine Zeile in docrollen_hist |
Regel in rollen.yml festlegen |
nur_archiv |
keine Versionszeilen, Rückfall auf ecosimsarchive |
normal, kein Handlungsbedarf |
datei_fehlt |
Version verweist auf nicht vorhandene Datei | Export unvollständig, prüfen |
xml_referenz_ohne_datei |
XML verweist auf fehlende Datei | bei Office-Originalen normal |
geloeschte_stammdaten |
Dokumentart oder Status ist gelöscht markiert | meist unkritisch |
papierkorb |
Dokument ist im ecoDMS-Papierkorb | wird übersprungen |
notiz_ohne_bezug |
Notiz zeigt auf unbekannte docs_id |
in anderer Tranche, oder verwaist |
Rollen prüfen
Die Zeile Rollen ist die wichtigste für den nächsten Schritt. Jede
genannte Rolle muss in rollen.yml stehen — entweder mit einem Ziel oder
ausdrücklich als type: null.
Stufe 2 bricht sonst ab, bevor die erste Datei hochgeht.
Das Manifest
Je Dokument entsteht ein Eintrag:
{
"ecodms_docid": 10347,
"tranche": "export1",
"title": "Rechnung Beispiel GmbH",
"created": "2025-07-29",
"document_type": "Rechnung",
"status": "Erledigt",
"folders": ["Allgemeines / Haushalt / Kleingeräte",
"Allgemeines / Steuer / ESt / Anlage N"],
"folder_parts": [["Allgemeines","Haushalt","Kleingeräte"],
["Allgemeines","Steuer","ESt","Anlage N"]],
"custom_fields": {
"Belegnummer": {"value": "902500503118", "type": "String"},
"Wiedervorlage": {"value": "2026-02-02", "type": "Date"}
},
"versions": [
{"version": 1, "file": "ecodms_docid_0010347_revision_0001.pdf",
"saved_at": "2025-07-29 14:12:03.881", "checksum": "…",
"source": "ecosimsversions"}
],
"permissions": [{"role": "r_benutzer_a", "right": "W"}],
"notes": [{"created": "2014-07-14 00:45:00", "text": "…", "user": ""}],
"docs_ids": [10829, 10834],
"conflicts": {}
}
folder_parts ist maßgeblich, folders nur für die Anzeige.
Ordnernamen können Schrägstriche enthalten (Telefon / Internet) — ein
späteres Zerlegen am Schrägstrich würde solche Namen zerschneiden.
conflicts wird bei Mehrfachklassifizierung gefüllt, wenn Titel,
Dokumentart, Status, Datum oder Wiedervorlage zwischen den Instanzen
abweichen. Dann gewinnt der jüngste ctimestamp, aber man sieht, dass
etwas verworfen wurde.
Die Ausschlussliste
--skipped-csv schreibt alle Dokumente, deren Dateityp Paperless nicht
konsumieren kann:
ecodms_docid,endungen,titel,erstellt,ordner,dateien
1247,.html,Webseite Beispiel,2019-03-12,Allgemeines | EDV,ecodms_docid_0001247_revision_0001.html
1533,.zip,Belege Sammlung,2020-11-04,Steuer,ecodms_docid_0001533_revision_0001.zip
Diese Dokumente sind nicht im Manifest. Sie scheitern deshalb nicht beim Import, sondern stehen sauber auf einer Liste.
Die Liste der akzeptierten Endungen steht als SUPPORTED_EXTENSIONS oben
im Skript. Sie sollte gegen die Zielinstanz geprüft werden:
docker compose exec webserver python manage.py shell -c \
"from documents.parsers import get_supported_file_extensions as f; print(sorted(f()))"
Regeln, die Stufe 1 anwendet
Alle stammen aus der Analyse eines produktiven Bestands. Ausführlich in referenz-ecodms-exportformat.md.
- Nach
docidgruppieren, nicht nachdocs_id. Einedocidkann mehrere Klassifizierungsinstanzen haben. - Ordner vereinigen, jüngster
ctimestampgewinnt bei skalaren Feldern. - Immer
realnameverwenden, niepdfrealname. Sonst gehen eingebettete ZUGFeRD-XML und E-Mail-Anhänge verloren. - Ohne Versionszeilen auf
ecosimsarchivezurückfallen. Sonst verschwinden solche Dokumente stillschweigend. docrollen_hist.docidenthältdocs_id. Join überdocs.id.- Nur die höchste in
docrollen_histvorhandene Revision jedocs_idgilt. Ältere enthalten noch das globale Importrecht. - Revisionen als Zahlentupel sortieren.
'1.10'ist lexikografisch kleiner als'1.9'. - Base64 selektiv dekodieren.
savedateundfilenamesind kodiert,realnameundpdfrealnamenicht. - Wiedervorlage aus
klassifizierung.defdate, nicht ausdynattribute. econotice.nidenthältdocs_id, der Text ist base64-kodiertes Qt-HTML.
Wiederholbarkeit
Stufe 1 schreibt nichts außer den Ausgabedateien. Sie kann beliebig oft laufen — bei jeder Änderung an den Regeln, bei jedem neuen Export.
Nach einer Änderung am Skript alle Manifeste neu erzeugen, damit sie dasselbe Format haben.
Alle Tranchen auf einmal
for n in 1 2 3 4 5 6; do
python3 ecodms_extract.py "export-daten/export$n/archive" \
-t "export$n" -o "export$n.json" \
--skipped-csv "export$n-nacharbeit.csv"
done
Danach eine Gesamtübersicht der Dateitypen:
python3 -c "
import json, glob, collections
v = collections.Counter(); d = collections.Counter()
for f in sorted(glob.glob('export*.json')):
for doc in json.load(open(f))['documents']:
exts = set()
for ver in doc['versions']:
e = '.' + ver['file'].rsplit('.',1)[-1].lower()
v[e] += 1; exts.add(e)
for e in exts: d[e] += 1
print(f\"{'Endung':10s} {'Versionen':>10s} {'Dokumente':>10s}\")
for k, n in v.most_common(): print(f'{k:10s} {n:10d} {d[k]:10d}')
"
Das ist die Zahl, die vor dem Import bekannt sein sollte — sonst stolpert man bei Tranche 4 über einen Dateityp, der in Tranche 1 nicht vorkam.
Weiter mit Import.