BROR-Mehrfachauswahl, zweistufiger Fortschrittsdialog, Landkreis-Fallback

- Fortschrittsdialog beim Laden aus ALKIS schließt jetzt zuverlässig bei
  jedem Abbruch (progress.close() zentral im finally-Block).
- Fortschrittsanzeige in zwei Dialoge aufgeteilt: Suche (1 Schritt) läuft
  und schließt separat, bevor der eigentliche Workflow (Gemarkung/
  Flurstücke/Vergleich, 6 Schritte) startet – auch während des BROR-
  Auswahldialogs läuft kein störender Hintergrund-Fortschrittsbalken mehr.
- verfahrensgebiet_alkis() in Suche-/Auswahl-Phasen aufgeteilt, damit
  mehrere gefundene BauRaumOderBodenordnungsrecht-Objekte gemeinsam
  ausgewählt und verschmolzen werden können (öffentliche Signatur
  unverändert).
- Neu: Landkreis-weite Suche als Fallback, wenn keine Verfahrensnummer
  gesetzt ist (Landkreiskennzahl aus Projektvariable), mit Mehrfach-
  auswahl/"Alle auswählen" und "Nur laden"-Option (überspringt den
  restlichen Workflow). Gruppierung der Auswahl nach VKZ, damit je
  Verfahren ein eigenes Feature entsteht statt eines Gesamt-Blobs.
- Tests entsprechend angepasst/ergänzt (29 Tests in
  test_verfahrensgebiet_alkis_komplett.py, davon 3 neu für den
  Landkreis-Fallback).
This commit is contained in:
Michael Otto
2026-08-27 15:14:07 +02:00
parent 2cf0cda199
commit 148d4dfa15
5 changed files with 457 additions and 97 deletions
+105 -25
View File
@@ -129,7 +129,7 @@ def _make_progress_mock(canceled=False):
# ---------------------------------------------------------------------------
_MOD = "sn_verfahrensgebiet.functions.verfahrensgebiet_alkis_komplett"
_PATCH_VG_ALKIS = f"{_MOD}.verfahrensgebiet_alkis"
_PATCH_VG_ALKIS = f"{_MOD}._suche_verfahrensgebiet_features"
_PATCH_GEM = f"{_MOD}.gemarkung_schluessel_und_namen_von_vg_geometrie"
_PATCH_FLST = f"{_MOD}.flurstueck_alkis"
_PATCH_SELECT = f"{_MOD}._select_by_location"
@@ -146,6 +146,8 @@ _PATCH_ASK_DIALOG = f"{_MOD}.ask_detailpruefung_oder_vg_laden"
_PATCH_GP_ALKIS = f"{_MOD}.lade_punktort_ta"
_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"
# ---------------------------------------------------------------------------
@@ -265,9 +267,10 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
@patch(_PATCH_PROGRESS)
@patch(_PATCH_PROJECT)
@patch(_PATCH_VG_ALKIS)
def test_vg_alkis_none_aborts(self, mock_vg, mock_proj, mock_prog):
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
def test_vg_alkis_none_aborts(self, mock_get_var, mock_vg, mock_proj, mock_prog):
mock_proj.instance.return_value = _DummyProject()
mock_vg.return_value = (None, LoadStatus.NONE, [])
mock_vg.return_value = (None, LoadStatus.NONE, {}, None)
mock_prog.return_value = _make_progress_mock()
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
@@ -281,10 +284,11 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
@patch(_PATCH_PROJECT)
@patch(_PATCH_GEM)
@patch(_PATCH_VG_ALKIS)
def test_flst_none_aborts(self, mock_vg, mock_gem, mock_proj, mock_prog):
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
def test_flst_none_aborts(self, mock_get_var, mock_vg, mock_gem, mock_proj, mock_prog):
mock_proj.instance.return_value = _DummyProject()
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
mock_gem.return_value = [("1234", "Muster")]
mock_prog.return_value = _make_progress_mock()
@@ -302,13 +306,14 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
@patch(_PATCH_GEM)
@patch(_PATCH_FLST)
@patch(_PATCH_VG_ALKIS)
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
def test_no_selection_returns_none_enriched(
self, mock_vg, mock_flst, mock_gem, mock_proj, mock_select, mock_msgbox, mock_prog
self, mock_get_var, mock_vg, mock_flst, mock_gem, mock_proj, mock_select, mock_msgbox, mock_prog
):
mock_proj.instance.return_value = _DummyProject()
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
flst_layer = _make_layer(name="Flurstücke")
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
mock_gem.return_value = []
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
mock_select.return_value = 0
@@ -330,15 +335,16 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
@patch(_PATCH_GEM)
@patch(_PATCH_FLST)
@patch(_PATCH_VG_ALKIS)
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
def test_invalid_enriched_layer_returns_none(
self, mock_vg, mock_flst, mock_gem, mock_proj, mock_select, mock_erstelle, mock_msgbox, mock_prog
self, mock_get_var, mock_vg, mock_flst, mock_gem, mock_proj, mock_select, mock_erstelle, mock_msgbox, mock_prog
):
mock_proj.instance.return_value = _DummyProject()
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
flst_layer = _make_layer(name="Flurstücke")
bad_enriched = _make_layer(valid=False)
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
mock_gem.return_value = []
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
mock_select.return_value = 5
@@ -356,11 +362,12 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
@patch(_PATCH_PROGRESS)
@patch(_PATCH_PROJECT)
@patch(_PATCH_VG_ALKIS)
def test_progress_cancel_after_step1_returns_none(self, mock_vg, mock_proj, mock_prog):
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
def test_progress_cancel_after_step1_returns_none(self, mock_get_var, mock_vg, mock_proj, mock_prog):
"""Abbruch nach Schritt 1 → sofortiger sauberer Rückgabestatus."""
mock_proj.instance.return_value = _DummyProject()
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)")
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
prog = MagicMock()
prog.is_canceled.side_effect = [False, True] # 1. Aufruf False, 2. True
@@ -398,7 +405,7 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
flst_layer = _make_layer(name="Flurstücke", lid="FLST1")
enriched = _make_layer(name="Verfahrensgebiet", lid="VG_ENRICHED", valid=True)
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
mock_gem.return_value = [("1234", "Muster")]
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
mock_select.return_value = 3
@@ -437,18 +444,18 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
flst_layer = _make_layer(name="Flurstücke")
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
mock_gem.return_value = []
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
mock_select.return_value = 2
mock_erstelle.return_value = (enriched, 2, 0)
mock_get_var.return_value = ""
mock_get_var.return_value = "VF-2024-001"
mock_vergl.return_value = (1000.0, 1000.0, True)
mock_prog.return_value = _make_progress_mock()
verfahrensgebiet_alkis_komplett(None)
mock_vkz.assert_called_once_with(enriched, "")
mock_vkz.assert_called_once_with(enriched, "VF-2024-001")
# ------------------------------------------------------------------ #
# Flächenvergleich – Abweichung + Abbrechen
@@ -476,12 +483,12 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
flst_layer = _make_layer(name="Flurstücke")
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
mock_gem.return_value = []
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
mock_select.return_value = 3
mock_erstelle.return_value = (enriched, 3, 0)
mock_get_var.return_value = ""
mock_get_var.return_value = "VF-2024-001"
mock_vergl.return_value = (1000.0, 800.0, False)
mock_ask.return_value = "abbrechen"
mock_prog.return_value = _make_progress_mock()
@@ -521,12 +528,12 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
flst_layer = _make_layer(name="Flurstücke")
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
mock_gem.return_value = []
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
mock_select.return_value = 3
mock_erstelle.return_value = (enriched, 3, 0)
mock_get_var.return_value = ""
mock_get_var.return_value = "VF-2024-001"
mock_vergl.return_value = (1000.0, 800.0, False)
mock_ask.return_value = "als_vg_laden"
mock_prog.return_value = _make_progress_mock()
@@ -577,12 +584,12 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
punktort_layer = _make_layer(name="PunktortTA", lid="PTA_L")
ergebnis_layer = _make_layer(name="Knickpunktprüfung", lid="KNICK_L")
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
mock_gem.return_value = []
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
mock_select.return_value = 3
mock_erstelle.return_value = (enriched, 3, 0)
mock_get_var.return_value = ""
mock_get_var.return_value = "VF-2024-001"
mock_vergl.return_value = (1000.0, 800.0, False)
mock_ask.return_value = "detailpruefung"
mock_extrahiere.return_value = [MagicMock(), MagicMock(), MagicMock()] # 3 Knickpunkte
@@ -630,12 +637,12 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
flst_layer = _make_layer(name="Flurstücke")
enriched = _make_layer(name="Verfahrensgebiet", valid=True)
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
mock_gem.return_value = []
mock_flst.return_value = (flst_layer, LoadStatus.FIRST)
mock_select.return_value = 3
mock_erstelle.return_value = (enriched, 3, 0)
mock_get_var.return_value = ""
mock_get_var.return_value = "VF-2024-001"
mock_vergl.return_value = (1000.0, 1000.0, True)
mock_prog.return_value = _make_progress_mock()
@@ -647,15 +654,16 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
@patch(_PATCH_PROJECT)
@patch(_PATCH_GEM)
@patch(_PATCH_VG_ALKIS)
@patch(_PATCH_GET_VAR, return_value="VF-2024-001")
def test_bror_layer_wird_bei_fehler_entfernt(
self, mock_vg, mock_gem, mock_proj, mock_prog
self, mock_get_var, mock_vg, mock_gem, mock_proj, mock_prog
):
"""Bei Fehler (flst_alkis NONE) wird der BROR-Layer aus dem Projekt entfernt."""
proj_inst = _DummyProject()
mock_proj.instance.return_value = proj_inst
vg_layer = _make_layer(name="BauRaumOrdnungsrecht (ALKIS)", lid="BROR_ERR")
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, [])
mock_vg.return_value = (vg_layer, LoadStatus.FIRST, {}, None)
mock_gem.return_value = []
mock_prog.return_value = _make_progress_mock()
@@ -664,6 +672,78 @@ class TestVerfahrensgebietAlkisKomplett(unittest.TestCase):
self.assertIn("BROR_ERR", proj_inst.removed)
# ------------------------------------------------------------------ #
# Landkreis-Fallback (keine Verfahrensnummer)
# ------------------------------------------------------------------ #
@patch(_PATCH_PROGRESS)
@patch(_PATCH_MSGBOX)
@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."""
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
self.assertIsNone(enriched)
self.assertIsNone(flst)
self.assertIsNone(bror)
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)
def test_landkreis_modus_wird_bei_fehlender_vkz_verwendet(
self, mock_such, mock_bau, mock_proj, mock_prog
):
"""Keine Verfahrensnummer, aber Landkreis gesetzt → Landkreis-Suchpfad
statt VKZ-Pfad wird verwendet."""
def _get_var_side_effect(key, scope="project"):
return {"verfahrensnummer": "", "landkreis_kennzahl": "22"}.get(key, "")
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()
with patch(_PATCH_GET_VAR, side_effect=_get_var_side_effect):
enriched, flst, bror, status = verfahrensgebiet_alkis_komplett(None)
mock_such.assert_called_once()
mock_bau.assert_called_once()
self.assertIs(enriched, vg_layer)
self.assertIsNone(flst)
self.assertIsNone(bror)
self.assertEqual(status, LoadStatus.FIRST)
@patch(_PATCH_PROGRESS)
@patch(_PATCH_PROJECT)
@patch(_PATCH_LANDKREIS_BAU)
@patch(_PATCH_LANDKREIS_SUCHE)
def test_landkreis_modus_nur_laden_ueberspringt_dialog2(
self, mock_such, mock_bau, mock_proj, mock_prog
):
"""nur_laden=True → Funktion kehrt sofort zurück, Dialog 2 (Gemarkung
etc.) wird nicht gestartet."""
def _get_var_side_effect(key, scope="project"):
return {"verfahrensnummer": "", "landkreis_kennzahl": "22"}.get(key, "")
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)
mock_prog.return_value = _make_progress_mock()
with patch(_PATCH_GET_VAR, side_effect=_get_var_side_effect), \
patch(_PATCH_GEM) as mock_gem:
verfahrensgebiet_alkis_komplett(None)
mock_gem.assert_not_called()
if __name__ == "__main__":
unittest.main()