8.0 KiB
4. Import
Stufe 2 spielt ein Manifest über die REST-API in Paperless ein und führt dabei ein eigenes SQLite-Journal.
Das Journal
migration.sqlite ist der Kern. Es hält je Dokument fest:
| Feld | Bedeutung |
|---|---|
ecodms_docid |
Quell-ID |
paperless_id |
Ziel-ID |
versions_total / versions_done |
Fortschritt der Kette |
status |
pending, root_created, done, failed, uebersprungen |
last_error |
Fehlertext im Klartext |
Daraus folgen drei Eigenschaften:
- Wiederaufsetzbar. Ein abgebrochener Lauf setzt fort, ohne Duplikate.
- Tranchen dürfen sich überschneiden. Bekannte Dokumente werden übersprungen.
- Tranchenübergreifend auswertbar. Nötig für Zeitstempel und Verknüpfungen am Ende.
Das Journal nicht löschen, solange die Migration läuft. Und nicht zusammen mit dem Paperless-Bestand zurücksetzen, ohne beides gemeinsam zu tun — sonst entstehen Doppelanlagen.
Rollenübersetzung
rollen.yml.example nach rollen.yml kopieren und anpassen.
default_owner: benutzer_a
roles:
r_benutzer_a:
type: user
paperless: benutzer_a
owner: true # wird Eigentümer, wenn beteiligt
FFN:
type: group
paperless: Gruppe_A
ecoSIMSUSER:
type: null # globales Importrecht, entfällt bewusst
rights:
R: [view]
W: [view, change]
no_rights_policy: owner_only
Warum ecoSIMSUSER verworfen wird
Das ist das globale Importrecht — jeder hat Zugriff. Es wird beim Import gesetzt und bei der ersten Klassifizierung ersetzt. Wer es übernimmt, öffnet jedes Dokument für alle.
Warum W beide Rechte bekommt
In Paperless implizieren change und view einander nicht. Ein change
ohne view ergibt keinen sinnvollen Zustand.
Warum unbekannte Rollen zum Abbruch führen
In Paperless bedeutet „keine Rechte gesetzt" nicht gesperrt, sondern
unbeschränkt. Ein Tippfehler im Rollennamen würde Dokumente öffnen, nicht
schließen. Deshalb: entweder abbilden oder ausdrücklich als type: null
eintragen.
Zusatzfelder vorab anlegen
custom_fields:
Belegnummer: string
Zeitraum: string
Eingangsdatum: date
Zahlungsdatum: date
Wiedervorlage: date
ecoDMS-ID: string
ecoDMS-Verknuepfung: documentlink
Nicht von Hand in der Oberfläche anlegen. Weicht der Name auch nur minimal ab, erzeugt das Skript ein zweites gleichnamiges Feld, und die Werte verteilen sich auf beide.
Ablauf
Trockenlauf
export PT=dein_api_token
python3 paperless_import.py export1.json --dry-run
Schreibt nichts, prüft aber die Rollen gegen rollen.yml und meldet
Unbekanntes.
Stammdaten
python3 paperless_import.py export1.json --setup
Legt Tags, Dokumenttypen und Zusatzfelder an. Vorhandene werden erkannt.
Danach in der Oberfläche ansehen: Passen die Tags? Bei tiefen Ordnerbäumen entstehen schnell mehrere hundert. Das ist der Moment, es zu ändern — später kostet es eine Umstellung des gesamten Bestands.
Probelauf
python3 paperless_import.py export1.json --limit 2 --stop-on-error
Zwei Dokumente, Abbruch beim ersten Fehler. Danach in der Oberfläche prüfen:
- Tags einzeln statt als Pfad?
- Archiv-Seriennummer gleich der ecoDMS-ID?
- Zusatzfelder gefüllt?
- Eigentümer gesetzt?
- Bei einem mehrversionigen Dokument: Kette vollständig?
Volle Tranche
python3 paperless_import.py export1.json
Ohne --stop-on-error, damit einzelne Fehlschläge den Lauf nicht
anhalten. Sie landen als failed im Journal.
Prüfen
python3 paperless_import.py --verify
Vergleicht Journal und Instanz. Meldet abweichende Versionszahlen, fehlende Eigentümer, abweichende Seriennummern und Dokumente ohne Journaleintrag — das sind mögliche Doppelanlagen.
Tag-Modus
Standard ist segments: Aus Beruf/Fortbildung/Steuerberater werden drei
Tags. Das erlaubt Filtern nach einzelnen Ebenen, kostet aber den
Hierarchiekontext — Steuerberater allein ist mehrdeutig.
Alternative:
python3 paperless_import.py export1.json --tag-mode path
Ein Tag je vollständigem Pfad. Übersichtlicher, aber nicht nach Ebenen filterbar.
Die Entscheidung vor dem ersten --setup treffen. Ein Wechsel später
bedeutet, alle Tags neu zu vergeben.
Was Stufe 2 je Dokument tut
POST /api/documents/post_document/ → Version 1, mit Titel, created,
Dokumenttyp, Tags
Task pollen → paperless_id ins Journal
POST /api/documents/{id}/update_version/ → Version 2…n, mit version_label
Task pollen, streng seriell
PATCH /api/documents/{id}/ → Eigentümer, Rechte,
Zusatzfelder, Seriennummer
Warum seriell
update_version hängt an den aktuellen Head an. Innerhalb einer
Versionskette muss jede Version fertig konsumiert sein, bevor die nächste
kommt. Parallelisiert wird über Dokumente hinweg, nicht innerhalb.
Rechte nur auf die Wurzel
Versionen sind eigene Dokumentdatensätze, erben die Rechte aber über
root_document. Ein Zugriffstest mit einem nicht berechtigten Benutzer
liefert bei allen Versionen 403. Es genügt also, die Rechte einmal auf das
Wurzeldokument zu setzen.
Was die API nicht kann
update_version nimmt ausschließlich document und version_label
entgegen — keinen Zeitstempel. Das added-Feld je Version muss
nachträglich gesetzt werden, siehe Nacharbeit.
Fehler behandeln
sqlite3 -header -column migration.sqlite \
"SELECT tranche, status, COUNT(*) FROM mapping GROUP BY tranche, status;"
sqlite3 -header migration.sqlite \
"SELECT ecodms_docid, last_error FROM mapping WHERE status='failed' LIMIT 20;"
Nach einer Korrektur die Fehlschläge erneut anstoßen:
sqlite3 migration.sqlite \
"UPDATE mapping SET status='pending', last_error=NULL WHERE status='failed';"
python3 paperless_import.py export1.json
Dauerhaft nicht importierbare Dokumente auf einen eigenen Status setzen, damit sie nicht bei jedem Lauf erneut versucht werden:
sqlite3 migration.sqlite \
"UPDATE mapping SET status='uebersprungen' WHERE ecodms_docid IN (…);"
Häufige Fehlerbilder
| Meldung | Ursache |
|---|---|
File type … not supported |
Dateityp, oder Inhalt passt nicht zur Endung |
HTTP 406 bei Office/.eml |
Tika-Version zu neu |
Error while queuing document |
oft eine falsche DEFAULT_PERMISSIONS_OWNER-ID |
Task … nach 600s ohne Ergebnis |
großer Scan mit OCR, TASK_TIMEOUT erhöhen |
HTTP 400 beim Anlegen eines Tags |
Namenskollision nach Normalisierung |
Zwischenzustand nach Abbruch
Wird ein Lauf zwischen Upload und Journaleintrag unterbrochen, existiert
das Dokument in Paperless ohne Journaleintrag. --verify findet das.
Behandlung: Zeigt das Journal failed und das Dokument existiert, ist der
Eintrag nachzutragen statt neu hochzuladen:
sqlite3 migration.sqlite \
"UPDATE mapping SET paperless_id=<id>, versions_done=1,
status='root_created', last_error=NULL WHERE ecodms_docid=<docid>;"
python3 paperless_import.py exportN.json
Der Lauf ergänzt dann Rechte, Zusatzfelder und Seriennummer.
Snapshots
Vor jeder Tranche einen Snapshot des Datenverzeichnisses:
docker compose down
btrfs subvolume snapshot -r data ../snapshots/paperless-vor-tranche2
docker compose up -d
Container vorher stoppen, sonst ist der Datenbankstand nicht konsistent zu den Dateien. Beim ersten Durchlauf wird man das brauchen.
Laufzeit
Bei PDFs mit vorhandenem Textlayer und OCR_MODE=auto liegt ein
Konsumvorgang unter einer Sekunde. Für 12.000 Versionen bedeutet das
wenige Stunden.
Der Engpass ist nicht die CPU, sondern die serielle Arbeitsweise plus das Abfrageintervall. Mehr Worker beschleunigen nichts, solange immer nur eine Aufgabe gleichzeitig läuft.
Deutlich länger dauern Office-Dokumente und E-Mails, weil sie über Gotenberg und Tika laufen.
Weiter mit Nacharbeit.