# 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. ```bash 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. ```bash 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 ```bash curl -s -H "Authorization: Token $PT" \ "http://localhost:8000/api/documents/?archive_serial_number=" \ | 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 ```bash 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. ```bash 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 ```bash 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: ```csv src_docid,dst_docid 10346,10351 ``` ```bash 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:** ```bash 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 ```bash 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? ```bash 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. ```bash export PT2=token_eines_eingeschraenkten_benutzers curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Token $PT2" \ http://localhost:8000/api/documents// ``` 403 ist richtig. --- ## 7. Umzug in die Produktivumgebung Falls die Migration auf einer anderen Maschine lief: ```bash # 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: ```bash 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.