added description details and logging info
This commit is contained in:
parent
af16940596
commit
5b74570731
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue