Files
2026-09-04 19:51:02 +02:00

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.