added category deletion
This commit is contained in:
parent
44c7c7354a
commit
9f8ca957c5
|
|
@ -112,6 +112,31 @@ public function getMagento2CategoryTree()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a category
|
||||
*/
|
||||
public function deleteCategory(Request $request, $categoryId)
|
||||
{
|
||||
$request->validate([
|
||||
'source' => 'nullable|in:m1,m2',
|
||||
]);
|
||||
|
||||
try {
|
||||
$source = $request->input('source', 'm2');
|
||||
$result = $this->migrationService->deleteCategory($categoryId, $source);
|
||||
|
||||
return response()->json($result, $result['success'] ? 200 : 400);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Category deletion error: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Deletion failed: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the migration
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ class MagentoCategoryMigrationService
|
|||
protected $storeMapping = [];
|
||||
protected $categoryMapping = [];
|
||||
protected $migrationLog = [];
|
||||
protected $addedCount = 0;
|
||||
protected $existingCount = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
@ -310,6 +312,8 @@ public function migrateCategories($storeMapping = [])
|
|||
$this->storeMapping = $storeMapping;
|
||||
$this->migrationLog = [];
|
||||
$this->categoryMapping = [];
|
||||
$this->addedCount = 0;
|
||||
$this->existingCount = 0;
|
||||
|
||||
try {
|
||||
DB::connection($this->magento2Connection)->beginTransaction();
|
||||
|
|
@ -330,6 +334,9 @@ public function migrateCategories($storeMapping = [])
|
|||
}
|
||||
}
|
||||
|
||||
// Update children_count for all affected parent categories
|
||||
$this->updateChildrenCounts();
|
||||
|
||||
// Migrate category attributes for each store
|
||||
foreach ($this->storeMapping as $m1StoreId => $m2StoreId) {
|
||||
$this->migrateCategoryAttributes($m1StoreId, $m2StoreId);
|
||||
|
|
@ -341,6 +348,8 @@ public function migrateCategories($storeMapping = [])
|
|||
'success' => true,
|
||||
'message' => 'Categories migrated successfully',
|
||||
'migrated_count' => count($this->categoryMapping),
|
||||
'added_count' => $this->addedCount,
|
||||
'existing_count' => $this->existingCount,
|
||||
'log' => $this->migrationLog
|
||||
];
|
||||
|
||||
|
|
@ -373,13 +382,64 @@ protected function buildCategoryTree($categories)
|
|||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find existing category in Magento 2 by name and parent
|
||||
*/
|
||||
protected function findExistingCategoryInM2($categoryName, $parentId)
|
||||
{
|
||||
if (!$categoryName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get name attribute ID for M2
|
||||
$entityTypeId = DB::connection($this->magento2Connection)
|
||||
->table($this->magento2Prefix . 'eav_entity_type')
|
||||
->where('entity_type_code', 'catalog_category')
|
||||
->value('entity_type_id');
|
||||
|
||||
if (!$entityTypeId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nameAttributeId = DB::connection($this->magento2Connection)
|
||||
->table($this->magento2Prefix . 'eav_attribute')
|
||||
->where('entity_type_id', $entityTypeId)
|
||||
->where('attribute_code', 'name')
|
||||
->value('attribute_id');
|
||||
|
||||
if (!$nameAttributeId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find categories with the same name and parent (checking default store view)
|
||||
$existingCategories = DB::connection($this->magento2Connection)
|
||||
->table($this->magento2Prefix . 'catalog_category_entity', 'e')
|
||||
->join($this->magento2Prefix . 'catalog_category_entity_varchar as v', function($join) use ($nameAttributeId) {
|
||||
$join->on('e.entity_id', '=', 'v.entity_id')
|
||||
->where('v.attribute_id', '=', $nameAttributeId)
|
||||
->where('v.store_id', '=', 0);
|
||||
})
|
||||
->where('v.value', '=', $categoryName)
|
||||
->where('e.parent_id', '=', $parentId)
|
||||
->select('e.entity_id')
|
||||
->first();
|
||||
|
||||
return $existingCategories ? $existingCategories->entity_id : null;
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error("Error finding existing category in M2: " . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate a single category
|
||||
*/
|
||||
protected function migrateCategory($m1Category, $parentId = null)
|
||||
{
|
||||
try {
|
||||
// Check if category already exists
|
||||
// Check if category already exists in mapping (already processed in this migration)
|
||||
if (isset($this->categoryMapping[$m1Category->entity_id])) {
|
||||
return $this->categoryMapping[$m1Category->entity_id];
|
||||
}
|
||||
|
|
@ -391,32 +451,46 @@ protected function migrateCategory($m1Category, $parentId = null)
|
|||
$m2ParentId = $this->categoryMapping[$m1Category->parent_id] ?? $parentId;
|
||||
}
|
||||
|
||||
// Insert category entity
|
||||
$m2EntityId = DB::connection($this->magento2Connection)
|
||||
->table($this->magento2Prefix . 'catalog_category_entity')
|
||||
->insertGetId([
|
||||
'attribute_set_id' => 3, // Default category attribute set
|
||||
'parent_id' => $m2ParentId,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'path' => '',
|
||||
'position' => $m1Category->position ?? 0,
|
||||
'level' => $m1Category->level ?? 1,
|
||||
'children_count' => 0,
|
||||
]);
|
||||
// Check if category already exists in M2 database
|
||||
$existingM2CategoryId = null;
|
||||
if ($m1Category->name) {
|
||||
$existingM2CategoryId = $this->findExistingCategoryInM2($m1Category->name, $m2ParentId);
|
||||
}
|
||||
|
||||
// Update path
|
||||
$path = $this->buildCategoryPath($m2EntityId, $m2ParentId);
|
||||
DB::connection($this->magento2Connection)
|
||||
->table($this->magento2Prefix . 'catalog_category_entity')
|
||||
->where('entity_id', $m2EntityId)
|
||||
->update(['path' => $path]);
|
||||
if ($existingM2CategoryId) {
|
||||
// Category already exists in M2, use existing ID
|
||||
$m2EntityId = $existingM2CategoryId;
|
||||
$this->existingCount++;
|
||||
$this->migrationLog[] = "Found existing category: ID {$m1Category->entity_id} -> {$m2EntityId} ({$m1Category->name})";
|
||||
} else {
|
||||
// Category doesn't exist, create new one
|
||||
$m2EntityId = DB::connection($this->magento2Connection)
|
||||
->table($this->magento2Prefix . 'catalog_category_entity')
|
||||
->insertGetId([
|
||||
'attribute_set_id' => 3, // Default category attribute set
|
||||
'parent_id' => $m2ParentId,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'path' => '',
|
||||
'position' => $m1Category->position ?? 0,
|
||||
'level' => $m1Category->level ?? 1,
|
||||
'children_count' => 0,
|
||||
]);
|
||||
|
||||
// Update path
|
||||
$path = $this->buildCategoryPath($m2EntityId, $m2ParentId);
|
||||
DB::connection($this->magento2Connection)
|
||||
->table($this->magento2Prefix . 'catalog_category_entity')
|
||||
->where('entity_id', $m2EntityId)
|
||||
->update(['path' => $path]);
|
||||
|
||||
$this->addedCount++;
|
||||
$this->migrationLog[] = "Added new category: ID {$m1Category->entity_id} -> {$m2EntityId} ({$m1Category->name})";
|
||||
}
|
||||
|
||||
// Store mapping
|
||||
$this->categoryMapping[$m1Category->entity_id] = $m2EntityId;
|
||||
|
||||
$this->migrationLog[] = "Migrated category ID {$m1Category->entity_id} -> {$m2EntityId} ({$m1Category->name})";
|
||||
|
||||
return $m2EntityId;
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
|
@ -426,6 +500,40 @@ protected function migrateCategory($m1Category, $parentId = null)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update children_count for all parent categories
|
||||
*/
|
||||
protected function updateChildrenCounts()
|
||||
{
|
||||
try {
|
||||
// Get all unique parent IDs from migrated categories
|
||||
$parentIds = DB::connection($this->magento2Connection)
|
||||
->table($this->magento2Prefix . 'catalog_category_entity')
|
||||
->whereIn('entity_id', array_values($this->categoryMapping))
|
||||
->distinct()
|
||||
->pluck('parent_id')
|
||||
->toArray();
|
||||
|
||||
// Update children_count for each parent
|
||||
foreach ($parentIds as $parentId) {
|
||||
if ($parentId > 0) {
|
||||
$childrenCount = DB::connection($this->magento2Connection)
|
||||
->table($this->magento2Prefix . 'catalog_category_entity')
|
||||
->where('parent_id', $parentId)
|
||||
->count();
|
||||
|
||||
DB::connection($this->magento2Connection)
|
||||
->table($this->magento2Prefix . 'catalog_category_entity')
|
||||
->where('entity_id', $parentId)
|
||||
->update(['children_count' => $childrenCount]);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error("Error updating children counts: " . $e->getMessage());
|
||||
// Don't throw, just log the error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build category path
|
||||
*/
|
||||
|
|
@ -585,6 +693,120 @@ protected function getMagento2RootCategoryId()
|
|||
return $rootId ?: 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a category from Magento 2
|
||||
* Only allows deletion of root-level categories (parent_id = 0 or 1) with no children
|
||||
*/
|
||||
public function deleteCategory($categoryId, $source = 'm2')
|
||||
{
|
||||
try {
|
||||
$connection = $source === 'm1' ? $this->magento1Connection : $this->magento2Connection;
|
||||
$prefix = $source === 'm1' ? $this->magento1Prefix : $this->magento2Prefix;
|
||||
|
||||
// Get category information
|
||||
$category = DB::connection($connection)
|
||||
->table($prefix . 'catalog_category_entity')
|
||||
->where('entity_id', $categoryId)
|
||||
->first();
|
||||
|
||||
if (!$category) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Category not found',
|
||||
];
|
||||
}
|
||||
|
||||
// Check if category is root level (parent_id = 0 or 1)
|
||||
if ($category->parent_id != 0 && $category->parent_id != 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',
|
||||
];
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
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();
|
||||
|
||||
// Update parent's children_count if parent exists
|
||||
if ($category->parent_id > 0) {
|
||||
$parentChildrenCount = DB::connection($connection)
|
||||
->table($prefix . 'catalog_category_entity')
|
||||
->where('parent_id', $category->parent_id)
|
||||
->count();
|
||||
|
||||
DB::connection($connection)
|
||||
->table($prefix . 'catalog_category_entity')
|
||||
->where('entity_id', $category->parent_id)
|
||||
->update(['children_count' => $parentChildrenCount]);
|
||||
}
|
||||
|
||||
DB::connection($connection)->commit();
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Category deleted successfully',
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
if (isset($connection)) {
|
||||
DB::connection($connection)->rollBack();
|
||||
}
|
||||
Log::error("Error deleting category {$categoryId}: " . $e->getMessage());
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Failed to delete category: ' . $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test database connections
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -399,6 +399,27 @@
|
|||
border-radius: 6px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.tree-delete-btn {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.75em;
|
||||
cursor: pointer;
|
||||
margin-left: 8px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.tree-delete-btn:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.tree-delete-btn:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -625,7 +646,13 @@ function startMigration() {
|
|||
|
||||
if (data.success) {
|
||||
addLogEntry(`✓ Migration completed successfully!`, 'success');
|
||||
addLogEntry(`Migrated ${data.migrated_count} categories`, 'success');
|
||||
addLogEntry(`Total categories processed: ${data.migrated_count}`, 'success');
|
||||
if (data.added_count !== undefined) {
|
||||
addLogEntry(` - Added new categories: ${data.added_count}`, 'success');
|
||||
}
|
||||
if (data.existing_count !== undefined) {
|
||||
addLogEntry(` - Found existing categories: ${data.existing_count}`, 'success');
|
||||
}
|
||||
|
||||
if (data.log && data.log.length > 0) {
|
||||
data.log.forEach(log => {
|
||||
|
|
@ -695,7 +722,7 @@ function loadM1Tree() {
|
|||
if (data.success) {
|
||||
container.innerHTML = '';
|
||||
if (data.tree && data.tree.length > 0) {
|
||||
renderTree(container, data.tree);
|
||||
renderTree(container, data.tree, 'm1');
|
||||
} else {
|
||||
container.innerHTML = '<div class="tree-loading">No categories found</div>';
|
||||
}
|
||||
|
|
@ -719,7 +746,7 @@ function loadM2Tree() {
|
|||
if (data.success) {
|
||||
container.innerHTML = '';
|
||||
if (data.tree && data.tree.length > 0) {
|
||||
renderTree(container, data.tree);
|
||||
renderTree(container, data.tree, 'm2');
|
||||
} else {
|
||||
container.innerHTML = '<div class="tree-loading">No categories found</div>';
|
||||
}
|
||||
|
|
@ -732,20 +759,64 @@ 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>';
|
||||
|
||||
const url = `{{ route("migration.delete-category", ["categoryId" => ":id"]) }}`.replace(':id', categoryId);
|
||||
|
||||
fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({ source: source })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Reload the tree
|
||||
if (source === 'm1') {
|
||||
loadM1Tree();
|
||||
} else {
|
||||
loadM2Tree();
|
||||
}
|
||||
alert('Category deleted successfully!');
|
||||
} else {
|
||||
container.innerHTML = originalContent;
|
||||
alert('Error: ' + (data.message || 'Failed to delete category'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
container.innerHTML = originalContent;
|
||||
alert('Error: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
// Render tree structure
|
||||
function renderTree(container, nodes) {
|
||||
function renderTree(container, nodes, source = 'm2') {
|
||||
nodes.forEach(node => {
|
||||
const nodeElement = createTreeNode(node);
|
||||
const nodeElement = createTreeNode(node, source);
|
||||
container.appendChild(nodeElement);
|
||||
});
|
||||
}
|
||||
|
||||
// Create a tree node element
|
||||
function createTreeNode(node) {
|
||||
function createTreeNode(node, source = 'm2') {
|
||||
const nodeDiv = document.createElement('div');
|
||||
nodeDiv.className = 'tree-node';
|
||||
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;
|
||||
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.className = 'tree-node-item';
|
||||
|
|
@ -773,6 +844,19 @@ function createTreeNode(node) {
|
|||
label.appendChild(labelText);
|
||||
label.appendChild(badge);
|
||||
|
||||
// Add delete button if category is eligible for deletion
|
||||
if (canDelete) {
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.className = 'tree-delete-btn';
|
||||
deleteBtn.textContent = 'Delete';
|
||||
deleteBtn.title = 'Delete this category';
|
||||
deleteBtn.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
deleteCategory(node.id, node.name, source);
|
||||
};
|
||||
label.appendChild(deleteBtn);
|
||||
}
|
||||
|
||||
itemDiv.appendChild(toggle);
|
||||
itemDiv.appendChild(label);
|
||||
|
||||
|
|
@ -782,7 +866,7 @@ function createTreeNode(node) {
|
|||
const childrenDiv = document.createElement('div');
|
||||
childrenDiv.className = 'tree-children';
|
||||
node.children.forEach(child => {
|
||||
childrenDiv.appendChild(createTreeNode(child));
|
||||
childrenDiv.appendChild(createTreeNode(child, source));
|
||||
});
|
||||
nodeDiv.appendChild(childrenDiv);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,4 +14,5 @@
|
|||
Route::get('/magento1-categories', [MagentoMigrationController::class, 'getMagento1Categories'])->name('migration.magento1-categories');
|
||||
Route::get('/magento1-category-tree', [MagentoMigrationController::class, 'getMagento1CategoryTree'])->name('migration.magento1-category-tree');
|
||||
Route::get('/magento2-category-tree', [MagentoMigrationController::class, 'getMagento2CategoryTree'])->name('migration.magento2-category-tree');
|
||||
Route::delete('/category/{categoryId}', [MagentoMigrationController::class, 'deleteCategory'])->name('migration.delete-category');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue