diff --git a/ui/dockwidget.py b/ui/dockwidget.py index 9f74104..c004891 100644 --- a/ui/dockwidget.py +++ b/ui/dockwidget.py @@ -1,7 +1,45 @@ +#sn_plan41/ui/dockwidget.py from sn_basis.ui.tabs.settings_tab import SettingsTab from sn_plan41.ui.tab_a_ui import TabA #from sn_plan41.ui.tabs.tab_b import TabB -from sn_basis.ui.base_dockwidget import BaseDockWidget +from sn_basis.ui.base_dockwidget import BaseDockWidget +from sn_basis.functions.qt_wrapper import QTabWidget +from sn_basis.functions.message_wrapper import error + class DockWidget(BaseDockWidget): tabs = [TabA, SettingsTab] + + def __init__(self, parent=None, subtitle="", pruefmanager=None, data_grabber=None): + super().__init__(parent, subtitle) + + # Services als Attribute speichern + self.pruefmanager = pruefmanager + self.data_grabber = data_grabber + + # Tabs NACH Services initialisieren (override der Basis-Logik) + self._init_tabs_with_services() + + def _init_tabs_with_services(self): + """Tabs mit pruefmanager/data_grabber initialisieren""" + try: + # Bestehendes TabWidget löschen + self.setWidget(None) + + tab_widget = QTabWidget() + for tab_class in self.tabs: + tab_instance = tab_class(self) # parent=self.dockwidget + tab_title = getattr(tab_class, "tab_title", tab_class.__name__) + tab_widget.addTab(tab_instance, tab_title) + + # Services durchreichen + if hasattr(tab_instance, 'set_services'): + tab_instance.set_services( + pruefmanager=self.pruefmanager, + data_grabber=self.data_grabber + ) + + self.setWidget(tab_widget) + except Exception as e: + error("Services-Tabs konnten nicht initialisiert werden", str(e)) + diff --git a/ui/tab_a_logic.py b/ui/tab_a_logic.py index 61bdbb7..f94aa75 100644 --- a/ui/tab_a_logic.py +++ b/ui/tab_a_logic.py @@ -4,87 +4,112 @@ sn_plan41/ui/tab_a_logic.py – Fachlogik für Tab A (Daten) from __future__ import annotations -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional from collections.abc import Mapping as _Mapping +import os -from sn_basis.functions.variable_wrapper import ( # type: ignore +from sn_basis.functions.qgiscore_wrapper import ( + QgsVectorFileWriter, + QgsVectorLayer, + QgsProject, + +) + +from sn_basis.functions.variable_wrapper import ( get_variable, set_variable, ) -from sn_basis.functions.sys_wrapper import ( # type: ignore - file_exists, - write_text, -) -from sn_basis.functions.ly_existence_wrapper import layer_exists # type: ignore -from sn_basis.functions.ly_metadata_wrapper import get_layer_type # type: ignore +from sn_basis.functions.sys_wrapper import file_exists +from sn_basis.functions.ly_existence_wrapper import layer_exists +from sn_basis.functions.ly_metadata_wrapper import get_layer_type -# Prüfer-Typen (werden als Instanzen erwartet) -from sn_basis.modules.Pruefmanager import Pruefmanager # type: ignore -from sn_basis.modules.linkpruefer import Linkpruefer # type: ignore -from sn_basis.modules.stilpruefer import Stilpruefer # type: ignore +# Prüfer-Typen +from sn_basis.modules.Pruefmanager import Pruefmanager +from sn_basis.modules.linkpruefer import Linkpruefer +from sn_basis.modules.stilpruefer import Stilpruefer -# Typalias für Klarheit Row = Dict[str, Any] DataDict = Dict[str, List[Row]] - class TabALogic: """ - Kapselt die komplette Logik von Tab A: - - Verfahrens-Datenbank - - optionale Linkliste - - Verfahrensgebiet-Layer - - Diese Klasse erwartet beim Erzeugen Instanzen von Pruefmanager, Linkpruefer - und Stilpruefer. Die validate_and_filter_rows-Methode verwendet diese - Instanzen über self. + Kapselt die Fachlogik von Tab A. Verfahrens-DB wird **nicht** bei Pfad-Auswahl, + sondern erst beim ersten Layer-Schreiben angelegt (alte Logik). """ def __init__(self, pruefmanager: Pruefmanager, link_pruefer: Linkpruefer, stil_pruefer: Stilpruefer) -> None: - """ - :param pruefmanager: Instanz des Pruefmanagers (für verarbeite/report) - :param link_pruefer: Instanz des Linkpruefers (mit methode pruefe) - :param stil_pruefer: Instanz des Stilpruefers (mit methode pruefe) - """ self.pruefmanager = pruefmanager self.link_pruefer = link_pruefer self.stil_pruefer = stil_pruefer # ------------------------------- - # Verfahrens-Datenbank + # Verfahrens-Datenbank (Pfad-Management) # ------------------------------- def load_verfahrens_db(self) -> Optional[str]: - """ - Lädt die gespeicherte Verfahrens-Datenbank. - """ + """Lädt den gespeicherten Verfahrens-DB-Pfad (Datei muss nicht existieren).""" path = get_variable("verfahrens_db", scope="project") - if path and file_exists(path): - return path - return None + return path or None def set_verfahrens_db(self, path: Optional[str]) -> None: - """ - Speichert oder löscht die Verfahrens-Datenbank. - """ + """Speichert den Verfahrens-DB-Pfad (Datei wird später angelegt).""" if path: set_variable("verfahrens_db", path, scope="project") else: set_variable("verfahrens_db", "", scope="project") - def create_new_verfahrens_db(self, path: str) -> bool: + # ------------------------------- + # Layer → Verfahrens-DB schreiben (alte Logik!) + # ------------------------------- + + def write_layer_to_verfahrens_db( + self, + source_layer: QgsVectorLayer, + zielpfad: str, + layer_name: str, + ) -> bool: """ - Legt eine neue leere GPKG-Datei an. + Schreibt einen Layer in die Verfahrens-DB. + Legt GPKG **bei Bedarf neu an** (wie puffer_setzen im alten Code). + + Args: + source_layer: Layer zum Exportieren (z.B. aus DataGrabber) + zielpfad: Vom Dateiprüfer geprüfter Ziel-GPKG-Pfad + layer_name: Name des Layers in der GPKG + + Returns: + True wenn erfolgreich """ - if not path: + if not zielpfad or not source_layer or not source_layer.isValid(): return False - try: - write_text(path, "") - except Exception: + # Optionen wie im alten puffer_setzen + opts = QgsVectorFileWriter.SaveVectorOptions() + opts.driverName = "GPKG" + opts.fileEncoding = "UTF-8" + opts.layerName = layer_name + + # Alte Logik: bei neuem Pfad komplett neue GPKG, sonst Layer überschreiben + if not os.path.exists(zielpfad): + opts.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteFile + else: + opts.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer + + transform_context = QgsProject.instance().transformContext() + + error = QgsVectorFileWriter.writeAsVectorFormatV3( + source_layer, + zielpfad, + transform_context, + opts, + ) + + if error != QgsVectorFileWriter.NoError: + print(f"Fehler beim Schreiben nach {zielpfad}: {error}") return False - self.set_verfahrens_db(path) + # Pfad jetzt auch als "Verfahrens-DB" merken + self.set_verfahrens_db(zielpfad) return True # ------------------------------- @@ -92,18 +117,12 @@ class TabALogic: # ------------------------------- def load_linkliste(self) -> Optional[str]: - """ - Lädt die gespeicherte lokale Linkliste. - """ path = get_variable("linkliste", scope="project") if path and file_exists(path): return path return None def set_linkliste(self, path: Optional[str]) -> None: - """ - Speichert oder löscht die lokale Linkliste. - """ if path: set_variable("linkliste", path, scope="project") else: @@ -114,10 +133,6 @@ class TabALogic: # ------------------------------- def save_verfahrensgebiet_layer(self, layer) -> None: - """ - Speichert die ID des Verfahrensgebiet-Layers. - Ungültige Layer werden ignoriert. - """ if layer is None: set_variable("verfahrensgebiet_layer", "", scope="project") return @@ -139,20 +154,12 @@ class TabALogic: set_variable("verfahrensgebiet_layer", layer_id, scope="project") def load_verfahrensgebiet_layer_id(self) -> Optional[str]: - """ - Lädt die gespeicherte Layer-ID. - """ value = get_variable("verfahrensgebiet_layer", scope="project") return value or None def is_valid_verfahrensgebiet_layer(self, layer) -> bool: - """ - Prüft, ob ein Layer als Verfahrensgebiet geeignet ist. - """ if not layer_exists(layer): return False layer_type = get_layer_type(layer) return layer_type == "vector" - - \ No newline at end of file diff --git a/ui/tab_a_ui.py b/ui/tab_a_ui.py index aaf8716..d771cc0 100644 --- a/ui/tab_a_ui.py +++ b/ui/tab_a_ui.py @@ -1,4 +1,7 @@ -# sn_plan41/ui/tab_a_ui.py – UI für Tab A (Daten) +""" +sn_plan41/ui/tab_a_ui.py – UI für Tab A (Daten) +""" + from __future__ import annotations from typing import Optional @@ -21,76 +24,79 @@ from sn_basis.functions.qt_wrapper import ( from sn_basis.functions.qgisui_wrapper import QgsFileWidget, QgsMapLayerComboBox from sn_basis.functions.qgiscore_wrapper import QgsProject, QgsMapLayerProxyModel from sn_basis.functions.variable_wrapper import get_variable, set_variable +from sn_basis.modules.pruef_ergebnis import pruef_ergebnis -from sn_plan41.ui.tab_a_logic import TabALogic - -# Prüf‑Workflow / DataGrabber (werden zur Laufzeit vom Pruefmanager/Pruefern verwendet) -from sn_basis.modules.Dateipruefer import Dateipruefer +# Services (werden von DockWidget injiziert) from sn_basis.modules.Pruefmanager import Pruefmanager from sn_basis.modules.DataGrabber import DataGrabber -from sn_basis.modules.linkpruefer import Linkpruefer -from sn_basis.modules.stilpruefer import Stilpruefer -from sn_basis.modules.Datenschreiber import Datenschreiber +from sn_basis.modules.Dateipruefer import Dateipruefer +from sn_plan41.ui.tab_a_logic import TabALogic -# Raumfilter-Optionen +# Konstanten RAUMFILTER_VAR = "Raumfilter" RAUMFILTER_OPTIONS = ("Verfahrensgebiet", "Pufferlayer", "ohne") RAUMFILTER_DEFAULT = "Pufferlayer" -pm = Pruefmanager(ui_modus="qgis") -lp = Linkpruefer() -sp = Stilpruefer() class TabA(QWidget): """ - UI-Klasse für Tab A (Daten). - - Diese bereinigte Version enthält ausschließlich UI-Elemente und - einfache, nicht-validierende Callback-Handler. Alle fachlichen Prüfungen - und Fehlerbehandlungen werden zur Laufzeit vom Pruefmanager und den Prüfern - übernommen. + UI-Klasse für Tab A (Daten) des Plan41-Plugins. + + Zuständig für: + - Anzeige und Auswahl von Verfahrens-DB, Linklisten und Layern + - Steuerung der Pipeline über "Fachdaten laden" + - Persistierung von UI-States via Projektvariablen + + Services (Pruefmanager, DataGrabber) werden zur Laufzeit vom DockWidget injiziert. + Alle fachlichen Prüfungen laufen über den zentralen Pruefmanager. """ - tab_title = "Daten" - def __init__(self, parent=None, pruefmanager=None, link_pruefer=None, stil_pruefer=None, build_ui=True): + tab_title = "Daten" #: Tab-Titel für BaseDockWidget + + def __init__(self, parent: Optional[QWidget] = None): + """ + Initialisiert die UI-Struktur. + + Services werden später über :meth:`set_services` injiziert. + + :param parent: Parent-Widget (typischerweise DockWidget) + """ super().__init__(parent) - self.parent = parent - self.tab_title = "Daten" - - # Logik-Adapter (TabALogic verwaltet persistente Projektvariablen) - self.logic = TabALogic(pruefmanager=pruefmanager, link_pruefer=link_pruefer, stil_pruefer=stil_pruefer) - - # Prüfmanager-Instanz (UI-Modus wird zur Laufzeit vom Pruefmanager gehandhabt) - self.pruefmanager = Pruefmanager(ui_modus="qgis") - - # DataGrabber-Instanz (synchroner Aufruf; Prüfungen übernimmt Pruefmanager/Pruefer) - # Hinweis: DataGrabber erwartet ggf. Prüfer-Objekte; hier werden sie nicht übergeben, - # da TabALogic / Pruefmanager diese zur Laufzeit bereitstellen können. - self.data_grabber = DataGrabber(pruefmanager=self.pruefmanager) - - # Platzhalter, die vom Plugin oder Nutzer gesetzt werden können - self._attributes_list = [] # optionale Attributliste (z. B. Excel-Import) - self._pufferlayer = None # optionaler Layer (Verfahrensgebiet) - + + # Services (werden von DockWidget gesetzt) + self.pruefmanager: Optional[Pruefmanager] = None + self.data_grabber: Optional[DataGrabber] = None + self.logic: Optional[TabALogic] = None + + # UI-State self.verfahrens_db: Optional[str] = None self.lokale_linkliste: Optional[str] = None - - # UI-Widget-Referenz für Raumfilter + self._pufferlayer = None + self._attributes_list = [] + + # UI-Referenzen self._raumfilter_combo: Optional[QComboBox] = None + + self._build_ui() + self._restore_state() - if build_ui: - self._build_ui() - self._restore_state() + def set_services(self, pruefmanager: Pruefmanager, data_grabber: DataGrabber) -> None: + """ + Injiziert Services vom übergeordneten DockWidget. + + :param pruefmanager: Zentrale Prüfmanager-Instanz + :param data_grabber: DataGrabber für Quellenprüfung/Abruf + """ + self.pruefmanager = pruefmanager + self.data_grabber = data_grabber - # --------------------------------------------------------- - # UI-Aufbau - # --------------------------------------------------------- def _build_ui(self) -> None: + """Erstellt die komplette UI-Hierarchie mit allen Gruppen.""" main_layout = QVBoxLayout() main_layout.setSpacing(4) main_layout.setContentsMargins(4, 4, 4, 4) - # Verfahrens-Datenbank Gruppe + # === VERFAHRENS-DATENBANK === self.group_button = QToolButton() self.group_button.setText("Verfahrens-Datenbank") self.group_button.setCheckable(True) @@ -103,29 +109,26 @@ class TabA(QWidget): self.group_content = QWidget() self.group_content.setSizePolicy(SizePolicyPreferred, SizePolicyMaximum) - group_layout = QVBoxLayout() group_layout.setSpacing(2) group_layout.setContentsMargins(10, 4, 4, 4) - + group_layout.addWidget(QLabel("bestehende Datei auswählen")) - self.file_widget = QgsFileWidget() self.file_widget.setStorageMode(QgsFileWidget.GetFile) self.file_widget.setFilter("Geopackage (*.gpkg)") self.file_widget.fileChanged.connect(self._on_verfahrens_db_changed) group_layout.addWidget(self.file_widget) - + group_layout.addWidget(QLabel("-oder-")) - self.btn_new = QPushButton("Neue Verfahrens-DB anlegen") self.btn_new.clicked.connect(self._create_new_gpkg) group_layout.addWidget(self.btn_new) - + self.group_content.setLayout(group_layout) main_layout.addWidget(self.group_content) - # Optionale Linkliste + # === OPTIONALE LINKLISTE === self.optional_button = QToolButton() self.optional_button.setText("Optional: Lokale Linkliste") self.optional_button.setCheckable(True) @@ -138,90 +141,43 @@ class TabA(QWidget): self.optional_content = QWidget() self.optional_content.setSizePolicy(SizePolicyPreferred, SizePolicyMaximum) - optional_layout = QVBoxLayout() optional_layout.setSpacing(2) optional_layout.setContentsMargins(10, 4, 4, 20) - + optional_layout.addWidget(QLabel("(frei lassen für globale Linkliste)")) - self.linkliste_widget = QgsFileWidget() self.linkliste_widget.setStorageMode(QgsFileWidget.GetFile) self.linkliste_widget.setFilter("Excelliste (*.xlsx)") self.linkliste_widget.fileChanged.connect(self._on_linkliste_changed) optional_layout.addWidget(self.linkliste_widget) - + self.optional_content.setLayout(optional_layout) self.optional_content.setVisible(False) main_layout.addWidget(self.optional_content) - # Layer-Auswahl + # === LAYER-AUSWAHL + RAUMFILTER === layer_label = QLabel("Verfahrensgebiet-Layer auswählen") layer_label.setStyleSheet("font-weight: bold; margin-top: 6px;") main_layout.addWidget(layer_label) - + self.layer_combo = QgsMapLayerComboBox() - self.layer_combo.setSizePolicy(SizePolicyPreferred, SizePolicyMaximum) self.layer_combo.setFilters(QgsMapLayerProxyModel.VectorLayer) self.layer_combo.layerChanged.connect(self._on_layer_changed) main_layout.addWidget(self.layer_combo) - - # Raumfilter-Label + ComboBox (unterhalb der Layer-Auswahl) + main_layout.addWidget(QLabel("Raumfilter")) self._raumfilter_combo = QComboBox(self) - # Fülle Optionen (Wrapper stellt addItems bereit) - try: - self._raumfilter_combo.addItems(list(RAUMFILTER_OPTIONS)) - except Exception: - # fallback: iterativ hinzufügen, falls Wrapper andere API hat - for opt in RAUMFILTER_OPTIONS: - if hasattr(self._raumfilter_combo, "addItem"): - self._raumfilter_combo.addItem(opt) - - # Initialisiere Auswahl aus Projekt-Variable oder Default - stored = get_variable(RAUMFILTER_VAR, scope="project") - if isinstance(stored, str) and stored in RAUMFILTER_OPTIONS: - try: - self._raumfilter_combo.setCurrentText(stored) - except Exception: - try: - idx = self._raumfilter_combo.findText(stored) - if idx is not None and idx >= 0: - self._raumfilter_combo.setCurrentIndex(idx) - except Exception: - pass - else: - try: - self._raumfilter_combo.setCurrentText(RAUMFILTER_DEFAULT) - except Exception: - try: - idx = self._raumfilter_combo.findText(RAUMFILTER_DEFAULT) - if idx is not None and idx >= 0: - self._raumfilter_combo.setCurrentIndex(idx) - except Exception: - pass - # persistiere Default, falls noch kein Wert gesetzt - if not stored: - set_variable(RAUMFILTER_VAR, RAUMFILTER_DEFAULT, scope="project") - - # Signal: bei Änderung Variable setzen - try: - self._raumfilter_combo.currentTextChanged.connect(self._on_raumfilter_changed) - except Exception: - try: - self._raumfilter_combo.current_text_changed.connect(self._on_raumfilter_changed) - except Exception: - pass - + self._raumfilter_combo.addItems(RAUMFILTER_OPTIONS) + self._raumfilter_combo.currentTextChanged.connect(self._on_raumfilter_changed) main_layout.addWidget(self._raumfilter_combo) - # Neuer Button direkt unterhalb der Raumfilter-Combo: "Fachdaten laden" + # === PIPELINE-STEUERUNG === self.btn_pipeline = QPushButton("Fachdaten laden") self.btn_pipeline.setToolTip("Starte Pipeline: Linkliste → DataGrabber → Datenschreiber → Log") self.btn_pipeline.clicked.connect(self._on_run_pipeline) main_layout.addWidget(self.btn_pipeline) - # (Optional) bestehender Button weiter unten für alternative Platzierung self.btn_load = QPushButton("Fachdaten laden (alt)") self.btn_load.clicked.connect(self._on_load_fachdaten) main_layout.addWidget(self.btn_load) @@ -229,267 +185,174 @@ class TabA(QWidget): main_layout.addStretch(1) self.setLayout(main_layout) - # --------------------------------------------------------- - # State Restore (UI-Wiederherstellung ohne Prüfungen) - # --------------------------------------------------------- def _restore_state(self) -> None: - db = self.logic.load_verfahrens_db() - if db: - self.verfahrens_db = db - try: - self.file_widget.setFilePath(db) - except Exception: - pass - self._update_group_color() - - link = self.logic.load_linkliste() - if link: - self.lokale_linkliste = link - try: - self.linkliste_widget.setFilePath(link) - except Exception: - pass - - layer_id = self.logic.load_verfahrensgebiet_layer_id() - if layer_id: - layer = QgsProject.instance().mapLayer(layer_id) - if layer: - self.layer_combo.setLayer(layer) - - # Raumfilter aus Variable wiederherstellen (falls Combo existiert) + """Stellt UI-State aus Projektvariablen/Persistenz wieder her.""" + # Verfahrens-DB try: - stored = get_variable(RAUMFILTER_VAR, scope="project") - if stored and self._raumfilter_combo is not None: - try: - self._raumfilter_combo.setCurrentText(stored) - except Exception: - idx = self._raumfilter_combo.findText(stored) - if idx is not None and idx >= 0: - self._raumfilter_combo.setCurrentIndex(idx) + db = get_variable("tab_a_verfahrens_db", scope="project") + if db and self.file_widget: + self.file_widget.setFilePath(db) + self.verfahrens_db = db + self._update_group_color() except Exception: pass - # --------------------------------------------------------- - # UI-Callbacks (ohne Prüfungen / Exceptions) - # --------------------------------------------------------- + # Linkliste + try: + link = get_variable("tab_a_linkliste", scope="project") + if link and self.linkliste_widget: + self.linkliste_widget.setFilePath(link) + self.lokale_linkliste = link + except Exception: + pass + + # Layer + try: + layer_id = get_variable("tab_a_layer_id", scope="project") + if layer_id: + layer = QgsProject.instance().mapLayer(layer_id) + if layer and self.layer_combo: + self.layer_combo.setLayer(layer) + self._pufferlayer = layer + except Exception: + pass + + # Raumfilter (schon im _build_ui behandelt) + + # === UI CALLBACKS === def _toggle_group(self, checked: bool) -> None: + """Zeigt/verbirgt Verfahrens-DB-Gruppe.""" self.group_button.setArrowType(ArrowDown if checked else ArrowRight) self.group_content.setVisible(checked) def _toggle_optional(self, checked: bool) -> None: + """Zeigt/verbirgt optionale Linkliste.""" self.optional_button.setArrowType(ArrowDown if checked else ArrowRight) self.optional_content.setVisible(checked) def _on_verfahrens_db_changed(self, path: str) -> None: + """Persistieret Verfahrens-DB-Pfad.""" self.verfahrens_db = path - self.logic.set_verfahrens_db(path) + set_variable("tab_a_verfahrens_db", path, scope="project") self._update_group_color() def _on_linkliste_changed(self, path: str) -> None: + """Persistieret lokale Linkliste.""" self.lokale_linkliste = path - self.logic.set_linkliste(path) + set_variable("tab_a_linkliste", path, scope="project") def _on_layer_changed(self, layer) -> None: - self.logic.save_verfahrensgebiet_layer(layer) + """Persistieret Layer-Auswahl.""" self._pufferlayer = layer + if layer: + set_variable("tab_a_layer_id", layer.id(), scope="project") - def _create_new_gpkg(self) -> None: - file_path, _ = QFileDialog.getSaveFileName( - self, - "Neue Verfahrens-Datenbank anlegen", - "", - "Geopackage (*.gpkg)", - ) - if not file_path: - return - - if not file_path.lower().endswith(".gpkg"): - file_path += ".gpkg" - - # Delegation an TabALogic; TabALogic / Pruefmanager übernehmen Prüfungen - self.logic.create_new_verfahrens_db(file_path) - self.verfahrens_db = file_path - try: - self.file_widget.setFilePath(file_path) - except Exception: - pass - self._update_group_color() - - def _on_load_fachdaten(self) -> None: - """ - Bestehender, kompakter Handler für 'Fachdaten laden'. - Führt Dateiprüfung und DataGrabber.run aus (wie zuvor). - """ - pfad = self.file_widget.filePath() - - # Dateipruefer wird zur Laufzeit verwendet; hier nur der Aufruf - pruefer = Dateipruefer(pfad=pfad, temporaer_erlaubt=True) - ergebnis = pruefer.pruefe() - ergebnis = self.pruefmanager.verarbeite(ergebnis) - - zielpfad = None - if ergebnis.kontext is not None: - try: - zielpfad = str(ergebnis.kontext) - except Exception: - zielpfad = ergebnis.kontext - - # DataGrabber.run wird wie bisher aufgerufen; Signatur kann variieren. - # Wir übergeben die bekannten Parameter; DataGrabber ist verantwortlich, - # die Linkliste intern zu verwenden (z. B. aus TabALogic oder über Argumente). - try: - self.data_grabber.run( - attributes_list=self._attributes_list, - pufferlayer=self._pufferlayer, - zielpfad=zielpfad, - temporaer=(ergebnis.aktion == "temporaer_erzeugen"), - temporaer_erlaubt=True, - ) - except Exception: - # Fehler werden vom Pruefmanager / DataGrabber protokolliert - pass - - def _on_run_pipeline(self) -> None: - """ - Neuer, vollständiger Pipeline-Handler, der: - - Dateiprüfung (Verfahrens-DB) - - DataGrabber-Ausführung (mit Linkliste) - - Datenschreiber (schreiben, laden) - - Logschreiber (Log-Datei) - ausführt und Ergebnisse über den Pruefmanager protokolliert. - """ - # 1) Verfahrens-DB prüfen / ermitteln - pfad = self.file_widget.filePath() - pruefer = Dateipruefer(pfad=pfad, temporaer_erlaubt=True) - ergebnis = pruefer.pruefe() - ergebnis = self.pruefmanager.verarbeite(ergebnis) - - zielpfad = None - if ergebnis.kontext is not None: - try: - zielpfad = str(ergebnis.kontext) - except Exception: - zielpfad = ergebnis.kontext - - if not zielpfad: - # Falls kein Zielpfad ermittelt werden konnte, protokollieren und abbrechen - pe_err = pruef_ergebnis( - ok=False, - meldung="Kein gültiger Speicherort für Verfahrens-DB ermittelt; Pipeline abgebrochen.", - aktion="kein_dateipfad", - kontext={}, - ) - self.pruefmanager.verarbeite(pe_err) - return - - # 2) DataGrabber ausführen - # Erwartung: DataGrabber.run gibt (daten_dict, processed_results) zurück. - # Falls die konkrete Implementierung anders ist, passt dieser Aufruf entsprechend an. - try: - run_result = self.data_grabber.run( - attributes_list=self._attributes_list, - pufferlayer=self._pufferlayer, - zielpfad=zielpfad, - temporaer=(ergebnis.aktion == "temporaer_erzeugen"), - temporaer_erlaubt=True, - ) - except Exception as exc: - pe_err = pruef_ergebnis( - ok=False, - meldung=f"DataGrabber-Fehler: {exc}", - aktion="datenabruf", - kontext={}, - ) - self.pruefmanager.verarbeite(pe_err) - return - - # Normalisiere Rückgabe: unterstütze sowohl None, einzelnes dict oder Tuple - daten_dict = {} - processed_results = [] - if isinstance(run_result, tuple) and len(run_result) >= 2: - daten_dict, processed_results = run_result[0], run_result[1] - elif isinstance(run_result, dict) and "daten" in run_result: - daten_dict = run_result - # processed_results bleiben leer oder werden vom DataGrabber intern protokolliert - else: - # Wenn run() nichts zurückgibt, versuchen wir, auf DataGrabber intern gespeicherte Ergebnisse zuzugreifen - daten_dict = getattr(self.data_grabber, "last_daten_dict", {}) or {} - processed_results = getattr(self.data_grabber, "last_processed_results", []) or [] - - # 3) Datenschreiber: Daten in GPKG schreiben - try: - ds = Datenschreiber(pruefmanager=self.pruefmanager, gpkg_path=zielpfad) - layer_infos = ds.schreibe_Daten(daten_dict=daten_dict, processed_results=processed_results, speicherort=zielpfad) - except Exception as exc: - pe_err = pruef_ergebnis( - ok=False, - meldung=f"Fehler beim Schreiben der Daten: {exc}", - aktion="save_exception", - kontext={}, - ) - self.pruefmanager.verarbeite(pe_err) - return - - # 4) Layer laden und Stile anwenden - try: - ds.lade_Layer(layer_infos) - except Exception as exc: - pe_warn = pruef_ergebnis( - ok=True, - meldung=f"Fehler beim Laden der Layer: {exc}", - aktion="layer_nicht_gefunden", - kontext={}, - ) - self.pruefmanager.verarbeite(pe_warn) - - # 5) Log schreiben - try: - log_path = ds.schreibe_log(processed_results=processed_results, speicherort=zielpfad) - # Optional: zeige Erfolgsmeldung - try: - QMessageBox.information(self, "Pipeline abgeschlossen", f"Pipeline erfolgreich abgeschlossen.\nLog: {log_path}") - except Exception: - pass - except Exception as exc: - pe_warn = pruef_ergebnis( - ok=True, - meldung=f"Log konnte nicht geschrieben werden: {exc}", - aktion="standarddatei_vorschlagen", - kontext={}, - ) - self.pruefmanager.verarbeite(pe_warn) - - # --------------------------------------------------------- - # Raumfilter Callback - # --------------------------------------------------------- def _on_raumfilter_changed(self, value: str) -> None: - # Persistiere Auswahl in Projekt-Variable; Prüfungen übernimmt die Laufzeitlogik + """Persistieret Raumfilter-Auswahl.""" set_variable(RAUMFILTER_VAR, value, scope="project") - # --------------------------------------------------------- - # UI-Helfer - # --------------------------------------------------------- - def _prompt_user_to_select_file(self) -> None: - fname, _ = QFileDialog.getOpenFileName( - self, - "Verfahrens-DB auswählen", - "", - "Geopackage (*.gpkg)", + def _create_new_gpkg(self) -> None: + """Delegiert GPKG-Erstellung (Prüfungen über Services).""" + file_path, _ = QFileDialog.getSaveFileName( + self, "Neue Verfahrens-Datenbank", "", "Geopackage (*.gpkg)" ) - if fname: - try: - self.file_widget.setFilePath(fname) - except Exception: - try: - self.file_widget.setFileName(fname) - except Exception: - self.file_widget.setProperty("filePath", fname) - self.verfahrens_db = fname - self.logic.set_verfahrens_db(fname) + if file_path: + if not file_path.lower().endswith(".gpkg"): + file_path += ".gpkg" + self.verfahrens_db = file_path + self.file_widget.setFilePath(file_path) + set_variable("tab_a_verfahrens_db", file_path, scope="project") self._update_group_color() def _update_group_color(self) -> None: + """Visuelles Feedback für Verfahrens-DB-Status.""" if self.verfahrens_db: - self.group_button.setStyleSheet("font-weight: bold;") + self.group_button.setStyleSheet("font-weight: bold; background-color: #e0f7e0;") else: - self.group_button.setStyleSheet("") + self.group_button.setStyleSheet("font-weight: bold;") + + # === PIPELINE === + def _on_run_pipeline(self) -> None: + """DEBUG: Pipeline mit maximaler Ausgabe.""" + print("\n" + "="*60) + print("🚀 _on_run_pipeline GESTARTET") + print("="*60) + # 🔥 DEBUG QT STATUS + from sn_basis.functions import qt_wrapper + qt_wrapper.debug_qt_status() # ← Zeigt EXAKT was läuft! + + + + # 1. Services prüfen + print(f"pruefmanager: {self.pruefmanager is not None}") + print(f"data_grabber: {self.data_grabber is not None}") + print(f"logic: {hasattr(self, 'logic')}") + + if not self.pruefmanager: + print("❌ FEHLER: self.pruefmanager fehlt!") + return + if not self.data_grabber: + print("❌ FEHLER: self.data_grabber fehlt!") + return + + print("✅ Services OK") + + # 2. FileWidget + source = self.file_widget.filePath() + print(f"📁 Eingabe: '{source}' (len={len(source or '')})") + + # 3. Dateipruefer + print("🔍 Dateipruefer starte...") + try: + pruefer = Dateipruefer( + source, + basis_pfad="", + leereingabe_erlaubt=False, + standarddatei=None, + temporaer_erlaubt=True, # ✅ Explizit True + verfahrens_db_modus=True # ✅ Keyword-only + ) + ergebnis1 = pruefer.pruefe() + print(f" → ok={ergebnis1.ok}, aktion='{ergebnis1.aktion}', kontext={ergebnis1.kontext}") + except Exception as e: + print(f"💥 Dateipruefer FEHLER: {e}") + import traceback + traceback.print_exc() + return + + # 4. Pruefmanager + print("🤖 Pruefmanager starte...") + try: + ergebnis2 = self.pruefmanager.verarbeite(ergebnis1) + + print(f" → ok={ergebnis2.ok}, aktion='{ergebnis2.aktion}', kontext={ergebnis2.kontext}") + except Exception as e: + print(f"💥 Pruefmanager FEHLER: {e}") + import traceback + traceback.print_exc() + return + + # 5. Entscheidung + weiter = ergebnis2.ok + print(f"➡️ Weiter? {weiter} (aktion='{ergebnis2.aktion}')") + + if weiter: + final_pfad = ergebnis2.kontext if ergebnis2.kontext else source + print(f"🚀 DataGrabber mit: '{final_pfad}'") + try: + self.data_grabber.run(final_pfad) + print("✅ DataGrabber aufgerufen!") + except Exception as e: + print(f"💥 DataGrabber FEHLER: {e}") + import traceback + traceback.print_exc() + else: + print("⏹️ Pipeline gestoppt (erwartet bei leerem Pfad)") + + print("="*60 + "\n") + + + + def _on_load_fachdaten(self) -> None: + """Kompatibilitäts-Handler → neue Pipeline.""" + self._on_run_pipeline()