From 5816b2af22c4e6729df99b1899387810568f7f4d Mon Sep 17 00:00:00 2001 From: Michael Otto Date: Fri, 28 Aug 2026 13:55:03 +0200 Subject: [PATCH] Landkreis-Auswahldialog bei fehlenden Projekteigenschaften, Style-Fix "Nur laden" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ist weder Verfahrensnummer noch Landkreis in den Projekteigenschaften gesetzt, wird jetzt per Dialog ein Landkreis abgefragt (inkl. "Alle Landkreise") statt direkt abzubrechen. - Verfahrensgebiet-spezifische ALKIS-Dialoge aus sn_basis hierher verschoben: neue Datei functions/verfahrensgebiet_dialoge.py (ask_bror_kombinationen, ask_landkreis_objekte, ask_landkreis_auswahl). - Bugfix: BROR-Layer bekommt seinen Style (BauRaumOderBodenordnungsrecht.qml) jetzt direkt beim Laden in verfahrensgebiet_alkis_komplett(), unabhängig vom weiteren Ablauf. working_tab.py überspringt bei "Nur Objekte laden" jetzt den gesamten VG-Nachbehandlungsblock (Style/Extent/Haken/Combobox/ Erfolgsmeldung), da dort kein echter VG-Layer entsteht. Detailprüfung/ Verifikation im echten QGIS (nicht nur per Unittest) steht noch aus. - Bugfix: Erfolgsmeldung "Layer wurde ... geladen (inkl. Flurstücksverschneidung)" erscheint nicht mehr bei "Nur Objekte laden". Co-Authored-By: Claude Sonnet 5 --- changelog.txt | 5 + functions/verfahrensgebiet_alkis.py | 13 +- functions/verfahrensgebiet_alkis_komplett.py | 31 +- functions/verfahrensgebiet_dialoge.py | 299 ++++++++++++++++++ tests/test_verfahrensgebiet_alkis_komplett.py | 36 ++- ui/tabs/working_tab.py | 37 ++- 6 files changed, 392 insertions(+), 29 deletions(-) create mode 100644 functions/verfahrensgebiet_dialoge.py diff --git a/changelog.txt b/changelog.txt index df8d39d..6d17ae6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,11 @@ - Fortschrittsanzeige beim Laden aus ALKIS in zwei Dialoge aufgeteilt (Suche / eigentlicher Ladevorgang), inkl. eigenem Suchschritt - Mehrfachauswahl bei mehreren BauRaumOderBodenordnungsrecht-Treffern zur Verfahrensnummer (gemeinsames Laden/Verschmelzen mehrerer Objekte) - Neu: Laden aller/mehrerer Objekte eines ganzen Landkreises als Fallback, wenn keine Verfahrensnummer gesetzt ist (Gruppierung je Verfahren, optional "Nur laden" ohne weiteren Ablauf) +- Neu: Ist weder Verfahrensnummer noch Landkreis in den Projekteigenschaften gesetzt, fragt ein Dialog den Landkreis ab (inkl. Option "Alle Landkreise"), statt direkt abzubrechen +- Landkreisweiter Objekt-Auswahldialog überarbeitet: Tabelle mit sortierbaren Spalten "Nummer"/"Name" (Standard: aufsteigend nach Nummer), Suchfeld, "Alle auswählen" jetzt korrekt (vorheriger Qt-Bug bei programmatischer Mehrfachauswahl behoben) +- Bugfix: BROR-Layer bekommt seinen ALKIS-Style jetzt direkt beim Laden (auch bei "Nur Objekte laden") statt später fälschlich den Verfahrensgebiet-Style zu erhalten – **Detailprüfung/Verifikation im echten QGIS-Test steht noch aus** +- Bugfix: Erfolgsmeldung "Layer wurde ... geladen (inkl. Flurstücksverschneidung)" erscheint nicht mehr fälschlich bei "Nur Objekte laden" (da dort keine Verschneidung stattfindet) +- Verfahrensgebiet-spezifische ALKIS-Dialoge aus sn_basis nach sn_verfahrensgebiet verschoben (functions/verfahrensgebiet_dialoge.py) --- Version 26.7.1-unstable: - Verfahrensgebiet als shape laden, inkl. Abgleich mit BROBOR-Objekt diff --git a/functions/verfahrensgebiet_alkis.py b/functions/verfahrensgebiet_alkis.py index a58332c..1ba8d9d 100644 --- a/functions/verfahrensgebiet_alkis.py +++ b/functions/verfahrensgebiet_alkis.py @@ -9,7 +9,8 @@ from sn_basis.functions import ( from sn_basis.functions import QMessageBox from sn_basis.functions.qt_wrapper import QVariant from sn_basis.functions.variable_wrapper import get_variable -from sn_basis.functions.dialog_wrapper import ask_bror_kombinationen, ask_landkreis_objekte +from sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge import ask_bror_kombinationen, ask_landkreis_objekte +from sn_basis.functions.landkreise import ALLE_LANDKREISE alkis_sf_url = "https://geodienste.sachsen.de/aaa/public_alkis/sf/wfs" typename = "adv:AX_BauRaumOderBodenordnungsrecht" @@ -219,10 +220,14 @@ def _suche_landkreis_kombinationen(tab_widget, landkreis_kennzahl: str, skip_exi if status == LoadStatus.KEEP: return existing, status, {}, None + # "Alle Landkreise" (Sentinel) -> kein Kennzahl-Präfix, LIKE '%' matcht alles. + praefix = "" if landkreis_kennzahl == ALLE_LANDKREISE else landkreis_kennzahl + landkreis_anzeige = "allen Landkreisen" if landkreis_kennzahl == ALLE_LANDKREISE else f"Landkreis {landkreis_kennzahl}" + wfs_uri_find = ( f"url='{alkis_sf_url}' typename='{typename}' srsname='EPSG:25833' " f"sql=SELECT * FROM AX_BauRaumOderBodenordnungsrecht " - f"WHERE bezeichnung LIKE '{landkreis_kennzahl}%'" + f"WHERE bezeichnung LIKE '{praefix}%'" ) temp_layer = QgsVectorLayer(wfs_uri_find, "TempVerfahrensgebiet", "WFS") @@ -233,7 +238,7 @@ def _suche_landkreis_kombinationen(tab_widget, landkreis_kennzahl: str, skip_exi features = list(temp_layer.getFeatures()) if not features: QMessageBox.critical(tab_widget, "Fehler", - f"Für Landkreis {landkreis_kennzahl} wurden keine Objekte im WFS gefunden.") + f"Für {landkreis_anzeige} wurden keine Objekte im WFS gefunden.") return None, LoadStatus.NONE, {}, None kombinationen: dict[tuple[str, str], list] = {} @@ -255,7 +260,7 @@ def _suche_landkreis_kombinationen(tab_widget, landkreis_kennzahl: str, skip_exi def _baue_layer_aus_landkreis_auswahl(tab_widget, kombinationen: dict, crs: str, status: "LoadStatus"): """Auswahl + Bau für den Landkreis-Fallback. - Zeigt :func:`~sn_basis.functions.dialog_wrapper.ask_landkreis_objekte` + Zeigt :func:`~sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge.ask_landkreis_objekte` (Mehrfachauswahl inkl. "Alle auswählen" und "Nur laden"-Checkbox). Die ausgewählten Kombinationen werden anhand der ersten 5 Zeichen von ``bezeichnung`` (= die eigentliche VKZ) gruppiert; je Gruppe wird EIN diff --git a/functions/verfahrensgebiet_alkis_komplett.py b/functions/verfahrensgebiet_alkis_komplett.py index 8e329c8..880dbfb 100644 --- a/functions/verfahrensgebiet_alkis_komplett.py +++ b/functions/verfahrensgebiet_alkis_komplett.py @@ -52,6 +52,9 @@ from sn_basis.functions.dialog_wrapper import ( show_info_dialog, ask_detailpruefung_oder_vg_laden, ) +from sn_basis.functions.landkreise import LANDKREISE, ALLE_LANDKREISE +from sn_basis.functions.ly_style_wrapper import apply_style +from sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge import ask_landkreis_auswahl from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis import ( _suche_verfahrensgebiet_features, @@ -79,6 +82,8 @@ from sn_verfahrensgebiet.functions.knickpunkt_pruefung import ( FELD_VKZ = "verfahrensnummer" #: Anzeigename des BROR-Layers im Projekt BROR_LAYER_NAME = "BauRaumOderBodenordnungsrecht (ALKIS)" +#: QML-Stildatei für den BROR-Layer (sn_verfahrensgebiet/styles/) +BROR_STYLE_QML = "BauRaumOderBodenordnungsrecht.qml" #: Projektvariable für die Flächentoleranz (in m²); Default 0 FLAECHEN_TOLERANZ_VARIABLE = "flaechen_toleranz_m2" #: Minimale implizite Flächentoleranz – Abweichungen unter diesem Wert @@ -309,13 +314,17 @@ def verfahrensgebiet_alkis_komplett( landkreis_kennzahl = get_variable("landkreis_kennzahl") if not verfahrensnummer and not landkreis_kennzahl: - QMessageBox.critical( - tab_widget, - "Fehler", - "Weder eine Verfahrensnummer noch ein Landkreis ist in den " - "Projekteigenschaften eingetragen!", - ) - return None, None, None, LoadStatus.NONE + # Keine Projekteigenschaften vorhanden – Landkreis per Dialog abfragen, + # statt direkt abzubrechen. + landkreis_kennzahl = ask_landkreis_auswahl(tab_widget, LANDKREISE) + if not landkreis_kennzahl: + QMessageBox.critical( + tab_widget, + "Fehler", + "Weder eine Verfahrensnummer noch ein Landkreis ist in den " + "Projekteigenschaften eingetragen!", + ) + return None, None, None, LoadStatus.NONE landkreis_modus = not verfahrensnummer @@ -323,7 +332,10 @@ def verfahrensgebiet_alkis_komplett( # Dialog 1: Verfahrensgebiet(e) in ALKIS ermitteln/suchen # ------------------------------------------------------------------ if landkreis_modus: - such_label = f"Objekte in Landkreis {landkreis_kennzahl} ermitteln/suchen …" + if landkreis_kennzahl == ALLE_LANDKREISE: + such_label = "Objekte in allen Landkreisen ermitteln/suchen …" + else: + such_label = f"Objekte in Landkreis {landkreis_kennzahl} ermitteln/suchen …" else: such_label = "Verfahrensgebiet(e) in ALKIS ermitteln/suchen …" @@ -378,6 +390,9 @@ def verfahrensgebiet_alkis_komplett( if vg_status in (LoadStatus.FIRST, LoadStatus.RELOAD): project.addMapLayer(alkis_vg_layer) bror_was_added_by_us = True + # Style direkt nach dem Laden anwenden – unabhängig vom weiteren + # Ablauf, damit auch "Nur Objekte laden" den Style erhält. + apply_style(alkis_vg_layer, BROR_STYLE_QML) if nur_laden: # Nutzer hat "Nur laden" angehakt – kein Dialog 2, kein weiterer diff --git a/functions/verfahrensgebiet_dialoge.py b/functions/verfahrensgebiet_dialoge.py new file mode 100644 index 0000000..d4c6eef --- /dev/null +++ b/functions/verfahrensgebiet_dialoge.py @@ -0,0 +1,299 @@ +"""sn_verfahrensgebiet/functions/verfahrensgebiet_dialoge.py – Dialoge zum +Laden eines Verfahrensgebiets aus ALKIS (Qt5/6/Mock-kompatibel). +""" +from typing import Any, Optional +from sn_basis.functions.qt_wrapper import ( + QT_VERSION, exec_dialog, QInputDialog, QLineEdit, + QDialog, QDialogButtonBox, QListWidget, QListWidgetItem, QAbstractItemView, + QTableWidget, QTableWidgetItem, QTableWidgetSelectionRange, + QIcon, QVBoxLayout, QHBoxLayout, QLabel, QCheckBox, + DialogAccepted, ExtendedSelection, SelectRows, NoEditTriggers, AscendingOrder, +) +from sn_basis.functions.landkreise import ALLE_LANDKREISE +import os + +# .../python/plugins (drei Ebenen über dieser Datei) +_PLUGINS_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) +_WAPPEN_ICON = os.path.join(_PLUGINS_DIR, "sn_basis", "favicon.ico") + + +def ask_bror_kombinationen( + parent: Any, + kombinationen: list[tuple[str, str]], +) -> list[tuple[str, str]]: + """Zeigt Dialog zur Auswahl einer oder mehrerer BROR-Kombinationen + (adv_name + bezeichnung). + + Wird aufgerufen, wenn der WFS mehrere verschiedene Kombinationen aus + ``adv_name`` und ``bezeichnung`` zurückliefert. Es können ein oder + mehrere Objekte gemeinsam ausgewählt und geladen (verschmolzen) werden. + + Parameters + ---------- + parent : + Eltern-Widget oder None. + kombinationen : + Sortierte Liste von ``(adv_name, bezeichnung)``-Tupeln. + + Returns + ------- + list[tuple[str, str]] + Die gewählten ``(adv_name, bezeichnung)``-Tupel. Leere Liste bei + Abbruch oder wenn nichts ausgewählt wurde. + """ + if not kombinationen: + return [] + + if QT_VERSION == 0: # Mock-Modus + print(f"Mock-Modus: ask_bror_kombinationen → {kombinationen[:1]}") + return kombinationen[:1] + + dialog = QDialog(parent) + dialog.setWindowTitle("Verfahrensgebiet aus ALKIS laden") + if os.path.exists(_WAPPEN_ICON): + dialog.setWindowIcon(QIcon(_WAPPEN_ICON)) + layout = QVBoxLayout(dialog) + + hinweis = QLabel( + f"Es wurden {len(kombinationen)} Objekte gefunden.\n\n" + "Bitte wählen Sie eines oder mehrere Objekte aus:" + ) + hinweis.setWordWrap(True) + layout.addWidget(hinweis) + + list_widget = QListWidget() + list_widget.setSelectionMode(ExtendedSelection) + for adv, bez in kombinationen: + item = QListWidgetItem(f"{adv} | {bez}") + list_widget.addItem(item) + list_widget.item(0).setSelected(True) + layout.addWidget(list_widget, 1) # Stretch-Faktor: wächst mit Fenstergröße + + mehrfachauswahl_hinweis = QLabel("Mehrfachauswahl mit Strg + Umschalt") + layout.addWidget(mehrfachauswahl_hinweis) + + buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) + buttons.accepted.connect(dialog.accept) + buttons.rejected.connect(dialog.reject) + layout.addWidget(buttons) + + dialog.adjustSize() + breite = dialog.sizeHint().width() + + # Liste auf max. 5 sichtbare Zeilen begrenzen, Rest per Scrollbar + sichtbare_zeilen = min(len(kombinationen), 5) + zeilenhoehe = list_widget.sizeHintForRow(0) if list_widget.count() else 20 + # + Puffer für eine ggf. eingeblendete horizontale Scrollbar (bei langen + # Einträgen), damit diese nicht die letzte Zeile abschneidet. + scrollbar_puffer = list_widget.horizontalScrollBar().sizeHint().height() + list_hoehe = zeilenhoehe * sichtbare_zeilen + 2 * list_widget.frameWidth() + 4 + scrollbar_puffer + list_widget.setMinimumHeight(list_hoehe) + + dialog.resize(breite, dialog.sizeHint().height()) + + if exec_dialog(dialog) != DialogAccepted: + return [] + + ausgewaehlte_indizes = sorted(list_widget.row(item) for item in list_widget.selectedItems()) + return [kombinationen[i] for i in ausgewaehlte_indizes] + + +def ask_landkreis_objekte( + parent: Any, + kombinationen: list[tuple[str, str]], +) -> tuple[list[tuple[str, str]], bool]: + """Zeigt Dialog zur Auswahl eines, mehrerer oder aller BROR-Objekte eines + Landkreises (Fallback, wenn keine Verfahrensnummer gesetzt ist). + + Wie :func:`ask_bror_kombinationen`, zusätzlich mit einer "Alle + auswählen"-Checkbox (da hier potenziell deutlich mehr Treffer als bei der + VKZ-Suche auftreten) und einer "Nur laden"-Checkbox, mit der gesteuert + wird, ob nach dem Laden der weitere Workflow (Gemarkung/Flurstücke/ + Flächenvergleich) fortgesetzt werden soll. + + Parameters + ---------- + parent : + Eltern-Widget oder None. + kombinationen : + Sortierte Liste von ``(adv_name, bezeichnung)``-Tupeln. + + Returns + ------- + tuple[list[tuple[str, str]], bool] + ``(ausgewaehlte_kombinationen, nur_laden)``. ``([], False)`` bei + Abbruch oder wenn nichts ausgewählt wurde. + """ + if not kombinationen: + return [], False + + if QT_VERSION == 0: # Mock-Modus + print(f"Mock-Modus: ask_landkreis_objekte → {kombinationen[:1]}") + return kombinationen[:1], False + + dialog = QDialog(parent) + dialog.setWindowTitle("Verfahrensgebiete aus Landkreis laden") + if os.path.exists(_WAPPEN_ICON): + dialog.setWindowIcon(QIcon(_WAPPEN_ICON)) + layout = QVBoxLayout(dialog) + + hinweis = QLabel( + f"Es wurden {len(kombinationen)} Objekte gefunden.\n\n" + "Bitte wählen Sie eines oder mehrere Objekte aus:" + ) + hinweis.setWordWrap(True) + layout.addWidget(hinweis) + + suche_zeile = QHBoxLayout() + suche_zeile.addWidget(QLabel("Suche:")) + suche_feld = QLineEdit() + suche_feld.setPlaceholderText("Nummer oder Name …") + suche_zeile.addWidget(suche_feld) + layout.addLayout(suche_zeile) + + alle_auswaehlen = QCheckBox("Alle auswählen") + layout.addWidget(alle_auswaehlen) + + # Standardsortierung nach Bezeichnung; per Klick auf Spaltenkopf änderbar + # (setSortingEnabled aktiviert die eingebaute Tabellensortierung). + sortierte_kombinationen = sorted(kombinationen, key=lambda kb: kb[1]) + + table = QTableWidget(len(sortierte_kombinationen), 2) + table.setHorizontalHeaderLabels(["Nummer", "Name"]) + table.setSelectionMode(ExtendedSelection) + table.setSelectionBehavior(SelectRows) + table.setEditTriggers(NoEditTriggers) + table.verticalHeader().setVisible(False) + table.verticalHeader().setDefaultSectionSize(22) # flachere Zeilen + table.horizontalHeader().setStretchLastSection(True) + table.setSortingEnabled(False) + for row, (adv, bez) in enumerate(sortierte_kombinationen): + table.setItem(row, 0, QTableWidgetItem(bez)) + table.setItem(row, 1, QTableWidgetItem(adv)) + table.setSortingEnabled(True) + table.sortByColumn(0, AscendingOrder) # Standard: aufsteigend nach Nummer + table.resizeColumnToContents(0) + table.setColumnWidth(0, max(110, table.columnWidth(0))) + table.selectRow(0) + layout.addWidget(table, 1) # Stretch-Faktor: wächst mit Fenstergröße + + def _suche_geaendert(text): + text_lower = text.strip().lower() + for row in range(table.rowCount()): + bez_text = table.item(row, 0).text().lower() + adv_text = table.item(row, 1).text().lower() + treffer = not text_lower or text_lower in bez_text or text_lower in adv_text + table.setRowHidden(row, not treffer) + + suche_feld.textChanged.connect(_suche_geaendert) + + def _alle_auswaehlen_umschalten(checked): + table.clearSelection() + if checked: + letzte_spalte = table.columnCount() - 1 + for row in range(table.rowCount()): + if not table.isRowHidden(row): + # setRangeSelected (statt selectRow) fügt der Selektion + # hinzu, ohne vorherige Zeilen wieder abzuwählen. + table.setRangeSelected( + QTableWidgetSelectionRange(row, 0, row, letzte_spalte), True + ) + + alle_auswaehlen.stateChanged.connect(_alle_auswaehlen_umschalten) + + mehrfachauswahl_hinweis = QLabel("Mehrfachauswahl mit Strg + Umschalt") + layout.addWidget(mehrfachauswahl_hinweis) + + nur_laden_checkbox = QCheckBox("Nur Objekte laden (ohne weitere Prüfungen)") + layout.addWidget(nur_laden_checkbox) + + buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) + buttons.accepted.connect(dialog.accept) + buttons.rejected.connect(dialog.reject) + layout.addWidget(buttons) + + dialog.adjustSize() + # Bezeichnungs-Spalte + großzügige Breite für den ADV-Namen (lange Namen + # sollen möglichst ohne Abschneiden sichtbar sein) + Rahmen/Scrollbar-Puffer. + breite = max(dialog.sizeHint().width(), table.columnWidth(0) + 480) + + # Tabelle auf max. 15 sichtbare Zeilen begrenzen, Rest per Scrollbar – hier + # potenziell deutlich mehr Treffer als bei der VKZ-Suche, daher großzügiger + # bemessen als bei ask_bror_kombinationen (dort 5). + sichtbare_zeilen = min(len(sortierte_kombinationen), 15) + zeilenhoehe = table.verticalHeader().defaultSectionSize() if table.rowCount() else 20 + kopfzeile_hoehe = table.horizontalHeader().height() + # + Puffer für eine ggf. eingeblendete horizontale Scrollbar (bei langen + # Einträgen), damit diese nicht die letzte Zeile abschneidet. + scrollbar_puffer = table.horizontalScrollBar().sizeHint().height() + table_hoehe = ( + zeilenhoehe * sichtbare_zeilen + kopfzeile_hoehe + + 2 * table.frameWidth() + 4 + scrollbar_puffer + ) + table.setMinimumHeight(table_hoehe) + + dialog.resize(breite, dialog.sizeHint().height()) + + if exec_dialog(dialog) != DialogAccepted: + return [], False + + ausgewaehlte_zeilen = sorted(set(item.row() for item in table.selectedItems())) + ausgewaehlt = [(table.item(row, 1).text(), table.item(row, 0).text()) for row in ausgewaehlte_zeilen] + return ausgewaehlt, nur_laden_checkbox.isChecked() + + +def ask_landkreis_auswahl( + parent: Any, + landkreise: list[tuple[str, str]], +) -> Optional[str]: + """Fragt per Dropdown-Dialog einen Landkreis ab (Fallback, wenn weder + Verfahrensnummer noch Landkreis in den Projekteigenschaften gesetzt ist). + + Parameters + ---------- + parent : + Eltern-Widget oder None. + landkreise : + Liste von ``(kennzahl, name)``-Tupeln, z.B. ``LANDKREISE`` aus + :mod:`sn_basis.functions.landkreise`. + + Die Auswahlliste beginnt mit einem leeren Platzhalter (der Nutzer muss + aktiv einen Eintrag wählen) und enthält zusätzlich "Alle Landkreise" + (Sentinel :data:`~sn_basis.functions.landkreise.ALLE_LANDKREISE`). + + Returns + ------- + Optional[str] + Gewählte Landkreiskennzahl, :data:`ALLE_LANDKREISE` bei Auswahl aller + Landkreise, oder ``None`` bei Abbruch, leerem Platzhalter oder leerer + Landkreisliste. + """ + if not landkreise: + return None + + if QT_VERSION == 0: # Mock-Modus + print("Mock-Modus: ask_landkreis_auswahl → None (keine aktive Auswahl)") + return None + + ALLE_LABEL = "Alle Landkreise" + namen = [""] + [ALLE_LABEL] + [name for _, name in landkreise] + + dialog = QInputDialog(parent) + dialog.setWindowTitle("Landkreis auswählen") + if os.path.exists(_WAPPEN_ICON): + dialog.setWindowIcon(QIcon(_WAPPEN_ICON)) + dialog.setLabelText("Landkreis:") + dialog.setComboBoxItems(namen) + dialog.setComboBoxEditable(False) + + if exec_dialog(dialog) != DialogAccepted: + return None + + name = dialog.textValue() + if not name: + return None + if name == ALLE_LABEL: + return ALLE_LANDKREISE + + name_zu_kennzahl = {n: k for k, n in landkreise} + return name_zu_kennzahl.get(str(name)) diff --git a/tests/test_verfahrensgebiet_alkis_komplett.py b/tests/test_verfahrensgebiet_alkis_komplett.py index 04f4287..c6b11ce 100644 --- a/tests/test_verfahrensgebiet_alkis_komplett.py +++ b/tests/test_verfahrensgebiet_alkis_komplett.py @@ -148,6 +148,7 @@ _PATCH_KNICK = f"{_MOD}.knickpunkt_pruefung" _PATCH_EXTRAHIERE = f"{_MOD}._extrahiere_knickpunkte" _PATCH_LANDKREIS_SUCHE = f"{_MOD}._suche_landkreis_kombinationen" _PATCH_LANDKREIS_BAU = f"{_MOD}._baue_layer_aus_landkreis_auswahl" +_PATCH_ASK_LANDKREIS = f"{_MOD}.ask_landkreis_auswahl" # --------------------------------------------------------------------------- @@ -678,9 +679,13 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase): @patch(_PATCH_PROGRESS) @patch(_PATCH_MSGBOX) + @patch(_PATCH_ASK_LANDKREIS, return_value=None) @patch(_PATCH_GET_VAR, return_value="") - def test_kein_vkz_kein_landkreis_zeigt_fehler(self, mock_get_var, mock_msgbox, mock_prog): - """Weder Verfahrensnummer noch Landkreis gesetzt → Fehlermeldung, kein Layer.""" + def test_kein_vkz_kein_landkreis_zeigt_fehler( + self, mock_get_var, mock_ask_landkreis, mock_msgbox, mock_prog + ): + """Weder Verfahrensnummer noch Landkreis gesetzt, Nutzer bricht den + Landkreis-Auswahldialog ab → Fehlermeldung, kein Layer.""" enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None) self.assertIsNone(enriched) @@ -689,6 +694,33 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase): self.assertEqual(status, LoadStatus.NONE) mock_msgbox.critical.assert_called_once() + @patch(_PATCH_PROGRESS) + @patch(_PATCH_PROJECT) + @patch(_PATCH_LANDKREIS_BAU) + @patch(_PATCH_LANDKREIS_SUCHE) + @patch(_PATCH_ASK_LANDKREIS, return_value="22") + @patch(_PATCH_GET_VAR, return_value="") + def test_kein_vkz_kein_landkreis_dialog_liefert_landkreis( + self, mock_get_var, mock_ask_landkreis, mock_such, mock_bau, mock_proj, mock_prog + ): + """Weder Verfahrensnummer noch Landkreis gesetzt, Nutzer wählt einen + Landkreis im Dialog → Landkreis-Suchpfad wird mit dieser Kennzahl + verwendet (wie im Fall 'Landkreis aus Projekteigenschaften').""" + proj_inst = _DummyProject() + mock_proj.instance.return_value = proj_inst + vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)") + mock_such.return_value = (None, LoadStatus.FIRST, {("A", "22015"): [MagicMock()]}, "EPSG:25833") + mock_bau.return_value = (vg_layer, LoadStatus.FIRST, True) # nur_laden=True + mock_prog.return_value = _make_progress_mock() + + enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None) + + mock_ask_landkreis.assert_called_once() + mock_such.assert_called_once_with(None, "22") + mock_bau.assert_called_once() + self.assertIs(enriched, vg_layer) + self.assertEqual(status, LoadStatus.FIRST) + @patch(_PATCH_PROGRESS) @patch(_PATCH_PROJECT) @patch(_PATCH_LANDKREIS_BAU) diff --git a/ui/tabs/working_tab.py b/ui/tabs/working_tab.py index 813a10f..5c090d8 100644 --- a/ui/tabs/working_tab.py +++ b/ui/tabs/working_tab.py @@ -21,7 +21,10 @@ from sn_verfahrensgebiet.functions.flurstueck_alkis import ( ALKIS_SF_URL, FLST_LAYER_NAME, ) -from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import verfahrensgebiet_alkis_komplett +from sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett import ( + verfahrensgebiet_alkis_komplett, + BROR_LAYER_NAME, +) from sn_verfahrensgebiet.functions.verfahrensgebiet_shape_komplett import verfahrensgebiet_shape_komplett from sn_verfahrensgebiet.functions.grenzpunkt_alkis import grenzpunkte_gemarkungsweise, JOINED_LAYER_NAME from sn_verfahrensgebiet.functions.gemarkung_alkis import ( @@ -201,23 +204,27 @@ class WorkingTab(QWidget): apply_style(flst_layer, "GIS_Flst_Beschriftung_ALKIS_NAS.qml") self.setze_haken(self.haken_flurst, True) - # BROR-Layer: QML-Stil anwenden - if bror_layer and bror_layer.isValid(): - apply_style(bror_layer, "BauRaumOderBodenordnungsrecht.qml") + # BROR-Layer bekommt seinen Style bereits direkt beim Laden in + # verfahrensgebiet_alkis_komplett() (auch im "Nur laden"-Fall). - # Enriched VG-Layer ins Projekt (ggf. bereits registriert, z.B. bei "als VG laden") - if not project.mapLayer(enriched_layer.id()): - project.addMapLayer(enriched_layer) - apply_style(enriched_layer, "verfahrensgebiet.qml") - iface.mapCanvas().setExtent(enriched_layer.extent()) - iface.mapCanvas().refresh() - self.setze_haken(self.haken_verf, True) + # Bei "Nur Objekte laden" (kein weiterer Ablauf) ist enriched_layer + # tatsächlich nur der rohe BROR-Layer (erkennbar am Namen) – hier + # fand keine Flurstücksverschneidung statt, es gibt also keinen + # echten VG-Layer: VG-Style/-Kombobox/-Haken/-Meldung überspringen. + if enriched_layer.name() != BROR_LAYER_NAME: + # Enriched VG-Layer ins Projekt (ggf. bereits registriert, z.B. bei "als VG laden") + if not project.mapLayer(enriched_layer.id()): + project.addMapLayer(enriched_layer) + apply_style(enriched_layer, "verfahrensgebiet.qml") + iface.mapCanvas().setExtent(enriched_layer.extent()) + iface.mapCanvas().refresh() + self.setze_haken(self.haken_verf, True) - self.verf_layer_combo.setLayer(enriched_layer) - self._on_verf_layer_changed(enriched_layer) + self.verf_layer_combo.setLayer(enriched_layer) + self._on_verf_layer_changed(enriched_layer) - msg = "erstmalig geladen" if status == LoadStatus.FIRST else "neu geladen" - success("Verfahrensgebiet", f"Layer wurde {msg} (inkl. Flurstücksverschneidung).", duration=5) + msg = "erstmalig geladen" if status == LoadStatus.FIRST else "neu geladen" + success("Verfahrensgebiet", f"Layer wurde {msg} (inkl. Flurstücksverschneidung).", duration=5) finally: self._set_busy(self.btn_verf_alkis, False, "Aus ALKIS laden")