PER_RECHTSPERSON als Rohwert in der Trefferliste zeigen (#56)

PER_RECHTSPERSON kennt die Werte 0, 1, 5 und 9 und ist kein Ja/Nein-Feld.
Die Trefferliste bekommt dafür eine eigene Spalte mit dem unveränderten
Wert; abweichende Werte einer Person werden gesammelt. Die Entschlüsselung
folgt später. „verstorben“ (PER_VERSTORBEN 0/1) bleibt als Hinweis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 13:13:50 +02:00
co-authored by Claude Opus 5.5
parent 29719a8a99
commit b105894241
5 changed files with 40 additions and 23 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
- Neu: Eigentümersuche im Tab „Eigentümer“. Den Serienbriefexport (CSV) von Hand als Layer laden, als Eigentümerdatei wählen, Name und/oder Vorname eingeben: Die Trefferliste zeigt Name, Anschrift, E-Mail und die Kennzeichen „verstorben“/„Rechtsperson“. Gleiche Personen mit gleicher Anschrift werden zusammengefasst, Groß-/Kleinschreibung und Umlautschreibweise spielen keine Rolle (#56) - Neu: Eigentümersuche im Tab „Eigentümer“. Den Serienbriefexport (CSV) von Hand als Layer laden, als Eigentümerdatei wählen, Name und/oder Vorname eingeben: Die Trefferliste zeigt Name, Anschrift, E-Mail, den Wert von PER_RECHTSPERSON (0, 1, 5 oder 9, noch nicht entschlüsselt) und das Kennzeichen „verstorben“. Gleiche Personen mit gleicher Anschrift werden zusammengefasst, Groß-/Kleinschreibung und Umlautschreibweise spielen keine Rolle (#56)
- Intern: Die Auswahltabelle aus „Verfahrensgebiete aus Landkreis laden“ (Suchfeld, „Alle auswählen“, Mehrfachauswahl) ist als gemeinsame Funktion herausgelöst und wird von der Eigentümer-Trefferliste mitgenutzt; der Landkreis-Dialog verhält sich unverändert (#56) - Intern: Die Auswahltabelle aus „Verfahrensgebiete aus Landkreis laden“ (Suchfeld, „Alle auswählen“, Mehrfachauswahl) ist als gemeinsame Funktion herausgelöst und wird von der Eigentümer-Trefferliste mitgenutzt; der Landkreis-Dialog verhält sich unverändert (#56)
- Neu: Tab „Eigentümer“ mit Flurstückssuche. Gemarkung (Name oder Nummer) und Flurstücksnummer (z. B. 562 oder 12/3) eingeben, die Treffer werden im Layer „Flurstücke“ ausgewählt und angezoomt. Ohne Nenner werden alle Flurstücke mit diesem Zähler gefunden. Fehlt der Layer „Flurstücke“, weist ein Dialog darauf hin, zuerst Verfahrensgebiet und Flurstücke aus ALKIS zu laden (#56) - Neu: Tab „Eigentümer“ mit Flurstückssuche. Gemarkung (Name oder Nummer) und Flurstücksnummer (z. B. 562 oder 12/3) eingeben, die Treffer werden im Layer „Flurstücke“ ausgewählt und angezoomt. Ohne Nenner werden alle Flurstücke mit diesem Zähler gefunden. Fehlt der Layer „Flurstücke“, weist ein Dialog darauf hin, zuerst Verfahrensgebiet und Flurstücke aus ALKIS zu laden (#56)
- Bugfix: Nach dem Laden des Verfahrensgebiets (ALKIS oder Shape) sprang die Karte an die falsche Stelle, wenn ein anderes Projekt-KBS als EPSG:25833 eingestellt war. Der Zoom rechnet die Ausdehnung jetzt in das KBS der Kartenansicht um (`zoom_auf_layer` aus sn_basis, setzt sn_basis mit Basis #99 voraus) (Basis #99) - Bugfix: Nach dem Laden des Verfahrensgebiets (ALKIS oder Shape) sprang die Karte an die falsche Stelle, wenn ein anderes Projekt-KBS als EPSG:25833 eingestellt war. Der Zoom rechnet die Ausdehnung jetzt in das KBS der Kartenansicht um (`zoom_auf_layer` aus sn_basis, setzt sn_basis mit Basis #99 voraus) (Basis #99)
+11 -7
View File
@@ -62,7 +62,7 @@ class Eigentuemer:
ortsteil: str = "" ortsteil: str = ""
emails: Tuple[str, ...] = () emails: Tuple[str, ...] = ()
verstorben: bool = False verstorben: bool = False
rechtsperson: bool = False rechtsperson: Tuple[str, ...] = ()
per_ids: Tuple[str, ...] = () per_ids: Tuple[str, ...] = ()
konten: Tuple[str, ...] = () konten: Tuple[str, ...] = ()
feature_ids: Tuple[int, ...] = () feature_ids: Tuple[int, ...] = ()
@@ -85,8 +85,12 @@ class Eigentuemer:
@property @property
def hinweise(self) -> str: def hinweise(self) -> str:
"""Kennzeichen für die Trefferliste (nicht für das Anzeigefenster).""" """Kennzeichen für die Trefferliste (nicht für das Anzeigefenster)."""
return ", ".join(t for t, gesetzt in (("verstorben", self.verstorben), return "verstorben" if self.verstorben else ""
("Rechtsperson", self.rechtsperson)) if gesetzt)
@property
def rechtsperson_text(self) -> str:
"""Rohwert(e) von ``PER_RECHTSPERSON`` (bekannt: 0, 1, 5, 9), noch nicht entschlüsselt."""
return ", ".join(self.rechtsperson)
def _text(wert) -> str: def _text(wert) -> str:
@@ -192,17 +196,17 @@ def suche_eigentuemer(layer, nachname: str, vorname: str) -> List[Eigentuemer]:
gruppe = gruppen.setdefault(_gruppenschluessel(werte), { gruppe = gruppen.setdefault(_gruppenschluessel(werte), {
"werte": werte, "emails": [], "per_ids": [], "konten": [], "feature_ids": [], "werte": werte, "emails": [], "per_ids": [], "konten": [], "feature_ids": [],
"verstorben": False, "rechtsperson": False, "verstorben": False, "rechtsperson": [],
}) })
for k in ("anrede", "titel"): for k in ("anrede", "titel"):
if not gruppe["werte"][k] and werte[k]: if not gruppe["werte"][k] and werte[k]:
gruppe["werte"][k] = werte[k] gruppe["werte"][k] = werte[k]
for liste, wert in (("emails", werte["email"]), ("per_ids", werte["per_id"]), ("konten", werte["konto"])): for liste, wert in (("emails", werte["email"]), ("per_ids", werte["per_id"]),
("konten", werte["konto"]), ("rechtsperson", werte["rechtsperson"])):
if wert and wert not in gruppe[liste]: if wert and wert not in gruppe[liste]:
gruppe[liste].append(wert) gruppe[liste].append(wert)
gruppe["feature_ids"].append(feature.id()) gruppe["feature_ids"].append(feature.id())
gruppe["verstorben"] = gruppe["verstorben"] or _ist_gesetzt(werte["verstorben"]) gruppe["verstorben"] = gruppe["verstorben"] or _ist_gesetzt(werte["verstorben"])
gruppe["rechtsperson"] = gruppe["rechtsperson"] or _ist_gesetzt(werte["rechtsperson"])
treffer = [] treffer = []
for gruppe in gruppen.values(): for gruppe in gruppen.values():
@@ -211,7 +215,7 @@ def suche_eigentuemer(layer, nachname: str, vorname: str) -> List[Eigentuemer]:
anrede=w["anrede"], titel=w["titel"], vorname=w["vorname"], nachname=w["nachname"], anrede=w["anrede"], titel=w["titel"], vorname=w["vorname"], nachname=w["nachname"],
strasse=w["strasse"], plz=w["plz"], wohnort=w["wohnort"], ortsteil=w["ortsteil"], strasse=w["strasse"], plz=w["plz"], wohnort=w["wohnort"], ortsteil=w["ortsteil"],
emails=tuple(gruppe["emails"]), emails=tuple(gruppe["emails"]),
verstorben=gruppe["verstorben"], rechtsperson=gruppe["rechtsperson"], verstorben=gruppe["verstorben"], rechtsperson=tuple(gruppe["rechtsperson"]),
per_ids=tuple(gruppe["per_ids"]), konten=tuple(gruppe["konten"]), per_ids=tuple(gruppe["per_ids"]), konten=tuple(gruppe["konten"]),
feature_ids=tuple(gruppe["feature_ids"]), feature_ids=tuple(gruppe["feature_ids"]),
)) ))
+19 -8
View File
@@ -139,18 +139,29 @@ class TestSuche(unittest.TestCase):
treffer = es.suche_eigentuemer(layer, "Beispiel", "Anna") treffer = es.suche_eigentuemer(layer, "Beispiel", "Anna")
self.assertEqual([e.wohnort for e in treffer], ["Altdorf", "Zettlitz"]) self.assertEqual([e.wohnort for e in treffer], ["Altdorf", "Zettlitz"])
def test_kennzeichen_verstorben_und_rechtsperson(self): def test_kennzeichen_verstorben(self):
"""Prüft, dass „verstorben“ und „Rechtsperson“ aus üblichen Ja-Werten erkannt werden und Nein-Werte, Leeres und NULL nicht zählen.""" """Prüft, dass „verstorben“ beim Exportwert 1 (auch als Zahl gelesen) gesetzt ist und bei 0, Leerem und NULL nicht."""
ja = ["J", "ja", "1", "true", "X"] ja = ["1", 1]
nein = ["", "0", "N", "nein", "false", None, _Null()] nein = ["0", 0, "", None, _Null()]
zeilen = [_zeile(f"Ja{i}", "Test", PER_VERSTORBEN=w, PER_RECHTSPERSON=w) for i, w in enumerate(ja)] zeilen = [_zeile(f"Ja{i}", "Test", PER_VERSTORBEN=w) for i, w in enumerate(ja)]
zeilen += [_zeile(f"Nein{i}", "Test", PER_VERSTORBEN=w, PER_RECHTSPERSON=w) for i, w in enumerate(nein)] zeilen += [_zeile(f"Nein{i}", "Test", PER_VERSTORBEN=w) for i, w in enumerate(nein)]
for e in es.suche_eigentuemer(_Layer(zeilen), "", "Test"): for e in es.suche_eigentuemer(_Layer(zeilen), "", "Test"):
erwartet = e.nachname.startswith("Ja") erwartet = e.nachname.startswith("Ja")
with self.subTest(name=e.nachname): with self.subTest(name=e.nachname):
self.assertEqual(e.verstorben, erwartet) self.assertEqual(e.verstorben, erwartet)
self.assertEqual(e.rechtsperson, erwartet) self.assertEqual(e.hinweise, "verstorben" if erwartet else "")
self.assertEqual(e.hinweise, "verstorben, Rechtsperson" if erwartet else "")
def test_rechtsperson_als_rohwert(self):
"""Prüft, dass PER_RECHTSPERSON unverändert angezeigt wird (0, 1, 5, 9; noch nicht entschlüsselt) und abweichende Werte einer Person gesammelt werden."""
layer = _Layer([
_zeile("Beispiel", "Anna", PER_RECHTSPERSON="0"),
_zeile("Gemeinde Musterstadt", "", strasse="Markt 1", PER_RECHTSPERSON=5),
_zeile("Muster", "Bernd", PER_RECHTSPERSON="1"),
_zeile("Muster", "Bernd", PER_RECHTSPERSON="9"),
_zeile("Ohnewert", "Clara", PER_RECHTSPERSON=_Null()),
])
werte = {e.nachname: e.rechtsperson_text for e in es.suche_eigentuemer(layer, "e", "")}
self.assertEqual(werte, {"Beispiel": "0", "Gemeinde Musterstadt": "5", "Muster": "1, 9", "Ohnewert": ""})
def test_plz_mit_fuehrender_null(self): def test_plz_mit_fuehrender_null(self):
"""Prüft, dass eine von QGIS als Zahl gelesene PLZ wieder fünfstellig mit führender Null erscheint und Zeilen dadurch zusammenfinden.""" """Prüft, dass eine von QGIS als Zahl gelesene PLZ wieder fünfstellig mit führender Null erscheint und Zeilen dadurch zusammenfinden."""
+4 -3
View File
@@ -103,7 +103,8 @@ def _eig_tab(nachname="Beispiel", vorname="", layer_id="csv1"):
_ANNA = Eigentuemer(vorname="Anna", nachname="Beispiel", strasse="Hauptstraße 1", plz="01234", _ANNA = Eigentuemer(vorname="Anna", nachname="Beispiel", strasse="Hauptstraße 1", plz="01234",
wohnort="Musterstadt", emails=("anna@example.org",), verstorben=True) wohnort="Musterstadt", emails=("anna@example.org",), verstorben=True,
rechtsperson=("0",))
@patch.object(et, "info") @patch.object(et, "info")
@@ -152,10 +153,10 @@ class TestEigentuemerSuchen(unittest.TestCase):
class TestTrefferliste(unittest.TestCase): class TestTrefferliste(unittest.TestCase):
def test_trefferzeile_mit_hinweis(self): def test_trefferzeile_mit_hinweis(self):
"""Prüft, dass die Trefferliste Name, Anschrift, E-Mail und die Kennzeichen „verstorben“/„Rechtsperson“ zeigt.""" """Prüft, dass die Trefferliste Name, Anschrift, E-Mail, den Rohwert von PER_RECHTSPERSON und das Kennzeichen „verstorben“ zeigt."""
self.assertEqual( self.assertEqual(
trefferzeile(_ANNA), trefferzeile(_ANNA),
("Beispiel, Anna", "Hauptstraße 1, 01234 Musterstadt", "anna@example.org", "verstorben"), ("Beispiel, Anna", "Hauptstraße 1, 01234 Musterstadt", "anna@example.org", "0", "verstorben"),
) )
def test_ausgewaehlte_eigentuemer(self): def test_ausgewaehlte_eigentuemer(self):
+5 -4
View File
@@ -1,8 +1,8 @@
"""sn_verfahrensgebiet/ui/eigentuemer_treffer.py – Trefferliste der Eigentümersuche (#56). """sn_verfahrensgebiet/ui/eigentuemer_treffer.py – Trefferliste der Eigentümersuche (#56).
Nicht modales Fenster für den Bearbeiter. Es zeigt die zusammengefassten Nicht modales Fenster für den Bearbeiter. Es zeigt die zusammengefassten
Treffer mit den Kennzeichen „verstorben“ und „Rechtsperson“ und erlaubt die Treffer mit dem Wert von ``PER_RECHTSPERSON`` (noch nicht entschlüsselt) und dem
Mehrfachauswahl. Die Tabelle kommt aus Kennzeichen „verstorben“ und erlaubt die Mehrfachauswahl. Die Tabelle kommt aus
:func:`~sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge.baue_auswahltabelle`, :func:`~sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge.baue_auswahltabelle`,
dieselbe wie bei „Verfahrensgebiete aus Landkreis laden“. dieselbe wie bei „Verfahrensgebiete aus Landkreis laden“.
""" """
@@ -17,15 +17,16 @@ from sn_verfahrensgebiet.functions.verfahrensgebiet_dialoge import (
ausgewaehlte_indizes, ausgewaehlte_indizes,
) )
SPALTENKOEPFE = ["Name", "Anschrift", "E-Mail", "Hinweis"] SPALTENKOEPFE = ["Name", "Anschrift", "E-Mail", "Rechtsperson", "Hinweis"]
def trefferzeile(eigentuemer: Eigentuemer) -> Tuple[str, str, str, str]: def trefferzeile(eigentuemer: Eigentuemer) -> Tuple[str, str, str, str, str]:
"""Spaltentexte eines Treffers in der Reihenfolge von :data:`SPALTENKOEPFE`.""" """Spaltentexte eines Treffers in der Reihenfolge von :data:`SPALTENKOEPFE`."""
return ( return (
eigentuemer.name, eigentuemer.name,
eigentuemer.anschrift, eigentuemer.anschrift,
", ".join(eigentuemer.emails), ", ".join(eigentuemer.emails),
eigentuemer.rechtsperson_text,
eigentuemer.hinweise, eigentuemer.hinweise,
) )