Zum Inhalt

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 (aus authorization.tmpl)
  • $localOwnerid — kommaseparierte ID-Liste aus getUntervermittlerString($wedb, $ownerid) — löst die Hierarchie auf:

    Ein Vermittler sieht einen Untervermittler nur, wenn der gesamte Zweig von ihm bis dorthin mit SIEHT_UNTERVERMITTLER = 1 markiert 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".

Vertragsauskunft – Einstiegsseite 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:

  1. Gesamtsumme: Anzahl Verträge + Netto-Jahres-Summe, aufgeteilt nach STATUS_ID
  2. Pro Sparte: lebend (STATUS_ID=1) / ruhend (STATUS_ID=3)
  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-Panel aufgeklappt 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.

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)

  1. Verträge (Default) — zeigt contractlist.tmpl (165) eingebunden
  2. Dokumente — tablesorter-Tabelle mit Typ, Datum, VSN, Datei-Link, Kategorie
  3. 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).

contractlistperson – Verträge-Tab für Max Mustermakler 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:

  1. Personen-Kopfdaten
  2. Verträge der Person (gefiltert auf ver.OWNER IN ($localOwnerid) und pers_id = $persid; optional status_id=1)
  3. 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.

contractdetails – Einzelvertrag 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

  1. Vertrag (Default) — Kopfdaten aus contractbasedetails.tmpl (161) + spartenspezifische Detail-Tabelle
  2. Dokumente — alle Dokumente mit passender VSN
  3. 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:

<div class="infoVermittler">(VM: Vorname Nachname)</div>

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.php zentralisieren) 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)
  • 🔴 $benutzer in searchstart.tmpl Owner-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>, &nbsp ohne Semikolon, leere url()
  • 🟢 Tippfehler-Pfad compatiblity-layer

Siehe auch