Initial commit
This commit is contained in:
296
docs/04-import.md
Normal file
296
docs/04-import.md
Normal file
@@ -0,0 +1,296 @@
|
||||
# 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.
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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](05-nacharbeit.md).
|
||||
|
||||
---
|
||||
|
||||
## Fehler behandeln
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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](05-nacharbeit.md).
|
||||
Reference in New Issue
Block a user