From af16940596c03e73d8786a4b29201bd20b8439d1 Mon Sep 17 00:00:00 2001 From: Chris Rosenau Date: Sat, 8 Nov 2025 14:44:02 -0700 Subject: [PATCH] aded delete to all categories --- .../MagentoMigrationController.php | 4 + .../MagentoCategoryMigrationService.php | 187 ++++++++++++------ resources/views/migration/index.blade.php | 93 ++++++++- 3 files changed, 220 insertions(+), 64 deletions(-) diff --git a/app/Http/Controllers/MagentoMigrationController.php b/app/Http/Controllers/MagentoMigrationController.php index 6b2d63d..7e2fd22 100644 --- a/app/Http/Controllers/MagentoMigrationController.php +++ b/app/Http/Controllers/MagentoMigrationController.php @@ -24,12 +24,16 @@ public function index() $m2Stores = $this->migrationService->getMagento2Stores(); $connectionTest = $this->migrationService->testConnections(); $m1Categories = $this->migrationService->getMagento1Categories(); + $m2Categories = $this->migrationService->getMagento2Categories(); + $m2CategoriesNotInM1 = $this->migrationService->getM2CategoriesNotInM1(); return view('migration.index', [ 'm1Stores' => $m1Stores, 'm2Stores' => $m2Stores, 'connectionTest' => $connectionTest, 'm1CategoriesCount' => $m1Categories->count(), + 'm2CategoriesCount' => $m2Categories->count(), + 'm2CategoriesNotInM1' => $m2CategoriesNotInM1, ]); } diff --git a/app/Services/MagentoCategoryMigrationService.php b/app/Services/MagentoCategoryMigrationService.php index 8058b21..e57b2d8 100644 --- a/app/Services/MagentoCategoryMigrationService.php +++ b/app/Services/MagentoCategoryMigrationService.php @@ -245,8 +245,66 @@ public function getMagento2Categories($storeId = null) } /** - * Get product counts for categories + * Find categories in Magento 2 that don't exist in Magento 1 + * Compares by category name (case-insensitive) */ + public function getM2CategoriesNotInM1() + { + try { + $m1Categories = $this->getMagento1Categories(); + $m2Categories = $this->getMagento2Categories(); + + // Create a set of M1 category names (case-insensitive, normalized) + $m1CategoryNames = $m1Categories->map(function($cat) { + return strtolower(trim($cat->name ?? '')); + })->filter(function($name) { + return !empty($name); + })->unique()->toArray(); + + // Get all M2 categories to build a map for root category lookup + $m2CategoryMap = []; + foreach ($m2Categories as $cat) { + $m2CategoryMap[$cat->entity_id] = $cat; + } + + // Find M2 categories that don't have a matching name in M1 + $missingCategories = $m2Categories->filter(function($m2Cat) use ($m1CategoryNames) { + $m2Name = strtolower(trim($m2Cat->name ?? '')); + return !empty($m2Name) && !in_array($m2Name, $m1CategoryNames); + })->map(function($m2Cat) use ($m2CategoryMap) { + // Extract root category ID from path (typically second element: 1/2/3/4 -> 2) + $rootCategoryId = null; + $rootCategoryName = 'N/A'; + + if (!empty($m2Cat->path)) { + $pathParts = explode('/', $m2Cat->path); + // Root category is typically at index 1 (second element) + if (count($pathParts) > 1 && isset($pathParts[1])) { + $rootCategoryId = (int)$pathParts[1]; + + // Get root category name if it exists in our map + if (isset($m2CategoryMap[$rootCategoryId])) { + $rootCategoryName = $m2CategoryMap[$rootCategoryId]->name ?? "ID: {$rootCategoryId}"; + } else { + $rootCategoryName = "ID: {$rootCategoryId}"; + } + } + } + + // Add root category info to the category object + $m2Cat->root_category_id = $rootCategoryId; + $m2Cat->root_category_name = $rootCategoryName; + + return $m2Cat; + })->values(); + + return $missingCategories; + } catch (Exception $e) { + Log::error('Error finding M2 categories not in M1: ' . $e->getMessage()); + return collect([]); + } + } + protected function getCategoryProductCounts($categoryIds, $source = 'm2') { if (empty($categoryIds)) { @@ -738,8 +796,7 @@ protected function getMagento2RootCategoryId() } /** - * Delete a category from Magento 2 - * Only allows deletion of root-level categories (parent_id = 0 or 1) with no children + * Delete a category and all its children recursively */ public function deleteCategory($categoryId, $source = 'm2') { @@ -760,67 +817,24 @@ public function deleteCategory($categoryId, $source = 'm2') ]; } - // Check if category is root level (parent_id = 0 or 1) - if ($category->parent_id != 0 && $category->parent_id != 1) { + // Prevent deletion of system root (ID 0 or 1) + if ($categoryId == 0 || $categoryId == 1) { return [ 'success' => false, - 'message' => 'Only root-level categories (parent_id = 0 or 1) can be deleted', - ]; - } - - // Check if category has children - $childrenCount = DB::connection($connection) - ->table($prefix . 'catalog_category_entity') - ->where('parent_id', $categoryId) - ->count(); - - if ($childrenCount > 0) { - return [ - 'success' => false, - 'message' => 'Cannot delete category with subcategories', + 'message' => 'Cannot delete system root category', ]; } DB::connection($connection)->beginTransaction(); - // Get entity type ID - $entityTypeId = DB::connection($connection) - ->table($prefix . 'eav_entity_type') - ->where('entity_type_code', 'catalog_category') - ->value('entity_type_id'); + // Recursively delete all children first + $this->deleteCategoryChildren($categoryId, $connection, $prefix); - if ($entityTypeId) { - // Get all attribute IDs for this entity type - $attributeIds = DB::connection($connection) - ->table($prefix . 'eav_attribute') - ->where('entity_type_id', $entityTypeId) - ->pluck('attribute_id') - ->toArray(); - - // Delete attribute values from all attribute tables - $attributeTables = ['varchar', 'int', 'text', 'decimal', 'datetime']; - foreach ($attributeTables as $tableType) { - $table = $prefix . 'catalog_category_entity_' . $tableType; - try { - DB::connection($connection) - ->table($table) - ->where('entity_id', $categoryId) - ->delete(); - } catch (Exception $e) { - // Table might not exist, continue - Log::warning("Table {$table} might not exist or has no data: " . $e->getMessage()); - } - } - } - - // Delete the category entity - DB::connection($connection) - ->table($prefix . 'catalog_category_entity') - ->where('entity_id', $categoryId) - ->delete(); + // Now delete the category itself + $this->deleteCategoryData($categoryId, $connection, $prefix); // Update parent's children_count if parent exists - if ($category->parent_id > 0) { + if ($category->parent_id > 0 && $category->parent_id != 1) { $parentChildrenCount = DB::connection($connection) ->table($prefix . 'catalog_category_entity') ->where('parent_id', $category->parent_id) @@ -836,7 +850,7 @@ public function deleteCategory($categoryId, $source = 'm2') return [ 'success' => true, - 'message' => 'Category deleted successfully', + 'message' => 'Category and all subcategories deleted successfully', ]; } catch (Exception $e) { @@ -877,5 +891,68 @@ public function testConnections() return $results; } + + /** + * Recursively delete all children of a category + */ + protected function deleteCategoryChildren($categoryId, $connection, $prefix) + { + // Get all children + $children = DB::connection($connection) + ->table($prefix . 'catalog_category_entity') + ->where('parent_id', $categoryId) + ->get(); + + // Recursively delete each child + foreach ($children as $child) { + $this->deleteCategoryChildren($child->entity_id, $connection, $prefix); + $this->deleteCategoryData($child->entity_id, $connection, $prefix); + } + } + + /** + * Delete category data (attributes and entity) + */ + protected function deleteCategoryData($categoryId, $connection, $prefix) + { + // Get entity type ID + $entityTypeId = DB::connection($connection) + ->table($prefix . 'eav_entity_type') + ->where('entity_type_code', 'catalog_category') + ->value('entity_type_id'); + + if ($entityTypeId) { + // Delete attribute values from all attribute tables + $attributeTables = ['varchar', 'int', 'text', 'decimal', 'datetime']; + foreach ($attributeTables as $tableType) { + $table = $prefix . 'catalog_category_entity_' . $tableType; + try { + DB::connection($connection) + ->table($table) + ->where('entity_id', $categoryId) + ->delete(); + } catch (Exception $e) { + // Table might not exist, continue + Log::warning("Table {$table} might not exist or has no data: " . $e->getMessage()); + } + } + + // Delete category product associations + try { + DB::connection($connection) + ->table($prefix . 'catalog_category_product') + ->where('category_id', $categoryId) + ->delete(); + } catch (Exception $e) { + Log::warning("Table {$prefix}catalog_category_product might not exist: " . $e->getMessage()); + } + } + + // Delete the category entity + DB::connection($connection) + ->table($prefix . 'catalog_category_entity') + ->where('entity_id', $categoryId) + ->delete(); + } } diff --git a/resources/views/migration/index.blade.php b/resources/views/migration/index.blade.php index 8d05dfd..780e0d4 100644 --- a/resources/views/migration/index.blade.php +++ b/resources/views/migration/index.blade.php @@ -546,6 +546,80 @@
+ +
+

📊 Category Statistics

+
+
+
{{ $m1CategoriesCount }}
+
Magento 1 Categories
+
+
+
{{ $m2CategoriesCount }}
+
Magento 2 Categories
+
+
+
{{ $m1CategoriesCount - $m2CategoriesCount }}
+
Difference
+
+
+
+ + +
+

⚠️ Magento 2 Categories Not Found in Magento 1

+

These categories exist in Magento 2 but do not have a matching name in Magento 1:

+ + @if($m2CategoriesNotInM1->count() > 0) +
+ + + + + + + + + + + + + @foreach($m2CategoriesNotInM1 as $category) + + + + + + + + + @endforeach + +
IDCategory NameLevelStatusRoot CategoryPath
{{ $category->entity_id }}{{ $category->name ?? 'Unnamed Category' }}{{ $category->level ?? 'N/A' }} + + {{ ($category->is_active ?? 0) ? 'Active' : 'Inactive' }} + + + @if(isset($category->root_category_name) && $category->root_category_name !== 'N/A') + {{ $category->root_category_name }} + @if(isset($category->root_category_id)) + (ID: {{ $category->root_category_id }}) + @endif + @else + N/A + @endif + {{ $category->path ?? 'N/A' }}
+
+
+ Total: {{ $m2CategoriesNotInM1->count() }} {{ Str::plural('category', $m2CategoriesNotInM1->count()) }} found in Magento 2 but not in Magento 1. +
+ @else +
+ ✓ All Magento 2 categories have matching names in Magento 1. +
+ @endif +
+

🌳 Category Trees

View category hierarchies from Magento 1 and Magento 2

@@ -764,10 +838,6 @@ function loadM2Tree() { // Delete category function deleteCategory(categoryId, categoryName, source) { - if (!confirm(`Are you sure you want to delete the category "${categoryName}"?\n\nThis action cannot be undone.`)) { - return; - } - const container = source === 'm1' ? document.getElementById('m1-tree-container') : document.getElementById('m2-tree-container'); const originalContent = container.innerHTML; container.innerHTML = '
Deleting category...
'; @@ -818,8 +888,8 @@ function createTreeNode(node, source = 'm2') { nodeDiv.setAttribute('data-category-id', node.id); const hasChildren = node.children && node.children.length > 0; - const isRootLevel = node.parent_id == 0 || node.parent_id == 1; - const canDelete = isRootLevel && !hasChildren; + // Prevent deletion of system root (ID 0 or 1) + const canDelete = node.id != 0 && node.id != 1; const itemDiv = document.createElement('div'); itemDiv.className = 'tree-node-item'; @@ -848,15 +918,20 @@ function createTreeNode(node, source = 'm2') { label.appendChild(labelText); label.appendChild(badge); - // Add delete button if category is eligible for deletion + // Add delete button for all categories (except system root) if (canDelete) { const deleteBtn = document.createElement('button'); deleteBtn.className = 'tree-delete-btn'; deleteBtn.textContent = 'Delete'; - deleteBtn.title = 'Delete this category'; + const warningText = hasChildren + ? `Delete this category and all ${node.children.length} subcategory(ies)?\n\nThis action cannot be undone.` + : `Are you sure you want to delete the category "${node.name}"?\n\nThis action cannot be undone.`; + deleteBtn.title = hasChildren ? 'Delete this category and all subcategories' : 'Delete this category'; deleteBtn.onclick = function(e) { e.stopPropagation(); - deleteCategory(node.id, node.name, source); + if (confirm(warningText)) { + deleteCategory(node.id, node.name, source); + } }; label.appendChild(deleteBtn); }