anzahl, wenn erst der Hersteller geklaert werden muss $v2_marke = isset($_GET['marke']) ? trim($_GET['marke']) : ''; // gewaehlter Hersteller $v2_variants = []; // BSH-Bauzustaende /01 /02 ..., wenn nur der Zeitraum unterscheidet $v2_variant_base = ''; // gemeinsame Typnummer ohne Suffix, z.B. "WT46W560BY" $v2_shopmap = []; // navision_id => Shop-Artikel, fuer die Links an den Chips try { // MySQL wird in beiden Fällen gebraucht (rechte Spalte = Artikel-Suche) $db = new KremplDB(true); if ($search_term !== '') { // Links: Geräte-Suche (PostgreSQL) $start_time = microtime(true); $results = $db->searchGeraete($search_term, 20); if ($show_shop_items) { foreach ($results as &$device) { $device['shop_items'] = $db->getAvailableItemsForGeraet($device['geraet_id'], 200); } unset($device); } $search_time = (microtime(true) - $start_time) * 1000; // Rechts: Artikel-/Ersatzteil-Suche (MySQL) $start_time2 = microtime(true); $item_results = $db->searchItems($search_term, 20); $item_search_time = (microtime(true) - $start_time2) * 1000; // --- Hersteller-Filter (kommt aus der Hersteller-Rueckfrage) --- if ($v2_marke !== '') { $results = array_values(array_filter($results, function ($d) use ($v2_marke) { return isset($d['marke']) && trim($d['marke']) === $v2_marke; })); } // --- Stufe 1: muss erst der Hersteller geklaert werden? --- // Dasselbe Modell laeuft oft unter mehreren Konzern-/Versandhausmarken. Dann haben // alle Top-Treffer denselben Score und denselben Modellnamen -- eine Bestaetigung // waere geraten. Der Hersteller steht dem Kunden aber auf dem Typenschild zur // Verfuegung, also fragen wir zuerst danach. Beispiel MUFF21VLQS: 10x Score 100 // ueber AEG, Electrolux, Frigidaire, Gibson. if (count($results) > 1) { $top_score = (int)$results[0]['score']; $topgroup = array_filter($results, function ($d) use ($top_score) { return (int)$d['score'] === $top_score; }); $brands = []; $models = []; foreach ($topgroup as $d) { $m = trim($d['marke'] ?? ''); if ($m !== '') { $brands[$m] = ($brands[$m] ?? 0) + 1; } $models[mb_strtolower(trim($d['modell_bezeichnung'] ?? ''))] = true; } // Nur fragen, wenn es wirklich EIN Modell bei MEHREREN Marken ist. if (count($brands) > 1 && count($models) === 1) { arsort($brands); $v2_brandpick = $brands; } // --- Stufe 1b: Varianten-Auswahl (BSH-Bauzustaende) --- // SIEMENS/BOSCH haengen "/01", "/02" ... an die Typnummer in nr. Laesst der Kunde // das Suffix weg ("WT46W560BY"), kommen alle Bauzustaende mit gleichem Score // zurueck und unterscheiden sich NUR im Produktionszeitraum. Dann fragen wir // danach, statt zehn optisch identische Zeilen zu zeigen. if (!$v2_brandpick) { $basis = []; foreach ($topgroup as $d) { $n = trim($d['nr'] ?? ''); $basis[($p = strpos($n, '/')) !== false ? substr($n, 0, $p) : $n] = true; } if (count($basis) === 1 && count($topgroup) > 1 && count($brands) <= 1) { $v2_variant_base = (string)array_key_first($basis); // Nur wenn die Treffer wirklich Suffix-Varianten sind, nicht zufaellig // gleichnamige Geraete: mindestens einer muss ein "/" tragen. $has_suffix = false; foreach ($topgroup as $d) { if (strpos((string)($d['nr'] ?? ''), '/') !== false) { $has_suffix = true; break; } } if ($has_suffix) { $det_batch = $db->getGeraetDetailsBatch(array_column($topgroup, 'geraet_id')); foreach ($topgroup as $d) { $det = $det_batch[$d['geraet_id']] ?? []; $v2_variants[] = [ 'nr' => (string)($d['nr'] ?? ''), 'id' => $d['geraet_id'], 'von' => $det['produktionsstart'] ?? '', 'bis' => $det['produktionsende'] ?? '', 'typ_de'=> $det['typ_de'] ?? '', 'marke' => $d['marke'] ?? '', ]; } usort($v2_variants, function ($a, $b) { return strcmp($a['nr'], $b['nr']); }); } } } } // --- Stufe 2: gibt es EINEN klaren Top-Treffer? --- // Nur dann darf man "Das ist dein Geraet" behaupten. Bedingung: Top-Score >= 95 // UND mindestens 10 Punkte Abstand zum Zweiten. Beispiel "Lavatherm 400": 100 vs. 75. // Entfaellt, solange die Hersteller-Frage offen ist. if (!$v2_brandpick && !$v2_variants && count($results) > 0) { $v2_top_score_raw = (int)$results[0]['score']; $v2_second_raw = isset($results[1]) ? (int)$results[1]['score'] : 0; if ($v2_top_score_raw >= 95 && ($v2_top_score_raw - $v2_second_raw) >= 10) { $v2_guided = true; $v2_top_detail = $db->getGeraetDetails($results[0]['geraet_id']); } } // --- Shop-Links fuer die Ersatzteil-Chips --- // Die Chips kommen aus PostgreSQL und kennen nur navision_id. Damit sie -- wie die // Artikel weiter unten -- in den Shop verlinken, wird einmalig gesammelt aufgeloest. // Nur die tatsaechlich gerenderten (max. 10 je Geraet), sonst waeren es Tausende. if ($results) { $nav_ids = []; foreach ($results as $d) { foreach (array_slice($d['ersatzteile'] ?? [], 0, 10) as $p) { if (!empty($p['navision_id'])) { $nav_ids[] = $p['navision_id']; } } } if ($nav_ids) { $v2_shopmap = $db->getShopItemsByNavisionIds($nav_ids); } } } } catch (Exception $e) { $error_message = $e->getMessage(); } ?> Krempl PostgreSQL Search Test

Krempl PostgreSQL Search Test

Fehler bei der Datenbankverbindung:
Bauzustand -> Bestaetigung. // ================================================================== if (!function_exists('v2_confidence')) { /** * Anzeigename eines Geraets. modell_bezeichnung ist bei 490.522 von 1.675.828 * Geraeten LEER (29%) -- vor allem bei BSH (SIEMENS/BOSCH), wo die Typnummer * stattdessen in nr steht (z.B. "WT46W560BY/01"). Ohne Fallback rendern die * Kacheln komplett namenlos. */ function v2_gname($d) { foreach (['modell_bezeichnung', 'bezeichnung_original', 'nr'] as $k) { if (isset($d[$k]) && trim((string)$d[$k]) !== '') return trim($d[$k]); } return '(ohne Bezeichnung)'; } /** * Produktionsdatum -> Jahr. In produktionsstart/-ende stecken ZWEI Formate: * YYYYMMDD z.B. "19790101" (AEG Lavatherm 400) * YYMM z.B. "8810" (SIEMENS WT46W560BY/01 = Oktober 1988) * Bei YYMM gilt: >= 70 -> 19YY, sonst 20YY. */ function v2_jahr4($v) { $s = preg_replace('/\D/', '', (string)$v); if ($s === '') return ''; if (strlen($s) >= 6) return substr($s, 0, 4); // YYYYMM(DD) if (strlen($s) === 4) { // YYMM $yy = (int)substr($s, 0, 2); return (string)($yy >= 70 ? 1900 + $yy : 2000 + $yy); } if (strlen($s) === 2) { // nur YY $yy = (int)$s; return (string)($yy >= 70 ? 1900 + $yy : 2000 + $yy); } return ''; } /** Monat/Jahr fuer die Variantenliste, z.B. "10/1988". Leer, wenn nicht ableitbar. */ function v2_monat($v) { $s = preg_replace('/\D/', '', (string)$v); $j = v2_jahr4($s); if ($j === '') return ''; if (strlen($s) >= 6) $m = substr($s, 4, 2); // YYYYMM(DD) elseif (strlen($s) === 4) $m = substr($s, 2, 2); // YYMM else return $j; $m = (int)$m; return ($m >= 1 && $m <= 12) ? sprintf('%02d/%s', $m, $j) : $j; } /** Shop-URL zu einer items.id -- leer, wenn kein Shop-Artikel bekannt ist. */ function v2_shopurl($item_id) { return $item_id ? 'https://intelectra.de/index.php?item_id=' . (int)$item_id : ''; } /** * Chip fuer ein Ersatzteil/Artikel. Gibt einen aus, wenn ein Shop-Artikel * bekannt ist, sonst ein -- damit nichts ins Leere klickt. */ function v2_chip($label, $sub, $item_id, $buyclass = false) { $cls = 'v2-chip' . ($buyclass ? ' v2-buy' : ''); $inner = '' . htmlspecialchars($label) . '' . ($sub !== '' ? ' · ' . htmlspecialchars($sub) : ''); $url = v2_shopurl($item_id); if ($url === '') { return '' . $inner . ''; } return '' . $inner . ''; } /** * Artikel-ZEILE statt der alten Chip-"Blase": Bild, Name, Art-Nr, Preis. * Wird in der Bestaetigungskarte und in den Geraete-Kacheln verwendet, damit * die Teile-Liste dieselbe Form hat wie die Artikelliste weiter unten. * Ohne Shop-Artikel-ID wird ein div statt eines Links ausgegeben. */ function v2_artikel_row($it) { $url = v2_shopurl($it['id'] ?? null); $img = !empty($it['default_image_file_name']) ? 'https://intelectra.de/web/intelectra_shop/images/article/' . rawurlencode($it['default_image_file_name']) : ''; $pb = isset($it['price_brutto']) ? (float)$it['price_brutto'] : 0; $bb = !empty($it['bargain_brutto']) ? (float)$it['bargain_brutto'] : 0; $nml = v2_nml_info((string)($it['name'] ?? '')); // nml zusaetzlich ueber die Warengruppe erkennen: die ist verlaesslicher als // der Namensmarker (erkennt 2.207 Faelle, die der Regex uebersieht). $is_nml = $nml['is_nml'] || v2_is_nml_wg($it['wg_name'] ?? ''); $h = $url !== '' ? '' : ''); } /** Warengruppen-Name deutet auf "nicht mehr lieferbar" hin? */ function v2_is_nml_wg($wg) { return (bool)preg_match('/lieferbar|NMLE|Nachfolge|kein Ersatz/i', (string)$wg); } /** * nml-Status eines Shop-Artikels aus BEIDEN Quellen: Warengruppe und Namensmarker. * * Die Warengruppe ist der verlaesslichere Marker -- gemessen am 30.07.2026: * ueber WG erkannt 9.848 Artikel, ueber (nml)-Regex 7.694, davon erkennt nur die * WG 2.207 Faelle (Klartext "Nicht mehr lieferbar +++ kein Ersatz" ohne Suffix) * und nur der Name 51. Zusammen also ~9.899 von 16.923 aktiven. * Die nml-Warengruppen sind 9929, 10649, 10746, 10750; ihr Name "weitere Treffer: * Nachfolge-Artikel" (10649) ist irrefuehrend -- dort liegen ausgelaufene Artikel * (1.241 von 1.274 tragen selbst einen nml-Marker), nicht die Nachfolger. * Ein Nachfolger laesst sich weiterhin nur aus dem NAMEN lesen, daher bleibt die * Unterscheidung g_succ/g_dead am Namenstext haengen. */ function v2_nml_full($it) { $info = v2_nml_info((string)($it['name'] ?? '')); if (!$info['is_nml'] && v2_is_nml_wg($it['wg_name'] ?? '')) { $info['is_nml'] = true; } return $info; } /** * Artikel-Liste als Zeilen. Ab $threshold Treffern wird nach Warengruppe * gruppiert -- genau wie die alte Shop-Suche, weil eine flache Liste mit 30+ * Positionen unbrauchbar ist. nml-Warengruppen ("Nicht mehr Lieferbar +++ ...", * "NMLE-ohne WG", "weitere Treffer: Nachfolge-Artikel") wandern ans Ende. */ function v2_artikel_liste(array $items, $threshold = 12, $max = null) { if (!$items) return ''; // nml-Artikel ans Ende: sie sind nicht bestellbar und sollen die Liste nicht // anfuehren. Muss VOR dem Abschneiden passieren, sonst landen kaufbare Teile // im versteckten Rest, waehrend nml-Positionen die sichtbaren Plaetze belegen. $nml_flag = function ($it) { return (v2_is_nml_wg($it['wg_name'] ?? '') || v2_nml_info((string)($it['name'] ?? ''))['is_nml']) ? 1 : 0; }; usort($items, function ($a, $b) use ($nml_flag) { return $nml_flag($a) - $nml_flag($b); }); // In den eingeklappten Geraete-Kacheln wird begrenzt ($max), sonst rendert eine // Suche wie "WT46W560BY" 10 Kacheln x bis 200 Artikel = mehrere Tausend Zeilen. // Die aktive Bestaetigungskarte zeigt alles ($max = null). Der Rest wird NICHT // verworfen, sondern aufklappbar angehaengt -- vorher war "+ N weitere" ein // toter Text und der Kunde erfuhr nie, was er noch kaufen koennte. $restitems = []; if ($max !== null && count($items) > $max) { $restitems = array_slice($items, $max); $items = array_slice($items, 0, $max); } $more = ''; if ($restitems) { $more = '
+ ' . count($restitems) . ' weitere Artikel für dieses Gerät anzeigen
'; foreach ($restitems as $it) { $more .= v2_artikel_row($it); } $more .= '
'; } if (count($items) <= $threshold) { $h = '
'; foreach ($items as $it) { $h .= v2_artikel_row($it); } return $h . '
' . $more; } $groups = []; foreach ($items as $it) { $wg = trim((string)($it['wg_name'] ?? '')) ?: 'Ohne Warengruppe'; $groups[$wg][] = $it; } // Echte Warengruppen zuerst (die groesste oben), nml-Gruppen ans Ende. uksort($groups, function ($a, $b) use (&$groups) { $na = v2_is_nml_wg($a) ? 1 : 0; $nb = v2_is_nml_wg($b) ? 1 : 0; if ($na !== $nb) return $na - $nb; return count($groups[$b]) <=> count($groups[$a]); }); $h = ''; foreach ($groups as $wg => $its) { $cls = v2_is_nml_wg($wg) ? ' v2-wg-nml' : ''; $h .= '

' . htmlspecialchars($wg) . ' ' . count($its) . '

'; foreach ($its as $it) { $h .= v2_artikel_row($it); } $h .= '
'; } return $h . $more; } function v2_confidence($score) { $s = (int)$score; if ($s >= 100) return ['Exakter Treffer', '#2e7d32', '#e8f5e9', '✓']; if ($s >= 90) return ['Sehr wahrscheinlich dein Gerät','#388e3c', '#edf7ee', '✓']; if ($s >= 75) return ['Könnte passen', '#f9a825', '#fff8e1', '≈']; return ['Ähnlich – bitte prüfen', '#9e9e9e', '#f5f5f5', '?']; } // nml/Nachfolger steckt im Artikelnamen: "(nml)"/"(nmle)" + "Nachfolger " function v2_nml_info($name) { $is_nml = (bool)preg_match('/\(nml[e]?\)/i', $name); $nachf = null; // Nachfolger-Nummer muss mind. eine Ziffer enthalten (schließt "Nachfolger Original" aus) if (preg_match('/Nachfolger\s+([A-Za-z0-9\-\/\.]*\d[A-Za-z0-9\-\/\.]*)/i', $name, $m)) { $nachf = $m[1]; } $kein_ersatz = (stripos($name, 'kein Ersatz') !== false); // Anzeigename säubern: (nml)-Marker + "Nachfolger ... Original(teil)" raus $clean = preg_replace('/\s*\(nml[e]?\)\s*/i', ' ', $name); $clean = preg_replace('/\s*Nachfolger\s+[A-Za-z0-9\-\/\.]*\d[A-Za-z0-9\-\/\.]*\s*/i', ' ', $clean); $clean = preg_replace('/\s*(Originalteil|Original)\s*$/i', '', $clean); $clean = trim(preg_replace('/\s{2,}/', ' ', $clean)); return ['is_nml'=>$is_nml, 'nachfolger'=>$nachf, 'kein_ersatz'=>$kein_ersatz, 'clean'=>($clean ?: $name)]; } } // Treffer in "stark" (>=75) und "schwach" (<75) teilen $v2_strong = []; $v2_weak = []; foreach ($results as $d) { ((int)$d['score'] >= 75) ? $v2_strong[] = $d : $v2_weak[] = $d; } // Artikel klassifizieren: kaufbar / ausgelaufen-mit-Nachfolger / tot $g_buy = []; $g_succ = []; $g_dead = []; foreach ($item_results as $it) { $it['_nml'] = v2_nml_full($it); if (!$it['_nml']['is_nml']) $g_buy[] = $it; // regulär elseif ($it['_nml']['nachfolger']) $g_succ[] = $it; // ausgelaufen, Nachfolger da else $g_dead[] = $it; // ausgelaufen, kein Ersatz } $v2_top_score = isset($results[0]) ? (int)$results[0]['score'] : 0; // Intent-Einordnung if ($v2_top_score >= 90) $v2_headline = 'Wir haben dein Gerät gefunden.'; elseif (count($v2_strong) > 0) $v2_headline = 'Das könnte dein Gerät sein:'; elseif (count($item_results)) $v2_headline = 'Passende Artikel gefunden:'; else $v2_headline = 'Mehrere mögliche Treffer – grenz gern ein.'; ?>
· kaufbare Artikel
Mehrere Hersteller führen dieses Modell
Welcher Hersteller steht auf deinem Gerät?
$n): ?>
Der Hersteller steht meist auf dem Typenschild oder als Logo auf der Blende.
Dieses Modell gibt es in mehreren Bauzuständen
· Varianten
Wann wurde dein Gerät gebaut? Die Nummer auf dem Typenschild endet auf /01, /02 … – wenn du sie findest, ist das der schnellste Weg.
Ist das dein Gerät?
Gerätetyp
Bauart
Baujahr

