Initial commit

This commit is contained in:
2026-09-04 19:51:02 +02:00
parent 7840783dc6
commit 13bae7d600
14 changed files with 3766 additions and 0 deletions

210
docs/03-extraktion.md Normal file
View File

@@ -0,0 +1,210 @@
# 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).