Fix: Feinsortierung im Scheduler + DB-Reconnect
- execute_sortierregeln verarbeitet jetzt OrdnerRegel-Zuweisungen (wie der Button, nicht nur freie_ordner) - DB-Verbindung mit Retry bei Verbindungsfehlern (5 Versuche) - SQLAlchemy 2.0 kompatibel (text() für Raw-SQL) - Bessere Pool-Einstellungen für MariaDB Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f7a84c50e2
commit
bb84f200d0
4 changed files with 293 additions and 39 deletions
BIN
Docker - Image/V 2.6.tar
Executable file
BIN
Docker - Image/V 2.6.tar
Executable file
Binary file not shown.
|
|
@ -26,15 +26,53 @@ if is_sqlite:
|
||||||
cursor.execute("PRAGMA busy_timeout=30000")
|
cursor.execute("PRAGMA busy_timeout=30000")
|
||||||
cursor.close()
|
cursor.close()
|
||||||
else:
|
else:
|
||||||
# MariaDB/MySQL
|
# MariaDB/MySQL mit Retry bei Verbindungsfehlern
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
DATABASE_URL,
|
DATABASE_URL,
|
||||||
echo=False,
|
echo=False,
|
||||||
pool_pre_ping=True,
|
pool_pre_ping=True, # Prüft Verbindung vor Nutzung
|
||||||
pool_recycle=3600
|
pool_recycle=1800, # Recycled Verbindungen nach 30 Min
|
||||||
|
pool_size=5, # Max 5 Verbindungen im Pool
|
||||||
|
max_overflow=10, # Bis zu 10 zusätzliche bei Bedarf
|
||||||
|
pool_timeout=30, # Timeout beim Warten auf Verbindung
|
||||||
|
connect_args={
|
||||||
|
"connect_timeout": 10 # Timeout beim Verbindungsaufbau
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
SessionLocal = sessionmaker(bind=engine)
|
SessionLocal = sessionmaker(bind=engine)
|
||||||
|
|
||||||
|
|
||||||
|
def get_db_with_retry(max_retries: int = 5, retry_delay: int = 5):
|
||||||
|
"""
|
||||||
|
Gibt eine DB-Session zurück, mit Retry bei Verbindungsfehlern.
|
||||||
|
Für Scheduler und Background-Tasks.
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
from sqlalchemy.exc import OperationalError, DisconnectionError
|
||||||
|
from sqlalchemy import text
|
||||||
|
|
||||||
|
last_error = None
|
||||||
|
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
db = SessionLocal()
|
||||||
|
# Test-Query um Verbindung zu prüfen
|
||||||
|
db.execute(text("SELECT 1"))
|
||||||
|
return db
|
||||||
|
except (OperationalError, DisconnectionError) as e:
|
||||||
|
last_error = e
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
print(f"[DB] ⚠️ Verbindungsfehler (Versuch {attempt + 1}/{max_retries}): {e}", flush=True)
|
||||||
|
print(f"[DB] Warte {retry_delay} Sekunden vor erneutem Versuch...", flush=True)
|
||||||
|
time.sleep(retry_delay)
|
||||||
|
else:
|
||||||
|
print(f"[DB] ❌ Verbindung fehlgeschlagen nach {max_retries} Versuchen: {e}", flush=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DB] ❌ Unerwarteter Fehler: {e}", flush=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
raise last_error
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from pathlib import Path
|
||||||
from typing import Optional, Dict, List
|
from typing import Optional, Dict, List
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from ..models.database import SessionLocal, Zeitplan, Postfach, QuellOrdner
|
from ..models.database import SessionLocal, Zeitplan, Postfach, QuellOrdner, OrdnerRegel, SortierRegel
|
||||||
from ..modules.mail_fetcher import MailFetcher
|
from ..modules.mail_fetcher import MailFetcher
|
||||||
from ..modules.sorter import Sorter
|
from ..modules.sorter import Sorter
|
||||||
from ..config import INBOX_DIR
|
from ..config import INBOX_DIR
|
||||||
|
|
@ -342,8 +342,46 @@ def create_trigger(zp: Zeitplan) -> Optional[CronTrigger]:
|
||||||
|
|
||||||
|
|
||||||
def execute_zeitplan(zeitplan_id: int):
|
def execute_zeitplan(zeitplan_id: int):
|
||||||
"""Führt einen Zeitplan aus"""
|
"""Führt einen Zeitplan aus mit Retry bei DB-Verbindungsfehlern"""
|
||||||
|
from sqlalchemy.exc import OperationalError, DisconnectionError
|
||||||
|
from sqlalchemy import text
|
||||||
|
import time
|
||||||
|
|
||||||
|
max_db_retries = 5
|
||||||
|
retry_delay = 10
|
||||||
|
|
||||||
|
# Versuche DB-Verbindung mit Retry
|
||||||
|
db = None
|
||||||
|
for attempt in range(max_db_retries):
|
||||||
|
try:
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
|
# Test-Query um Verbindung zu prüfen
|
||||||
|
db.execute(text("SELECT 1"))
|
||||||
|
break
|
||||||
|
except (OperationalError, DisconnectionError) as e:
|
||||||
|
print(f"[SCHEDULER] ⚠️ DB-Verbindungsfehler bei Zeitplan {zeitplan_id} (Versuch {attempt + 1}/{max_db_retries}): {e}", flush=True)
|
||||||
|
if db:
|
||||||
|
try:
|
||||||
|
db.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if attempt < max_db_retries - 1:
|
||||||
|
print(f"[SCHEDULER] Warte {retry_delay} Sekunden vor erneutem Versuch...", flush=True)
|
||||||
|
time.sleep(retry_delay)
|
||||||
|
else:
|
||||||
|
print(f"[SCHEDULER] ❌ DB-Verbindung fehlgeschlagen nach {max_db_retries} Versuchen - Zeitplan übersprungen", flush=True)
|
||||||
|
logger.error(f"DB-Verbindung fehlgeschlagen für Zeitplan {zeitplan_id}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[SCHEDULER] ❌ Unerwarteter DB-Fehler: {e}", flush=True)
|
||||||
|
logger.error(f"Unerwarteter DB-Fehler für Zeitplan {zeitplan_id}: {e}")
|
||||||
|
if db:
|
||||||
|
try:
|
||||||
|
db.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
zeitplan = db.query(Zeitplan).filter(Zeitplan.id == zeitplan_id).first()
|
zeitplan = db.query(Zeitplan).filter(Zeitplan.id == zeitplan_id).first()
|
||||||
if not zeitplan:
|
if not zeitplan:
|
||||||
|
|
@ -386,15 +424,36 @@ def execute_zeitplan(zeitplan_id: int):
|
||||||
db.commit()
|
db.commit()
|
||||||
logger.info(f"Zeitplan abgeschlossen: {zeitplan.name} - {zeitplan.letzter_status}")
|
logger.info(f"Zeitplan abgeschlossen: {zeitplan.name} - {zeitplan.letzter_status}")
|
||||||
|
|
||||||
|
except (OperationalError, DisconnectionError) as e:
|
||||||
|
# DB-Verbindung während Ausführung verloren
|
||||||
|
print(f"[SCHEDULER] ❌ DB-Verbindung verloren während Zeitplan '{zeitplan.name}': {e}", flush=True)
|
||||||
|
logger.error(f"DB-Verbindung verloren bei Zeitplan {zeitplan.name}: {e}")
|
||||||
|
# Versuche Status trotzdem zu speichern
|
||||||
|
try:
|
||||||
|
db.rollback()
|
||||||
|
zeitplan.letzte_ausfuehrung = datetime.utcnow()
|
||||||
|
zeitplan.letzter_status = "fehler"
|
||||||
|
zeitplan.letzte_meldung = f"DB-Verbindungsfehler: {str(e)[:450]}"
|
||||||
|
db.commit()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
zeitplan.letzte_ausfuehrung = datetime.utcnow()
|
zeitplan.letzte_ausfuehrung = datetime.utcnow()
|
||||||
zeitplan.letzter_status = "fehler"
|
zeitplan.letzter_status = "fehler"
|
||||||
zeitplan.letzte_meldung = str(e)[:500]
|
zeitplan.letzte_meldung = str(e)[:500]
|
||||||
|
try:
|
||||||
db.commit()
|
db.commit()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
logger.error(f"Fehler bei Zeitplan {zeitplan.name}: {e}")
|
logger.error(f"Fehler bei Zeitplan {zeitplan.name}: {e}")
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
if db:
|
||||||
|
try:
|
||||||
db.close()
|
db.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def execute_mail_abruf(db, zeitplan: Zeitplan) -> Dict:
|
def execute_mail_abruf(db, zeitplan: Zeitplan) -> Dict:
|
||||||
|
|
@ -743,33 +802,193 @@ def execute_grobsortierung(db, zeitplan: Zeitplan) -> Dict:
|
||||||
|
|
||||||
|
|
||||||
def execute_sortierregeln(db, zeitplan: Zeitplan) -> Dict:
|
def execute_sortierregeln(db, zeitplan: Zeitplan) -> Dict:
|
||||||
"""Führt nur Sortierregeln aus (freie Ordner von Regeln)"""
|
"""Führt Feinsortierung aus (Ordner-Zuweisungen + freie Ordner von Regeln)"""
|
||||||
from ..models.database import SortierRegel, VerarbeiteteDatei
|
from ..models.database import SortierRegel, VerarbeiteteDatei, OrdnerRegel
|
||||||
from ..modules.pdf_processor import PDFProcessor
|
from ..modules.pdf_processor import PDFProcessor
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Regeln laden (optional spezifische Regel)
|
print("", flush=True)
|
||||||
if zeitplan.regel_id:
|
print("[FEINSORTIERUNG] === START ===", flush=True)
|
||||||
regeln = db.query(SortierRegel).filter(
|
print(f"[FEINSORTIERUNG] Zeitplan: {zeitplan.name} (ID: {zeitplan.id})", flush=True)
|
||||||
SortierRegel.id == zeitplan.regel_id,
|
|
||||||
SortierRegel.aktiv == True
|
|
||||||
).all()
|
|
||||||
else:
|
|
||||||
regeln = db.query(SortierRegel).filter(SortierRegel.aktiv == True).all()
|
|
||||||
|
|
||||||
if not regeln:
|
|
||||||
return {"erfolg": True, "meldung": "Keine aktiven Regeln gefunden"}
|
|
||||||
|
|
||||||
pdf_processor = PDFProcessor()
|
pdf_processor = PDFProcessor()
|
||||||
gesamt_sortiert = 0
|
gesamt_sortiert = 0
|
||||||
gesamt_fehler = 0
|
gesamt_fehler = 0
|
||||||
|
gesamt_ohne_regel = 0
|
||||||
fehler_meldungen = []
|
fehler_meldungen = []
|
||||||
|
|
||||||
for regel in regeln:
|
# Fallback-Regeln laden (gelten für alle Ordner wenn keine andere Regel passt)
|
||||||
|
fallback_regeln = db.query(SortierRegel).filter(
|
||||||
|
SortierRegel.aktiv == True,
|
||||||
|
SortierRegel.ist_fallback == True
|
||||||
|
).order_by(SortierRegel.prioritaet).all()
|
||||||
|
|
||||||
|
fallback_dicts = [{
|
||||||
|
"id": r.id, "name": r.name, "prioritaet": r.prioritaet,
|
||||||
|
"muster": r.muster, "extraktion": r.extraktion,
|
||||||
|
"schema": r.schema, "unterordner": r.unterordner,
|
||||||
|
"ziel_ordner": getattr(r, 'ziel_ordner', None),
|
||||||
|
"nur_umbenennen": getattr(r, 'nur_umbenennen', False)
|
||||||
|
} for r in fallback_regeln]
|
||||||
|
|
||||||
|
fallback_sorter = Sorter(fallback_dicts) if fallback_dicts else None
|
||||||
|
print(f"[FEINSORTIERUNG] Fallback-Regeln: {len(fallback_dicts)}", flush=True)
|
||||||
|
|
||||||
|
# ============ TEIL 1: Ordner-Zuweisungen verarbeiten ============
|
||||||
|
# (wie der Button /api/sortierung/starten)
|
||||||
|
quell_ordner_liste = db.query(QuellOrdner).filter(QuellOrdner.aktiv == True).all()
|
||||||
|
print(f"[FEINSORTIERUNG] Aktive Quellordner: {len(quell_ordner_liste)}", flush=True)
|
||||||
|
|
||||||
|
for quell_ordner in quell_ordner_liste:
|
||||||
|
# Feinsortierung arbeitet im ZIEL-Ordner (wo Dateien nach Grobsortierung liegen)
|
||||||
|
pfad = Path(quell_ordner.ziel_ordner)
|
||||||
|
|
||||||
|
print(f"[FEINSORTIERUNG] --- Ordner: {quell_ordner.name} ---", flush=True)
|
||||||
|
print(f"[FEINSORTIERUNG] Ziel-Pfad: {pfad}", flush=True)
|
||||||
|
|
||||||
|
if not pfad.exists():
|
||||||
|
print(f"[FEINSORTIERUNG] ⚠️ Pfad existiert nicht - überspringe", flush=True)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Dateien sammeln
|
||||||
|
dateien = [f for f in pfad.glob("**/*") if f.is_file() and f.suffix.lower() == ".pdf"]
|
||||||
|
print(f"[FEINSORTIERUNG] Gefunden: {len(dateien)} PDF-Dateien", flush=True)
|
||||||
|
|
||||||
|
# Lade nur Regeln die diesem Ordner zugewiesen sind
|
||||||
|
zuweisungen = db.query(OrdnerRegel).filter(OrdnerRegel.ordner_id == quell_ordner.id).all()
|
||||||
|
zugewiesene_regel_ids = [z.regel_id for z in zuweisungen]
|
||||||
|
|
||||||
|
if zugewiesene_regel_ids:
|
||||||
|
regeln = db.query(SortierRegel).filter(
|
||||||
|
SortierRegel.id.in_(zugewiesene_regel_ids),
|
||||||
|
SortierRegel.aktiv == True,
|
||||||
|
SortierRegel.ist_fallback == False
|
||||||
|
).order_by(SortierRegel.prioritaet).all()
|
||||||
|
else:
|
||||||
|
regeln = []
|
||||||
|
|
||||||
|
# Regeln in Dict-Format
|
||||||
|
regeln_dicts = [{
|
||||||
|
"id": r.id, "name": r.name, "prioritaet": r.prioritaet,
|
||||||
|
"muster": r.muster, "extraktion": r.extraktion,
|
||||||
|
"schema": r.schema, "unterordner": r.unterordner,
|
||||||
|
"ziel_ordner": getattr(r, 'ziel_ordner', None),
|
||||||
|
"nur_umbenennen": getattr(r, 'nur_umbenennen', False)
|
||||||
|
} for r in regeln]
|
||||||
|
|
||||||
|
sorter = Sorter(regeln_dicts) if regeln_dicts else None
|
||||||
|
print(f"[FEINSORTIERUNG] Zugewiesene Regeln: {len(regeln_dicts)}", flush=True)
|
||||||
|
|
||||||
|
for datei in dateien:
|
||||||
|
try:
|
||||||
|
text = ""
|
||||||
|
ist_zugferd = False
|
||||||
|
ocr_gemacht = False
|
||||||
|
|
||||||
|
# PDF verarbeiten
|
||||||
|
ocr_erlaubt = getattr(quell_ordner, 'ocr_aktivieren', True)
|
||||||
|
original_backup = getattr(quell_ordner, 'original_sichern', None)
|
||||||
|
|
||||||
|
pdf_result = pdf_processor.verarbeite(
|
||||||
|
str(datei),
|
||||||
|
ocr_erlaubt=ocr_erlaubt,
|
||||||
|
original_backup_pfad=original_backup
|
||||||
|
)
|
||||||
|
|
||||||
|
if pdf_result.get("fehler"):
|
||||||
|
raise Exception(pdf_result["fehler"])
|
||||||
|
|
||||||
|
text = pdf_result.get("text", "")
|
||||||
|
ist_zugferd = pdf_result.get("ist_zugferd", False)
|
||||||
|
ocr_gemacht = pdf_result.get("ocr_durchgefuehrt", False)
|
||||||
|
|
||||||
|
# Dokument-Info für Regel-Matching
|
||||||
|
doc_info = {
|
||||||
|
"text": text,
|
||||||
|
"original_name": datei.name,
|
||||||
|
"absender": "",
|
||||||
|
"dateityp": datei.suffix.lower()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Passende Regel finden (erst zugewiesene, dann Fallback)
|
||||||
|
regel = None
|
||||||
|
if sorter:
|
||||||
|
regel = sorter.finde_passende_regel(doc_info)
|
||||||
|
|
||||||
|
# Fallback-Regel wenn keine zugewiesene passt
|
||||||
|
ist_fallback = False
|
||||||
|
if not regel and fallback_sorter:
|
||||||
|
regel = fallback_sorter.finde_passende_regel(doc_info)
|
||||||
|
if regel:
|
||||||
|
ist_fallback = True
|
||||||
|
|
||||||
|
if not regel:
|
||||||
|
print(f"[FEINSORTIERUNG] ⚠️ Keine Regel für: {datei.name}", flush=True)
|
||||||
|
gesamt_ohne_regel += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"[FEINSORTIERUNG] ✓ Regel '{regel.get('name')}' für: {datei.name}", flush=True)
|
||||||
|
|
||||||
|
# Felder extrahieren
|
||||||
|
aktiver_sorter = sorter if not ist_fallback else fallback_sorter
|
||||||
|
extrahiert = aktiver_sorter.extrahiere_felder(regel, doc_info)
|
||||||
|
|
||||||
|
# Dateiendung beibehalten
|
||||||
|
schema = regel.get("schema", "{datum} - Dokument.pdf")
|
||||||
|
if schema.endswith(".pdf"):
|
||||||
|
schema = schema[:-4] + datei.suffix
|
||||||
|
neuer_name = aktiver_sorter.generiere_dateinamen(
|
||||||
|
{"schema": schema, **regel}, extrahiert
|
||||||
|
)
|
||||||
|
|
||||||
|
# Zielordner bestimmen
|
||||||
|
if regel.get("nur_umbenennen"):
|
||||||
|
ziel = datei.parent
|
||||||
|
elif regel.get("ziel_ordner"):
|
||||||
|
ziel = Path(regel["ziel_ordner"])
|
||||||
|
if regel.get("unterordner"):
|
||||||
|
ziel = ziel / regel["unterordner"]
|
||||||
|
else:
|
||||||
|
ziel = pfad
|
||||||
|
if regel.get("unterordner"):
|
||||||
|
ziel = ziel / regel["unterordner"]
|
||||||
|
ziel.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Verschieben
|
||||||
|
print(f"[FEINSORTIERUNG] → Verschiebe: {datei.name} -> {ziel}/{neuer_name}", flush=True)
|
||||||
|
neuer_pfad = aktiver_sorter.verschiebe_datei(str(datei), str(ziel), neuer_name)
|
||||||
|
|
||||||
|
gesamt_sortiert += 1
|
||||||
|
|
||||||
|
db.add(VerarbeiteteDatei(
|
||||||
|
original_pfad=str(datei),
|
||||||
|
original_name=datei.name,
|
||||||
|
neuer_pfad=neuer_pfad,
|
||||||
|
neuer_name=neuer_name,
|
||||||
|
ist_zugferd=ist_zugferd,
|
||||||
|
ocr_durchgefuehrt=ocr_gemacht,
|
||||||
|
status="sortiert",
|
||||||
|
extrahierte_daten=extrahiert
|
||||||
|
))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
gesamt_fehler += 1
|
||||||
|
print(f"[FEINSORTIERUNG] ❌ Fehler bei {datei.name}: {e}", flush=True)
|
||||||
|
logger.error(f"Fehler bei Datei {datei}: {e}")
|
||||||
|
|
||||||
|
# ============ TEIL 2: Freie Ordner von Regeln verarbeiten ============
|
||||||
|
print("", flush=True)
|
||||||
|
print("[FEINSORTIERUNG] --- Freie Ordner ---", flush=True)
|
||||||
|
|
||||||
|
alle_regeln = db.query(SortierRegel).filter(SortierRegel.aktiv == True).all()
|
||||||
|
|
||||||
|
for regel in alle_regeln:
|
||||||
freie_ordner = regel.freie_ordner if regel.freie_ordner else []
|
freie_ordner = regel.freie_ordner if regel.freie_ordner else []
|
||||||
if not freie_ordner:
|
if not freie_ordner:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
print(f"[FEINSORTIERUNG] Regel '{regel.name}' hat {len(freie_ordner)} freie Ordner", flush=True)
|
||||||
|
|
||||||
regel_dict = {
|
regel_dict = {
|
||||||
"id": regel.id,
|
"id": regel.id,
|
||||||
"name": regel.name,
|
"name": regel.name,
|
||||||
|
|
@ -786,18 +1005,17 @@ def execute_sortierregeln(db, zeitplan: Zeitplan) -> Dict:
|
||||||
for freier_ordner_pfad in freie_ordner:
|
for freier_ordner_pfad in freie_ordner:
|
||||||
freier_pfad = Path(freier_ordner_pfad)
|
freier_pfad = Path(freier_ordner_pfad)
|
||||||
if not freier_pfad.exists() or not freier_pfad.is_dir():
|
if not freier_pfad.exists() or not freier_pfad.is_dir():
|
||||||
|
print(f"[FEINSORTIERUNG] ⚠️ Freier Ordner existiert nicht: {freier_ordner_pfad}", flush=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Dateien sammeln
|
|
||||||
dateien = [f for f in freier_pfad.glob("**/*") if f.is_file() and f.suffix.lower() == ".pdf"]
|
dateien = [f for f in freier_pfad.glob("**/*") if f.is_file() and f.suffix.lower() == ".pdf"]
|
||||||
|
print(f"[FEINSORTIERUNG] Freier Ordner {freier_ordner_pfad}: {len(dateien)} Dateien", flush=True)
|
||||||
|
|
||||||
for datei in dateien:
|
for datei in dateien:
|
||||||
try:
|
try:
|
||||||
ist_pdf = datei.suffix.lower() == ".pdf"
|
|
||||||
text = ""
|
text = ""
|
||||||
ist_zugferd = False
|
ist_zugferd = False
|
||||||
|
|
||||||
if ist_pdf:
|
|
||||||
pdf_result = pdf_processor.verarbeite(str(datei))
|
pdf_result = pdf_processor.verarbeite(str(datei))
|
||||||
if pdf_result.get("fehler"):
|
if pdf_result.get("fehler"):
|
||||||
raise Exception(pdf_result["fehler"])
|
raise Exception(pdf_result["fehler"])
|
||||||
|
|
@ -811,15 +1029,12 @@ def execute_sortierregeln(db, zeitplan: Zeitplan) -> Dict:
|
||||||
"dateityp": datei.suffix.lower()
|
"dateityp": datei.suffix.lower()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Prüfe ob Regel passt
|
|
||||||
passend = regel_sorter.finde_passende_regel(doc_info)
|
passend = regel_sorter.finde_passende_regel(doc_info)
|
||||||
if not passend:
|
if not passend:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Felder extrahieren
|
|
||||||
extrahiert = regel_sorter.extrahiere_felder(passend, doc_info)
|
extrahiert = regel_sorter.extrahiere_felder(passend, doc_info)
|
||||||
|
|
||||||
# Dateiname generieren
|
|
||||||
schema = passend.get("schema", "{datum} - Dokument.pdf")
|
schema = passend.get("schema", "{datum} - Dokument.pdf")
|
||||||
if schema.endswith(".pdf"):
|
if schema.endswith(".pdf"):
|
||||||
schema = schema[:-4] + datei.suffix
|
schema = schema[:-4] + datei.suffix
|
||||||
|
|
@ -827,23 +1042,18 @@ def execute_sortierregeln(db, zeitplan: Zeitplan) -> Dict:
|
||||||
{"schema": schema, **passend}, extrahiert
|
{"schema": schema, **passend}, extrahiert
|
||||||
)
|
)
|
||||||
|
|
||||||
# Zielordner bestimmen
|
|
||||||
if passend.get("nur_umbenennen"):
|
if passend.get("nur_umbenennen"):
|
||||||
# Nur umbenennen - Datei bleibt im aktuellen Ordner
|
|
||||||
ziel = datei.parent
|
ziel = datei.parent
|
||||||
elif passend.get("ziel_ordner"):
|
elif passend.get("ziel_ordner"):
|
||||||
# Regel hat eigenen Zielordner
|
|
||||||
ziel = Path(passend["ziel_ordner"])
|
ziel = Path(passend["ziel_ordner"])
|
||||||
if passend.get("unterordner"):
|
if passend.get("unterordner"):
|
||||||
ziel = ziel / passend["unterordner"]
|
ziel = ziel / passend["unterordner"]
|
||||||
else:
|
else:
|
||||||
# Kein Zielordner - bleibt im freien Ordner
|
|
||||||
ziel = freier_pfad
|
ziel = freier_pfad
|
||||||
if passend.get("unterordner"):
|
if passend.get("unterordner"):
|
||||||
ziel = ziel / passend["unterordner"]
|
ziel = ziel / passend["unterordner"]
|
||||||
ziel.mkdir(parents=True, exist_ok=True)
|
ziel.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Verschieben/Umbenennen
|
|
||||||
regel_sorter.verschiebe_datei(str(datei), str(ziel), neuer_name)
|
regel_sorter.verschiebe_datei(str(datei), str(ziel), neuer_name)
|
||||||
gesamt_sortiert += 1
|
gesamt_sortiert += 1
|
||||||
|
|
||||||
|
|
@ -863,12 +1073,18 @@ def execute_sortierregeln(db, zeitplan: Zeitplan) -> Dict:
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
meldung = f"{gesamt_sortiert} Dateien mit Regeln sortiert"
|
meldung = f"{gesamt_sortiert} Dateien sortiert"
|
||||||
|
if gesamt_ohne_regel > 0:
|
||||||
|
meldung += f", {gesamt_ohne_regel} ohne passende Regel"
|
||||||
if gesamt_fehler > 0:
|
if gesamt_fehler > 0:
|
||||||
meldung += f", {gesamt_fehler} Fehler"
|
meldung += f", {gesamt_fehler} Fehler"
|
||||||
if fehler_meldungen:
|
if fehler_meldungen:
|
||||||
meldung += f" ({'; '.join(fehler_meldungen)})"
|
meldung += f" ({'; '.join(fehler_meldungen)})"
|
||||||
|
|
||||||
|
print("", flush=True)
|
||||||
|
print(f"[FEINSORTIERUNG] === ENDE === {meldung}", flush=True)
|
||||||
|
print("", flush=True)
|
||||||
|
|
||||||
return {"erfolg": gesamt_fehler == 0, "meldung": meldung}
|
return {"erfolg": gesamt_fehler == 0, "meldung": meldung}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
BIN
dateiverwaltung-latest.tar.gz
Executable file
BIN
dateiverwaltung-latest.tar.gz
Executable file
Binary file not shown.
Loading…
Reference in a new issue