251 lines
7.5 KiB
Markdown
251 lines
7.5 KiB
Markdown
# 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=<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
|
|
|
|
```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/<fremde-id>/
|
|
```
|
|
|
|
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.
|