searchGeraete('AWB 921 PH'); */ class KremplDB { private $pg_conn = null; private $mysql_conn = null; private $config = null; public function __construct($connect_mysql = false) { $config_file = __DIR__ . '/config.ini'; if (!file_exists($config_file)) { throw new Exception("Config file not found: $config_file"); } $this->config = parse_ini_file($config_file, true); if (!isset($this->config['postgresql'])) { throw new Exception("PostgreSQL config section not found in config.ini"); } $this->connectPostgres(); if ($connect_mysql) { $this->connectMySQL(); } } private function connectPostgres() { $pg = $this->config['postgresql']; $conn_string = sprintf( "host=%s port=%s dbname=%s user=%s password=%s", $pg['host'] ?? 'localhost', $pg['port'] ?? '5432', $pg['database'] ?? 'krempl_data', $pg['user'] ?? 'krempl_user', $pg['password'] ?? '' ); $this->pg_conn = @pg_connect($conn_string); if (!$this->pg_conn) { throw new Exception("PostgreSQL connection failed. Check credentials in config.ini"); } } private function connectMySQL() { if (!isset($this->config['mysql'])) { throw new Exception("MySQL config section not found in config.ini"); } $my = $this->config['mysql']; $this->mysql_conn = @mysqli_connect( $my['host'] ?? 'localhost', $my['user'] ?? '', $my['password'] ?? '', $my['database'] ?? '' ); if (!$this->mysql_conn) { throw new Exception("MySQL connection failed: " . mysqli_connect_error()); } mysqli_set_charset($this->mysql_conn, 'utf8mb4'); } public function getPostgresConnection() { return $this->pg_conn; } public function getMySQLConnection() { return $this->mysql_conn; } /** * Search for devices using intelligent normalized search * * @param string $search_term The search term (e.g. "AWB 921 PH", "ZCS 2100B") * @param int $limit Maximum number of results (default: 20) * @return array Array of devices with scores and ersatzteile */ public function searchGeraete($search_term, $limit = 20) { if (empty($search_term)) { return []; } $results = []; // Search for devices using the smart search function $query = "SELECT * FROM search_geraete_smart($1, $2)"; $result = pg_query_params($this->pg_conn, $query, [$search_term, $limit]); if (!$result) { throw new Exception("Search query failed: " . pg_last_error($this->pg_conn)); } while ($row = pg_fetch_assoc($result)) { // For each device, find matching ersatzteile $row['ersatzteile'] = $this->getErsatzteileForGeraet($row['geraet_id']); $results[] = $row; } return $results; } /** * Get all ersatzteile (spare parts) for a specific device * * @param int $geraet_id The device ID * @param int $limit Maximum number of results (default: 200) * @return array Array of ersatzteile with navision_id, krempl_id, etc. */ public function getErsatzteileForGeraet($geraet_id, $limit = 200) { $query = " SELECT e.id AS krempl_id, e.navision_id, e.originalnummer, e.marke FROM ersatzteil_mapping em INNER JOIN ersatzteile e ON em.ersatzteil_id = e.id WHERE $1 = ANY(em.geraet_ids) ORDER BY e.navision_id LIMIT $2 "; $result = pg_query_params($this->pg_conn, $query, [$geraet_id, $limit]); if (!$result) { throw new Exception("Ersatzteile query failed: " . pg_last_error($this->pg_conn)); } $ersatzteile = []; while ($row = pg_fetch_assoc($result)) { $ersatzteile[] = $row; } return $ersatzteile; } /** * Get all devices compatible with a specific ersatzteil (by navision_id) * * @param int $navision_id The Navision ID from the shop * @param int $limit Maximum number of results (default: 500) * @return array Array of compatible devices */ public function getGeraeteForNavisionId($navision_id, $limit = 500) { // First find the krempl ersatzteil_id $query1 = "SELECT id FROM ersatzteile WHERE navision_id = $1 LIMIT 1"; $result1 = pg_query_params($this->pg_conn, $query1, [$navision_id]); if (!$result1) { throw new Exception("Navision lookup failed: " . pg_last_error($this->pg_conn)); } $row = pg_fetch_assoc($result1); if (!$row) { return []; // No krempl data for this navision_id } $ersatzteil_id = $row['id']; // Get the array of device IDs $query2 = "SELECT geraet_ids FROM ersatzteil_mapping WHERE ersatzteil_id = $1"; $result2 = pg_query_params($this->pg_conn, $query2, [$ersatzteil_id]); if (!$result2) { throw new Exception("Mapping lookup failed: " . pg_last_error($this->pg_conn)); } $row = pg_fetch_assoc($result2); if (!$row || !$row['geraet_ids']) { return []; } // Parse the PostgreSQL array format $geraet_ids_str = trim($row['geraet_ids'], '{}'); $geraet_ids = array_map('intval', explode(',', $geraet_ids_str)); // Get device details $query3 = " SELECT id, nr, modell_bezeichnung, typ, typ_de, marke, zusatz FROM geraete WHERE id = ANY($1) ORDER BY marke, modell_bezeichnung LIMIT $2 "; $result3 = pg_query_params($this->pg_conn, $query3, ['{' . implode(',', $geraet_ids) . '}', $limit]); if (!$result3) { throw new Exception("Geraete lookup failed: " . pg_last_error($this->pg_conn)); } $geraete = []; while ($row = pg_fetch_assoc($result3)) { $geraete[] = $row; } return $geraete; } /** * Rebuild the search index after CSV re-import * Call this after running import.py * * @return int Number of rows indexed */ public function rebuildSearchIndex() { $query = "SELECT rebuild_geraete_search_index()"; $result = pg_query($this->pg_conn, $query); if (!$result) { throw new Exception("Rebuild index failed: " . pg_last_error($this->pg_conn)); } $row = pg_fetch_row($result); return (int)$row[0]; } /** * Get database statistics * * @return array Statistics about the database */ public function getStats() { $stats = []; $queries = [ 'geraete_count' => "SELECT COUNT(*) FROM geraete", 'ersatzteile_count' => "SELECT COUNT(*) FROM ersatzteile", 'mapping_count' => "SELECT COUNT(*) FROM ersatzteil_mapping", 'passendwie_count' => "SELECT COUNT(*) FROM passendwie", 'search_index_count' => "SELECT COUNT(*) FROM geraete_search_index", ]; foreach ($queries as $key => $query) { $result = pg_query($this->pg_conn, $query); if ($result) { $row = pg_fetch_row($result); $stats[$key] = (int)$row[0]; } } return $stats; } /** * Get available shop items for a device (Hybrid: PostgreSQL + MySQL) * * This function combines Krempl data (PostgreSQL) with shop inventory (MySQL) * to show only the spare parts that are actually available in the shop. * * @param int $geraet_id The Krempl device ID * @param int $limit Maximum number of items to return (default: 200) * @return array Array of shop items with full details */ public function getAvailableItemsForGeraet($geraet_id, $limit = 200) { // Ensure MySQL is connected if (!$this->mysql_conn) { $this->connectMySQL(); } // Step 1: Get all Navision IDs from PostgreSQL for this device $navision_ids = []; $query = " SELECT e.navision_id FROM ersatzteil_mapping em INNER JOIN ersatzteile e ON em.ersatzteil_id = e.id WHERE $1 = ANY(em.geraet_ids) AND e.navision_id IS NOT NULL ORDER BY e.navision_id "; $result = pg_query_params($this->pg_conn, $query, [$geraet_id]); if (!$result) { throw new Exception("Failed to get navision_ids: " . pg_last_error($this->pg_conn)); } while ($row = pg_fetch_assoc($result)) { $navision_ids[] = (int)$row['navision_id']; } if (empty($navision_ids)) { return []; // No parts found } // Step 2: Query MySQL for items that match these Navision IDs // Logic: number >= 8 digits → navision_id is in `number` // number < 8 digits → navision_id is in `attribute_7` $navision_ids_str = implode(',', $navision_ids); $mysql_query = " SELECT i.id, i.number, i.name, i.attribute_7 AS navision_id_alt, i.inventory, i.base_price, i.manufacturer_id, i.structure_id, CASE WHEN LENGTH(i.number) >= 8 THEN i.number ELSE i.attribute_7 END AS matched_navision_id FROM items i WHERE ( (LENGTH(i.number) >= 8 AND i.number IN ($navision_ids_str)) OR (LENGTH(i.number) < 8 AND i.attribute_7 IN ($navision_ids_str)) ) AND i.active = 1 ORDER BY i.name LIMIT $limit "; $mysql_result = mysqli_query($this->mysql_conn, $mysql_query); if (!$mysql_result) { throw new Exception("MySQL query failed: " . mysqli_error($this->mysql_conn)); } $items = []; while ($row = mysqli_fetch_assoc($mysql_result)) { $items[] = $row; } return $items; } public function __destruct() { if ($this->pg_conn) { pg_close($this->pg_conn); } if ($this->mysql_conn) { mysqli_close($this->mysql_conn); } } }