Modul: Vertragsauskunft¶
Stand
Dokumentation basiert auf Code-Analyse vom 2026-04-22 gegen die Live-Instanz
sachpool.de, WebEdition 9.2.3.0, PHP 8.3.29. Template-Inhalte können sich
ändern — Stand-Datum und IDs erlauben zielgerichteten Re-Audit.
Zweck¶
Die Vertragsauskunft ist das Kernmodul des Sachpool-Login-Bereichs. Sie gibt Versicherungsmaklern Zugriff auf:
- Alle Verträge ihres eigenen Bestands (inkl. Untervermittler-Hierarchie)
- Alle Dokumente dazu (Anträge, Policen, Abrechnungen, Nachbearbeitungen)
- Stammdaten der Versicherungsnehmer
- Schadens-Historie
- Bestandsstatistik nach Sparten
Zugriff nur für eingeloggte Nutzer im Workspace 536 (Login-Bereich).
Einstiegspunkte (PHP-Dokumente)¶
Alle liegen unter /_login/vertragsauskunft/ und sind an Templates in
sachpool-portal/vertragsauskunft/ gebunden:
| Doc-ID | Pfad | Template-ID | Rolle |
|---|---|---|---|
| 2149 | searchstart.php |
152 | Einstieg: Dokumenten-Inbox + Statistik |
| 1655 | searchresult.php |
164 | Suchergebnisse (Person / Vertrag) |
| 2424 | contractlistperson.php |
163 | Vertrags-/Dokumenten-/Kontaktsicht einer Person |
| 2414 | contractdetails.php |
158 | Einzelvertrag mit Sparten-Details |
| 2188 | new-documents.php |
157 | „Neue Dokumente" im Popup-Fenster |
| 2186 | documentdownload.php |
156 | Einzeldokument-Download |
| 2184 | downloadmultifiles.php |
155 | Massen-Download ZIP |
| 50080 | abrechnungsuebersicht.php |
275 | Courtage-Abrechnungen |
Weitere Templates ohne eigene PHP-Einstiegsseite:
| Tpl-ID | Template | Zweck |
|---|---|---|
| 132 | phpfunctions.tmpl |
Hilfsfunktionen (getUntervermittlerString, startsWith, output_file_image_tag) |
| 153 | searchmask.tmpl |
Gemeinsame Suchmaske (wird überall eingebunden) |
| 154 | footer.tmpl |
Modul-Footer |
| 161 | contractbasedetails.tmpl |
Vertrags-Kopfdaten (Gesellschaft, Beitrag etc.) |
| 165 | contractlist.tmpl |
Wiederverwendbare Vertragsliste |
| 166 | customerlist.tmpl |
Personenliste (Suchergebnis Personen) |
| 169 | birthdaylist.tmpl |
Geburtstagsliste (separates Modul) |
| 167 | searchmask2.tmpl |
Alternative Suchmaske für Popup-Kontext |
Architektur¶
┌─────────────────────────────────────┐
│ master-template/master.tmpl │
│ (Session, Header, Navigation) │
└────────────────┬────────────────────┘
│
┌────────────────▼────────────────────┐
│ compatiblity-layer/ (sic!) │
│ • authorization.tmpl (160) │
│ → $benutzer, $ownerid, $sach_* │
│ • database.tmpl (290) │
│ → SachPoolDB::getInstance() │
└────────────────┬────────────────────┘
│
┌─────────────────────────────┼─────────────────────────────┐
│ │ │
┌───────▼─────────┐ ┌───────▼────────┐ ┌─────────▼────────┐
│ searchstart.php │ │ searchresult │ │ contractdetails │
│ (Einstieg) │ │ .php │ │ .php?vsn=… │
│ │ │ │ │ │
│ • Inbox │ │ • Person │ │ • Vertragskopf │
│ • Statistik │ │ • Vertrag │ │ • Sparte-Detail │
│ • hideAll │ │ │ │ • Dokumente │
└───────┬─────────┘ └───────┬────────┘ │ • Schäden │
│ │ └──────────────────┘
│ ┌─────────▼──────────┐
│ │ customerlist (166) │
│ │ contractlist (165) │
│ └────────────────────┘
│
└─► contractlistperson.php?persid=…
Tabs: Verträge | Dokumente | Kontakt
Kompatibilitätslayer-Abhängigkeit¶
Alle Seiten binden am Anfang zwei zentrale Templates ein:
<we:include type="template" id="160" once="true" /> <!-- authorization.tmpl -->
<we:include type="template" id="132" once="true" /> <!-- phpfunctions.tmpl -->
authorization.tmpl setzt $benutzer (= PORTAL_ID aus der Session) und lädt
daraus die Kontext-Variablen ($ownerid, $sach_user, $sach_forename, …).
phpfunctions.tmpl stellt die Helper-Funktionen bereit. Die Datenbankverbindung
kommt aus SachPoolDB::getInstance() (Template 290 — Nachfolger des alten
DB_WE()).
Tippfehler im Pfad
Der Template-Ordner heißt historisch compatiblity-layer (ohne erstes „i").
Verbreitet in allen Includes — Umbenennung nicht ohne Gesamt-Sweep möglich.
Datenmodell¶
Kerntabellen (Schema extranetportal_*, MySQL auf Sachpool.de)¶
| Tabelle | Inhalt | Wichtige Spalten |
|---|---|---|
extranetportal_personen |
Makler und Kunden (Vermittlernetz + Endkunden) | PERS_ID, PORTAL_ID, NAME, VORNAME, STRASSE, PLZ, ORT, TELEFON, EMAIL, STATUS, OWNER, UEBERMAKLER, SIEHT_UNTERVERMITTLER |
extranetportal_vertraege |
Versicherungsverträge | VSN, PERS_ID, OWNER, GESELLSCHAFT, SPARTE_ID, SPARTE, STATUS_ID, BEGINN, ABLAUF, ZAHLWEISE, NETTO_LT_ZW, BRUTTO_LT_ZW |
extranetportal_dokumente |
Dokumente (PDF, etc.) | id, PERS_ID, VSN, DATUM, TYP, DATEINAME, KATEGORIE_ID, KATEGORIE_GRUPPE, KATEGORIE_BEZEICHNUNG, NACHBEARBEITUNG, gelesen |
extranetportal_schaeden |
Schäden zu einem Vertrag | VSN, SCHADENNUMMER, SCHADENDATUM, BETRAG, ERLEDIGT |
Sparten-spezifische Detail-Tabellen¶
Je Sparte gibt es eine eigene Tabelle, verknüpft über VSN / ID:
SPARTE_ID |
Sparte | Detail-Tabelle |
|---|---|---|
| 30 | Unfall | extranetportal_unfall |
| 40 | Haftpflicht | extranetportal_haftpflicht |
| 50 | KFZ | extranetportal_kfz |
| 70 | Rechtsschutz | extranetportal_rechtsschutz |
| 130 | Hausrat | extranetportal_hausrat |
| 140 | Verbundene Gebäude (neu seit 07.02.2025) | extranetportal_vgebaeude |
Weitere Sparten (Glas, Gruppen-Unfall, …) verwenden nur die generische
extranetportal_vertraege-Tabelle und werden in contractdetails.tmpl ohne
spartenspezifischen Detail-Block gerendert.
STATUS_ID in extranetportal_vertraege¶
| Wert | Bedeutung |
|---|---|
1 |
Lebend (aktiv) |
3 |
Ruhend / Anwartschaft |
4 |
offen — siehe interne Datei OFFENE-FRAGEN.md im Repo-Root |
NACHBEARBEITUNG = 1¶
Intern vom Sachpool-System gesetzt, wenn ein Dokument manuell nachbearbeitet wurde. Auswirkungen:
- Das Dokument bleibt 365 Tage statt 6 Wochen in der Inbox (
searchstart) - Zeile wird rot markiert (CSS-Klasse
quietBackground)
Berechtigungsmodell¶
Die Sichtbarkeitsfilter laufen konsequent über zwei Variablen:
$ownerid— Personen-ID des eingeloggten Vermittlers (ausauthorization.tmpl)$localOwnerid— kommaseparierte ID-Liste ausgetUntervermittlerString($wedb, $ownerid)— löst die Hierarchie auf:Ein Vermittler sieht einen Untervermittler nur, wenn der gesamte Zweig von ihm bis dorthin mit
SIEHT_UNTERVERMITTLER = 1markiert ist.
Alle Produktivqueries filtern über ver.OWNER IN ($localOwnerid) oder
pers.OWNER IN ($localOwnerid).
Einstieg: searchstart.php (Template 152)¶
Zweck¶
Landing-Page mit Bestandsstatistik und Inbox „NEU eingestellte Dokumente".
Einstiegsseite mit Suchmaske, Bestandsstatistik-Panel (collapsed) und NEU-Dokumente-Inbox.
Eingeloggt: Max Mustermakler (Vermittler 90100-11).
Die rote Zeile zeigt ein Dokument mit NACHBEARBEITUNG = 1.
POST-Parameter¶
| Parameter | Zweck |
|---|---|
hideAll |
UPDATE gelesen = 1 auf alle Dokumente des Vermittlers |
hideSel + docIDs[] |
UPDATE gelesen = 1 auf markierte Dokumente |
nurEigeneDoks[] |
Filter „nur meine Dokumente" (bei Untervermittlern relevant) |
nurEigeneDoksChanged |
Flag für Submit-Detektion |
select_rows_on_page |
Page-Size (funktionsarm — Pager-Logik größtenteils in <we:comment>) |
Haupt-Query (Inbox, gekürzt)¶
-- UNION-Teil 1: Dokumente, die an einen Vertrag hängen
SELECT doc.*, pers.NAME, pers.VORNAME, ver.GESELLSCHAFT, ...
FROM extranetportal_dokumente doc
LEFT OUTER JOIN extranetportal_personen pers ON doc.pers_id = pers.pers_id
LEFT OUTER JOIN extranetportal_vertraege ver ON doc.vsn = ver.vsn
LEFT OUTER JOIN extranetportal_personen persOwner ON ver.owner = persOwner.pers_id
WHERE gelesen = 0 AND ver.owner IN ($ownerids)
UNION ALL
-- UNION-Teil 2: „schwebende" Dokumente ohne Vertragsnummer
SELECT doc.*, ... NULL AS GESELLSCHAFT, ...
FROM extranetportal_dokumente doc
LEFT OUTER JOIN extranetportal_personen pers ON doc.pers_id = pers.pers_id
LEFT OUTER JOIN extranetportal_personen persOwner ON pers.owner = persOwner.PERS_ID
WHERE doc.vsn = '0' AND doc.gelesen = 0
AND KATEGORIE_ID NOT IN (341, 371, 375, 376)
AND ( doc.pers_id IN ($ownerids) OR pers.owner IN ($ownerids) )
ORDER BY DATUM DESC
Offene Klärung
Die Filterkategorien 341 / 371 / 375 / 376 sind hartkodiert ohne Erklärung.
→ interne Datei OFFENE-FRAGEN.md im Repo-Root
Bestandsstatistik (optional aufklappbar)¶
Drei separate Queries über extranetportal_vertraege:
- Gesamtsumme: Anzahl Verträge + Netto-Jahres-Summe, aufgeteilt nach
STATUS_ID - Pro Sparte: lebend (
STATUS_ID=1) / ruhend (STATUS_ID=3) - Pro Sparte: mit roter Kategorie (
STATUS_ID=4), nach Netto-Summe sortiert
Beitrags-Normalisierung auf Jahreswert berücksichtigt ZAHLWEISE:
jährlich, halbjährlich, vierteljährlich, monatlich, Einmalbetrag,
sonstiges, beitragsfrei — als Strings fest im SQL verdrahtet.
Bestandsstatistik mit Balken-Visualisierung. Mustermakler-Testaccount: 14 Verträge gesamt,
aufgeschlüsselt nach KFZ, Gebäude, Unfall, Glas, Haftpflicht, Hausrat.
Suche: searchmask.php (Template 153) + searchresult.php (Template 164)¶
Suchmaske¶
Wird auf allen Vertragsauskunft-Seiten eingebunden. Zwei Modi:
- Person: Felder
name,vorname,plz,ort - Vertrag: Felder
vsnr,kfzkz
Umschaltung per Radio-Button, JS-Funktionen setPersonSearch() / setContractSearch()
(aus js/va-functions.js, globale Einbindung in Template 153).
Parameter-Persistenz¶
Alle Suchfeld-Werte werden beim Laden in die Session übernommen (auch leere):
$_SESSION['searchtype'] = $searchtype;
$_SESSION['name'] = $name;
$_SESSION['vorname'] = $vorname;
// ...
→ Auch beim Zurück-Navigieren steht die letzte Suche noch da.
BiPRO-Deeplink: frombipro=true¶
Sondervariante: Externe Aufrufe per GET-Parameter frombipro=true werden
automatisch in den passenden Suchmodus gesteuert:
frombipro=true + … |
Wirkung |
|---|---|
vsnr oder kfzkz gesetzt |
searchtype = contract |
| sonst | searchtype = person |
Zweck: Tracking, über welchen Weg ein Aufruf reinkommt. BiPRO (Brancheninstitut für Prozessoptimierung) definiert das Standard-Format für Schnittstellen in der Versicherungsbranche.
Suchergebnis-Queries¶
Person (searchresult.php mit searchtype=person):
SELECT pers.*, persOwner.NAME AS OWNERNAME, ...
FROM extranetportal_personen pers
LEFT OUTER JOIN extranetportal_personen persOwner ON pers.owner = persOwner.pers_id
WHERE ( pers.OWNER IN ($localOwnerid)
OR pers.pers_id IN (SELECT DISTINCT pers_id FROM extranetportal_vertraege
WHERE owner IN ($localOwnerid)) )
AND pers.NAME LIKE '$name%' -- nur wenn gefüllt
AND pers.VORNAME LIKE '$vorname%'
AND pers.PLZ LIKE '$plz%'
AND pers.ORT LIKE '$ort%'
ORDER BY pers.NAME, pers.VORNAME, pers.PLZ
Vertrag (searchtype=contract):
SELECT kfz.*, pers.*, ver.*, verOwner.NAME AS OWNERNAME, ...
FROM extranetportal_vertraege ver
LEFT OUTER JOIN extranetportal_personen pers ON ver.pers_id = pers.pers_id
LEFT OUTER JOIN extranetportal_kfz kfz ON ver.ID = kfz.ID
LEFT OUTER JOIN extranetportal_personen verOwner ON ver.owner = verOwner.pers_id
WHERE 1=1
AND ver.VSN LIKE '%$vsnr%'
AND REPLACE(REPLACE(kfz.KENNZEICHEN,' ',''),'-','') LIKE REPLACE(REPLACE('$kfzkz%',' ',''),'-','')
AND ver.owner IN ($localOwnerid)
ORDER BY pers.NAME, pers.VORNAME
Das doppelte REPLACE erlaubt eine flexible KFZ-Kennzeichen-Suche: Der Nutzer
kann „B MW 1234", „B-MW-1234" oder „BMW1234" eingeben — alle matchen dasselbe
Kennzeichen.
Eingabe-Werte werden per $wedbSearch->escape() maskiert.
Personensicht: contractlistperson.php (Template 163)¶
Zweck¶
Alle Daten zu einer Person (typischerweise der Kunde eines Vermittlers).
Aufruf per ?persid=<id> oder ?searchtype=person&persid=<id>.
Drei Tabs (clientseitig umgeschaltet)¶
- Verträge (Default) — zeigt
contractlist.tmpl(165) eingebunden - Dokumente — tablesorter-Tabelle mit Typ, Datum, VSN, Datei-Link, Kategorie
- Kontakt — Label/Wert-Liste (Name, Anschrift, Geburtsdatum, Telefon, E-Mail)
Tab „Dokumente" und „Schäden" werden nur gerendert wenn Datensätze vorhanden
($dataPersDocs_rows > 0).
Personensicht für Max Mustermakler. Filter „nur AKTIVE Verträge" ist aktiv (Session-persistent).
Zwei aktive Verträge sichtbar: AGILA Hundehalter-Haftpflicht + Allianz Glas/Hausrat.
Parameter¶
| Parameter | Typ | Zweck |
|---|---|---|
persid |
GET | Person-ID (Pflicht beim ersten Aufruf) |
Session persid |
— | Fallback, wenn GET fehlt |
include[] + includeChanged |
POST | Filter „nur aktive Verträge" (status_id=1) |
Session onlyActive |
— | Persistenter Filter-Zustand |
Datenbank¶
Drei Queries:
- Personen-Kopfdaten
- Verträge der Person (gefiltert auf
ver.OWNER IN ($localOwnerid)undpers_id = $persid; optionalstatus_id=1) - Dokumente (UNION ALL — mit/ohne Vertrag, wieder mit der Kategorie-Filter-Liste 341/371/375/376)
Vertrag-Detailsicht: contractdetails.php (Template 158)¶
Zweck¶
Einzelner Vertrag, aufgerufen per ?vsn=<versicherungsnummer>. Spartenspezifische
Detaildarstellung + Dokumente + Schäden.
Detailansicht für Vertrag 123456789001. Allianz Glas/Hausrat, monatlich,
88 € brutto. Tabs „Vertrag" und „Dokumente" sichtbar — „Schäden" fehlt, weil keine Schäden vorhanden.
Drei Tabs¶
- Vertrag (Default) — Kopfdaten aus
contractbasedetails.tmpl(161) + spartenspezifische Detail-Tabelle - Dokumente — alle Dokumente mit passender VSN
- Schäden — nur sichtbar, wenn Schäden existieren
Spartenspezifischer Detail-Block¶
Ein switch($SPARTE_ID) rendert je nach Sparte andere Felder:
- KFZ (50): Kennzeichen, HSN/TSN, Hersteller, Modell, SF KH/VK, SB KH/KT/KV, Fahrgestellnummer, Wagnis, Stärke in kW
- Hausrat (130): Risikoanschrift, Immobilienart, Bauart, Dachung, Variante, Unterversicherungsverzicht, Wohnfläche, Versicherungssumme, Gefahren 1–7, Wertsachen, Fahrrad (Ent.-Summe), unbewohnt, SB, junge-Leute-Nachlass
- Haftpflicht (40): 4 Wagnisversicherungssummen
- Verbundene Gebäude (140): (neuer Tarif seit 07.02.2025) Risikoanschrift, Objektdaten, Sanierungen (Dach/Wasser/Elektrik), Bausteine (Unbenannte Gefahren, Glas, Elementar, Starkregen Plus, Haustechnik, Photovoltaik, Solar, Best-Leistungsgarantie)
- Rechtsschutz (70): Risikogruppe, Laufzeit, Deckungssumme, Tarifjahr, Amtliches Kennzeichen, mitversicherte Person, Selbstbehalt
- Unfall (30): Grundsumme, Progression, Invalidität, Rente, Tagegeld, Reha, … (~26 Bausteine)
Doppelter Switch (Performance)
Der Sparten-switch wird aus historischen Gründen zweimal durchlaufen
(einmal vor dem HTML, einmal inline). Jede Query läuft damit doppelt.
→ interne Datei HANDLUNGSEMPFEHLUNGEN.md im Repo-Root
Datenbank (Kernquery)¶
SELECT ver.*, verOwner.NAME AS OWNERNAME, verOwner.VORNAME AS OWNERVORNAME, verOwner.pers_id AS OWNERID
FROM extranetportal_vertraege ver
LEFT OUTER JOIN extranetportal_personen verOwner ON ver.OWNER = verOwner.pers_id
WHERE ver.VSN = '$vsn'
AND ver.OWNER IN ($localOwnerid)
$vsn kommt aus <we:setVar from="get" to="local" prepareSQL="true"> — das ist
die WebEdition-eigene Escape-Variante, SQL-sicher.
Fremder Vermittler¶
Wenn ver.OWNER ≠ $ownerid (d. h. der Vertrag gehört einem Untervermittler),
wird in der Kundenbox zusätzlich angezeigt:
Download-Endpunkte¶
documentdownload.php?docid=<id> (Template 156)¶
Liefert ein einzelnes Dokument aus. Prüft Berechtigung über doc.pers_id/doc.vsn
gegen $localOwnerid.
downloadmultifiles.php (Template 155)¶
Massen-Download: POST-Form aus searchstart.php mit ids (kommasepariert).
Max. 50 Dokumente pro Aufruf (JS-Seite verhindert mehr per alert).
new-documents.php (Template 157)¶
Popup-Fenster für „NEU-Dokumente" — wird aus searchmask.php per
window.open(...) als eigenständiges Browser-Fenster geöffnet. Zweck: paralleles
Arbeiten (zweiter Monitor).
Abrechnungs-Übersicht: abrechnungsuebersicht.php (Template 275)¶
Liegt unter /_login/vertragsauskunft/abrechnungsuebersicht.php mit
published = -1 (Sonderstatus, siehe Handlungsempfehlungen). Wird über den
Hinweisblock auf searchstart.php verlinkt:
„Ihre Abrechnungen finden Sie ab sofort unter dem Menüpunkt 'Meine Daten' > Courtageabrechnungen"
Details folgen in eigener Doku-Runde.
Historische Kontext¶
- Dieses Modul war der erste Migrations-Prototyp von FastpublishCMS auf WebEdition. Alle Konzepte (Kompatibilitätslayer, Link-Modul, Modulkapselung) wurden hier entwickelt.
- Die alten FastpublishCMS-Artikel-IDs sind in Modulzugehörigkeiten (historisch) dokumentiert.
- Die ursprünglichen Portierungsschritte (mysqli, UTF-8, @-Operator-Entfernung,
phpfunctions.phpzentralisieren) sind abgeschlossen — siehe Portierung Vertragsauskunft (historisch).
Bekannte Altlasten¶
→ Alle aktuellen Audit-Befunde: interne Datei HANDLUNGSEMPFEHLUNGEN.md im Repo-Root (nicht publiziert)
Kurzauszug:
- 🔴
$_SERVER['HTTP_REFERER']ungeescaped im Zurück-Link (XSS-Vektor) - 🔴
$benutzerinsearchstart.tmplOwner-Resolution ohne Escape - 🟡 Doppelter Sparten-Switch in
contractdetails.tmpl - 🟡 Hartkodierte Sparten-IDs ohne zentrales Mapping
- 🟡 Debug-Reste, auskommentierte Pager-Blöcke
- 🟢 HTML-Validität: ungeschlossene
</tr>, ohne Semikolon, leereurl() - 🟢 Tippfehler-Pfad
compatiblity-layer
Siehe auch¶
- Architektur (Entwickler) — Gesamtsystem-Überblick
- Authentifizierungslayer —
$ownerid,SIEHT_UNTERVERMITTLER - Datenbank-Zugriff —
SachPoolDB::getInstance() - Link-Modul — Platzhalter-System
%SACHUSER% - Modulkapselung — Anwendungsfenster / Popup-Strategie