diff --git a/Source/backend/app/routes/api.py b/Source/backend/app/routes/api.py index abeccbb..2d4b057 100755 --- a/Source/backend/app/routes/api.py +++ b/Source/backend/app/routes/api.py @@ -958,6 +958,206 @@ def verarbeite_ordner(id: int, db: Session = Depends(get_db)): return ergebnis +@router.get("/grobsortierung/stream") +async def grobsortierung_stream(db: Session = Depends(get_db)): + """Streaming-Endpoint für Grobsortierung aller aktiven Ordner mit Live-Updates""" + from ..models.database import SessionLocal + + # Aktive Ordner vorab laden + ordner_liste = db.query(QuellOrdner).filter(QuellOrdner.aktiv == True).all() + ordner_daten = [{ + "id": o.id, "name": o.name, "pfad": o.pfad, "ziel_ordner": o.ziel_ordner, + "dateitypen": o.dateitypen, "rekursiv": o.rekursiv, + "direkt_verschieben": getattr(o, 'direkt_verschieben', False), + "zugferd_behandlung": getattr(o, 'zugferd_behandlung', 'separieren'), + "ocr_aktivieren": getattr(o, 'ocr_aktivieren', True), + "original_sichern": getattr(o, 'original_sichern', None) + } for o in ordner_liste] + + # Regeln vorab laden + regeln = db.query(SortierRegel).filter(SortierRegel.aktiv == True).order_by(SortierRegel.prioritaet).all() + regeln_dicts = [{ + "id": r.id, "name": r.name, "prioritaet": r.prioritaet, + "muster": r.muster, "extraktion": r.extraktion, + "schema": r.schema, "unterordner": r.unterordner + } for r in regeln] + + async def event_generator(): + def send_event(data): + return f"data: {json.dumps(data)}\n\n" + + if not ordner_daten: + yield send_event({"type": "fehler", "nachricht": "Keine aktiven Ordner konfiguriert"}) + return + + pdf_processor = PDFProcessor() + sorter = Sorter(regeln_dicts) if regeln_dicts else None + gesamt_stats = {"gesamt": 0, "sortiert": 0, "zugferd": 0, "fehler": 0} + + # Dateien zählen + total_dateien = 0 + for o in ordner_daten: + pfad = Path(o["pfad"]) + if pfad.exists(): + dateien = sammle_dateien_aus_pfad(str(pfad), o["dateitypen"], o["rekursiv"]) + total_dateien += len(dateien) + + yield send_event({"type": "start", "ordner_count": len(ordner_daten), "gesamt": total_dateien}) + await asyncio.sleep(0) + + session = SessionLocal() + try: + for quell_ordner in ordner_daten: + pfad = Path(quell_ordner["pfad"]) + if not pfad.exists(): + yield send_event({"type": "ordner_fehler", "ordner": quell_ordner["name"], "fehler": "Ordner existiert nicht"}) + await asyncio.sleep(0) + continue + + ziel_basis = Path(quell_ordner["ziel_ordner"]) + dateien = sammle_dateien_aus_pfad(str(pfad), quell_ordner["dateitypen"], quell_ordner["rekursiv"]) + + yield send_event({ + "type": "ordner", + "ordner": quell_ordner["name"], + "dateien": len(dateien) + }) + await asyncio.sleep(0) + + for datei in dateien: + gesamt_stats["gesamt"] += 1 + datei_info = {"original": datei.name, "ordner": quell_ordner["name"]} + + try: + ist_pdf = datei.suffix.lower() == ".pdf" + text = "" + ist_zugferd = False + ocr_gemacht = False + + # PDF verarbeiten + if ist_pdf: + pdf_result = pdf_processor.verarbeite( + str(datei), + ocr_erlaubt=quell_ordner.get("ocr_aktivieren", True), + original_backup_pfad=quell_ordner.get("original_sichern") + ) + 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) + + # Direkt verschieben? + if quell_ordner["direkt_verschieben"]: + ziel_basis.mkdir(parents=True, exist_ok=True) + + # Original sichern + if quell_ordner["original_sichern"]: + import shutil + backup_dir = Path(quell_ordner["original_sichern"]) + backup_dir.mkdir(parents=True, exist_ok=True) + backup_pfad = backup_dir / datei.name + counter = 1 + while backup_pfad.exists(): + backup_pfad = backup_dir / f"{datei.stem}_{counter}{datei.suffix}" + counter += 1 + shutil.copy2(str(datei), str(backup_pfad)) + + neuer_pfad = ziel_basis / datei.name + counter = 1 + while neuer_pfad.exists(): + neuer_pfad = ziel_basis / f"{datei.stem}_{counter}{datei.suffix}" + counter += 1 + + datei.rename(neuer_pfad) + gesamt_stats["sortiert"] += 1 + datei_info["neuer_name"] = neuer_pfad.name + datei_info["status"] = "verschoben" + + session.add(VerarbeiteteDatei( + original_pfad=str(datei), + original_name=datei.name, + neuer_pfad=str(neuer_pfad), + neuer_name=neuer_pfad.name, + ist_zugferd=ist_zugferd, + ocr_durchgefuehrt=ocr_gemacht, + status="direkt_verschoben" + )) + + yield send_event({"type": "datei_fertig", **datei_info}) + await asyncio.sleep(0) + continue + + # Mit Regeln sortieren + if sorter: + doc_info = { + "text": text, + "original_name": datei.name, + "absender": "", + "dateityp": datei.suffix.lower() + } + regel = sorter.finde_passende_regel(doc_info) + + if regel: + extrahiert = sorter.extrahiere_felder(regel, doc_info) + schema = regel.get("schema", "{datum} - Dokument.pdf") + if schema.endswith(".pdf"): + schema = schema[:-4] + datei.suffix + neuer_name = sorter.generiere_dateinamen({"schema": schema, **regel}, extrahiert) + + ziel = ziel_basis + if regel.get("unterordner"): + ziel = ziel / regel["unterordner"] + ziel.mkdir(parents=True, exist_ok=True) + + neuer_pfad = sorter.verschiebe_datei(str(datei), str(ziel), neuer_name) + gesamt_stats["sortiert"] += 1 + datei_info["neuer_name"] = neuer_name + datei_info["regel"] = regel.get("name") + + session.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 + )) + + yield send_event({"type": "datei_fertig", **datei_info}) + await asyncio.sleep(0) + continue + + # Keine Regel gefunden + datei_info["status"] = "keine_regel" + yield send_event({"type": "datei_keine_regel", **datei_info}) + await asyncio.sleep(0) + + except Exception as e: + gesamt_stats["fehler"] += 1 + datei_info["fehler"] = str(e) + yield send_event({"type": "datei_fehler", **datei_info}) + await asyncio.sleep(0) + + session.commit() + finally: + session.close() + + yield send_event({"type": "fertig", **gesamt_stats}) + + return StreamingResponse( + event_generator(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "X-Accel-Buffering": "no" + } + ) + + # ============ Regeln ============ @router.get("/regeln") diff --git a/Source/frontend/static/js/app.js b/Source/frontend/static/js/app.js index b4221b9..13a2e9a 100755 --- a/Source/frontend/static/js/app.js +++ b/Source/frontend/static/js/app.js @@ -2869,55 +2869,116 @@ async function alleOrdnerVerarbeiten() { logContainer.innerHTML = '