aded delete to all categories
This commit is contained in:
parent
8f6137dcbf
commit
af16940596
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue