Unit-Tests für das Verfahrensgebiet-Edit-Service-Modul.
flowchart TD
root(["test_verfahrensgebiet_edit_service.py"])
class root rootnode
root --> g1["TestPruefeFlurstueckLayer (3)"]
diff --git a/docs/tests/testkatalog.md b/docs/tests/testkatalog.md
index 2490ce1..ecde425 100644
--- a/docs/tests/testkatalog.md
+++ b/docs/tests/testkatalog.md
@@ -9,17 +9,17 @@ selbst (Docstrings) und aus der Testbaseline.
Stand der Baseline: `docs/status/testbaseline.json` (SHA-256 `aeabc946a7f3`)
-Gesamt: 3 Pakete, 62 Testdateien, 762 Testfälle – 719 grün, 35 rot (Baseline), 8 übersprungen.
-Mit Beschreibung (Docstring): 617 von 762 (81 %).
+Gesamt: 3 Pakete, 64 Testdateien, 783 Testfälle – 740 grün, 35 rot (Baseline), 8 übersprungen.
+Mit Beschreibung (Docstring): 638 von 783 (81 %).
## Zusammenfassung
| Paket | Testdateien | Testfälle | grün | rot (Baseline) | übersprungen | mit Beschreibung |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| `sn_basis` | 43 | 490 | 490 | 0 | 0 | 490 von 490 (100 %) |
-| `sn_verfahrensgebiet` | 14 | 228 | 186 | 34 | 8 | 126 von 228 (55 %) |
+| `sn_verfahrensgebiet` | 16 | 249 | 207 | 34 | 8 | 147 von 249 (59 %) |
| `sn_plan41` | 5 | 44 | 43 | 1 | 0 | 1 von 44 (2 %) |
-| **Summe** | **62** | **762** | **719** | **35** | **8** | **617 von 762 (81 %)** |
+| **Summe** | **64** | **783** | **740** | **35** | **8** | **638 von 783 (81 %)** |
### Legende
@@ -1071,7 +1071,7 @@ Testgruppen:
## Paket `sn_verfahrensgebiet`
-Fachplugin, Ordner `Plugin_SN_Verfahrensgebiet` – 14 Testdateien mit 228 Testfällen (186 grün, 34 rot, 8 übersprungen).
+Fachplugin, Ordner `Plugin_SN_Verfahrensgebiet` – 16 Testdateien mit 249 Testfällen (207 grün, 34 rot, 8 übersprungen).
### `Plugin_SN_Verfahrensgebiet/tests/test_beteiligung_pruefung.py`
@@ -1128,13 +1128,41 @@ Testgruppen:
| `TestDockWidget.test_init_tabs_with_services_injects_available_services` | — (Docstring fehlt) | grün |
| `TestDockWidget.test_init_tabs_with_services_reports_errors` | — (Docstring fehlt) | grün |
+### `Plugin_SN_Verfahrensgebiet/tests/test_eigentuemer_suche.py`
+
+Worum es in der Datei geht: Tests für die Eigentümersuche im Serienbriefexport (#56).
+
+Testgruppen:
+
+- `TestLayererkennung` (2 Testfälle): — (Docstring fehlt)
+- `TestSuche` (10 Testfälle): — (Docstring fehlt)
+- `TestAnzeigeformen` (1 Testfälle): — (Docstring fehlt)
+
+| Testfall | prüft | Status |
+| --- | --- | --- |
+| `TestLayererkennung.test_layer_mit_name_und_vorname_ist_eigentuemerlayer` | Prüft, dass ein Layer mit den Spalten Nachname und Vorname als Eigentümerdatei erkannt wird, auch mit BOM und anderer Schreibweise. | grün |
+| `TestLayererkennung.test_eigentuemerlayer_im_projekt` | Prüft, dass aus dem Projekt nur passende Layer kommen, nach Namen sortiert. | grün |
+| `TestSuche.test_ohne_eingabe_fehler` | Prüft, dass ohne Name und Vorname nicht die ganze Datei gelistet, sondern um eine Eingabe gebeten wird. | grün |
+| `TestSuche.test_layer_ohne_pflichtspalten` | Prüft, dass ein Layer ohne Nachname/Vorname mit Hinweis auf den Serienbriefexport abgelehnt wird. | grün |
+| `TestSuche.test_teilstring_ohne_gross_kleinschreibung` | Prüft, dass Name und Vorname als Teilstring ohne Rücksicht auf Groß-/Kleinschreibung gesucht werden. | grün |
+| `TestSuche.test_umlaute_und_eszett` | Prüft, dass „Müller“ auch „Mueller“ findet und „Strauss“ auch „Strauß“. | grün |
+| `TestSuche.test_gleiche_person_gleiche_anschrift_zusammengefasst` | Prüft, dass Zeilen mit gleichem Namen und gleicher Anschrift ein Treffer werden und Konten, Personen-IDs und E-Mails gesammelt werden. | grün |
+| `TestSuche.test_andere_anschrift_eigener_treffer` | Prüft, dass dieselbe Person mit anderer Anschrift ein eigener Treffer bleibt, sortiert nach Wohnort. | grün |
+| `TestSuche.test_kennzeichen_verstorben` | Prüft, dass „verstorben“ beim Exportwert 1 (auch als Zahl gelesen) gesetzt ist und bei 0, Leerem und NULL nicht. | grün |
+| `TestSuche.test_rechtsperson_als_rohwert` | Prüft, dass PER_RECHTSPERSON unverändert angezeigt wird (0, 1, 5, 9; noch nicht entschlüsselt) und abweichende Werte einer Person gesammelt werden. | grün |
+| `TestSuche.test_plz_mit_fuehrender_null` | Prüft, dass eine von QGIS als Zahl gelesene PLZ wieder fünfstellig mit führender Null erscheint und Zeilen dadurch zusammenfinden. | grün |
+| `TestSuche.test_suche_gibt_nichts_aus` | Prüft, dass die Suche keine Personendaten auf die Konsole schreibt (Datenschutz). | grün |
+| `TestAnzeigeformen.test_name_und_anschrift` | Prüft, dass Name mit Titel und einzeilige Anschrift mit Ortsteil lesbar zusammengesetzt werden. | grün |
+
### `Plugin_SN_Verfahrensgebiet/tests/test_eigentuemer_tab.py`
-Worum es in der Datei geht: Tests für den Tab „Eigentümer“ – Flurstückssuche (#56).
+Worum es in der Datei geht: Tests für den Tab „Eigentümer“ – Flurstückssuche und Eigentümersuche (#56).
Testgruppen:
- `TestFlurstueckSuchen` (5 Testfälle): — (Docstring fehlt)
+- `TestEigentuemerSuchen` (4 Testfälle): — (Docstring fehlt)
+- `TestTrefferliste` (2 Testfälle): — (Docstring fehlt)
- `TestDockWidgetTabs` (1 Testfälle): — (Docstring fehlt)
| Testfall | prüft | Status |
@@ -1144,6 +1172,12 @@ Testgruppen:
| `TestFlurstueckSuchen.test_kein_treffer` | Prüft, dass ohne Treffer ein Hinweis erscheint und keine Auswahl gesetzt wird. | grün |
| `TestFlurstueckSuchen.test_treffer_werden_ausgewaehlt_und_angezoomt` | Prüft, dass Treffer im Flurstückslayer ausgewählt, angezoomt und gemeldet werden. | grün |
| `TestFlurstueckSuchen.test_ohne_kartenansicht_bleibt_auswahl` | Prüft, dass die Auswahl auch ohne Kartenansicht gesetzt und gemeldet wird. | grün |
+| `TestEigentuemerSuchen.test_ohne_eigentuemerdatei_warnung` | Prüft, dass ohne gewählte Eigentümerdatei die Liste aufgefrischt und dann auf das Laden der CSV hingewiesen wird. | grün |
+| `TestEigentuemerSuchen.test_suchfehler_als_warnung` | Prüft, dass ein Fehler der Suche (z. B. leere Eingabe) als Warnung erscheint. | grün |
+| `TestEigentuemerSuchen.test_kein_treffer_ohne_namen_in_meldung` | Prüft, dass ohne Treffer ein Hinweis erscheint, der den gesuchten Namen nicht wiederholt (Datenschutz). | grün |
+| `TestEigentuemerSuchen.test_treffer_oeffnen_trefferliste_und_ersetzen_die_alte` | Prüft, dass Treffer in einer neuen Trefferliste erscheinen, eine offene alte Liste geschlossen wird und die Eingaben an die Suche gehen. | grün |
+| `TestTrefferliste.test_trefferzeile_mit_hinweis` | Prüft, dass die Trefferliste Name, Anschrift, E-Mail, den Rohwert von PER_RECHTSPERSON und das Kennzeichen „verstorben“ zeigt. | grün |
+| `TestTrefferliste.test_ausgewaehlte_eigentuemer` | Prüft, dass das Trefferfenster die in der Tabelle gewählten Einträge zurückgibt. | grün |
| `TestDockWidgetTabs.test_tab_ist_im_dockwidget_eingetragen` | Prüft, dass der Tab „Eigentümer“ im DockWidget direkt nach dem Bearbeitungs-Tab steht. | grün |
### `Plugin_SN_Verfahrensgebiet/tests/test_flurstueck_alkis.py`
@@ -1355,6 +1389,19 @@ Testgruppen:
| `TestVerfahrensgebietAlkisKomplett.test_landkreis_modus_wird_bei_fehlender_vkz_verwendet` | Keine Verfahrensnummer, aber Landkreis gesetzt → Landkreis-Suchpfad | grün |
| `TestVerfahrensgebietAlkisKomplett.test_landkreis_modus_nur_laden_ueberspringt_dialog2` | nur_laden=True → Funktion kehrt sofort zurück, Dialog 2 (Gemarkung | grün |
+### `Plugin_SN_Verfahrensgebiet/tests/test_verfahrensgebiet_dialoge.py`
+
+Worum es in der Datei geht: Tests für die gemeinsame Auswahltabelle in verfahrensgebiet_dialoge (#56).
+
+Testgruppen:
+
+- `TestAuswahltabelle` (2 Testfälle): — (Docstring fehlt)
+
+| Testfall | prüft | Status |
+| --- | --- | --- |
+| `TestAuswahltabelle.test_suchfilter_ueber_alle_spalten` | Prüft, dass der Suchfilter Teilstrings in jeder Spalte ohne Groß-/Kleinschreibung findet und leer alles zeigt. | grün |
+| `TestAuswahltabelle.test_auswahl_nach_umsortieren` | Prüft, dass die Auswahl über den gespeicherten Index auf die ursprünglichen Zeilen zurückgeführt wird, auch wenn die Tabelle umsortiert ist. | grün |
+
### `Plugin_SN_Verfahrensgebiet/tests/test_verfahrensgebiet_edit_service.py`
Worum es in der Datei geht: Unit-Tests für das Verfahrensgebiet-Edit-Service-Modul.
@@ -1605,7 +1652,7 @@ dann verschwindet er hier von selbst.
Keine Lücken.
-### `sn_verfahrensgebiet` – 102 von 228 Testfällen ohne Beschreibung
+### `sn_verfahrensgebiet` – 102 von 249 Testfällen ohne Beschreibung
#### `Plugin_SN_Verfahrensgebiet/tests/test_beteiligung_pruefung.py` (25 von 25)
diff --git a/docs/tests/verfahrensgebiet-eigentuemersuche.md b/docs/tests/verfahrensgebiet-eigentuemersuche.md
new file mode 100644
index 0000000..8bf5ac0
--- /dev/null
+++ b/docs/tests/verfahrensgebiet-eigentuemersuche.md
@@ -0,0 +1,88 @@
+# Testkonzept: Eigentümersuche im Tab „Eigentümer“
+
+Plugin: `sn_verfahrensgebiet` · Stand: 06.10.2026 · Pflege: bei jeder Änderung an diesem Ablauf
+
+## Worum es fachlich geht
+
+Im Termin mit Eigentümern gleicht die Sachbearbeitung die gespeicherten Personendaten ab: Name, Anschrift und
+E-Mail. Grundlage ist der Serienbriefexport als CSV-Datei, den die Sachbearbeitung von Hand ins Projekt lädt.
+Sie sucht nach Name und/oder Vorname. Die Trefferliste fasst Einträge derselben Person mit derselben Anschrift
+zusammen, denn im Export steht eine Person für jedes Eigentumskonto einmal. Dieselbe Person mit einer anderen
+Anschrift bleibt ein eigener Eintrag. Damit fällt eine abweichende Anschrift sofort auf. Verstorbene und
+Rechtspersonen sind in der Liste gekennzeichnet:
+verstorben als Hinweis (`PER_VERSTORBEN` = 1), die Art der Person als Rohwert von `PER_RECHTSPERSON`
+(0, 1, 5 oder 9; die Bedeutung der Werte wird später entschlüsselt).
+
+Die Trefferliste sieht nur die Sachbearbeitung. Das Anzeigefenster für den Eigentümer auf dem zweiten Bildschirm
+und die Korrekturen folgen im nächsten Schritt von Issue Verfahrensgebiet #56.
+
+## Der Ablauf
+
+```mermaid
+flowchart TD
+ A["1 Anwender lädt den Serienbriefexport
als CSV-Layer ins Projekt"] --> B["2 Eigentümerdatei wählen
(nur Layer mit Nachname und Vorname)"]
+ B --> C{"3 Eigentümerdatei
gewählt?"}
+ C -->|nein| D["Hinweis: CSV laden und wählen"]
+ C -->|ja| E{"4 Name oder Vorname
eingegeben?"}
+ E -->|nein| F["Hinweis: Namen eingeben"]
+ E -->|ja| G["5 Einträge suchen
(Teilwort, Groß-/Kleinschreibung
und Umlaute egal)"]
+ G --> H["6 Gleiche Person mit gleicher
Anschrift zusammenfassen"]
+ H --> I{"7 Treffer?"}
+ I -->|nein| J["Hinweis: keine Einträge
(ohne den Namen zu wiederholen)"]
+ I -->|ja| K["8 Trefferliste mit Anschrift, E-Mail,
Rechtsperson-Wert, verstorben;
Mehrfachauswahl"]
+
+ style D fill:#fff4e6,stroke:#d9822b
+ style F fill:#fff4e6,stroke:#d9822b
+```
+
+## Was geprüft wird
+
+Die Tests liegen in `Plugin_SN_Verfahrensgebiet/tests/test_eigentuemer_suche.py` (Suchlogik),
+`tests/test_eigentuemer_tab.py` (Bedienung, Trefferliste) und `tests/test_verfahrensgebiet_dialoge.py`
+(gemeinsame Auswahltabelle).
+
+| # | Schritt | Wird geprüft durch | Ebene |
+|---|---|---|---|
+| 2 | Nur Layer mit Nachname und Vorname, auch mit BOM oder anderer Schreibweise | `test_layer_mit_name_und_vorname_ist_eigentuemerlayer`, `test_eigentuemerlayer_im_projekt` | Mock |
+| 3 | Keine Eigentümerdatei gewählt | `test_ohne_eigentuemerdatei_warnung` | Mock |
+| 3 | Layer ohne die nötigen Spalten | `test_layer_ohne_pflichtspalten` | Mock |
+| 4 | Ohne Eingabe keine Komplettliste | `test_ohne_eingabe_fehler`, `test_suchfehler_als_warnung` | Mock |
+| 5 | Teilwort, Groß-/Kleinschreibung | `test_teilstring_ohne_gross_kleinschreibung` | Mock |
+| 5 | Umlaute und ß | `test_umlaute_und_eszett` | Mock |
+| 6 | Zusammenfassen, Konten und E-Mails sammeln | `test_gleiche_person_gleiche_anschrift_zusammengefasst` | Mock |
+| 6 | Andere Anschrift bleibt eigener Eintrag | `test_andere_anschrift_eigener_treffer` | Mock |
+| 7 | Kein Treffer, Meldung ohne Namen | `test_kein_treffer_ohne_namen_in_meldung` | Mock |
+| 8 | Trefferliste öffnen, alte ersetzen | `test_treffer_oeffnen_trefferliste_und_ersetzen_die_alte` | Mock |
+| 8 | Spalten der Trefferliste, Kennzeichen | `test_trefferzeile_mit_hinweis`, `test_kennzeichen_verstorben`, `test_rechtsperson_als_rohwert` | Mock |
+| 8 | Ausgewählte Einträge, auch nach Umsortieren | `test_ausgewaehlte_eigentuemer`, `test_auswahl_nach_umsortieren` | Mock |
+| 8 | Suchfeld in der Trefferliste | `test_suchfilter_ueber_alle_spalten` | Mock |
+| — | Keine Personendaten auf der Konsole | `test_suche_gibt_nichts_aus` | Mock |
+| — | Sieht das in QGIS richtig aus? | manueller Test | manuell |
+
+Ebene: `Mock` (ohne QGIS, Sekunden) · `QGIS` (gegen echtes QGIS) · `manuell` (Mensch in QGIS).
+
+## Fachliche Sonderfälle
+
+| Sonderfall | Kommt vor bei | Wird geprüft durch |
+|---|---|---|
+| Eine Person mit mehreren Eigentumskonten | Regelfall im Serienbriefexport | `test_gleiche_person_gleiche_anschrift_zusammengefasst` |
+| Schreibweise weicht ab (Groß-/Kleinschreibung, Leerzeichen) | Handeingaben über Jahre | `test_gleiche_person_gleiche_anschrift_zusammengefasst` |
+| `Müller` gesucht, `Mueller` gespeichert | ältere Datenbestände | `test_umlaute_und_eszett` |
+| PLZ ohne führende Null (`1067`) | QGIS erkennt beim CSV-Laden die Spalte als Zahl | `test_plz_mit_fuehrender_null` |
+| `PER_VERSTORBEN` als Text oder als Zahl gelesen | QGIS erkennt beim CSV-Laden die Spalte als Zahl | `test_kennzeichen_verstorben` |
+| Eine Person mit verschiedenen `PER_RECHTSPERSON`-Werten | mehrere Konten mit unterschiedlicher Erfassung | `test_rechtsperson_als_rohwert` (Werte werden gesammelt) |
+| Leere Felder bzw. QGIS-NULL | unvollständige Datensätze | `test_kennzeichen_verstorben`, `test_rechtsperson_als_rohwert` |
+| Rechtsperson ohne Vornamen (z. B. Gemeinde) | Körperschaften als Eigentümer | `test_name_und_anschrift`, `test_rechtsperson_als_rohwert` |
+
+## Bewusst nicht getestet
+
+| Was | Warum | Wie stattdessen abgesichert |
+|---|---|---|
+| Aufbau von Trefferfenster und Auswahltabelle mit echten Qt-Widgets | im Mock-Modus gibt es keine Widgets; die Filter- und Zuordnungslogik ist als reine Funktion getestet | manueller Test in QGIS |
+| Echter Serienbriefexport | personenbezogen, gehört nicht ins Repository | erfundene Testzeilen mit der Kopfzeile der Blanko-Vorlage aus dem Issue |
+
+## Bekannte Lücken
+
+| Lücke | Auswirkung | Issue |
+|---|---|---|
+| Der manuelle QGIS-Test steht aus, auch für den Landkreis-Dialog, dessen Tabelle jetzt aus der gemeinsamen Funktion kommt | Ein Fehler beim Herauslösen der Tabelle bliebe in beiden Dialogen unbemerkt | Verfahrensgebiet #56 |
diff --git a/docs/tests/verfahrensgebiet-flurstueckssuche.md b/docs/tests/verfahrensgebiet-flurstueckssuche.md
index 63c05fb..3eaf71a 100644
--- a/docs/tests/verfahrensgebiet-flurstueckssuche.md
+++ b/docs/tests/verfahrensgebiet-flurstueckssuche.md
@@ -9,7 +9,7 @@ Sie gibt Gemarkung und Flurstücksnummer ein, das Plugin wählt das Flurstück i
und springt dorthin. Gesucht wird nur in den Flurstücken, die vorher mit dem Verfahrensgebiet aus ALKIS geladen
wurden. So muss im Tab keine Gemarkung aus ganz Sachsen ausgewählt werden.
-Die Eigentümersuche aus Issue Verfahrensgebiet #56 folgt in weiteren Schritten und wird hier ergänzt.
+Die Eigentümersuche im selben Tab beschreibt [`verfahrensgebiet-eigentuemersuche.md`](verfahrensgebiet-eigentuemersuche.md).
## Der Ablauf
diff --git a/docs/wissen/fallstricke.md b/docs/wissen/fallstricke.md
index 6be7897..eafcf18 100644
--- a/docs/wissen/fallstricke.md
+++ b/docs/wissen/fallstricke.md
@@ -102,6 +102,8 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik
- **ArcGIS-REST-Layer nicht nach Nummer deuten.** Gleiche Layer-URLs werden in der Linkliste und im Alternativansatz unterschiedlich benannt; Bedeutung und Geometrie immer gegen die Dienstmetadaten prüfen. (Vergleich Alternativansatz 09/2026)
- **CRS:** Plan41 und VLN-API arbeiten in EPSG:25833. Der Alternativansatz erzeugt EPSG:25832. Für neue Layer 25833 verwenden, bis eine Entscheidung dokumentiert ist.
- **Shape-Import erwartet Feld `FBSchl`** mit `901/10110F` (Neuordnungsgebiet) oder `901/10120F` (Neuvermessungsgebiet). Exporte mit abweichendem Feldnamen (`FBSchl.`) wurden bis 26.8.3 abgewiesen; seit PR Verfahrensgebiet #45 (02.09.2026, 26.8.4-unstable) ist die Feldsuche tolerant. Neue Exportvarianten trotzdem gegen die Erkennung testen.
+- **Eigentümerdaten sind personenbezogen (Verfahrensgebiet #56).** Der Serienbriefexport wird vom Anwender von Hand als CSV-Layer geladen; das Plugin liest ihn nur. Namen und Anschriften gehören nicht in `print`, Meldungsleiste, Projektvariablen oder Testdaten (Tests nutzen erfundene Namen, `test_suche_gibt_nichts_aus` prüft die Konsole). Ein gespeichertes Projekt enthält nur den Pfad zur CSV, nicht die Daten. (06.10.2026)
+- **QGIS liest Zahlen-Spalten einer CSV als Zahl.** Beim Laden als Textdatei-Layer erkennt QGIS Feldtypen; eine PLZ `01067` wird dabei zur Ganzzahl `1067` – in Sachsen beginnt fast jede PLZ mit 0. `eigentuemer_suche._plz` stellt die Null wieder her. Kennungen wie `EIG_KONTO` oder `PER_ID` deshalb als Text vergleichen, nie als Zahl. (06.10.2026)
- **VLN-Upload ersetzt den Serverbestand der VKZ vollständig** (`PUT`). Vor dem Upload sicherstellen, dass alle Teillayer geladen sind.
- **API-Key liegt in `QSettings`** (Registry/Konfigurationsdatei), kein sicherer Schlüsselspeicher. Niemals in Logs oder Tests ausgeben.