211 lines
7.0 KiB
Markdown
211 lines
7.0 KiB
Markdown
# 3. Extraktion und Prüfung
|
|
|
|
Stufe 1 liest eine Exporttranche und erzeugt ein normalisiertes
|
|
JSON-Manifest, einen Prüfbericht und eine Ausschlussliste. **Paperless wird
|
|
dabei nicht berührt.**
|
|
|
|
Die Trennung ist Absicht: Der Lauf ist offline, beliebig wiederholbar, und
|
|
das Ergebnis lässt sich ansehen, bevor irgendetwas geschrieben wird.
|
|
|
|
---
|
|
|
|
## Aufruf
|
|
|
|
```bash
|
|
python3 ecodms_extract.py /pfad/export1/archive \
|
|
-t export1 \
|
|
-o export1.json \
|
|
--skipped-csv export1-nacharbeit.csv
|
|
```
|
|
|
|
Als Pfad das Verzeichnis angeben, in dem `export.data` und `export.xml`
|
|
liegen — also `archive`, nicht das Tranchenverzeichnis darüber.
|
|
|
|
---
|
|
|
|
## Der Prüfbericht
|
|
|
|
```
|
|
Tranche export1
|
|
Dokumente 1963
|
|
Uebersprungen 34 (.html 20, .zip 11, .indd 2, .dotx 1)
|
|
Versionen 2462
|
|
Mehrfachklassifiziert 87
|
|
Notizen 81
|
|
Rollen FFN, r_benutzer_a, r_benutzer_b, ecoSIMSUSER
|
|
Rechtearten R, W
|
|
|
|
Hinweise (12):
|
|
[nicht_unterstuetzt] 34
|
|
[mehrfachklassifizierung] 87
|
|
[ohne_rechte] 1204
|
|
[nur_archiv] 143
|
|
…
|
|
```
|
|
|
|
**Diesen Bericht lesen, bevor importiert wird.** Er beantwortet alle
|
|
Fragen, die sonst mitten im Import auftauchen.
|
|
|
|
### Was die Kategorien bedeuten
|
|
|
|
| Kategorie | Bedeutung | Handlung |
|
|
|---|---|---|
|
|
| `nicht_unterstuetzt` | Dateityp, den Paperless ablehnt | Nacharbeit, Liste in der CSV |
|
|
| `mehrfachklassifizierung` | Dokument in mehreren Ordnern | wird zusammengeführt, Konflikte protokolliert |
|
|
| `ohne_rechte` | keine Zeile in `docrollen_hist` | Regel in `rollen.yml` festlegen |
|
|
| `nur_archiv` | keine Versionszeilen, Rückfall auf `ecosimsarchive` | normal, kein Handlungsbedarf |
|
|
| `datei_fehlt` | Version verweist auf nicht vorhandene Datei | Export unvollständig, prüfen |
|
|
| `xml_referenz_ohne_datei` | XML verweist auf fehlende Datei | bei Office-Originalen normal |
|
|
| `geloeschte_stammdaten` | Dokumentart oder Status ist gelöscht markiert | meist unkritisch |
|
|
| `papierkorb` | Dokument ist im ecoDMS-Papierkorb | wird übersprungen |
|
|
| `notiz_ohne_bezug` | Notiz zeigt auf unbekannte `docs_id` | in anderer Tranche, oder verwaist |
|
|
|
|
### Rollen prüfen
|
|
|
|
Die Zeile `Rollen` ist die wichtigste für den nächsten Schritt. Jede
|
|
genannte Rolle muss in `rollen.yml` stehen — entweder mit einem Ziel oder
|
|
ausdrücklich als `type: null`.
|
|
|
|
Stufe 2 bricht sonst ab, bevor die erste Datei hochgeht.
|
|
|
|
---
|
|
|
|
## Das Manifest
|
|
|
|
Je Dokument entsteht ein Eintrag:
|
|
|
|
```json
|
|
{
|
|
"ecodms_docid": 10347,
|
|
"tranche": "export1",
|
|
"title": "Rechnung Beispiel GmbH",
|
|
"created": "2025-07-29",
|
|
"document_type": "Rechnung",
|
|
"status": "Erledigt",
|
|
"folders": ["Allgemeines / Haushalt / Kleingeräte",
|
|
"Allgemeines / Steuer / ESt / Anlage N"],
|
|
"folder_parts": [["Allgemeines","Haushalt","Kleingeräte"],
|
|
["Allgemeines","Steuer","ESt","Anlage N"]],
|
|
"custom_fields": {
|
|
"Belegnummer": {"value": "902500503118", "type": "String"},
|
|
"Wiedervorlage": {"value": "2026-02-02", "type": "Date"}
|
|
},
|
|
"versions": [
|
|
{"version": 1, "file": "ecodms_docid_0010347_revision_0001.pdf",
|
|
"saved_at": "2025-07-29 14:12:03.881", "checksum": "…",
|
|
"source": "ecosimsversions"}
|
|
],
|
|
"permissions": [{"role": "r_benutzer_a", "right": "W"}],
|
|
"notes": [{"created": "2014-07-14 00:45:00", "text": "…", "user": ""}],
|
|
"docs_ids": [10829, 10834],
|
|
"conflicts": {}
|
|
}
|
|
```
|
|
|
|
**`folder_parts` ist maßgeblich, `folders` nur für die Anzeige.**
|
|
Ordnernamen können Schrägstriche enthalten (`Telefon / Internet`) — ein
|
|
späteres Zerlegen am Schrägstrich würde solche Namen zerschneiden.
|
|
|
|
**`conflicts`** wird bei Mehrfachklassifizierung gefüllt, wenn Titel,
|
|
Dokumentart, Status, Datum oder Wiedervorlage zwischen den Instanzen
|
|
abweichen. Dann gewinnt der jüngste `ctimestamp`, aber man sieht, dass
|
|
etwas verworfen wurde.
|
|
|
|
---
|
|
|
|
## Die Ausschlussliste
|
|
|
|
`--skipped-csv` schreibt alle Dokumente, deren Dateityp Paperless nicht
|
|
konsumieren kann:
|
|
|
|
```csv
|
|
ecodms_docid,endungen,titel,erstellt,ordner,dateien
|
|
1247,.html,Webseite Beispiel,2019-03-12,Allgemeines | EDV,ecodms_docid_0001247_revision_0001.html
|
|
1533,.zip,Belege Sammlung,2020-11-04,Steuer,ecodms_docid_0001533_revision_0001.zip
|
|
```
|
|
|
|
Diese Dokumente sind **nicht** im Manifest. Sie scheitern deshalb nicht
|
|
beim Import, sondern stehen sauber auf einer Liste.
|
|
|
|
Die Liste der akzeptierten Endungen steht als `SUPPORTED_EXTENSIONS` oben
|
|
im Skript. Sie sollte gegen die Zielinstanz geprüft werden:
|
|
|
|
```bash
|
|
docker compose exec webserver python manage.py shell -c \
|
|
"from documents.parsers import get_supported_file_extensions as f; print(sorted(f()))"
|
|
```
|
|
|
|
---
|
|
|
|
## Regeln, die Stufe 1 anwendet
|
|
|
|
Alle stammen aus der Analyse eines produktiven Bestands. Ausführlich in
|
|
[referenz-ecodms-exportformat.md](referenz-ecodms-exportformat.md).
|
|
|
|
1. **Nach `docid` gruppieren, nicht nach `docs_id`.** Eine `docid` kann
|
|
mehrere Klassifizierungsinstanzen haben.
|
|
2. **Ordner vereinigen, jüngster `ctimestamp` gewinnt** bei skalaren
|
|
Feldern.
|
|
3. **Immer `realname` verwenden, nie `pdfrealname`.** Sonst gehen
|
|
eingebettete ZUGFeRD-XML und E-Mail-Anhänge verloren.
|
|
4. **Ohne Versionszeilen auf `ecosimsarchive` zurückfallen.** Sonst
|
|
verschwinden solche Dokumente stillschweigend.
|
|
5. **`docrollen_hist.docid` enthält `docs_id`.** Join über `docs.id`.
|
|
6. **Nur die höchste in `docrollen_hist` vorhandene Revision** je
|
|
`docs_id` gilt. Ältere enthalten noch das globale Importrecht.
|
|
7. **Revisionen als Zahlentupel sortieren.** `'1.10'` ist lexikografisch
|
|
kleiner als `'1.9'`.
|
|
8. **Base64 selektiv dekodieren.** `savedate` und `filename` sind
|
|
kodiert, `realname` und `pdfrealname` nicht.
|
|
9. **Wiedervorlage aus `klassifizierung.defdate`**, nicht aus
|
|
`dynattribute`.
|
|
10. **`econotice.nid` enthält `docs_id`**, der Text ist base64-kodiertes
|
|
Qt-HTML.
|
|
|
|
---
|
|
|
|
## Wiederholbarkeit
|
|
|
|
Stufe 1 schreibt nichts außer den Ausgabedateien. Sie kann beliebig oft
|
|
laufen — bei jeder Änderung an den Regeln, bei jedem neuen Export.
|
|
|
|
Nach einer Änderung am Skript **alle Manifeste neu erzeugen**, damit sie
|
|
dasselbe Format haben.
|
|
|
|
---
|
|
|
|
## Alle Tranchen auf einmal
|
|
|
|
```bash
|
|
for n in 1 2 3 4 5 6; do
|
|
python3 ecodms_extract.py "export-daten/export$n/archive" \
|
|
-t "export$n" -o "export$n.json" \
|
|
--skipped-csv "export$n-nacharbeit.csv"
|
|
done
|
|
```
|
|
|
|
Danach eine Gesamtübersicht der Dateitypen:
|
|
|
|
```bash
|
|
python3 -c "
|
|
import json, glob, collections
|
|
v = collections.Counter(); d = collections.Counter()
|
|
for f in sorted(glob.glob('export*.json')):
|
|
for doc in json.load(open(f))['documents']:
|
|
exts = set()
|
|
for ver in doc['versions']:
|
|
e = '.' + ver['file'].rsplit('.',1)[-1].lower()
|
|
v[e] += 1; exts.add(e)
|
|
for e in exts: d[e] += 1
|
|
print(f\"{'Endung':10s} {'Versionen':>10s} {'Dokumente':>10s}\")
|
|
for k, n in v.most_common(): print(f'{k:10s} {n:10d} {d[k]:10d}')
|
|
"
|
|
```
|
|
|
|
Das ist die Zahl, die vor dem Import bekannt sein sollte — sonst stolpert
|
|
man bei Tranche 4 über einen Dateityp, der in Tranche 1 nicht vorkam.
|
|
|
|
---
|
|
|
|
Weiter mit [Import](04-import.md).
|