瀏覽代碼

Anbindung ADF

Leonard Schulz 10 月之前
父節點
當前提交
4588bfaff4

+ 186 - 0
diwo_code_snippets/model_abfrage_db/VergleichsansichtBaseDbFunctions.java

@@ -0,0 +1,186 @@
+package com.tsystems.diwo.business.queries;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+import com.tsystems.common.model.db.interfaces.IInwoDBCall;
+
+import java.math.BigDecimal;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import oracle.jbo.JboException;
+
+/**
+ * Gemeinsame Basisklasse für Vergleichsansicht-JSON-DB-Funktionen.
+ * Enthält alle gemeinsamen Methoden für die Verarbeitung von Stammakten, Vorgängen und grundlegenden DB-Operationen.
+ */
+public class VergleichsansichtBaseDbFunctions {
+
+    protected static final ObjectMapper om =
+        new ObjectMapper().enable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS );
+
+    /* ===========================
+       STAMMAKTE (Root-Daten)
+       =========================== */
+
+    protected static Map<String, Object> fetchStammakte ( IInwoDBCall dbCall, Integer sakId ) throws SQLException {
+        String q =
+            "SELECT sak.AZ_ANZEIGE AS aktenzeichen, " + "       bew.NAME AS antragstellername, " +
+            "       TO_CHAR(bew.GEB_DAT, 'YYYY-MM-DD') AS antragstellerGeburtsdatum, " +
+            "       sak.A_STRASSE AS adr_strasse, TO_CHAR(sak.A_HAUSNR) AS adr_hausnummer, " +
+            "       sak.A_HAUSNRZUSATZ AS adr_zusatz, sak.A_PLZ_ORT AS adr_plz, sak.A_ORT AS adr_ort " +
+            "FROM T_STAMMAKTEN sak " + "LEFT JOIN T_BEWOHNER bew ON bew.ID = sak.PE_ID " + "WHERE sak.ID = :sak_id";
+        dbCall.query( q );
+        dbCall.in( "sak_id", sakId, Types.INTEGER );
+
+        Map<String, Object> stammakte = new LinkedHashMap<>();
+        ResultSet rs = null;
+
+        rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            stammakte.put( "aktenzeichen", rs.getString( "aktenzeichen" ) );
+            stammakte.put( "antragstellername", rs.getString( "antragstellername" ) );
+            String geb = rs.getString( "antragstellerGeburtsdatum" );
+            if ( geb != null )
+                stammakte.put( "antragstellerGeburtsdatum", geb );
+
+            Map<String, Object> adr = new LinkedHashMap<>();
+            adr.put( "strasse", rs.getString( "adr_strasse" ) );
+            adr.put( "hausnummer", rs.getString( "adr_hausnummer" ) );
+            // Fix: Handle null values for zusatz - schema expects string, not null
+            String zusatz = rs.getString( "adr_zusatz" );
+            adr.put( "zusatz", zusatz != null ? zusatz : "" );
+            adr.put( "plz", rs.getString( "adr_plz" ) );
+            adr.put( "ort", rs.getString( "adr_ort" ) );
+            stammakte.put( "anschrift", adr );
+        }
+
+        return stammakte;
+    }
+
+    /* ===========================
+       Vorgang / Listen / Felder
+       =========================== */
+
+    /** Holt BEZ und WOGNR aus T_AKTEN für eine gegebene SAK_ID */
+    protected static Map<String, Integer> fetchBezWognrFromAkten ( IInwoDBCall dbCall,
+                                                                   Integer sakId ) throws SQLException {
+        if ( sakId == null ) {
+            throw new JboException( "Fehler: SAK_ID ist null in fetchBezWognrFromAkten" );
+        }
+
+        String q = "SELECT a.BEZ, a.WOGNR FROM T_AKTEN a WHERE a.SAK_ID = :sak_id";
+        dbCall.query( q );
+        dbCall.in( "sak_id", sakId, Types.INTEGER );
+
+        Map<String, Integer> result = new LinkedHashMap<>();
+        ResultSet rs = null;
+
+        rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            Object bezObj = rs.getObject( "BEZ" );
+            Object wognrObj = rs.getObject( "WOGNR" );
+
+            if ( bezObj == null || wognrObj == null ) {
+                throw new JboException( "Fehler: BEZ oder WOGNR ist null für SAK_ID " + sakId + " - BEZ: " + bezObj +
+                                        ", WOGNR: " + wognrObj );
+            }
+
+            result.put( "bez", ( ( BigDecimal ) bezObj ).intValue() );
+            result.put( "wognr", ( ( BigDecimal ) wognrObj ).intValue() );
+        } else {
+            throw new JboException( "Fehler: Keine Daten in T_AKTEN für SAK_ID " + sakId + " gefunden" );
+        }
+
+
+        return result;
+    }
+
+    protected static Map<String, Object> fetchStammvorgang ( IInwoDBCall dbCall, Integer sakId,
+                                                             Integer lfdnr ) throws SQLException {
+        String q =
+            "SELECT TO_CHAR(sv.LFDNR) AS id, " + "       COALESCE(st.BESCHREIBUNG, sv.SVORGANGSTYP) AS vorgangstyp, " +
+            "       COALESCE(sa.BESCHREIBUNG, TO_CHAR(sv.STATUS)) AS vorgangsstatus " + "FROM T_STAMMVORGAENGE sv " +
+            "LEFT JOIN T_SANTRAGSTYPEN st ON st.WERT = sv.SVORGANGSTYP " +
+            "LEFT JOIN T_SANTRAGSSTATUS sa ON sa.WERT = sv.STATUS " + "WHERE sv.SAK_ID = :sak_id AND sv.LFDNR = :lfdnr";
+        dbCall.query( q );
+        dbCall.in( "sak_id", sakId, Types.INTEGER );
+        dbCall.in( "lfdnr", lfdnr, Types.INTEGER );
+        ResultSet rs = null;
+        Map<String, Object> out = new LinkedHashMap<>();
+        rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            out.put( "id", rs.getString( "id" ) ); // JSON erwartet String
+            String vt = rs.getString( "vorgangstyp" );
+            if ( vt != null )
+                out.put( "vorgangstyp", vt );
+            String vs = rs.getString( "vorgangsstatus" );
+            if ( vs != null )
+                out.put( "vorgangsstatus", vs );
+        }
+
+        return out;
+    }
+
+    protected static List<Integer> fetchAndereLfdnr ( IInwoDBCall dbCall, Integer sakId,
+                                                      Integer lfdnr ) throws SQLException {
+        String q = "SELECT LFDNR FROM T_STAMMVORGAENGE WHERE SAK_ID = :sak_id AND LFDNR <> :lfdnr ORDER BY LFDNR";
+        dbCall.query( q );
+        dbCall.in( "sak_id", sakId, Types.INTEGER );
+        dbCall.in( "lfdnr", lfdnr, Types.INTEGER );
+        ResultSet rs = null;
+        List<Integer> out = new ArrayList<>();
+
+        rs = dbCall.executeQuery( true );
+        while ( rs.next() )
+            out.add( ( ( BigDecimal ) rs.getObject( "LFDNR" ) ).intValue() );
+
+        return out;
+    }
+
+    /* ===================== Utils ===================== */
+
+    protected static void putNum ( Map<String, Object> target, String jsonKey, ResultSet rs,
+                                   String col ) throws SQLException {
+        Object o = rs.getObject( col );
+        if ( o != null ) {
+            if ( o instanceof BigDecimal )
+                target.put( jsonKey, ( BigDecimal ) o );
+            else if ( o instanceof Number )
+                target.put( jsonKey, ( ( Number ) o ).doubleValue() );
+            else
+                target.put( jsonKey, o );
+        }
+    }
+
+    protected static void putNumIfNotNull ( Map<String, Object> target, String jsonKey, ResultSet rs,
+                                            String col ) throws SQLException {
+        Object o = rs.getObject( col );
+        if ( o != null ) {
+            if ( o instanceof BigDecimal )
+                target.put( jsonKey, ( BigDecimal ) o );
+            else if ( o instanceof Number )
+                target.put( jsonKey, ( ( Number ) o ).doubleValue() );
+            else
+                target.put( jsonKey, o );
+        }
+    }
+
+    protected static void putStringIfNotNull ( Map<String, Object> target, String jsonKey, String value ) {
+        if ( value != null && !value.trim().isEmpty() ) {
+            target.put( jsonKey, value.trim() );
+        }
+    }
+
+    protected static String nullTrim ( String s ) {
+        return s == null ? null : s.trim();
+    }
+}

+ 86 - 0
diwo_code_snippets/model_abfrage_db/VergleichsansichtFassade.java

@@ -0,0 +1,86 @@
+package com.tsystems.diwo.business.facades;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import com.tsystems.common.InwoLogger;
+import com.tsystems.common.model.services.InwoApplicationModuleImpl;
+import com.tsystems.diwo.business.queries.VergleichsansichtHhmEinkommenToJsonDbFunctions;
+import com.tsystems.diwo.business.queries.VergleichsansichtMieteToJsonDbFunctions;
+
+import java.sql.SQLException;
+
+import oracle.jbo.JboException;
+
+public class VergleichsansichtFassade {
+
+    private static final InwoLogger LOG = InwoLogger.createInwoLogger( VergleichsansichtFassade.class );
+
+    private InwoApplicationModuleImpl service;
+
+    public VergleichsansichtFassade () {
+        super();
+    }
+
+    public VergleichsansichtFassade ( InwoApplicationModuleImpl service ) {
+        super();
+
+        if ( service == null ) {
+            throw new JboException( "InwoApplicationModuleImpl ist null." );
+        }
+
+        this.service = service;
+    }
+
+
+    public String getJsonAktuellerVorgang ( Integer sakId, Integer svgLfdnr, Integer konnr ) {
+        try {
+            return VergleichsansichtMieteToJsonDbFunctions.buildJsonAktuellerVorgang( service.getInwoDBCall(), sakId,
+                                                                                      svgLfdnr, konnr );
+        } catch ( SQLException sql ) {
+            sql.printStackTrace();
+            throw new JboException( "Fehler bei Verarbeiten der SQL Abfragen: " + sql.getMessage() );
+        } catch ( JsonProcessingException json ) {
+            json.printStackTrace();
+            throw new JboException( "Fehler beim Erstellen der JSON für den Vergleich: " + json.getMessage() );
+        }
+    }
+
+    public String getJsonAndereVorgaenge ( Integer sakId, Integer svgLfdnr ) {
+        try {
+            return VergleichsansichtMieteToJsonDbFunctions.buildJsonAndereVorgaenge( service.getInwoDBCall(), sakId,
+                                                                                     svgLfdnr );
+        } catch ( SQLException sql ) {
+            sql.printStackTrace();
+            throw new JboException( "Fehler bei Verarbeiten der SQL Abfragen: " + sql.getMessage() );
+        } catch ( JsonProcessingException json ) {
+            json.printStackTrace();
+            throw new JboException( "Fehler beim Erstellen der JSON für den Vergleich: " + json.getMessage() );
+        }
+    }
+
+    public String getJsonHhmEinkommenAktuellerVorgang ( Integer sakId, Integer svgLfdnr, Integer konnr ) {
+        try {
+            return VergleichsansichtHhmEinkommenToJsonDbFunctions.buildJsonAktuellerVorgang( service.getInwoDBCall(),
+                                                                                             sakId, svgLfdnr, konnr );
+        } catch ( SQLException sql ) {
+            sql.printStackTrace();
+            throw new JboException( "Fehler bei Verarbeiten der SQL Abfragen: " + sql.getMessage() );
+        } catch ( JsonProcessingException json ) {
+            json.printStackTrace();
+            throw new JboException( "Fehler beim Erstellen der JSON für den Vergleich: " + json.getMessage() );
+        }
+    }
+
+    public String getJsonHhmEinkommennAndereVorgaenge ( Integer sakId, Integer svgLfdnr ) {
+        try {
+            return VergleichsansichtHhmEinkommenToJsonDbFunctions.buildJsonAndereVorgaenge( service.getInwoDBCall(),
+                                                                                            sakId, svgLfdnr );
+        } catch ( SQLException sql ) {
+            sql.printStackTrace();
+            throw new JboException( "Fehler bei Verarbeiten der SQL Abfragen: " + sql.getMessage() );
+        } catch ( JsonProcessingException json ) {
+            json.printStackTrace();
+            throw new JboException( "Fehler beim Erstellen der JSON für den Vergleich: " + json.getMessage() );
+        }
+    }
+}

+ 501 - 0
diwo_code_snippets/model_abfrage_db/VergleichsansichtHhmEinkommenToJsonDbFunctions.java

@@ -0,0 +1,501 @@
+package com.tsystems.diwo.business.queries;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import com.tsystems.common.InwoLogger;
+import com.tsystems.common.model.db.interfaces.IInwoDBCall;
+
+import java.math.BigDecimal;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class VergleichsansichtHhmEinkommenToJsonDbFunctions extends VergleichsansichtBaseDbFunctions {
+
+    private static final InwoLogger LOG =
+        InwoLogger.createInwoLogger( VergleichsansichtHhmEinkommenToJsonDbFunctions.class );
+
+    /* ============================================================
+       ÖFFENTLICHE EINSTIEGE (Root: "stammakte" -> vorgaenge[])
+       ============================================================ */
+
+    public static String buildJsonAktuellerVorgang ( IInwoDBCall dbCall, Integer sakId, Integer lfdnr,
+                                                     Integer konnr ) throws SQLException, JsonProcessingException {
+        Map<String, Object> stammakte = fetchStammakte( dbCall, sakId );
+
+        Map<String, Object> stammvorgang = fetchStammvorgang( dbCall, sakId, lfdnr );
+
+        // Hole bez und wognr aus t_akten
+        Map<String, Integer> aktenData = fetchBezWognrFromAkten( dbCall, sakId );
+
+        // Hole Eingangsdatum für zeitabhängige Lookups
+        Date eingangsdatum = fetchEingangsdatum( dbCall, sakId, lfdnr );
+
+        List<Map<String, Object>> zeitraeume =
+            fetchZeitraeumeUndDetails( dbCall, aktenData.get( "bez" ), aktenData.get( "wognr" ), lfdnr, eingangsdatum,
+                                       /*onlyKonnr*/konnr );
+
+        Map<String, Object> vorgangObj = new LinkedHashMap<>();
+        vorgangObj.put( "stammvorgang", stammvorgang );
+        vorgangObj.put( "zeitraeume", zeitraeume );
+
+        List<Map<String, Object>> vorgaenge = new ArrayList<>();
+        vorgaenge.add( vorgangObj );
+        stammakte.put( "vorgaenge", vorgaenge );
+
+        String result = om.writeValueAsString( Collections.singletonMap( "stammakte", stammakte ) );
+
+        LOG.finest( "Resulting JSON: " + result );
+
+        // Close database call at the very end after all queries are completed
+        dbCall.closeQuery();
+
+        return result;
+    }
+
+    /** Alle anderen Vorgänge (≠ lfdnr) + alle deren Zeiträume -> {"stammakte":{..., "vorgaenge":[{...}, ...]}} */
+    public static String buildJsonAndereVorgaenge ( IInwoDBCall dbCall, Integer sakId,
+                                                    Integer lfdnr ) throws SQLException, JsonProcessingException {
+        Map<String, Object> stammakte = fetchStammakte( dbCall, sakId );
+
+        List<Integer> andere = fetchAndereLfdnr( dbCall, sakId, lfdnr );
+        List<Map<String, Object>> vorgaenge = new ArrayList<>();
+
+        // Hole bez und wognr aus t_akten
+        Map<String, Integer> aktenData = fetchBezWognrFromAkten( dbCall, sakId );
+
+        for ( Integer otherLfdnr : andere ) {
+            Map<String, Object> stammvorgang = fetchStammvorgang( dbCall, sakId, otherLfdnr );
+
+            // Hole Eingangsdatum für zeitabhängige Lookups
+            Date eingangsdatum = fetchEingangsdatum( dbCall, sakId, otherLfdnr );
+
+            List<Map<String, Object>> zeitraeume =
+                fetchZeitraeumeUndDetails( dbCall, aktenData.get( "bez" ), aktenData.get( "wognr" ), otherLfdnr,
+                                           eingangsdatum, /*onlyKonnr*/null );
+
+            Map<String, Object> vorgangObj = new LinkedHashMap<>();
+            vorgangObj.put( "stammvorgang", stammvorgang );
+            vorgangObj.put( "zeitraeume", zeitraeume );
+            vorgaenge.add( vorgangObj );
+        }
+
+        stammakte.put( "vorgaenge", vorgaenge );
+
+        String result = om.writeValueAsString( Collections.singletonMap( "stammakte", stammakte ) );
+
+        LOG.finest( "Resulting JSON: " + result );
+
+        // Close database call at the very end after all queries are completed
+        dbCall.closeQuery();
+
+        return result;
+    }
+
+    /* ===========================
+       Eingangsdatum
+       =========================== */
+
+    private static Date fetchEingangsdatum ( IInwoDBCall dbCall, Integer sakId, Integer lfdnr ) throws SQLException {
+        String q = "SELECT EING_DAT FROM T_STAMMVORGAENGE WHERE SAK_ID = :sak_id AND LFDNR = :lfdnr";
+        dbCall.query( q );
+        dbCall.in( "sak_id", sakId, Types.INTEGER );
+        dbCall.in( "lfdnr", lfdnr, Types.INTEGER );
+
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() )
+            return rs.getDate( "EING_DAT" );
+
+        return null;
+    }
+
+    /* ===========================
+       Zeiträume + Details
+       =========================== */
+
+    /**
+     * Lädt Zeiträume für einen Vorgang.
+     * - onlyKonnr != null  -> nur dieser KONNR (aktueller Vorgang)
+     * - onlyKonnr == null  -> alle Zeiträume (andere Vorgänge)
+     */
+    private static List<Map<String, Object>> fetchZeitraeumeUndDetails ( IInwoDBCall dbCall, Integer bez, Integer wognr,
+                                                                         Integer lfdnr, Date eingangsdatum,
+                                                                         Integer onlyKonnr ) throws SQLException {
+        String base =
+            "SELECT TO_CHAR(k.KONNR) AS zeitraum_id, " + "       TO_CHAR(k.BEGINN_DAT, 'YYYY-MM-DD') AS beginn, " +
+            "       TO_CHAR(k.ENDE_DAT,   'YYYY-MM-DD') AS ende " + "FROM T_KONSTELLATIONEN k " +
+            "WHERE k.AN_WOGNR = :wognr AND k.AN_BEZ = :bez AND k.AN_LFDNR = :lfdnr ";
+        String filter = ( onlyKonnr != null ) ? "AND k.KONNR = :konnr " : "";
+        String order = "ORDER BY k.BEGINN_DAT NULLS FIRST, k.KONNR";
+        String q = base + filter + order;
+
+        dbCall.query( q );
+        dbCall.in( "wognr", wognr, Types.INTEGER );
+        dbCall.in( "bez", bez, Types.INTEGER );
+        dbCall.in( "lfdnr", lfdnr, Types.INTEGER );
+        if ( onlyKonnr != null )
+            dbCall.in( "konnr", onlyKonnr, Types.INTEGER );
+
+        List<Map<String, Object>> out = new ArrayList<>();
+        ResultSet rs = dbCall.executeQuery( true );
+        while ( rs.next() ) {
+            String zeitraumId = rs.getString( "zeitraum_id" );
+            String beginn = rs.getString( "beginn" );
+            String ende = rs.getString( "ende" );
+
+            Map<String, Object> zr = new LinkedHashMap<>();
+            zr.put( "id", zeitraumId );
+            if ( beginn != null )
+                zr.put( "beginn", beginn );
+            if ( ende != null )
+                zr.put( "ende", ende );
+
+            // Personen für diesen Zeitraum laden
+            List<Map<String, Object>> personen =
+                fetchPersonenImZeitraum( dbCall, bez, wognr, lfdnr, Integer.parseInt( zeitraumId ), eingangsdatum );
+            zr.put( "personen", personen );
+
+            out.add( zr );
+        }
+
+        return out;
+    }
+
+    /* ===========================
+       Personen im Zeitraum
+       =========================== */
+
+    private static List<Map<String, Object>> fetchPersonenImZeitraum ( IInwoDBCall dbCall, Integer bez, Integer wognr,
+                                                                       Integer lfdnr, Integer konnr,
+                                                                       Date eingangsdatum ) throws SQLException {
+        // Hole alle BIK_IDs für diesen Zeitraum
+        String q =
+            "SELECT kb.BIK_ID " + "FROM T_KON_BIK kb " + "WHERE kb.KON_AN_WOGNR = :wognr AND kb.KON_AN_BEZ = :bez " +
+            "AND kb.KON_AN_LFDNR = :lfdnr AND kb.KON_KONNR = :konnr";
+
+        dbCall.query( q );
+        dbCall.in( "wognr", wognr, Types.INTEGER );
+        dbCall.in( "bez", bez, Types.INTEGER );
+        dbCall.in( "lfdnr", lfdnr, Types.INTEGER );
+        dbCall.in( "konnr", konnr, Types.INTEGER );
+
+        List<Integer> bikIds = new ArrayList<>();
+        ResultSet rs = dbCall.executeQuery( true );
+        while ( rs.next() ) {
+            bikIds.add( ( ( BigDecimal ) rs.getObject( "BIK_ID" ) ).intValue() );
+        }
+
+        List<Map<String, Object>> personen = new ArrayList<>();
+        for ( Integer bikId : bikIds ) {
+            Map<String, Object> personImZeitraum = fetchPersonImZeitraum( dbCall, bikId, eingangsdatum );
+            if ( personImZeitraum != null ) {
+                personen.add( personImZeitraum );
+            }
+        }
+
+        return personen;
+    }
+
+    private static Map<String, Object> fetchPersonImZeitraum ( IInwoDBCall dbCall, Integer bikId,
+                                                               Date eingangsdatum ) throws SQLException {
+        // Hole Bewohner-ID aus T_BEWOHNER_IN_KONST
+        Integer bewId = fetchBewohnerIdFromBik( dbCall, bikId );
+        if ( bewId == null )
+            return null;
+
+        Map<String, Object> person = fetchPersonBasis( dbCall, bewId, bikId, eingangsdatum );
+        if ( person == null )
+            return null;
+
+        Map<String, Object> personImZeitraum = new LinkedHashMap<>();
+        personImZeitraum.put( "person", person );
+
+        // Einkommen laden
+        List<Map<String, Object>> einkommen = fetchEinkommen( dbCall, bikId );
+        if ( !einkommen.isEmpty() ) {
+            personImZeitraum.put( "einkommen", einkommen );
+        }
+
+        // Freibeträge laden
+        List<Map<String, Object>> freibetraege = fetchFreibetraege( dbCall, bikId );
+        if ( !freibetraege.isEmpty() ) {
+            personImZeitraum.put( "freibetraege", freibetraege );
+        }
+
+        // Kinderbetreuung laden
+        List<Map<String, Object>> kinderbetreuung = fetchKinderbetreuung( dbCall, bikId );
+        if ( !kinderbetreuung.isEmpty() ) {
+            personImZeitraum.put( "kinderbetreuung", kinderbetreuung );
+        }
+
+        return personImZeitraum;
+    }
+
+    private static Integer fetchBewohnerIdFromBik ( IInwoDBCall dbCall, Integer bikId ) throws SQLException {
+        String q = "SELECT BEW_ID FROM T_BEWOHNER_IN_KONST WHERE ID = :bik_id";
+        dbCall.query( q );
+        dbCall.in( "bik_id", bikId, Types.INTEGER );
+
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            return ( ( BigDecimal ) rs.getObject( "BEW_ID" ) ).intValue();
+        }
+
+        return null;
+    }
+
+    /* ===========================
+       PersonBasis
+       =========================== */
+
+    private static Map<String, Object> fetchPersonBasis ( IInwoDBCall dbCall, Integer bewId, Integer bikId,
+                                                          Date eingangsdatum ) throws SQLException {
+        java.sql.Date eingangDat =
+            ( eingangsdatum != null ) ? new java.sql.Date( eingangsdatum.getTime() ) :
+            java.sql
+                                                                                                                  .Date
+                                                                                                                  .valueOf( "2099-12-31" );
+
+        String q =
+            "SELECT " + "    TO_CHAR(b.ID) AS id, " + "    b.NAME AS nachname, " + "    b.VORNAME AS vorname, " +
+            "    b.GEBURTSNAME AS geburtsname, " + "    b.NAME_ALT AS alterName, " + "    b.TITEL AS titel, " +
+            "    b.ANREDE AS anrede_raw, " + "    COALESCE(anr.BESCHREIBUNG, b.ANREDE) AS anrede, " +
+            "    TO_CHAR(b.GEB_DAT, 'YYYY-MM-DD') AS geburtsdatum, " +
+            "    TO_CHAR(b.STERBE_DAT, 'YYYY-MM-DD') AS sterbedatum, " + "    b.GEBURTSORT AS geburtsort, " +
+            "    b.GESCHLECHT AS geschlecht_raw, " + "    COALESCE(gsch.RV_MEANING, b.GESCHLECHT) AS geschlecht, " +
+            "    b.TELEFON AS telefon, " + "    b.EMAIL AS email, " + "    b.BLOCKNAME_JN AS blockname_raw, " +
+            "    b.VSNR AS postabrutionsnummer " + "FROM T_BEWOHNER b " + "LEFT JOIN T_SANREDEN anr " +
+            "       ON anr.WERT = b.ANREDE " + "LEFT JOIN DIWO_REF_CODES gsch " +
+            "       ON gsch.RV_LOW_VALUE = b.GESCHLECHT " + "      AND gsch.RV_DOMAIN = 'DV_GESCHLECHT' " +
+            "WHERE b.ID = :bew_id";
+
+        dbCall.query( q );
+        dbCall.in( "bew_id", bewId, Types.INTEGER );
+
+        Map<String, Object> person = new LinkedHashMap<>();
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            person.put( "id", rs.getString( "id" ) );
+            person.put( "nachname", rs.getString( "nachname" ) );
+            person.put( "vorname", rs.getString( "vorname" ) );
+            putStringIfNotNull( person, "geburtsname", rs.getString( "geburtsname" ) );
+            putStringIfNotNull( person, "alterName", rs.getString( "alterName" ) );
+            putStringIfNotNull( person, "titel", rs.getString( "titel" ) );
+            putStringIfNotNull( person, "anrede", rs.getString( "anrede" ) );
+            putStringIfNotNull( person, "geburtsdatum", rs.getString( "geburtsdatum" ) );
+            putStringIfNotNull( person, "sterbedatum", rs.getString( "sterbedatum" ) );
+            putStringIfNotNull( person, "geburtsort", rs.getString( "geburtsort" ) );
+            putStringIfNotNull( person, "geschlecht", rs.getString( "geschlecht" ) );
+            putStringIfNotNull( person, "telefon", rs.getString( "telefon" ) );
+            putStringIfNotNull( person, "email", rs.getString( "email" ) );
+            putStringIfNotNull( person, "postabrutionsnummer", rs.getString( "postabrutionsnummer" ) );
+            person.put( "blockname", "J".equalsIgnoreCase( rs.getString( "blockname_raw" ) ) );
+        }
+
+        // Zusätzliche Daten aus T_BEWOHNER_IN_KONST
+        fetchPersonBasisZusatz( dbCall, person, bikId, eingangDat );
+
+        return person.isEmpty() ? null : person;
+    }
+
+    private static void fetchPersonBasisZusatz ( IInwoDBCall dbCall, Map<String, Object> person, Integer bikId,
+                                                 java.sql.Date eingangDat ) throws SQLException {
+        String q =
+            "SELECT " + "    bik.AZ_SOZ AS aktenzeichenSozial, " + "    bik.VERWANDT AS verwandt_raw, " +
+            "    COALESCE(verw.BESCHREIBUNG, bik.VERWANDT) AS verwandtschaftsverhaeltnis, " +
+            "    bik.SOZ_STELLUNG AS sozstellung_raw, " +
+            "    COALESCE(sozst.BESCHREIBUNG, bik.SOZ_STELLUNG) AS sozialstellung, " +
+            "    bik.FAMILIENSTAND AS familienstand_raw, " +
+            "    COALESCE(fam.BESCHREIBUNG, bik.FAMILIENSTAND) AS familienstand, " +
+            "    TO_CHAR(bik.ENDE_AUFENTHALTSTITEL, 'YYYY-MM-DD') AS aufenthaltstitelEnde " +
+            "FROM T_BEWOHNER_IN_KONST bik " + "LEFT JOIN T_SVERWANDTSCHAFT verw " +
+            "       ON verw.WERT = bik.VERWANDT " + "      AND :eingang_dat >= verw.AB_DAT " +
+            "      AND :eingang_dat <= NVL(verw.BIS_DAT, DATE '9999-12-31') " + "LEFT JOIN T_SSOZIALSTELLUNGEN sozst " +
+            "       ON sozst.WERT = bik.SOZ_STELLUNG " + "      AND :eingang_dat >= sozst.AB_DAT " +
+            "      AND :eingang_dat <= NVL(sozst.BIS_DAT, DATE '9999-12-31') " + "LEFT JOIN T_SFAMILIENSTAND fam " +
+            "       ON fam.WERT = bik.FAMILIENSTAND " + "      AND :eingang_dat >= fam.AB_DAT " +
+            "      AND :eingang_dat <= NVL(fam.BIS_DAT, DATE '9999-12-31') " + "WHERE bik.ID = :bik_id";
+
+        dbCall.query( q );
+        dbCall.in( "eingang_dat", eingangDat, Types.DATE );
+        dbCall.in( "eingang_dat", eingangDat, Types.DATE );
+        dbCall.in( "eingang_dat", eingangDat, Types.DATE );
+        dbCall.in( "eingang_dat", eingangDat, Types.DATE );
+        dbCall.in( "eingang_dat", eingangDat, Types.DATE );
+        dbCall.in( "eingang_dat", eingangDat, Types.DATE );
+        dbCall.in( "bik_id", bikId, Types.INTEGER );
+
+
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            putStringIfNotNull( person, "aktenzeichenSozial", rs.getString( "aktenzeichenSozial" ) );
+            putStringIfNotNull( person, "verwandtschaftsverhaeltnis", rs.getString( "verwandtschaftsverhaeltnis" ) );
+            putStringIfNotNull( person, "sozialstellung", rs.getString( "sozialstellung" ) );
+            putStringIfNotNull( person, "familienstand", rs.getString( "familienstand" ) );
+            putStringIfNotNull( person, "aufenthaltstitelEnde", rs.getString( "aufenthaltstitelEnde" ) );
+        }
+    }
+
+    /* ===========================
+       Einkommen
+       =========================== */
+
+    private static List<Map<String, Object>> fetchEinkommen ( IInwoDBCall dbCall, Integer bikId ) throws SQLException {
+        String q =
+            "SELECT " + "    TO_CHAR(e.LFDNR) AS id, " + "    e.ART AS art, " + "    e.ART_ZUSATZ AS artZusatz, " +
+            "    e.FREQUENZ AS faktor, " + "    e.BETRAG AS betrag, " + "    e.WERB_KOST AS werbungskosten, " +
+            "    e.ZUSCHLAG AS zuschlag, " + "    e.ABSCHLAG AS abschlag " + "FROM T_EINKOMMEN e " +
+            "WHERE e.BIK_ID = :bik_id " + "ORDER BY e.LFDNR";
+
+        dbCall.query( q );
+        dbCall.in( "bik_id", bikId, Types.INTEGER );
+
+        List<Map<String, Object>> einkommen = new ArrayList<>();
+        ResultSet rs = dbCall.executeQuery( true );
+        while ( rs.next() ) {
+            Map<String, Object> e = new LinkedHashMap<>();
+            e.put( "id", rs.getString( "id" ) );
+
+            String art = rs.getString( "art" );
+            String artBeschreibung = lookupEinkommensartBeschreibung( dbCall, art );
+            e.put( "art", artBeschreibung );
+
+            putStringIfNotNull( e, "artZusatz", rs.getString( "artZusatz" ) );
+            putNumIfNotNull( e, "faktor", rs, "faktor" );
+            putNumIfNotNull( e, "betrag", rs, "betrag" );
+            putNumIfNotNull( e, "werbungskosten", rs, "werbungskosten" );
+            putNumIfNotNull( e, "zuschlag", rs, "zuschlag" );
+            putNumIfNotNull( e, "abschlag", rs, "abschlag" );
+            einkommen.add( e );
+        }
+
+        return einkommen;
+    }
+
+    private static String lookupEinkommensartBeschreibung ( IInwoDBCall dbCall, String art ) throws SQLException {
+        String q = "SELECT BESCHREIBUNG FROM T_SEINKOMMENSARTEN WHERE WERT = :art";
+        dbCall.query( q );
+        dbCall.in( "art", art, Types.VARCHAR );
+
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            return rs.getString( "BESCHREIBUNG" );
+        }
+
+        return art; // Fallback auf ursprünglichen Wert
+    }
+
+    /* ===========================
+       Freibeträge
+       =========================== */
+
+    private static List<Map<String, Object>> fetchFreibetraege ( IInwoDBCall dbCall,
+                                                                 Integer bikId ) throws SQLException {
+        String q =
+            "SELECT " + "    TO_CHAR(f.LFDNR) AS id, " + "    f.ART AS art, " + "    f.FREQUENZ AS faktor, " +
+            "    f.BETRAG AS betrag, " + "    TO_CHAR(f.FRIST_DAT, 'YYYY-MM-DD') AS fristDatum " +
+            "FROM T_FREIBETRAEGE f " + "WHERE f.BIK_ID = :bik_id " + "ORDER BY f.LFDNR";
+
+        dbCall.query( q );
+        dbCall.in( "bik_id", bikId, Types.INTEGER );
+
+        List<Map<String, Object>> freibetraege = new ArrayList<>();
+        ResultSet rs = dbCall.executeQuery( true );
+        while ( rs.next() ) {
+            Map<String, Object> f = new LinkedHashMap<>();
+            f.put( "id", rs.getString( "id" ) );
+            String art = rs.getString( "art" );
+            f.put( "art", lookupFreibetragBeschreibung( dbCall, art ) );
+
+            // Typ aus T_SFREIB_DETAILS ermitteln
+            String typ = lookupFreibetragTyp( dbCall, art );
+            if ( typ != null ) {
+                f.put( "typ", typ );
+            }
+
+            putNumIfNotNull( f, "faktor", rs, "faktor" );
+            putNumIfNotNull( f, "betrag", rs, "betrag" );
+            putStringIfNotNull( f, "fristDatum", rs.getString( "fristDatum" ) );
+            freibetraege.add( f );
+        }
+
+        return freibetraege;
+    }
+
+    private static String lookupFreibetragBeschreibung ( IInwoDBCall dbCall, String art ) throws SQLException {
+        String q = "SELECT BESCHREIBUNG FROM T_SFREIBETRAGSARTEN WHERE WERT = :art";
+        dbCall.query( q );
+        dbCall.in( "art", art, Types.VARCHAR );
+
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            return rs.getString( "BESCHREIBUNG" );
+        }
+
+        return art; // Fallback auf ursprünglichen Wert
+    }
+
+    private static String lookupFreibetragTyp ( IInwoDBCall dbCall, String art ) throws SQLException {
+        String q = "SELECT KENNUNG FROM T_SFREIB_DETAILS WHERE FREIB_ART = :art";
+        dbCall.query( q );
+        dbCall.in( "art", art, Types.VARCHAR );
+
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            String kennung = rs.getString( "KENNUNG" );
+            // Mapping: S = F, A = P
+            if ( "S".equals( kennung ) ) {
+                return "F";
+            } else if ( "A".equals( kennung ) ) {
+                return "P";
+            }
+            return kennung;
+        }
+
+        return null;
+    }
+
+    /* ===========================
+       Kinderbetreuung
+       =========================== */
+
+    private static List<Map<String, Object>> fetchKinderbetreuung ( IInwoDBCall dbCall,
+                                                                    Integer bikId ) throws SQLException {
+        String q =
+            "SELECT " + "    TO_CHAR(ROWNUM) AS id, " + "    kb.BIK_ID_KIND AS kindBikId, " +
+            "    kb.BETRAG AS betrag " + "FROM T_KINDERBETREUUNG kb " + "WHERE kb.BIK_ID = :bik_id " +
+            "ORDER BY kb.BIK_ID_KIND";
+
+        dbCall.query( q );
+        dbCall.in( "bik_id", bikId, Types.INTEGER );
+
+        List<Map<String, Object>> kinderbetreuung = new ArrayList<>();
+        ResultSet rs = dbCall.executeQuery( true );
+        while ( rs.next() ) {
+            Map<String, Object> kb = new LinkedHashMap<>();
+            kb.put( "id", rs.getString( "id" ) );
+
+            // Get the original Bewohner ID from the KIND_BEW_ID (which is a BIK_ID)
+            Integer kindBikId = ( ( BigDecimal ) rs.getObject( "kindBikId" ) ).intValue();
+            Integer kindBewId = null;
+            if ( kindBikId != null ) {
+                kindBewId = fetchBewohnerIdFromBik( dbCall, kindBikId );
+            }
+
+            kb.put( "kindRef", kindBewId != null ? String.valueOf( kindBewId ) : null );
+            putNumIfNotNull( kb, "betrag", rs, "betrag" );
+            kinderbetreuung.add( kb );
+        }
+
+        return kinderbetreuung;
+    }
+
+}

+ 528 - 0
diwo_code_snippets/model_abfrage_db/VergleichsansichtMieteToJsonDbFunctions.java

@@ -0,0 +1,528 @@
+package com.tsystems.diwo.business.queries;
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import com.tsystems.common.InwoLogger;
+import com.tsystems.common.model.db.interfaces.IInwoDBCall;
+
+import java.math.BigDecimal;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import oracle.jbo.JboException;
+
+public class VergleichsansichtMieteToJsonDbFunctions extends VergleichsansichtBaseDbFunctions {
+
+    private static final InwoLogger LOG = InwoLogger.createInwoLogger( VergleichsansichtMieteToJsonDbFunctions.class );
+
+    /* ============================================================
+       ÖFFENTLICHE EINSTIEGE (Root: "stammakte" -> vorgaenge[])
+       ============================================================ */
+
+    public static String buildJsonAktuellerVorgang ( IInwoDBCall dbCall, Integer sakId, Integer lfdnr,
+                                                     Integer konnr ) throws SQLException, JsonProcessingException {
+        Map<String, Object> stammakte = fetchStammakte( dbCall, sakId );
+        if ( stammakte == null ) {
+            stammakte = new LinkedHashMap<>();
+        }
+
+        Map<String, Object> stammvorgang = fetchStammvorgang( dbCall, sakId, lfdnr );
+        if ( stammvorgang == null ) {
+            stammvorgang = new LinkedHashMap<>();
+        }
+
+        Date eingang = fetchEingangsdatum( dbCall, sakId, lfdnr );
+
+        // Hole bez und wognr aus t_akten für fetchRecht
+        Map<String, Integer> aktenData = fetchBezWognrFromAkten( dbCall, sakId );
+        if ( aktenData == null || aktenData.get( "bez" ) == null || aktenData.get( "wognr" ) == null ) {
+            throw new JboException( "Fehler: Konnte BEZ/WOGNR für SAK_ID " + sakId + " nicht ermitteln" );
+        }
+
+        String recht = fetchRecht( dbCall, aktenData.get( "bez" ), aktenData.get( "wognr" ), lfdnr );
+
+        List<Map<String, Object>> zeitraeume =
+            fetchZeitraeumeUndDetails( dbCall, aktenData.get( "bez" ), aktenData.get( "wognr" ), lfdnr, eingang, recht,
+                                       /*onlyKonnr*/konnr );
+        if ( zeitraeume == null ) {
+            zeitraeume = new ArrayList<>();
+        }
+
+        Map<String, Object> vorgangObj = new LinkedHashMap<>();
+        vorgangObj.put( "stammvorgang", stammvorgang );
+        vorgangObj.put( "zeitraeume", zeitraeume );
+
+        List<Map<String, Object>> vorgaenge = new ArrayList<>();
+        vorgaenge.add( vorgangObj );
+        stammakte.put( "vorgaenge", vorgaenge );
+
+        String result = om.writeValueAsString( Collections.singletonMap( "stammakte", stammakte ) );
+        LOG.finest( "Resulting JSON: " + result );
+
+        // Close database call at the very end after all queries are completed
+        dbCall.closeQuery();
+
+        return result;
+    }
+
+    /** Alle anderen Vorgänge (≠ lfdnr) + alle deren Zeiträume -> {"stammakte":{..., "vorgaenge":[{...}, ...]}} */
+    public static String buildJsonAndereVorgaenge ( IInwoDBCall dbCall, Integer sakId,
+                                                    Integer lfdnr ) throws SQLException, JsonProcessingException {
+        Map<String, Object> stammakte = fetchStammakte( dbCall, sakId );
+        if ( stammakte == null ) {
+            stammakte = new LinkedHashMap<>();
+        }
+
+        List<Integer> andere = fetchAndereLfdnr( dbCall, sakId, lfdnr );
+        List<Map<String, Object>> vorgaenge = new ArrayList<>();
+
+        // Hole bez und wognr aus t_akten für fetchRecht
+        Map<String, Integer> aktenData = fetchBezWognrFromAkten( dbCall, sakId );
+        if ( aktenData == null || aktenData.get( "bez" ) == null || aktenData.get( "wognr" ) == null ) {
+            throw new JboException( "Fehler: Konnte BEZ/WOGNR für SAK_ID " + sakId + " nicht ermitteln" );
+        }
+
+        for ( Integer otherLfdnr : andere ) {
+            Map<String, Object> stammvorgang = fetchStammvorgang( dbCall, sakId, otherLfdnr );
+            if ( stammvorgang == null ) {
+                stammvorgang = new LinkedHashMap<>();
+            }
+
+            Date eingang = fetchEingangsdatum( dbCall, sakId, otherLfdnr );
+            String recht = fetchRecht( dbCall, aktenData.get( "bez" ), aktenData.get( "wognr" ), otherLfdnr );
+
+            List<Map<String, Object>> zeitraeume =
+                fetchZeitraeumeUndDetails( dbCall, aktenData.get( "bez" ), aktenData.get( "wognr" ), otherLfdnr,
+                                           eingang, recht, /*onlyKonnr*/null );
+            if ( zeitraeume == null ) {
+                zeitraeume = new ArrayList<>();
+            }
+
+            Map<String, Object> vorgangObj = new LinkedHashMap<>();
+            vorgangObj.put( "stammvorgang", stammvorgang );
+            vorgangObj.put( "zeitraeume", zeitraeume );
+            vorgaenge.add( vorgangObj );
+        }
+
+        stammakte.put( "vorgaenge", vorgaenge );
+        String result = om.writeValueAsString( Collections.singletonMap( "stammakte", stammakte ) );
+
+        LOG.finest( "Resulting JSON: " + result );
+
+        // Close database call at the very end after all queries are completed
+        dbCall.closeQuery();
+
+        return result;
+    }
+
+    /* ===========================
+       Vorgang / Listen / Felder
+       =========================== */
+
+    private static Date fetchEingangsdatum ( IInwoDBCall dbCall, Integer sakId, Integer lfdnr ) throws SQLException {
+        String q = "SELECT EING_DAT FROM T_STAMMVORGAENGE WHERE SAK_ID = :sak_id AND LFDNR = :lfdnr";
+        dbCall.query( q );
+        dbCall.in( "sak_id", sakId, Types.INTEGER );
+        dbCall.in( "lfdnr", lfdnr, Types.INTEGER );
+
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() )
+            return rs.getDate( "EING_DAT" );
+
+        return null;
+    }
+
+    /** Holt RECHT; Fallback auf SGESETZESFASSUNG (String). */
+    private static String fetchRecht ( IInwoDBCall dbCall, Integer bez, Integer wognr,
+                                       Integer lfdnr ) throws SQLException {
+        String q = "SELECT RECHT " + "FROM T_ANTRAEGE WHERE WOGNR = :wognr AND BEZ = :bez AND LFDNR = :lfdnr";
+        dbCall.query( q );
+        dbCall.in( "wognr", wognr, Types.INTEGER );
+        dbCall.in( "bez", bez, Types.INTEGER );
+        dbCall.in( "lfdnr", lfdnr, Types.INTEGER );
+
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            String recht = nullTrim( rs.getString( "RECHT" ) );
+            return recht;
+        }
+
+        return null;
+    }
+
+    /* ===========================
+       Zeiträume + Details
+       =========================== */
+
+    /**
+     * Lädt Zeiträume für einen Vorgang.
+     * - onlyKonnr != null  -> nur dieser KONNR (aktueller Vorgang)
+     * - onlyKonnr == null  -> alle Zeiträume (andere Vorgänge)
+     */
+    private static List<Map<String, Object>> fetchZeitraeumeUndDetails ( IInwoDBCall dbCall, Integer bez, Integer wognr,
+                                                                         Integer lfdnr, Date eingang, String recht,
+                                                                         Integer onlyKonnr ) throws SQLException {
+        LOG.finest( "fetchZeitraeumeUndDetails BEZ: " + bez + " - WOGNR: " + wognr + " - LFDNR: " + lfdnr +
+                    " - onlyKonnr: " + onlyKonnr );
+
+        String base =
+            "SELECT TO_CHAR(k.KONNR) AS zeitraum_id, " + "       TO_CHAR(k.BEGINN_DAT, 'YYYY-MM-DD') AS beginn, " +
+            "       TO_CHAR(k.ENDE_DAT,   'YYYY-MM-DD') AS ende, " +
+            "       k.MIETE AS miete_ref_id, k.AUSSTATTUNG AS ausstattung_ref_id " + "FROM T_KONSTELLATIONEN k " +
+            "WHERE k.AN_WOGNR = :wognr AND k.AN_BEZ = :bez AND k.AN_LFDNR = :lfdnr ";
+        String filter = ( onlyKonnr != null ) ? "AND k.KONNR = :konnr " : "";
+        String order = "ORDER BY k.BEGINN_DAT NULLS FIRST, k.KONNR";
+        String q = base + filter + order;
+
+        LOG.finest( "SQL Query: " + q );
+        LOG.finest( "Parameters - bez: " + bez + ", wognr: " + wognr + ", lfdnr: " + lfdnr + ", konnr: " + onlyKonnr );
+
+        dbCall.query( q );
+        dbCall.in( "wognr", wognr, Types.INTEGER );
+        dbCall.in( "bez", bez, Types.INTEGER );
+        dbCall.in( "lfdnr", lfdnr, Types.INTEGER );
+        if ( onlyKonnr != null )
+            dbCall.in( "konnr", onlyKonnr, Types.INTEGER );
+
+        List<Map<String, Object>> out = new ArrayList<>();
+        ResultSet rs = dbCall.executeQuery( true );
+        LOG.finest( "SQL executed, checking for results..." );
+        int rowCount = 0;
+        while ( rs.next() ) {
+            rowCount++;
+            LOG.finest( "Processing row " + rowCount );
+            String zeitraumId = rs.getString( "zeitraum_id" );
+            String beginn = rs.getString( "beginn" );
+            String ende = rs.getString( "ende" );
+            Integer mieteId = ( ( BigDecimal ) rs.getObject( "miete_ref_id" ) ).intValue();
+            Integer ausstId = ( ( BigDecimal ) rs.getObject( "ausstattung_ref_id" ) ).intValue();
+
+            Map<String, Object> zr = new LinkedHashMap<>();
+            zr.put( "id", zeitraumId );
+            if ( beginn != null )
+                zr.put( "beginn", beginn );
+            if ( ende != null )
+                zr.put( "ende", ende );
+
+            Map<String, Object> miete = fetchMiete( dbCall, mieteId, recht );
+            if ( miete == null ) {
+                miete = new LinkedHashMap<>();
+            }
+
+            List<Map<String, Object>> untermieter = fetchUntermieter( dbCall, mieteId, recht );
+            if ( untermieter == null ) {
+                untermieter = new ArrayList<>();
+            }
+            miete.put( "untermieter", untermieter );
+
+            List<Map<String, Object>> mitbewohner = fetchMitbewohner( dbCall, mieteId, recht );
+            if ( mitbewohner == null ) {
+                mitbewohner = new ArrayList<>();
+            }
+            miete.put( "mitbewohner", mitbewohner );
+            zr.put( "miete", miete );
+
+            Map<String, Object> ausstattung = fetchAusstattung( dbCall, ausstId, eingang );
+            if ( ausstattung == null ) {
+                ausstattung = new LinkedHashMap<>();
+            }
+            zr.put( "ausstattung", ausstattung );
+
+            out.add( zr );
+        }
+
+        LOG.finest( "Returning " + out.size() + " zeitraeume" );
+        return out;
+    }
+
+    /* ===================== Miete inkl. Pauschalen ===================== */
+
+    private static Map<String, Object> fetchMiete ( IInwoDBCall dbCall, Integer mieteIdOrNull,
+                                                    String recht ) throws SQLException {
+        String q =
+            "SELECT " + "  TO_CHAR(m.ID) AS id, " +
+            "  m.MIETE AS miete, m.MIETE_ALT AS mieteAlt, m.SONST_ZUSCHLAG AS sonstZuschlag, " +
+            "  m.BEITRAG_DRITTER AS beitragDritter, " + "  m.HEIZUNG AS heizung, m.HEIZUNG_P AS heizungP_raw, " +
+            "  m.WARMWASSER AS warmwasser, m.WARMWASSER_P AS warmwasserP_raw, " +
+            "  m.MOEBLIERUNG AS moeblierung, m.MOEBLIERUNG_P AS moeblierungP_raw, " +
+            "  m.UMIET_ZUSCHLAG AS umietZuschlag, " +
+            "  m.GEWERBE_ZUSCHLAG AS gewerbeZuschlag, m.GEWERBE_ZUSCHLAG_P AS gewerbeZuschlagP_raw, " +
+            "  m.GEWERBE_FLAECHE AS gewerbeFlaeche, " +
+            "  m.JHRL_FERNWARMW AS jhrlFernwarmw, m.JHRL_FERNWAERME AS jhrlFernwaerme, " +
+            "  m.ANT_MIET_WW AS antMietWw, m.ANT_BRUT_WW AS antBrutWw, m.JHRL_FERN_W_UND_W AS jhrlFernWUndW, " +
+            "  m.LSTG_AUFENTHALT_PERSON AS lstgAufenthaltPerson, " +
+            "  m.SONST_WOG_FAEHIG_BETRAG AS sonstWogFaehigBetrag, m.SONST_WOG_FAEHIG_TEXT AS sonstWogFaehigText, " +
+            "  m.SONST_ZUSCHLAG_BESCHR AS sonstZuschlagBeschr, " +
+            "  m.ENERGIE AS energie, m.ENERGIE_P AS energieP_raw, " +
+            "  m.STELLPLATZ AS stellplatz, m.STELLPLATZ_P AS stellplatzP_raw " +
+            "FROM T_MIETEN m WHERE m.ID = COALESCE(:miete_id, 1)";
+        dbCall.query( q );
+        dbCall.in( "miete_id", mieteIdOrNull, Types.INTEGER );
+
+        Map<String, Object> out = new LinkedHashMap<>();
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            out.put( "id", "1" ); // Always set miete ID to "1" in JSON output
+            putNum( out, "miete", rs, "miete" );
+            putNum( out, "mieteAlt", rs, "mieteAlt" );
+            putNum( out, "sonstZuschlag", rs, "sonstZuschlag" );
+            putNum( out, "beitragDritter", rs, "beitragDritter" );
+            putNum( out, "heizung", rs, "heizung" );
+
+            // Pauschalen-Text nur, wenn *_P nicht NULL und Lookup Treffer:
+            out.put( "heizungP",
+                     resolvePauschaleOrNull( dbCall, rs.getString( "heizungP_raw" ), "HEIZKOSTENPAUSCHALE", recht ) );
+            putNum( out, "warmwasser", rs, "warmwasser" );
+            out.put( "warmwasserP",
+                     resolvePauschaleOrNull( dbCall, rs.getString( "warmwasserP_raw" ), "WARMWASSERPAUSCHALE",
+                                             recht ) );
+            putNum( out, "moeblierung", rs, "moeblierung" );
+            out.put( "moeblierungP",
+                     resolvePauschaleOrNull( dbCall, rs.getString( "moeblierungP_raw" ), "MOEBLIERUNGSPAUSCHALE",
+                                             recht ) );
+
+            putNum( out, "umietZuschlag", rs, "umietZuschlag" );
+            putNum( out, "gewerbeZuschlag", rs, "gewerbeZuschlag" );
+            out.put( "gewerbeZuschlagP",
+                     resolvePauschaleOrNull( dbCall, rs.getString( "gewerbeZuschlagP_raw" ), "GEWERBEPAUSCHALE",
+                                             recht ) );
+
+            putNum( out, "gewerbeFlaeche", rs, "gewerbeFlaeche" );
+            putNum( out, "jhrlFernwarmw", rs, "jhrlFernwarmw" );
+            putNum( out, "jhrlFernwaerme", rs, "jhrlFernwaerme" );
+            putNum( out, "antMietWw", rs, "antMietWw" );
+            putNum( out, "antBrutWw", rs, "antBrutWw" );
+            putNum( out, "jhrlFernWUndW", rs, "jhrlFernWUndW" );
+            putNum( out, "lstgAufenthaltPerson", rs, "lstgAufenthaltPerson" );
+            putNum( out, "sonstWogFaehigBetrag", rs, "sonstWogFaehigBetrag" );
+
+            // Ensure string fields are never null
+            String sonstWogFaehigText = rs.getString( "sonstWogFaehigText" );
+            out.put( "sonstWogFaehigText", sonstWogFaehigText != null ? sonstWogFaehigText : "" );
+
+            String sonstZuschlagBeschr = rs.getString( "sonstZuschlagBeschr" );
+            out.put( "sonstZuschlagBeschr", sonstZuschlagBeschr != null ? sonstZuschlagBeschr : "" );
+
+            putNum( out, "energie", rs, "energie" );
+            out.put( "energieP",
+                     resolvePauschaleOrNull( dbCall, rs.getString( "energieP_raw" ), "ENERGIEPAUSCHALE", recht ) );
+
+            putNum( out, "stellplatz", rs, "stellplatz" );
+
+            out.put( "stellplatzP",
+                     resolvePauschaleOrNull( dbCall, rs.getString( "stellplatzP_raw" ), "STELLPLATZPAUSCHALE",
+                                             recht ) );
+        }
+
+        return out;
+    }
+
+    private static String resolvePauschaleOrNull ( IInwoDBCall dbCall, String rawValue, String art,
+                                                   String recht ) throws SQLException {
+        if ( rawValue == null )
+            return null;
+        if ( recht == null || recht.isEmpty() )
+            return null;
+        return lookupPauschaleBeschreibung( dbCall, rawValue, art, recht ); // null, wenn kein Treffer
+    }
+
+    private static String lookupPauschaleBeschreibung ( IInwoDBCall dbCall, String kuerzel, String art,
+                                                        String recht ) throws SQLException {
+        String q = "SELECT BESCHREIBUNG FROM T_PAUSCHALE WHERE KUERZEL = :kuerzel AND ART = :art AND RECHT = :recht";
+        dbCall.query( q );
+        dbCall.in( "kuerzel", kuerzel, Types.VARCHAR );
+        dbCall.in( "art", art, Types.VARCHAR );
+        dbCall.in( "recht", recht, Types.VARCHAR );
+
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() )
+            return rs.getString( "BESCHREIBUNG" );
+
+        return null;
+    }
+
+    /* ===================== Untermieter / Mitbewohner ===================== */
+
+    private static List<Map<String, Object>> fetchUntermieter ( IInwoDBCall dbCall, Integer mieteIdOrNull,
+                                                                String recht ) throws SQLException {
+        String q =
+            "SELECT TO_CHAR(u.MI_ID) AS miId, u.LFDNR, u.NAME, u.VORNAME, u.FLAECHE, u.MIETE, u.HEIZUNG, " +
+            "       u.HEIZUNG_P AS heizungP_raw, u.WARMWASSER, u.WARMWASSER_P AS warmwasserP_raw, " +
+            "       u.MOEBLIERUNG, u.MOEBLIERUNG_P AS moeblierungP_raw, u.SONST " +
+            "FROM T_UNTERMIETEN u WHERE u.MI_ID = COALESCE(:miete_id, 1) ORDER BY u.LFDNR";
+        dbCall.query( q );
+        dbCall.in( "miete_id", mieteIdOrNull, Types.INTEGER );
+
+        List<Map<String, Object>> out = new ArrayList<>();
+        ResultSet rs = dbCall.executeQuery( true );
+        while ( rs.next() ) {
+            Map<String, Object> row = new LinkedHashMap<>();
+            row.put( "miId", rs.getString( "miId" ) );
+            row.put( "lfdnr", ( ( BigDecimal ) rs.getObject( "LFDNR" ) ).intValue() );
+
+            String name = rs.getString( "NAME" );
+            row.put( "name", name != null ? name : "" );
+
+            String vorname = rs.getString( "VORNAME" );
+            row.put( "vorname", vorname != null ? vorname : "" );
+
+            putNum( row, "flaeche", rs, "FLAECHE" );
+            putNum( row, "miete", rs, "MIETE" );
+            putNum( row, "heizung", rs, "HEIZUNG" );
+
+            // Pauschalen-Text nur, wenn *_P nicht NULL und Lookup Treffer:
+            row.put( "heizungP",
+                     resolvePauschaleOrNull( dbCall, rs.getString( "heizungP_raw" ), "HEIZKOSTENPAUSCHALE", recht ) );
+
+            putNum( row, "warmwasser", rs, "WARMWASSER" );
+
+            row.put( "warmwasserP",
+                     resolvePauschaleOrNull( dbCall, rs.getString( "warmwasserP_raw" ), "WARMWASSERPAUSCHALE",
+                                             recht ) );
+
+            putNum( row, "moeblierung", rs, "MOEBLIERUNG" );
+
+            row.put( "moeblierungP",
+                     resolvePauschaleOrNull( dbCall, rs.getString( "moeblierungP_raw" ), "MOEBLIERUNGSPAUSCHALE",
+                                             recht ) );
+
+            putNum( row, "sonst", rs, "SONST" );
+            out.add( row );
+        }
+
+        return out;
+    }
+
+    private static List<Map<String, Object>> fetchMitbewohner ( IInwoDBCall dbCall,
+                                                                Integer mieteIdOrNull, String recht ) throws SQLException {
+        String q =
+            "SELECT TO_CHAR(mb.MI_ID) AS miId, mb.LFDNR, mb.NAME, mb.VORNAME, mb.ENTGELT, mb.HEIZUNG, " +
+            "       mb.HEIZUNG_P AS heizungP_raw, mb.WARMWASSER, mb.WARMWASSER_P AS warmwasserP_raw, mb.SONST " +
+            "FROM T_MITBEWOHNER mb WHERE mb.MI_ID = COALESCE(:miete_id, 1) ORDER BY mb.LFDNR";
+        dbCall.query( q );
+        dbCall.in( "miete_id", mieteIdOrNull, Types.INTEGER );
+
+        List<Map<String, Object>> out = new ArrayList<>();
+        ResultSet rs = dbCall.executeQuery( true );
+        while ( rs.next() ) {
+            Map<String, Object> row = new LinkedHashMap<>();
+            row.put( "miId", rs.getString( "miId" ) );
+            row.put( "lfdnr", ( ( BigDecimal ) rs.getObject( "LFDNR" ) ).intValue() );
+
+            String name = rs.getString( "NAME" );
+            row.put( "name", name != null ? name : "" );
+
+            String vorname = rs.getString( "VORNAME" );
+            row.put( "vorname", vorname != null ? vorname : "" );
+
+            putNum( row, "entgelt", rs, "ENTGELT" );
+            putNum( row, "heizung", rs, "HEIZUNG" );
+
+            // Pauschalen-Text nur, wenn *_P nicht NULL und Lookup Treffer:
+            row.put( "heizungP",
+                     resolvePauschaleOrNull( dbCall, rs.getString( "heizungP_raw" ), "HEIZKOSTENPAUSCHALE", recht ) );
+
+            putNum( row, "warmwasser", rs, "WARMWASSER" );
+
+            row.put( "warmwasserP",
+                     resolvePauschaleOrNull( dbCall, rs.getString( "warmwasserP_raw" ), "WARMWASSERPAUSCHALE", recht ) );
+
+            putNum( row, "sonst", rs, "SONST" );
+            out.add( row );
+        }
+
+        return out;
+    }
+
+    /* ===================== Ausstattung (mit S-Tabellen) ===================== */
+
+    private static Map<String, Object> fetchAusstattung ( IInwoDBCall dbCall, Integer ausstattungId,
+                                                          Date eingangsdatum ) throws SQLException {
+        java.sql.Date ed =
+            ( eingangsdatum != null ) ? new java.sql.Date( eingangsdatum.getTime() ) :
+            java.sql
+                                                                                                          .Date
+                                                                                                          .valueOf( "2099-12-31" );
+
+        String q =
+            "SELECT " + "    TO_CHAR(a.ID) AS id, " + "    a.FLAECHE AS flaeche, " +
+            "    COALESCE(heiz.BESCHREIBUNG, a.HEIZUNG) AS heizung, " +
+            "    COALESCE(badart.BESCHREIBUNG, a.BAD) AS bad, " + "    a.SOZBAU_JN AS sozBauJn_raw, " +
+            "    COALESCE(mietv.BESCHREIBUNG, a.MIETVERHAELTNIS) AS mietverhaeltnis, " +
+            "    COALESCE(eigent.BESCHREIBUNG, a.EIGENTUM) AS eigentum, " +
+            "    TO_CHAR(a.FERTIG_DAT, 'YYYY-MM-DD') AS fertigDat, " +
+            "    a.NAME_EIG AS nameEig, a.ADRESSE_EIG AS adresseEig, a.TELEFON_EIG AS telefonEig, " +
+            "    a.WOFG_JN AS wofgJn_raw, a.FOERDERUNG_JN AS foerderungJn_raw " + "FROM T_AUSSTATTUNGEN a " +
+            "LEFT JOIN T_SHEIZUNGSARTEN heiz " + "       ON heiz.WERT = a.HEIZUNG " +
+            "      AND :eingang_dat >= heiz.AB_DAT " +
+            "      AND :eingang_dat <= NVL(heiz.BIS_DAT, DATE '9999-12-31') " + "LEFT JOIN T_SBADARTEN badart " +
+            "       ON badart.WERT = a.BAD " + "      AND :eingang_dat >= badart.AB_DAT " +
+            "      AND :eingang_dat <= NVL(badart.BIS_DAT, DATE '9999-12-31') " +
+            "LEFT JOIN T_SMIETVERHAELTNISSE mietv " + "       ON mietv.WERT = a.MIETVERHAELTNIS " +
+            "      AND :eingang_dat >= mietv.AB_DAT " +
+            "      AND :eingang_dat <= NVL(mietv.BIS_DAT, DATE '9999-12-31') " + "LEFT JOIN T_SEIGENTUMSARTEN eigent " +
+            "       ON eigent.WERT = a.EIGENTUM " + "      AND :eingang_dat >= eigent.AB_DAT " +
+            "      AND :eingang_dat <= NVL(eigent.BIS_DAT, DATE '9999-12-31') " + "WHERE a.ID = :ausstattung_id";
+
+        dbCall.query( q );
+        dbCall.in( "eingang_dat", ed, Types.DATE );
+        dbCall.in( "eingang_dat", ed, Types.DATE );
+        dbCall.in( "eingang_dat", ed, Types.DATE );
+        dbCall.in( "eingang_dat", ed, Types.DATE );
+        dbCall.in( "eingang_dat", ed, Types.DATE );
+        dbCall.in( "eingang_dat", ed, Types.DATE );
+        dbCall.in( "eingang_dat", ed, Types.DATE );
+        dbCall.in( "eingang_dat", ed, Types.DATE );
+        dbCall.in( "ausstattung_id", ausstattungId, Types.INTEGER );
+
+        Map<String, Object> out = new LinkedHashMap<>();
+        ResultSet rs = dbCall.executeQuery( true );
+        if ( rs.next() ) {
+            out.put( "id", "1" ); // Always set ausstattung ID to "1" in JSON output
+            putNum( out, "flaeche", rs, "flaeche" );
+
+            String heizung = rs.getString( "heizung" );
+            out.put( "heizung", heizung != null ? heizung : "" );
+
+            String bad = rs.getString( "bad" );
+            out.put( "bad", bad != null ? bad : "" );
+
+            out.put( "sozBauJn", "J".equalsIgnoreCase( rs.getString( "sozBauJn_raw" ) ) );
+
+            String mietverhaeltnis = rs.getString( "mietverhaeltnis" );
+            out.put( "mietverhaeltnis", mietverhaeltnis != null ? mietverhaeltnis : "" );
+
+            String eigentum = rs.getString( "eigentum" );
+            out.put( "eigentum", eigentum != null ? eigentum : "" );
+
+            String fertig = rs.getString( "fertigDat" );
+            if ( fertig != null )
+                out.put( "fertigDat", fertig );
+
+            String nameEig = rs.getString( "nameEig" );
+            out.put( "nameEig", nameEig != null ? nameEig : "" );
+
+            String adresseEig = rs.getString( "adresseEig" );
+            out.put( "adresseEig", adresseEig != null ? adresseEig : "" );
+
+            String telefonEig = rs.getString( "telefonEig" );
+            out.put( "telefonEig", telefonEig != null ? telefonEig : "" );
+
+            out.put( "wofgJn", "J".equalsIgnoreCase( rs.getString( "wofgJn_raw" ) ) );
+            out.put( "foerderungJn", "J".equalsIgnoreCase( rs.getString( "foerderungJn_raw" ) ) );
+        }
+
+        return out;
+    }
+
+}

+ 130 - 0
diwo_code_snippets/view_js/inwo-vgl.js

@@ -0,0 +1,130 @@
+// Verbesserte Popup-Funktionen für externe Anwendungen
+// Diese Datei kann in externe Anwendungen eingebunden werden
+
+var popupWin = null;
+
+function forcePostToPopup(url, params) {
+    console.log("forcePostToPopup aufgerufen mit URL:", url);
+    console.log("Parameter:", params);
+    
+    // Popup erstellen oder wiederverwenden
+    var needNewPopup = true;
+    if (popupWin) {
+        try {
+            // Try to access the closed property - this might fail due to cross-origin restrictions
+            needNewPopup = popupWin.closed;
+            console.log("Popup Status - geschlossen:", needNewPopup);
+        }
+        catch (e) {
+            console.log("Cross-origin access to popup window, assuming it's still open:", e);
+            needNewPopup = false;
+        }
+    }
+
+    if (needNewPopup) {
+        console.log("Creating new popup window");
+        // WICHTIG: Mit about:blank starten für bessere Kompatibilität
+        popupWin = window.open("about:blank", "vglPopup", "width=1920,height=1000,scrollbars=yes,resizable=yes");
+
+        if (!popupWin) {
+            alert("Popup konnte nicht geöffnet werden. Bitte überprüfen Sie Ihre Popup-Blocker-Einstellungen.");
+            return;
+        }
+
+        // Minimal-HTML schreiben, damit das Popup einen korrekten Document-Context hat
+        try {
+            popupWin.document.open();
+            popupWin.document.write(`
+                <!DOCTYPE html>
+                <html>
+                <head>
+                    <meta charset="UTF-8">
+                    <title>Lade DIWO Vergleich...</title>
+                    <style>
+                        body { 
+                            font-family: Arial, sans-serif; 
+                            text-align: center; 
+                            padding: 50px; 
+                            background: #f5f5f5;
+                        }
+                        .loader {
+                            border: 4px solid #f3f3f3;
+                            border-top: 4px solid #3498db;
+                            border-radius: 50%;
+                            width: 40px;
+                            height: 40px;
+                            animation: spin 2s linear infinite;
+                            margin: 20px auto;
+                        }
+                        @keyframes spin {
+                            0% { transform: rotate(0deg); }
+                            100% { transform: rotate(360deg); }
+                        }
+                    </style>
+                </head>
+                <body>
+                    <h2>DIWO Vergleich wird geladen...</h2>
+                    <div class="loader"></div>
+                    <p>Bitte warten Sie, die Vergleichsdaten werden übertragen.</p>
+                </body>
+                </html>
+            `);
+            popupWin.document.close();
+        }
+        catch (e) {
+            console.log("Konnte nicht in Popup schreiben:", e);
+        }
+    }
+    else {
+        console.log("Reusing existing popup window");
+    }
+
+    // Formular bauen - WICHTIG: Target muss mit Popup-Name übereinstimmen!
+    var form = document.createElement("form");
+    form.id = "vglPostForm";
+    form.method = "post";
+    form.action = url;
+    form.target = "vglPopup"; // Muss mit dem Namen des Popups übereinstimmen!
+    form.style.display = "none"; // Formular unsichtbar machen
+
+    // Parameter als Hidden-Fields hinzufügen
+    for (var key in params) {
+        if (params.hasOwnProperty(key)) {
+            var input = document.createElement("input");
+            input.type = "hidden";
+            input.name = key;
+            input.value = params[key] == null ? "" : params[key];
+            form.appendChild(input);
+            console.log("Parameter hinzugefügt - " + key + ": " + (params[key] == null ? "(leer)" : params[key]));
+        }
+    }
+
+    // Formular temporär zum DOM hinzufügen und absenden
+    document.body.appendChild(form);
+    
+    console.log("📤 Formular wird abgesendet...");
+    form.submit();
+    
+    // Formular wieder entfernen
+    document.body.removeChild(form);
+
+    // Fokus setzen und Popup in den Vordergrund bringen
+    try {
+        popupWin.focus();
+        
+        // Zusätzlicher Versuch nach kurzer Verzögerung
+        setTimeout(function() {
+            try {
+                popupWin.focus();
+            } catch (e) {
+                console.log("Konnte Popup nicht fokussieren (verzögert):", e);
+            }
+        }, 500);
+        
+    } catch (e) {
+        console.log("Konnte Popup nicht fokussieren:", e);
+    }
+    
+    console.log("Popup-Übertragung abgeschlossen");
+}
+