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();
|
$m2Stores = $this->migrationService->getMagento2Stores();
|
||||||
$connectionTest = $this->migrationService->testConnections();
|
$connectionTest = $this->migrationService->testConnections();
|
||||||
$m1Categories = $this->migrationService->getMagento1Categories();
|
$m1Categories = $this->migrationService->getMagento1Categories();
|
||||||
|
$m2Categories = $this->migrationService->getMagento2Categories();
|
||||||
|
$m2CategoriesNotInM1 = $this->migrationService->getM2CategoriesNotInM1();
|
||||||
|
|
||||||
return view('migration.index', [
|
return view('migration.index', [
|
||||||
'm1Stores' => $m1Stores,
|
'm1Stores' => $m1Stores,
|
||||||
'm2Stores' => $m2Stores,
|
'm2Stores' => $m2Stores,
|
||||||
'connectionTest' => $connectionTest,
|
'connectionTest' => $connectionTest,
|
||||||
'm1CategoriesCount' => $m1Categories->count(),
|
'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')
|
protected function getCategoryProductCounts($categoryIds, $source = 'm2')
|
||||||
{
|
{
|
||||||
if (empty($categoryIds)) {
|
if (empty($categoryIds)) {
|
||||||
|
|
@ -738,8 +796,7 @@ protected function getMagento2RootCategoryId()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a category from Magento 2
|
* Delete a category and all its children recursively
|
||||||
* Only allows deletion of root-level categories (parent_id = 0 or 1) with no children
|
|
||||||
*/
|
*/
|
||||||
public function deleteCategory($categoryId, $source = 'm2')
|
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)
|
// Prevent deletion of system root (ID 0 or 1)
|
||||||
if ($category->parent_id != 0 && $category->parent_id != 1) {
|
if ($categoryId == 0 || $categoryId == 1) {
|
||||||
return [
|
return [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Only root-level categories (parent_id = 0 or 1) can be deleted',
|
'message' => 'Cannot delete system root category',
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::connection($connection)->beginTransaction();
|
DB::connection($connection)->beginTransaction();
|
||||||
|
|
||||||
// Get entity type ID
|
// Recursively delete all children first
|
||||||
$entityTypeId = DB::connection($connection)
|
$this->deleteCategoryChildren($categoryId, $connection, $prefix);
|
||||||
->table($prefix . 'eav_entity_type')
|
|
||||||
->where('entity_type_code', 'catalog_category')
|
|
||||||
->value('entity_type_id');
|
|
||||||
|
|
||||||
if ($entityTypeId) {
|
// Now delete the category itself
|
||||||
// Get all attribute IDs for this entity type
|
$this->deleteCategoryData($categoryId, $connection, $prefix);
|
||||||
$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();
|
|
||||||
|
|
||||||
// Update parent's children_count if parent exists
|
// 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)
|
$parentChildrenCount = DB::connection($connection)
|
||||||
->table($prefix . 'catalog_category_entity')
|
->table($prefix . 'catalog_category_entity')
|
||||||
->where('parent_id', $category->parent_id)
|
->where('parent_id', $category->parent_id)
|
||||||
|
|
@ -836,7 +850,7 @@ public function deleteCategory($categoryId, $source = 'm2')
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => 'Category deleted successfully',
|
'message' => 'Category and all subcategories deleted successfully',
|
||||||
];
|
];
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|
@ -877,5 +891,68 @@ public function testConnections()
|
||||||
|
|
||||||
return $results;
|
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 -->
|
<!-- Category Trees Tab -->
|
||||||
<div id="tab-categories" class="tab-content">
|
<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">
|
<div class="section">
|
||||||
<h2>🌳 Category Trees</h2>
|
<h2>🌳 Category Trees</h2>
|
||||||
<p>View category hierarchies from Magento 1 and Magento 2</p>
|
<p>View category hierarchies from Magento 1 and Magento 2</p>
|
||||||
|
|
@ -764,10 +838,6 @@ function loadM2Tree() {
|
||||||
|
|
||||||
// Delete category
|
// Delete category
|
||||||
function deleteCategory(categoryId, categoryName, source) {
|
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 container = source === 'm1' ? document.getElementById('m1-tree-container') : document.getElementById('m2-tree-container');
|
||||||
const originalContent = container.innerHTML;
|
const originalContent = container.innerHTML;
|
||||||
container.innerHTML = '<div class="tree-loading">Deleting category...</div>';
|
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);
|
nodeDiv.setAttribute('data-category-id', node.id);
|
||||||
|
|
||||||
const hasChildren = node.children && node.children.length > 0;
|
const hasChildren = node.children && node.children.length > 0;
|
||||||
const isRootLevel = node.parent_id == 0 || node.parent_id == 1;
|
// Prevent deletion of system root (ID 0 or 1)
|
||||||
const canDelete = isRootLevel && !hasChildren;
|
const canDelete = node.id != 0 && node.id != 1;
|
||||||
|
|
||||||
const itemDiv = document.createElement('div');
|
const itemDiv = document.createElement('div');
|
||||||
itemDiv.className = 'tree-node-item';
|
itemDiv.className = 'tree-node-item';
|
||||||
|
|
@ -848,15 +918,20 @@ function createTreeNode(node, source = 'm2') {
|
||||||
label.appendChild(labelText);
|
label.appendChild(labelText);
|
||||||
label.appendChild(badge);
|
label.appendChild(badge);
|
||||||
|
|
||||||
// Add delete button if category is eligible for deletion
|
// Add delete button for all categories (except system root)
|
||||||
if (canDelete) {
|
if (canDelete) {
|
||||||
const deleteBtn = document.createElement('button');
|
const deleteBtn = document.createElement('button');
|
||||||
deleteBtn.className = 'tree-delete-btn';
|
deleteBtn.className = 'tree-delete-btn';
|
||||||
deleteBtn.textContent = 'Delete';
|
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) {
|
deleteBtn.onclick = function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
deleteCategory(node.id, node.name, source);
|
if (confirm(warningText)) {
|
||||||
|
deleteCategory(node.id, node.name, source);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
label.appendChild(deleteBtn);
|
label.appendChild(deleteBtn);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue