aded delete to all categories

This commit is contained in:
Chris Rosenau 2025-11-08 14:44:02 -07:00
parent 8f6137dcbf
commit af16940596
3 changed files with 220 additions and 64 deletions

View File

@ -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,
]);
}

View File

@ -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();
}
}

View File

@ -546,6 +546,80 @@
<!-- Category Trees Tab -->
<div id="tab-categories" class="tab-content">
<!-- Statistics -->
<div class="section">
<h2>📊 Category Statistics</h2>
<div class="stats">
<div class="stat-card">
<div class="number">{{ $m1CategoriesCount }}</div>
<div class="label">Magento 1 Categories</div>
</div>
<div class="stat-card">
<div class="number">{{ $m2CategoriesCount }}</div>
<div class="label">Magento 2 Categories</div>
</div>
<div class="stat-card">
<div class="number">{{ $m1CategoriesCount - $m2CategoriesCount }}</div>
<div class="label">Difference</div>
</div>
</div>
</div>
<!-- Missing Categories Section -->
<div class="section">
<h2>⚠️ Magento 2 Categories Not Found in Magento 1</h2>
<p>These categories exist in Magento 2 but do not have a matching name in Magento 1:</p>
@if($m2CategoriesNotInM1->count() > 0)
<div style="margin-top: 15px; max-height: 400px; overflow-y: auto;">
<table style="width: 100%; border-collapse: collapse; background: white; border-radius: 6px;">
<thead>
<tr style="background: #f8f9fa; border-bottom: 2px solid #dee2e6;">
<th style="padding: 12px; text-align: left; font-weight: 600; color: #333;">ID</th>
<th style="padding: 12px; text-align: left; font-weight: 600; color: #333;">Category Name</th>
<th style="padding: 12px; text-align: left; font-weight: 600; color: #333;">Level</th>
<th style="padding: 12px; text-align: left; font-weight: 600; color: #333;">Status</th>
<th style="padding: 12px; text-align: left; font-weight: 600; color: #333;">Root Category</th>
<th style="padding: 12px; text-align: left; font-weight: 600; color: #333;">Path</th>
</tr>
</thead>
<tbody>
@foreach($m2CategoriesNotInM1 as $category)
<tr style="border-bottom: 1px solid #dee2e6;">
<td style="padding: 10px 12px;">{{ $category->entity_id }}</td>
<td style="padding: 10px 12px; font-weight: 500;">{{ $category->name ?? 'Unnamed Category' }}</td>
<td style="padding: 10px 12px;">{{ $category->level ?? 'N/A' }}</td>
<td style="padding: 10px 12px;">
<span class="tree-badge {{ ($category->is_active ?? 0) ? 'active' : 'inactive' }}">
{{ ($category->is_active ?? 0) ? 'Active' : 'Inactive' }}
</span>
</td>
<td style="padding: 10px 12px;">
@if(isset($category->root_category_name) && $category->root_category_name !== 'N/A')
<span style="font-weight: 500;">{{ $category->root_category_name }}</span>
@if(isset($category->root_category_id))
<span style="font-size: 0.85em; color: #666;">(ID: {{ $category->root_category_id }})</span>
@endif
@else
<span style="color: #999;">N/A</span>
@endif
</td>
<td style="padding: 10px 12px; font-size: 0.9em; color: #666;">{{ $category->path ?? 'N/A' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div style="margin-top: 15px; padding: 12px; background: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px;">
<strong>Total:</strong> {{ $m2CategoriesNotInM1->count() }} {{ Str::plural('category', $m2CategoriesNotInM1->count()) }} found in Magento 2 but not in Magento 1.
</div>
@else
<div style="margin-top: 15px; padding: 15px; background: #d4edda; border-left: 4px solid #28a745; border-radius: 4px; color: #155724;">
All Magento 2 categories have matching names in Magento 1.
</div>
@endif
</div>
<div class="section">
<h2>🌳 Category Trees</h2>
<p>View category hierarchies from Magento 1 and Magento 2</p>
@ -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 = '<div class="tree-loading">Deleting category...</div>';
@ -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();
if (confirm(warningText)) {
deleteCategory(node.id, node.name, source);
}
};
label.appendChild(deleteBtn);
}