added category migration
This commit is contained in:
parent
eeccc6c5f9
commit
6afae19013
|
|
@ -23,11 +23,13 @@ public function index()
|
||||||
$m1Categories = $this->migrationService->getMagento1Categories();
|
$m1Categories = $this->migrationService->getMagento1Categories();
|
||||||
$m2Categories = $this->migrationService->getMagento2Categories();
|
$m2Categories = $this->migrationService->getMagento2Categories();
|
||||||
$m2CategoriesNotInM1 = $this->migrationService->getM2CategoriesNotInM1();
|
$m2CategoriesNotInM1 = $this->migrationService->getM2CategoriesNotInM1();
|
||||||
|
$m1CategoriesNotInM2 = $this->migrationService->getM1CategoriesNotInM2();
|
||||||
|
|
||||||
return view('categories.index', [
|
return view('categories.index', [
|
||||||
'm1CategoriesCount' => $m1Categories->count(),
|
'm1CategoriesCount' => $m1Categories->count(),
|
||||||
'm2CategoriesCount' => $m2Categories->count(),
|
'm2CategoriesCount' => $m2Categories->count(),
|
||||||
'm2CategoriesNotInM1' => $m2CategoriesNotInM1,
|
'm2CategoriesNotInM1' => $m2CategoriesNotInM1,
|
||||||
|
'm1CategoriesNotInM2' => $m1CategoriesNotInM2,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,6 +175,38 @@ public function getM2CategoriesNotInM1()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get M1 categories not in M2 (for AJAX refresh)
|
||||||
|
*/
|
||||||
|
public function getM1CategoriesNotInM2()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$categories = $this->migrationService->getM1CategoriesNotInM2();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'categories' => $categories->map(function($category) {
|
||||||
|
return [
|
||||||
|
'entity_id' => $category->entity_id,
|
||||||
|
'name' => $category->name ?? 'Unnamed Category',
|
||||||
|
'level' => $category->level ?? 'N/A',
|
||||||
|
'is_active' => $category->is_active ?? 0,
|
||||||
|
'root_category_name' => $category->root_category_name ?? 'N/A',
|
||||||
|
'root_category_id' => $category->root_category_id ?? null,
|
||||||
|
'path' => $category->path ?? 'N/A',
|
||||||
|
];
|
||||||
|
}),
|
||||||
|
'count' => $categories->count(),
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error fetching M1 categories not in M2: ' . $e->getMessage());
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to fetch categories: ' . $e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a category
|
* Delete a category
|
||||||
*/
|
*/
|
||||||
|
|
@ -198,6 +232,31 @@ public function deleteCategory(Request $request, $categoryId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate a single category from Magento 1 to Magento 2
|
||||||
|
*/
|
||||||
|
public function migrateCategory(Request $request, $categoryId)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'store_id' => 'nullable|integer',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$storeId = $request->input('store_id', 0);
|
||||||
|
$result = $this->migrationService->migrateSingleCategory($categoryId, $storeId);
|
||||||
|
|
||||||
|
return response()->json($result, $result['success'] ? 200 : 400);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Category migration error: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Migration failed: ' . $e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rename a Magento 2 category
|
* Rename a Magento 2 category
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,67 @@ public function getM2CategoriesNotInM1()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find categories in Magento 1 that don't exist in Magento 2
|
||||||
|
* Compares by category name (case-insensitive)
|
||||||
|
*/
|
||||||
|
public function getM1CategoriesNotInM2()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$m1Categories = $this->getMagento1Categories();
|
||||||
|
$m2Categories = $this->getMagento2Categories();
|
||||||
|
|
||||||
|
// Create a set of M2 category names (case-insensitive, normalized)
|
||||||
|
$m2CategoryNames = $m2Categories->map(function($cat) {
|
||||||
|
return strtolower(trim($cat->name ?? ''));
|
||||||
|
})->filter(function($name) {
|
||||||
|
return !empty($name);
|
||||||
|
})->unique()->toArray();
|
||||||
|
|
||||||
|
// Get all M1 categories to build a map for root category lookup
|
||||||
|
$m1CategoryMap = [];
|
||||||
|
foreach ($m1Categories as $cat) {
|
||||||
|
$m1CategoryMap[$cat->entity_id] = $cat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find M1 categories that don't have a matching name in M2
|
||||||
|
$missingCategories = $m1Categories->filter(function($m1Cat) use ($m2CategoryNames) {
|
||||||
|
$m1Name = strtolower(trim($m1Cat->name ?? ''));
|
||||||
|
return !empty($m1Name) && !in_array($m1Name, $m2CategoryNames);
|
||||||
|
})->map(function($m1Cat) use ($m1CategoryMap) {
|
||||||
|
// Extract root category ID from path (typically second element: 1/2/3/4 -> 2)
|
||||||
|
$rootCategoryId = null;
|
||||||
|
$rootCategoryName = 'N/A';
|
||||||
|
|
||||||
|
if (!empty($m1Cat->path)) {
|
||||||
|
$pathParts = explode('/', $m1Cat->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($m1CategoryMap[$rootCategoryId])) {
|
||||||
|
$rootCategoryName = $m1CategoryMap[$rootCategoryId]->name ?? "ID: {$rootCategoryId}";
|
||||||
|
} else {
|
||||||
|
$rootCategoryName = "ID: {$rootCategoryId}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add root category info to the category object
|
||||||
|
$m1Cat->root_category_id = $rootCategoryId;
|
||||||
|
$m1Cat->root_category_name = $rootCategoryName;
|
||||||
|
|
||||||
|
return $m1Cat;
|
||||||
|
})->values();
|
||||||
|
|
||||||
|
return $missingCategories;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error('Error finding M1 categories not in M2: ' . $e->getMessage());
|
||||||
|
return collect([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected function getCategoryProductCounts($categoryIds, $source = 'm2')
|
protected function getCategoryProductCounts($categoryIds, $source = 'm2')
|
||||||
{
|
{
|
||||||
if (empty($categoryIds)) {
|
if (empty($categoryIds)) {
|
||||||
|
|
@ -780,6 +841,146 @@ protected function getMagento2AttributeIds()
|
||||||
return $attributes;
|
return $attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate a single category from Magento 1 to Magento 2
|
||||||
|
*/
|
||||||
|
public function migrateSingleCategory($m1CategoryId, $storeId = 0)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DB::connection($this->magento2Connection)->beginTransaction();
|
||||||
|
|
||||||
|
// Get the M1 category
|
||||||
|
$m1Categories = $this->getMagento1Categories($storeId);
|
||||||
|
$m1Category = $m1Categories->firstWhere('entity_id', $m1CategoryId);
|
||||||
|
|
||||||
|
if (!$m1Category) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Category not found in Magento 1',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine parent ID in M2
|
||||||
|
$m2ParentId = $this->getMagento2RootCategoryId();
|
||||||
|
|
||||||
|
// If category has a parent in M1, try to find it in M2
|
||||||
|
if ($m1Category->parent_id && $m1Category->parent_id != 0 && $m1Category->parent_id != 1) {
|
||||||
|
// Check if parent was already migrated (check by name)
|
||||||
|
$m1ParentCategories = $this->getMagento1Categories($storeId);
|
||||||
|
$m1Parent = $m1ParentCategories->firstWhere('entity_id', $m1Category->parent_id);
|
||||||
|
|
||||||
|
if ($m1Parent && $m1Parent->name) {
|
||||||
|
$m2ParentId = $this->findExistingCategoryInM2($m1Parent->name, $this->getMagento2RootCategoryId());
|
||||||
|
if (!$m2ParentId) {
|
||||||
|
// Parent not found, use root
|
||||||
|
$m2ParentId = $this->getMagento2RootCategoryId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if category already exists in M2
|
||||||
|
$existingM2CategoryId = null;
|
||||||
|
if ($m1Category->name) {
|
||||||
|
$existingM2CategoryId = $this->findExistingCategoryInM2($m1Category->name, $m2ParentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existingM2CategoryId) {
|
||||||
|
DB::connection($this->magento2Connection)->rollBack();
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Category already exists in Magento 2 (ID: ' . $existingM2CategoryId . ')',
|
||||||
|
'm2_category_id' => $existingM2CategoryId,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new 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,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update path
|
||||||
|
$path = $this->buildCategoryPath($m2EntityId, $m2ParentId);
|
||||||
|
DB::connection($this->magento2Connection)
|
||||||
|
->table($this->magento2Prefix . 'catalog_category_entity')
|
||||||
|
->where('entity_id', $m2EntityId)
|
||||||
|
->update(['path' => $path]);
|
||||||
|
|
||||||
|
// Migrate attributes for default store (0)
|
||||||
|
$attributeIds = $this->getMagento2AttributeIds();
|
||||||
|
|
||||||
|
// Migrate name
|
||||||
|
if ($m1Category->name) {
|
||||||
|
$this->insertCategoryAttribute(
|
||||||
|
$m2EntityId,
|
||||||
|
$attributeIds['name'],
|
||||||
|
$storeId,
|
||||||
|
$m1Category->name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate is_active
|
||||||
|
if ($m1Category->is_active !== null) {
|
||||||
|
$this->insertCategoryAttribute(
|
||||||
|
$m2EntityId,
|
||||||
|
$attributeIds['is_active'],
|
||||||
|
$storeId,
|
||||||
|
$m1Category->is_active
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate url_key
|
||||||
|
if ($m1Category->url_key) {
|
||||||
|
$this->insertCategoryAttribute(
|
||||||
|
$m2EntityId,
|
||||||
|
$attributeIds['url_key'],
|
||||||
|
$storeId,
|
||||||
|
$m1Category->url_key
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update children_count for parent
|
||||||
|
if ($m2ParentId > 0) {
|
||||||
|
$childrenCount = DB::connection($this->magento2Connection)
|
||||||
|
->table($this->magento2Prefix . 'catalog_category_entity')
|
||||||
|
->where('parent_id', $m2ParentId)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
DB::connection($this->magento2Connection)
|
||||||
|
->table($this->magento2Prefix . 'catalog_category_entity')
|
||||||
|
->where('entity_id', $m2ParentId)
|
||||||
|
->update(['children_count' => $childrenCount]);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::connection($this->magento2Connection)->commit();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Category migrated successfully',
|
||||||
|
'm1_category_id' => $m1CategoryId,
|
||||||
|
'm2_category_id' => $m2EntityId,
|
||||||
|
'category_name' => $m1Category->name ?? 'Unnamed Category',
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
DB::connection($this->magento2Connection)->rollBack();
|
||||||
|
Log::error("Error migrating single category {$m1CategoryId}: " . $e->getMessage());
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Migration failed: ' . $e->getMessage(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rename a Magento 2 category
|
* Rename a Magento 2 category
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,27 @@ .tree-error {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tree-migrate-btn {
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 0.75em;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 8px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-migrate-btn:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-migrate-btn:disabled {
|
||||||
|
background: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.tree-delete-btn {
|
.tree-delete-btn {
|
||||||
background: #dc3545;
|
background: #dc3545;
|
||||||
color: white;
|
color: white;
|
||||||
|
|
|
||||||
|
|
@ -264,9 +264,15 @@ function deleteCategory(categoryId, categoryName, source) {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
if (source === 'm1') {
|
if (source === 'm1') {
|
||||||
loadM1Tree();
|
loadM1Tree();
|
||||||
|
// Reload M1 Categories Not Found in M2 section
|
||||||
|
loadM1CategoriesNotInM2();
|
||||||
|
// Reload page to refresh statistics
|
||||||
|
location.reload();
|
||||||
} else {
|
} else {
|
||||||
loadM2Tree();
|
loadM2Tree();
|
||||||
// Reload page to refresh statistics and M2 Categories Not Found in M1 section
|
// Reload M2 Categories Not Found in M1 section
|
||||||
|
loadM2CategoriesNotInM1();
|
||||||
|
// Reload page to refresh statistics
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -403,6 +409,8 @@ function saveRenameCategory(nodeDiv, node, newName, source) {
|
||||||
|
|
||||||
// Reload M2 Categories Not Found in M1 section
|
// Reload M2 Categories Not Found in M1 section
|
||||||
loadM2CategoriesNotInM1();
|
loadM2CategoriesNotInM1();
|
||||||
|
// Reload M1 Categories Not Found in M2 section (in case renamed M2 category now matches an M1 category)
|
||||||
|
loadM1CategoriesNotInM2();
|
||||||
} else {
|
} else {
|
||||||
alert('Error: ' + (data.message || 'Failed to rename category'));
|
alert('Error: ' + (data.message || 'Failed to rename category'));
|
||||||
// Re-enable input and buttons
|
// Re-enable input and buttons
|
||||||
|
|
@ -530,6 +538,167 @@ function loadM2CategoriesNotInM1() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load M1 Categories Not Found in M2 section
|
||||||
|
function loadM1CategoriesNotInM2() {
|
||||||
|
const tbody = document.getElementById('m1-categories-not-in-m2-tbody');
|
||||||
|
const wrapper = document.getElementById('m1-categories-not-in-m2-table-wrapper');
|
||||||
|
const totalDiv = document.getElementById('m1-categories-not-in-m2-total');
|
||||||
|
const emptyDiv = document.getElementById('m1-categories-not-in-m2-empty');
|
||||||
|
|
||||||
|
if (!tbody && !wrapper && !totalDiv && !emptyDiv) {
|
||||||
|
// Section doesn't exist on this page
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(routes.m1CategoriesNotInM2)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
const categories = data.categories || [];
|
||||||
|
const count = data.count || 0;
|
||||||
|
|
||||||
|
// Remove existing content
|
||||||
|
if (tbody) tbody.innerHTML = '';
|
||||||
|
if (totalDiv) totalDiv.style.display = 'none';
|
||||||
|
if (emptyDiv) emptyDiv.style.display = 'none';
|
||||||
|
if (wrapper) wrapper.style.display = 'none';
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
// Show table and populate it
|
||||||
|
if (wrapper) wrapper.style.display = 'block';
|
||||||
|
if (totalDiv) {
|
||||||
|
totalDiv.style.display = 'block';
|
||||||
|
totalDiv.innerHTML = `<strong>Total:</strong> ${count} ${count === 1 ? 'category' : 'categories'} found in Magento 1 but not in Magento 2.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tbody) {
|
||||||
|
categories.forEach(category => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.style.borderBottom = '1px solid #dee2e6';
|
||||||
|
|
||||||
|
const activeClass = category.is_active ? 'active' : 'inactive';
|
||||||
|
const activeText = category.is_active ? 'Active' : 'Inactive';
|
||||||
|
|
||||||
|
let rootCategoryHtml = '<span style="color: #999;">N/A</span>';
|
||||||
|
if (category.root_category_name && category.root_category_name !== 'N/A') {
|
||||||
|
rootCategoryHtml = `<span style="font-weight: 500;">${escapeHtml(category.root_category_name)}</span>`;
|
||||||
|
if (category.root_category_id) {
|
||||||
|
rootCategoryHtml += ` <span style="font-size: 0.85em; color: #666;">(ID: ${category.root_category_id})</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create migrate button with event listener
|
||||||
|
const migrateBtn = document.createElement('button');
|
||||||
|
migrateBtn.className = 'tree-migrate-btn';
|
||||||
|
migrateBtn.textContent = 'Migrate';
|
||||||
|
migrateBtn.title = 'Migrate this category to Magento 2';
|
||||||
|
migrateBtn.setAttribute('data-migrate-id', category.entity_id);
|
||||||
|
migrateBtn.onclick = function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
migrateM1Category(category.entity_id, category.name, migrateBtn);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create delete button with event listener
|
||||||
|
const deleteBtn = document.createElement('button');
|
||||||
|
deleteBtn.className = 'tree-delete-btn';
|
||||||
|
deleteBtn.textContent = 'Delete';
|
||||||
|
deleteBtn.title = 'Delete this category';
|
||||||
|
deleteBtn.onclick = function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
showDeleteConfirmPopup(deleteBtn, category.entity_id, category.name, 'm1', false, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
row.innerHTML = `
|
||||||
|
<td style="padding: 10px 12px;">${category.entity_id}</td>
|
||||||
|
<td style="padding: 10px 12px; font-weight: 500;">${escapeHtml(category.name)}</td>
|
||||||
|
<td style="padding: 10px 12px;">${category.level}</td>
|
||||||
|
<td style="padding: 10px 12px;">
|
||||||
|
<span class="tree-badge ${activeClass}">${activeText}</span>
|
||||||
|
</td>
|
||||||
|
<td style="padding: 10px 12px;">${rootCategoryHtml}</td>
|
||||||
|
<td style="padding: 10px 12px; font-size: 0.9em; color: #666;">${escapeHtml(category.path)}</td>
|
||||||
|
<td style="padding: 10px 12px;"></td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Append buttons to the actions cell
|
||||||
|
const actionsCell = row.querySelector('td:last-child');
|
||||||
|
if (actionsCell) {
|
||||||
|
actionsCell.appendChild(migrateBtn);
|
||||||
|
actionsCell.appendChild(deleteBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Show empty message
|
||||||
|
if (emptyDiv) {
|
||||||
|
emptyDiv.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error loading M1 categories not in M2:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate a single M1 category to M2
|
||||||
|
window.migrateM1Category = function(categoryId, categoryName, buttonElement) {
|
||||||
|
if (!confirm(`Are you sure you want to migrate the category "${categoryName}" (ID: ${categoryId}) to Magento 2?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
|
||||||
|
const migrateRoute = routes.migrateCategory.replace(':id', categoryId);
|
||||||
|
|
||||||
|
// Disable the migrate button
|
||||||
|
const button = buttonElement || document.querySelector(`button[data-migrate-id="${categoryId}"]`);
|
||||||
|
if (button) {
|
||||||
|
button.disabled = true;
|
||||||
|
button.textContent = 'Migrating...';
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(migrateRoute, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ store_id: 0 })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
alert(`Category "${categoryName}" migrated successfully to Magento 2 (ID: ${data.m2_category_id})`);
|
||||||
|
|
||||||
|
// Reload both sections
|
||||||
|
loadM1CategoriesNotInM2();
|
||||||
|
loadM2CategoriesNotInM1();
|
||||||
|
|
||||||
|
// Reload M2 tree to show the new category
|
||||||
|
loadM2Tree();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + (data.message || 'Failed to migrate category'));
|
||||||
|
|
||||||
|
// Re-enable button
|
||||||
|
if (button) {
|
||||||
|
button.disabled = false;
|
||||||
|
button.textContent = 'Migrate';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert('Error: ' + error.message);
|
||||||
|
|
||||||
|
// Re-enable button
|
||||||
|
if (button) {
|
||||||
|
button.disabled = false;
|
||||||
|
button.textContent = 'Migrate';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Helper function to escape HTML
|
// Helper function to escape HTML
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,77 @@ class="tree-delete-btn"
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Missing Categories Section - M1 Not in M2 -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>⚠️ Magento 1 Categories Not Found in Magento 2</h2>
|
||||||
|
<p>These categories exist in Magento 1 but do not have a matching name in Magento 2:</p>
|
||||||
|
|
||||||
|
@if($m1CategoriesNotInM2->count() > 0)
|
||||||
|
<div id="m1-categories-not-in-m2-table-wrapper" 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>
|
||||||
|
<th style="padding: 12px; text-align: left; font-weight: 600; color: #333;">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="m1-categories-not-in-m2-tbody">
|
||||||
|
@foreach($m1CategoriesNotInM2 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>
|
||||||
|
<td style="padding: 10px 12px;">
|
||||||
|
<button
|
||||||
|
class="tree-migrate-btn"
|
||||||
|
data-migrate-id="{{ $category->entity_id }}"
|
||||||
|
onclick="migrateM1Category({{ $category->entity_id }}, '{{ addslashes($category->name ?? 'Unnamed Category') }}', this)"
|
||||||
|
title="Migrate this category to Magento 2">
|
||||||
|
Migrate
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="tree-delete-btn"
|
||||||
|
onclick="showDeleteConfirmPopup(this, {{ $category->entity_id }}, '{{ addslashes($category->name ?? 'Unnamed Category') }}', 'm1', false, 0)"
|
||||||
|
title="Delete this category">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="m1-categories-not-in-m2-total" style="margin-top: 15px; padding: 12px; background: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px;">
|
||||||
|
<strong>Total:</strong> {{ $m1CategoriesNotInM2->count() }} {{ Str::plural('category', $m1CategoriesNotInM2->count()) }} found in Magento 1 but not in Magento 2.
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div id="m1-categories-not-in-m2-empty" style="margin-top: 15px; padding: 15px; background: #d4edda; border-left: 4px solid #28a745; border-radius: 4px; color: #155724;">
|
||||||
|
✓ All Magento 1 categories have matching names in Magento 2.
|
||||||
|
</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>
|
||||||
|
|
@ -118,7 +189,9 @@ class="tree-delete-btn"
|
||||||
magento2CategoryTree: '{{ route("categories.magento2-category-tree") }}',
|
magento2CategoryTree: '{{ route("categories.magento2-category-tree") }}',
|
||||||
deleteCategory: '{{ route("categories.delete-category", ["categoryId" => ":id"]) }}',
|
deleteCategory: '{{ route("categories.delete-category", ["categoryId" => ":id"]) }}',
|
||||||
renameCategory: '{{ route("categories.rename-category", ["categoryId" => ":id"]) }}',
|
renameCategory: '{{ route("categories.rename-category", ["categoryId" => ":id"]) }}',
|
||||||
m2CategoriesNotInM1: '{{ route("categories.m2-categories-not-in-m1") }}'
|
migrateCategory: '{{ route("categories.migrate-category", ["categoryId" => ":id"]) }}',
|
||||||
|
m2CategoriesNotInM1: '{{ route("categories.m2-categories-not-in-m1") }}',
|
||||||
|
m1CategoriesNotInM2: '{{ route("categories.m1-categories-not-in-m2") }}'
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@
|
||||||
Route::get('/magento2-tree', [CategoriesController::class, 'getMagento2CategoryTree'])->name('magento2-category-tree');
|
Route::get('/magento2-tree', [CategoriesController::class, 'getMagento2CategoryTree'])->name('magento2-category-tree');
|
||||||
Route::get('/magento2-tree-with-products', [CategoriesController::class, 'getMagento2CategoryTreeWithProducts'])->name('magento2-category-tree-with-products');
|
Route::get('/magento2-tree-with-products', [CategoriesController::class, 'getMagento2CategoryTreeWithProducts'])->name('magento2-category-tree-with-products');
|
||||||
Route::get('/m2-not-in-m1', [CategoriesController::class, 'getM2CategoriesNotInM1'])->name('m2-categories-not-in-m1');
|
Route::get('/m2-not-in-m1', [CategoriesController::class, 'getM2CategoriesNotInM1'])->name('m2-categories-not-in-m1');
|
||||||
|
Route::get('/m1-not-in-m2', [CategoriesController::class, 'getM1CategoriesNotInM2'])->name('m1-categories-not-in-m2');
|
||||||
|
Route::post('/{categoryId}/migrate', [CategoriesController::class, 'migrateCategory'])->name('migrate-category');
|
||||||
Route::delete('/{categoryId}', [CategoriesController::class, 'deleteCategory'])->name('delete-category');
|
Route::delete('/{categoryId}', [CategoriesController::class, 'deleteCategory'])->name('delete-category');
|
||||||
Route::put('/{categoryId}/rename', [CategoriesController::class, 'renameCategory'])->name('rename-category');
|
Route::put('/{categoryId}/rename', [CategoriesController::class, 'renameCategory'])->name('rename-category');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue