query("SELECT id, sku, name FROM product WHERE status = 'active' ORDER BY id"); foreach ($stmt->fetchAll() as $product) { $productName = (string) ($product['name'] ?? ''); $productKey = detect_product_family_key(normalize_match_key($productName)); if ($productKey === $familyKey) { return [ 'id' => (int) $product['id'], 'sku' => (string) $product['sku'], 'name' => $productName, ]; } } throw new RuntimeException("Kein aktives ERP-Produkt fuer '{$title}' gefunden"); } function find_alias_sellable_item_id(PDO $pdo, string $articleNumber, string $title): ?int { $stmt = $pdo->prepare( "SELECT si.id FROM external_item_alias eia JOIN sellable_item si ON si.id = eia.sellable_item_id WHERE eia.source_system = 'wix' AND eia.is_active = TRUE AND ( eia.external_article_number = :article_number OR eia.external_title = :title OR eia.title_normalized = :normalized_title ) ORDER BY eia.id LIMIT 1" ); $stmt->execute([ ':article_number' => trim($articleNumber), ':title' => trim($title), ':normalized_title' => normalize_match_key($title), ]); $id = $stmt->fetchColumn(); return $id === false ? null : (int) $id; } function sellable_item_exists(PDO $pdo, int $sellableItemId): bool { $stmt = $pdo->prepare('SELECT 1 FROM sellable_item WHERE id = :id LIMIT 1'); $stmt->execute([':id' => $sellableItemId]); return $stmt->fetchColumn() !== false; } function find_sellable_item_for_product(PDO $pdo, int $productId): ?int { $stmt = $pdo->prepare( 'SELECT sellable_item_id FROM sellable_item_component WHERE product_id = :product_id ORDER BY id LIMIT 1' ); $stmt->execute([':product_id' => $productId]); $id = $stmt->fetchColumn(); return $id === false ? null : (int) $id; } function find_product_name(PDO $pdo, int $productId): ?string { $stmt = $pdo->prepare('SELECT name FROM product WHERE id = :id LIMIT 1'); $stmt->execute([':id' => $productId]); $name = $stmt->fetchColumn(); return $name === false ? null : (string) $name; } function ensure_unique_sellable_item_code(PDO $pdo, string $preferred): string { $candidate = trim($preferred); if ($candidate === '') { $candidate = 'si'; } $base = $candidate; $counter = 1; while (true) { $stmt = $pdo->prepare('SELECT 1 FROM sellable_item WHERE item_code = :item_code LIMIT 1'); $stmt->execute([':item_code' => $candidate]); if ($stmt->fetchColumn() === false) { return $candidate; } $candidate = $base . '-' . $counter; $counter++; } } function resolve_product_id_fallback(PDO $pdo, string $articleNumber, string $title): ?int { $normalizedTitle = normalize_match_key($title); $familyKey = detect_product_family_key($normalizedTitle); if ($familyKey === null) { return null; } $stmt = $pdo->query("SELECT id, name FROM product WHERE status = 'active' ORDER BY id"); foreach ($stmt->fetchAll() as $product) { $productName = (string) ($product['name'] ?? ''); $productKey = detect_product_family_key(normalize_match_key($productName)); if ($productKey === $familyKey) { return (int) $product['id']; } } return null; } function infer_components_from_title(PDO $pdo, string $title): array { $productId = resolve_product_id_fallback($pdo, '', $title); if ($productId === null) { return []; } return [['product_id' => $productId, 'qty_per_item' => 1.0]]; } function ensure_alias_points_to_sellable_item( PDO $pdo, string $articleNumber, string $title, int $sellableItemId ): void { $stmt = $pdo->prepare( "INSERT INTO external_item_alias ( source_system, external_article_number, external_title, title_normalized, sellable_item_id, is_active, created_at, updated_at ) VALUES ( 'wix', :article_number, :title, :normalized_title, :sellable_item_id, TRUE, NOW(), NOW() ) ON CONFLICT DO NOTHING" ); $stmt->execute([ ':article_number' => trim($articleNumber), ':title' => trim($title), ':normalized_title' => normalize_match_key($title), ':sellable_item_id' => $sellableItemId, ]); } function ensure_sellable_mapping_from_title_components(PDO $pdo, string $articleNumber, string $title): ?int { $sellableItemId = find_alias_sellable_item_id($pdo, $articleNumber, $title); if ($sellableItemId !== null) { return $sellableItemId; } $components = infer_components_from_title($pdo, $title); if ($components === []) { return null; } $productId = (int) $components[0]['product_id']; $sellableItemId = find_sellable_item_for_product($pdo, $productId); if ($sellableItemId === null) { $productName = find_product_name($pdo, $productId) ?? 'Mapped item'; $code = ensure_unique_sellable_item_code($pdo, normalize_match_key($productName)); $stmt = $pdo->prepare( "INSERT INTO sellable_item (item_code, display_name, status, created_at, updated_at) VALUES (:item_code, :display_name, 'active', NOW(), NOW()) RETURNING id" ); $stmt->execute([ ':item_code' => $code, ':display_name' => $productName, ]); $sellableItemId = (int) $stmt->fetchColumn(); $componentStmt = $pdo->prepare( "INSERT INTO sellable_item_component (sellable_item_id, product_id, qty_per_item, created_at, updated_at) VALUES (:sellable_item_id, :product_id, 1, NOW(), NOW())" ); $componentStmt->execute([ ':sellable_item_id' => $sellableItemId, ':product_id' => $productId, ]); } ensure_alias_points_to_sellable_item($pdo, $articleNumber, $title, $sellableItemId); return $sellableItemId; } function ensure_sellable_mapping_from_product_fallback(PDO $pdo, string $articleNumber, string $title): ?int { return ensure_sellable_mapping_from_title_components($pdo, $articleNumber, $title); } function resolve_sellable_item_id(PDO $pdo, string $articleNumber, string $title): ?int { $id = find_alias_sellable_item_id($pdo, $articleNumber, $title); if ($id !== null) { return $id; } return ensure_sellable_mapping_from_product_fallback($pdo, $articleNumber, $title); } function get_item_components(PDO $pdo, int $sellableItemId): array { $stmt = $pdo->prepare( 'SELECT product_id, qty_per_item FROM sellable_item_component WHERE sellable_item_id = :sellable_item_id ORDER BY id' ); $stmt->execute([':sellable_item_id' => $sellableItemId]); return $stmt->fetchAll(); }