Verfügbare Artikel für dieses Gerät ():

passende Ersatzteile — Auswahl:

10): ?> + weitere
Ja – Artikel im Shop zeigen () des // Alternativen-Blocks direkt darunter. Das genuegt. ?>
count($a); }); $v2_list = []; foreach ($by_marke as $m => $ds) { $v2_mcount[$m] = count($ds); foreach ($ds as $d) { $d['_mgroup'] = $m; $v2_list[] = $d; } } } ?> $d): list($clabel,$ccol,$cbg,$cicon) = v2_confidence($d['score']); $parts = $d['ersatzteile'] ?? []; $buy = $d['shop_items'] ?? null; // nur wenn shop_items-Toggle an if (!$v2_alt_open && ($v2_guided && $v2_top_detail)) { echo '
Nicht mein Gerät – ' . $v2_alt_total . ' weitere Treffer anzeigen'; $v2_alt_open = true; } // Bei der Varianten-Karte stehen dieselben Geraete schon als Buttons oben -- // die ausfuehrliche Liste (mit Ersatzteilen) deshalb ebenfalls einklappen. if (!$v2_alt_open && $v2_variants) { echo '
Alle ' . count($v2_variants) . ' Bauzustände mit Ersatzteilen anzeigen'; $v2_alt_open = true; } // Dito bei der Hersteller-Rueckfrage: erst die Marke klaeren, die Rohliste // (oft 20 optisch identische Kacheln) gehoert dahinter. if (!$v2_alt_open && $v2_brandpick) { echo '
Alle ' . count($results) . ' Treffer ungefiltert anzeigen'; $v2_alt_open = true; } if (isset($d['_mgroup']) && $d['_mgroup'] !== $v2_last_mgroup) { $v2_last_mgroup = $d['_mgroup']; echo '

'.htmlspecialchars($v2_last_mgroup) . ' '.(int)($v2_mcount[$v2_last_mgroup] ?? 0).'

'; } ?>
Typ: · Nr: · Baujahr:

Verfügbare Artikel für dieses Gerät ():

passende Ersatzteile — Auswahl:

10): ?> + weitere
Weitere mögliche Geräte anzeigen () – schwächere Treffer
Typ: · Nr:
'; } ?>

🛒 Diese Artikel kannst du direkt kaufen

0 -- 10.160x 0, 8.724x NULL). // Bestellbar steuert sie ueber items.active, und searchItems() filtert // bereits mit WHERE i.active = 1 -> alles hier ist bestellbar. // Shop-Artikel und Bilder liegen nur auf LIVE -> absolute URLs $url = 'https://intelectra.de/index.php?item_id=' . (int)$it['id']; $img = !empty($it['default_image_file_name']) ? 'https://intelectra.de/web/intelectra_shop/images/article/'.rawurlencode($it['default_image_file_name']) : ''; $pb = isset($it['price_brutto']) ? (float)$it['price_brutto'] : 0; $bb = !empty($it['bargain_brutto']) ? (float)$it['bargain_brutto'] : 0; ?>
kein Bild
Art-Nr
0 && $pb > 0 && $bb < $pb): ?>  €  € 0): ?>  € Preis auf Anfrage
0): ?>
inkl. MwSt.
Zum Artikel

🔄 Ausgelaufen – aber es gibt einen Nachfolger

Art-Nr · nicht mehr lieferbar
Nicht mehr lieferbar, kein Ersatz () – ausgeblendet
Art-Nr
✕ entfällt
Gib einen Suchbegriff ein (z.B. "AWB 921 PH", "ZCS 2100B", "Kohlefilter Bauknecht")