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

7.5 KiB

5. Nacharbeit

Diese Schritte laufen nach allen Tranchen, nicht zwischendurch. Zeitstempel und Verknüpfungen brauchen ein vollständiges Journal.

Vorher einen Snapshot anlegen — die ersten beiden Schritte greifen direkt in die Datenbank ein.


1. Zeitstempel setzen

Der Endpunkt update_version nimmt keinen Zeitstempel entgegen. Alle Versionen tragen deshalb zunächst das Importdatum statt des ecoDMS-Zeitstempels.

python3 paperless_import.py --emit-timestamps > fix_added.py
wc -l fix_added.py
head -20 fix_added.py

Kurz hineinsehen: Die Liste sollte plausible Datumswerte enthalten und ungefähr so viele Einträge haben wie Versionen vorhanden sind.

docker compose exec -T webserver python manage.py shell < fix_added.py

Das erzeugte Skript benutzt Document.objects.filter(pk=…).update(added=…) statt save(). Das ist wichtig: update() umgeht auto_now-Felder und Signal-Handler, die den Wert sonst wieder überschreiben und nebenbei Reindexierungen auslösen würden.

Prüfen

curl -s -H "Authorization: Token $PT" \
  "http://localhost:8000/api/documents/?archive_serial_number=<docid>" \
  | python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin)['results'][0]['versions'], indent=2))"

Die added-Werte müssen aus dem Quellzeitraum stammen, nicht vom Importdatum.

Die versions-Liste ist absteigend sortiert, neueste zuerst.


2. Notizen übertragen

python3 ecodms_notizen.py --exports /pfad/export-daten --tranchen 1-6 \
        --dry-run --csv notizen.csv

Der Trockenlauf schreibt nichts, erzeugt aber die vollständige Liste. In die CSV sehen, bevor übertragen wird: Bei jeder Zeile stehen die aufgelöste ecodms_docid, die Paperless-ID, der Titel und der Notiztext. Passen Titel und Notiz inhaltlich zusammen, stimmt die Zuordnung.

python3 ecodms_notizen.py --exports /pfad/export-daten --tranchen 1-6 \
        --csv notizen-uebertragen.csv

Der Lauf ist wiederholbar — textgleiche Notizen am selben Dokument werden übersprungen.

Warum über die Exporte

econotice.nid enthält die docs_id. Die Auflösung nach docid steht in der docs-Tabelle, und die existiert nur im Export, nicht in der Live-Datenbank.

Das Skript liest deshalb alle Tranchen ein und baut eine gemeinsame Zuordnungstabelle, bevor es überträgt — eine Notiz kann zu einem Dokument aus einer anderen Tranche gehören.


3. Klassifikator und Suchindex

docker compose exec webserver python manage.py document_create_classifier
docker compose exec webserver python manage.py document_index reindex

Der Reindex über einen großen Bestand dauert.

Das ist der unterschätzte Nebeneffekt der Migration: Der Klassifikator hat jetzt einen vollständig klassifizierten Bestand als Trainingsbasis. Neue Dokumente werden dadurch von Anfang an brauchbar vorgeschlagen.


4. Verknüpfungen

Dokumentverknüpfungen sind im Export nicht enthalten und in der Live-Datenbank nicht auffindbar. Sie müssen von Hand nachgetragen werden.

Falls die Paare aus einer anderen Quelle vorliegen, geht es automatisch:

src_docid,dst_docid
10346,10351
python3 paperless_import.py --links verknuepfungen.csv

Das Skript setzt beide Richtungen ausdrücklich. Die automatische Gegenrichtung von Paperless greift bei Massenbearbeitung nachweislich nicht.

Von Hand geht es über das Zusatzfeld vom Typ Document Link. Die Archiv-Seriennummer entspricht der ecoDMS-ID, das Auffinden ist also unkompliziert.


5. Ausschlusslisten abarbeiten

Zwei Gruppen sind liegengeblieben:

Nicht unterstützte Dateitypen — aus exportN-nacharbeit.csv. Typischerweise .html, .zip, .indd. Je nach Inhalt: HTML über den Browser als PDF drucken, ZIP entpacken und einzeln ablegen, oder gar nicht übernehmen.

Fehlschläge aus dem Journal:

sqlite3 -header -csv migration.sqlite \
  "SELECT ecodms_docid, tranche, last_error FROM mapping
   WHERE status IN ('failed','uebersprungen');" > nacharbeit-journal.csv

Ein wiederkehrendes Muster: .eml-Dateien, die Paperless als text/html erkennt. Es sind gültige E-Mails mit korrekten Kopfzeilen, aber die Inhaltserkennung übergewichtet den HTML-Rumpf. Ein ausdrücklich mitgegebener MIME-Typ hilft nicht — Paperless prüft den Inhalt.

Behandlung: Mail im Mailprogramm öffnen und als PDF drucken. Das Ergebnis ist meist besser lesbar als Paperless' eigene Darstellung.


6. Abschlussprüfung

python3 paperless_import.py --verify

sqlite3 -header -column migration.sqlite \
  "SELECT status, COUNT(*) FROM mapping GROUP BY status;"

Stichproben in der Oberfläche:

  • Dokument mit mehreren Versionen — Kette und Zeitstempel korrekt?
  • Dokument mit Notiz — Text vorhanden und beim richtigen Dokument?
  • Mehrfach klassifiziertes Dokument — alle Tags vorhanden?
  • ZUGFeRD-Rechnung — eingebettete XML noch da?
    docker compose exec webserver bash -c \
      "pdfdetach -list /usr/src/paperless/media/documents/originals/*/*.pdf" | head
    
  • Volltextsuche nach einem bekannten Begriff
  • Anmeldung als eingeschränkter Benutzer — sieht er nur seine Dokumente?

Der letzte Punkt ist der wichtigste. Ein Dokument ohne Eigentümer ist in Paperless für jeden sichtbar, und in der Oberfläche fällt das als Superuser nicht auf.

export PT2=token_eines_eingeschraenkten_benutzers
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Token $PT2" \
  http://localhost:8000/api/documents/<fremde-id>/

403 ist richtig.


7. Umzug in die Produktivumgebung

Falls die Migration auf einer anderen Maschine lief:

# Quelle
docker compose stop webserver
docker compose exec -T db pg_dump -U paperless -Fc paperless > paperless.dump

rsync -rlt --no-perms --no-owner --no-group --info=progress2 \
      data/media/ ziel:/pfad/media/
rsync -rlt --no-perms --no-owner --no-group --info=progress2 \
      data/data/  ziel:/pfad/data/

--no-perms --no-owner --no-group ist wichtig, wenn das Ziel eigene Besitzverhältnisse oder ACLs hat — sonst überschreibt rsync sie mit denen der Quelle.

Auf dem Ziel erst nur Datenbank und Broker starten, damit Paperless nicht auf ein leeres Schema trifft und ein frisches anlegt:

docker compose up -d db broker
docker compose logs -f db          # auf "ready to accept connections" warten

docker compose exec -T db \
  pg_restore -U paperless -d paperless --clean --if-exists < paperless.dump

docker compose exec -T db psql -U paperless -d paperless -c \
  "SELECT count(*) FROM documents_document;"

docker compose up -d

Was identisch sein muss

Wert Warum
PAPERLESS_SECRET_KEY entwertet sonst alle Tokens und Freigabelinks
PAPERLESS_FILENAME_FORMAT benennt sonst den gesamten Bestand um
Postgres-Hauptversion ein Dump aus 17 läuft nicht in 16
Paperless-Version gleich oder neuer, nie älter

Die Quellinstanz stehen lassen, bis die Zielinstanz nachweislich läuft. Ein paar Wochen produktiver Betrieb sind eine bessere Freigabe als jede Prüfliste.


8. Betrieb

Was nach der Migration eingerichtet werden sollte, aber nicht Teil davon ist:

  • Reverse Proxy mit TLS, Paperless nie direkt exponieren
  • Zwei-Faktor für alle Konten
  • Sicherung: pg_dump plus media und data. Das Datenverzeichnis von Postgres nicht auf Dateiebene sichern — der Stand wäre inkonsistent
  • Consume-Ordner und Mail-Abruf
  • Papierkorb-Frist und Audit-Log

Und der Teil, den man auslässt: einmal wiederherstellen. Eine Sicherung, die nie zurückgespielt wurde, ist eine Vermutung.