added description details and logging info

This commit is contained in:
Chris Rosenau 2025-11-08 15:03:06 -07:00
parent af16940596
commit 5b74570731
4 changed files with 395 additions and 23 deletions

View File

@ -141,6 +141,33 @@ public function deleteCategory(Request $request, $categoryId)
}
}
/**
* Rename a Magento 2 category
*/
public function renameCategory(Request $request, $categoryId)
{
$request->validate([
'name' => 'required|string|max:255',
'store_id' => 'nullable|integer',
]);
try {
$newName = $request->input('name');
$storeId = $request->input('store_id', 0);
$result = $this->migrationService->renameCategory($categoryId, $newName, $storeId);
return response()->json($result, $result['success'] ? 200 : 400);
} catch (\Exception $e) {
Log::error('Category rename error: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Rename failed: ' . $e->getMessage(),
], 500);
}
}
/**
* Execute the migration
*/

View File

@ -780,6 +780,82 @@ protected function getMagento2AttributeIds()
return $attributes;
}
/**
* Rename a Magento 2 category
*/
public function renameCategory($categoryId, $newName, $storeId = 0)
{
try {
// Validate category exists
$category = DB::connection($this->magento2Connection)
->table($this->magento2Prefix . 'catalog_category_entity')
->where('entity_id', $categoryId)
->first();
if (!$category) {
return [
'success' => false,
'message' => 'Category not found',
];
}
// Prevent renaming system root
if ($categoryId == 0 || $categoryId == 1) {
return [
'success' => false,
'message' => 'Cannot rename system root category',
];
}
// Validate name
$newName = trim($newName);
if (empty($newName)) {
return [
'success' => false,
'message' => 'Category name cannot be empty',
];
}
// Get name attribute ID
$attributeIds = $this->getMagento2AttributeIds();
if (!isset($attributeIds['name'])) {
return [
'success' => false,
'message' => 'Name attribute not found',
];
}
DB::connection($this->magento2Connection)->beginTransaction();
// Update name attribute for the specified store (default store = 0)
$this->insertCategoryAttribute(
$categoryId,
$attributeIds['name'],
$storeId,
$newName
);
DB::connection($this->magento2Connection)->commit();
return [
'success' => true,
'message' => 'Category renamed successfully',
'new_name' => $newName,
];
} catch (Exception $e) {
if (isset($this->magento2Connection)) {
DB::connection($this->magento2Connection)->rollBack();
}
Log::error("Error renaming category {$categoryId}: " . $e->getMessage());
return [
'success' => false,
'message' => 'Failed to rename category: ' . $e->getMessage(),
];
}
}
/**
* Get Magento 2 root category ID
*/

View File

@ -420,6 +420,69 @@
background: #ccc;
cursor: not-allowed;
}
.tree-rename-btn {
background: #28a745;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
font-size: 0.75em;
cursor: pointer;
margin-left: 8px;
transition: background 0.2s;
}
.tree-rename-btn:hover {
background: #218838;
}
.tree-rename-input {
padding: 4px 8px;
border: 2px solid #28a745;
border-radius: 4px;
font-size: 0.9em;
width: 200px;
margin-left: 8px;
}
.tree-rename-input:focus {
outline: none;
border-color: #218838;
}
.tree-rename-actions {
display: inline-flex;
gap: 5px;
margin-left: 8px;
}
.tree-rename-save-btn, .tree-rename-cancel-btn {
padding: 4px 8px;
border: none;
border-radius: 4px;
font-size: 0.75em;
cursor: pointer;
transition: background 0.2s;
}
.tree-rename-save-btn {
background: #28a745;
color: white;
}
.tree-rename-save-btn:hover {
background: #218838;
}
.tree-rename-cancel-btn {
background: #6c757d;
color: white;
}
.tree-rename-cancel-btn:hover {
background: #5a6268;
}
</style>
</head>
<body>
@ -524,6 +587,20 @@
<!-- Actions -->
<div class="section">
<h2> Actions</h2>
<div class="info-box" style="margin-bottom: 20px;">
<h3 style="margin-bottom: 10px; color: #1976D2; font-size: 1.1em;">What happens when you click "Start Migration"?</h3>
<p style="margin: 5px 0; color: #1976D2;">The migration process will:</p>
<ul style="margin: 10px 0 0 20px; color: #1976D2; line-height: 1.8;">
<li><strong>Migrate category structure:</strong> Create categories in Magento 2 based on the hierarchy from Magento 1, preserving parent-child relationships and positions</li>
<li><strong>Migrate category attributes:</strong> For each mapped store, migrate category names, URL keys, and active status from Magento 1 to Magento 2</li>
<li><strong>Preserve hierarchy:</strong> Maintain the exact category tree structure with proper levels and paths</li>
<li><strong>Handle existing categories:</strong> If a category with the same name and parent already exists in Magento 2, it will be reused instead of creating a duplicate</li>
<li><strong>Generate logs:</strong> Provide detailed migration logs showing which categories were added, updated, or encountered errors</li>
</ul>
<p style="margin: 10px 0 0 0; color: #d32f2f; font-weight: 600;">⚠️ <strong>Warning:</strong> This will modify your Magento 2 database. Make sure you have a backup before proceeding.</p>
</div>
<div class="actions">
<button class="btn btn-primary" onclick="startMigration()" id="migrateBtn" {{ !$connectionTest['magento1'] || !$connectionTest['magento2'] ? 'disabled' : '' }}>
Start Migration
@ -537,9 +614,19 @@
<div class="spinner"></div>
<p style="margin-top: 15px;">Migration in progress...</p>
</div>
</div>
<div class="log-container" id="logContainer">
<div id="logContent"></div>
<!-- Migration Logs Section -->
<div class="section">
<h2>📋 Migration Logs</h2>
<p style="margin-bottom: 15px; color: #666;">Detailed logs showing which categories were added, updated, or encountered errors during migration:</p>
<div class="log-container" id="migrationLogContainer" style="display: block;">
<div id="migrationLogContent">
<div class="log-entry" style="color: #999; font-style: italic;">
No migration logs yet. Click "Start Migration" to begin.
</div>
</div>
</div>
</div>
</div>
@ -664,22 +751,18 @@ function testConnections() {
}
function previewCategories() {
fetch('{{ route("migration.magento1-categories") }}')
.then(response => response.json())
.then(data => {
if (data.success) {
let message = `Found ${data.count} categories in Magento 1.\n\nFirst 10 categories:\n\n`;
data.categories.forEach(cat => {
message += `ID: ${cat.id} - ${cat.name} (Level: ${cat.level})\n`;
});
alert(message);
} else {
alert('Failed to fetch categories');
}
})
.catch(error => {
alert('Error: ' + error.message);
});
// Find the Category Trees tab button and switch to it
const tabs = document.querySelectorAll('.tab');
let categoriesTab = null;
tabs.forEach(tab => {
if (tab.textContent.trim() === 'Category Trees') {
categoriesTab = tab;
}
});
if (categoriesTab) {
switchTab('categories', categoriesTab);
}
}
function startMigration() {
@ -705,8 +788,13 @@ function startMigration() {
document.getElementById('migrateBtn').disabled = true;
document.getElementById('loading').classList.add('active');
document.getElementById('logContainer').classList.add('active');
document.getElementById('logContent').innerHTML = '<div class="log-entry">Starting migration...</div>';
// Clear and initialize migration logs
const migrationLogContent = document.getElementById('migrationLogContent');
migrationLogContent.innerHTML = '<div class="log-entry">Starting migration...</div>';
// Scroll to logs section
document.getElementById('migrationLogContainer').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
fetch('{{ route("migration.migrate") }}', {
method: 'POST',
@ -754,12 +842,25 @@ function startMigration() {
}
function addLogEntry(message, type = '') {
const logContent = document.getElementById('logContent');
// Add to migration logs section (primary)
const migrationLogContent = document.getElementById('migrationLogContent');
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
entry.textContent = message;
logContent.appendChild(entry);
logContent.scrollTop = logContent.scrollHeight;
migrationLogContent.appendChild(entry);
// Auto-scroll to bottom
const migrationLogContainer = document.getElementById('migrationLogContainer');
migrationLogContainer.scrollTop = migrationLogContainer.scrollHeight;
// Also add to old log container if it exists (for backward compatibility)
const oldLogContent = document.getElementById('logContent');
if (oldLogContent) {
const oldEntry = document.createElement('div');
oldEntry.className = `log-entry ${type}`;
oldEntry.textContent = message;
oldLogContent.appendChild(oldEntry);
}
}
// Tab switching
@ -918,6 +1019,19 @@ function createTreeNode(node, source = 'm2') {
label.appendChild(labelText);
label.appendChild(badge);
// Add rename button for Magento 2 categories only (except system root)
if (canDelete && source === 'm2') {
const renameBtn = document.createElement('button');
renameBtn.className = 'tree-rename-btn';
renameBtn.textContent = 'Rename';
renameBtn.title = 'Rename this category';
renameBtn.onclick = function(e) {
e.stopPropagation();
startRenameCategory(nodeDiv, node, source);
};
label.appendChild(renameBtn);
}
// Add delete button for all categories (except system root)
if (canDelete) {
const deleteBtn = document.createElement('button');
@ -972,6 +1086,160 @@ function toggleNode(toggleElement) {
}
}
}
// Start renaming a category
function startRenameCategory(nodeDiv, node, source) {
const label = nodeDiv.querySelector('.tree-label');
const labelText = label.querySelector('.tree-label-text');
const originalName = node.name || 'Unnamed Category';
// Hide the label text and badge
labelText.style.display = 'none';
const badge = label.querySelector('.tree-badge');
if (badge) badge.style.display = 'none';
// Hide rename and delete buttons
const renameBtn = label.querySelector('.tree-rename-btn');
const deleteBtn = label.querySelector('.tree-delete-btn');
if (renameBtn) renameBtn.style.display = 'none';
if (deleteBtn) deleteBtn.style.display = 'none';
// Create input field
const input = document.createElement('input');
input.type = 'text';
input.className = 'tree-rename-input';
input.value = originalName;
input.onclick = function(e) {
e.stopPropagation();
};
// Create action buttons
const actionsDiv = document.createElement('div');
actionsDiv.className = 'tree-rename-actions';
const saveBtn = document.createElement('button');
saveBtn.className = 'tree-rename-save-btn';
saveBtn.textContent = 'Save';
saveBtn.onclick = function(e) {
e.stopPropagation();
saveRenameCategory(nodeDiv, node, input.value, source);
};
const cancelBtn = document.createElement('button');
cancelBtn.className = 'tree-rename-cancel-btn';
cancelBtn.textContent = 'Cancel';
cancelBtn.onclick = function(e) {
e.stopPropagation();
cancelRenameCategory(nodeDiv, labelText, badge, renameBtn, deleteBtn);
};
actionsDiv.appendChild(saveBtn);
actionsDiv.appendChild(cancelBtn);
label.appendChild(input);
label.appendChild(actionsDiv);
// Focus and select input
input.focus();
input.select();
// Handle Enter and Escape keys
input.onkeydown = function(e) {
if (e.key === 'Enter') {
e.preventDefault();
saveRenameCategory(nodeDiv, node, input.value, source);
} else if (e.key === 'Escape') {
e.preventDefault();
cancelRenameCategory(nodeDiv, labelText, badge, renameBtn, deleteBtn);
}
};
}
// Save category rename
function saveRenameCategory(nodeDiv, node, newName, source) {
const categoryId = node.id;
const label = nodeDiv.querySelector('.tree-label');
const input = label.querySelector('.tree-rename-input');
const actionsDiv = label.querySelector('.tree-rename-actions');
if (!newName || newName.trim() === '') {
alert('Category name cannot be empty');
return;
}
// Disable input and buttons
input.disabled = true;
const saveBtn = actionsDiv.querySelector('.tree-rename-save-btn');
const cancelBtn = actionsDiv.querySelector('.tree-rename-cancel-btn');
if (saveBtn) saveBtn.disabled = true;
if (cancelBtn) cancelBtn.disabled = true;
const url = `{{ route("migration.rename-category", ["categoryId" => ":id"]) }}`.replace(':id', categoryId);
fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
body: JSON.stringify({ name: newName.trim() })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update the node name
node.name = data.new_name;
// Update the label text
const labelText = label.querySelector('.tree-label-text');
const productCount = node.product_count !== undefined ? node.product_count : 0;
labelText.textContent = `${data.new_name} (${productCount})`;
// Restore display
labelText.style.display = '';
const badge = label.querySelector('.tree-badge');
if (badge) badge.style.display = '';
const renameBtn = label.querySelector('.tree-rename-btn');
const deleteBtn = label.querySelector('.tree-delete-btn');
if (renameBtn) renameBtn.style.display = '';
if (deleteBtn) deleteBtn.style.display = '';
// Remove input and actions
label.removeChild(input);
label.removeChild(actionsDiv);
} else {
alert('Error: ' + (data.message || 'Failed to rename category'));
// Re-enable input and buttons
input.disabled = false;
if (saveBtn) saveBtn.disabled = false;
if (cancelBtn) cancelBtn.disabled = false;
}
})
.catch(error => {
alert('Error: ' + error.message);
// Re-enable input and buttons
input.disabled = false;
if (saveBtn) saveBtn.disabled = false;
if (cancelBtn) cancelBtn.disabled = false;
});
}
// Cancel category rename
function cancelRenameCategory(nodeDiv, labelText, badge, renameBtn, deleteBtn) {
const label = nodeDiv.querySelector('.tree-label');
const input = label.querySelector('.tree-rename-input');
const actionsDiv = label.querySelector('.tree-rename-actions');
// Restore display
labelText.style.display = '';
if (badge) badge.style.display = '';
if (renameBtn) renameBtn.style.display = '';
if (deleteBtn) deleteBtn.style.display = '';
// Remove input and actions
if (input) label.removeChild(input);
if (actionsDiv) label.removeChild(actionsDiv);
}
</script>
</body>
</html>

View File

@ -15,4 +15,5 @@
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');
Route::put('/category/{categoryId}/rename', [MagentoMigrationController::class, 'renameCategory'])->name('migration.rename-category');
});