464 lines
17 KiB
JavaScript
464 lines
17 KiB
JavaScript
// Products page JavaScript
|
|
|
|
let routes = {};
|
|
let csrfToken = '';
|
|
|
|
// Initialize on page load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (window.productRoutes) {
|
|
routes = window.productRoutes;
|
|
csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
|
|
}
|
|
|
|
// Attach event listeners to buttons
|
|
const fixCategoryProductsBtn = document.getElementById('fixCategoryProductsBtn');
|
|
if (fixCategoryProductsBtn) {
|
|
fixCategoryProductsBtn.addEventListener('click', fixCategoryProducts);
|
|
}
|
|
});
|
|
|
|
function startProductMigration(dryRun) {
|
|
// Check if routes are available
|
|
if (!routes || !routes.migrateProducts) {
|
|
console.error('Product routes not available', routes);
|
|
alert('Error: Routes not initialized. Please refresh the page.');
|
|
return;
|
|
}
|
|
|
|
const button = dryRun ? document.getElementById('dryRunProductMigrationBtn') : document.getElementById('startProductMigrationBtn');
|
|
const otherButton = dryRun ? document.getElementById('startProductMigrationBtn') : document.getElementById('dryRunProductMigrationBtn');
|
|
|
|
if (!button) {
|
|
console.error('Button not found');
|
|
alert('Error: Button not found. Please refresh the page.');
|
|
return;
|
|
}
|
|
|
|
const originalText = button.textContent;
|
|
button.disabled = true;
|
|
if (otherButton) {
|
|
otherButton.disabled = true;
|
|
}
|
|
button.textContent = dryRun ? 'Running Dry Run...' : 'Migrating...';
|
|
button.style.cursor = 'not-allowed';
|
|
|
|
const logContent = document.getElementById('productMigrationLogContent');
|
|
if (!logContent) {
|
|
console.error('Log content element not found');
|
|
button.disabled = false;
|
|
if (otherButton) {
|
|
otherButton.disabled = false;
|
|
}
|
|
button.textContent = originalText;
|
|
return;
|
|
}
|
|
|
|
logContent.innerHTML = '<div class="log-entry">' + (dryRun ? 'Running dry run...' : 'Starting migration...') + '</div>';
|
|
|
|
fetch(routes.migrateProducts, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken
|
|
},
|
|
body: JSON.stringify({ dry_run: dryRun })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
button.disabled = false;
|
|
otherButton.disabled = false;
|
|
button.textContent = originalText;
|
|
button.style.cursor = 'pointer';
|
|
|
|
if (data.success) {
|
|
const successEntry = document.createElement('div');
|
|
successEntry.className = 'log-entry success';
|
|
successEntry.textContent = `✓ ${dryRun ? 'Dry run' : 'Migration'} completed! Added: ${data.added || 0}, Updated: ${data.updated || 0}, Errors: ${data.errors || 0}`;
|
|
logContent.appendChild(successEntry);
|
|
|
|
if (data.log && data.log.length > 0) {
|
|
data.log.forEach(log => {
|
|
const entry = document.createElement('div');
|
|
entry.className = 'log-entry ' + (log.includes('ERROR') ? 'error' : 'success');
|
|
entry.textContent = log;
|
|
logContent.appendChild(entry);
|
|
});
|
|
}
|
|
|
|
if (data.missing_attributes && data.missing_attributes.length > 0) {
|
|
const missingSection = document.getElementById('missingAttributesSection');
|
|
const tbody = document.getElementById('missingAttributesTableBody');
|
|
tbody.innerHTML = '';
|
|
data.missing_attributes.forEach(attr => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${attr.attribute_code}</td>
|
|
<td>${attr.frontend_label || 'N/A'}</td>
|
|
<td>${attr.backend_type || 'N/A'}</td>
|
|
`;
|
|
tbody.appendChild(row);
|
|
});
|
|
missingSection.style.display = 'block';
|
|
}
|
|
|
|
const logContainer = document.getElementById('productMigrationLogContainer');
|
|
logContainer.scrollTop = logContainer.scrollHeight;
|
|
} else {
|
|
const errorEntry = document.createElement('div');
|
|
errorEntry.className = 'log-entry error';
|
|
errorEntry.textContent = '✗ ' + (dryRun ? 'Dry run' : 'Migration') + ' failed: ' + (data.message || 'Unknown error');
|
|
logContent.appendChild(errorEntry);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
button.disabled = false;
|
|
otherButton.disabled = false;
|
|
button.textContent = originalText;
|
|
button.style.cursor = 'pointer';
|
|
|
|
const errorEntry = document.createElement('div');
|
|
errorEntry.className = 'log-entry error';
|
|
errorEntry.textContent = '✗ Error: ' + error.message;
|
|
logContent.appendChild(errorEntry);
|
|
});
|
|
}
|
|
|
|
function deleteM2Product(productId, productName, productSku) {
|
|
if (!confirm(`Are you sure you want to delete product "${productName}" (SKU: ${productSku})?`)) {
|
|
return;
|
|
}
|
|
|
|
const url = routes.deleteProduct.replace(':id', productId);
|
|
|
|
fetch(url, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Product deleted successfully!');
|
|
location.reload();
|
|
} else {
|
|
alert('Error: ' + (data.message || 'Failed to delete product'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('Error: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function deleteProductsAboveM1Max() {
|
|
if (!confirm('Are you sure you want to delete all Magento 2 products with entity_id greater than the maximum Magento 1 product ID? This action cannot be undone.')) {
|
|
return;
|
|
}
|
|
|
|
const button = document.getElementById('deleteProductsAboveM1MaxBtn');
|
|
const originalText = button.textContent;
|
|
button.disabled = true;
|
|
button.textContent = 'Deleting...';
|
|
|
|
fetch(routes.deleteProductsAboveM1Max, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
button.disabled = false;
|
|
button.textContent = originalText;
|
|
|
|
if (data.success) {
|
|
alert(`Successfully deleted ${data.deleted || 0} products.`);
|
|
location.reload();
|
|
} else {
|
|
alert('Error: ' + (data.message || 'Failed to delete products'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
button.disabled = false;
|
|
button.textContent = originalText;
|
|
alert('Error: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function syncProductCategories() {
|
|
if (!confirm('Are you sure you want to sync product category assignments from Magento 1 to Magento 2?')) {
|
|
return;
|
|
}
|
|
|
|
const button = document.getElementById('syncProductCategoriesBtn');
|
|
const originalText = button.textContent;
|
|
button.disabled = true;
|
|
button.textContent = 'Syncing...';
|
|
|
|
fetch(routes.syncProductCategories, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
button.disabled = false;
|
|
button.textContent = originalText;
|
|
|
|
if (data.success) {
|
|
alert(`Sync completed! Updated: ${data.updated || 0}, Skipped: ${data.skipped || 0}, Errors: ${data.errors || 0}`);
|
|
} else {
|
|
alert('Error: ' + (data.message || 'Sync failed'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
button.disabled = false;
|
|
button.textContent = originalText;
|
|
alert('Error: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function fixCategoryProducts() {
|
|
if (!confirm('Are you sure you want to fix category products? This will add missing products to the catalog_category_product table based on their category_ids attribute in Magento 2.')) {
|
|
return;
|
|
}
|
|
|
|
const button = document.getElementById('fixCategoryProductsBtn');
|
|
const logContainer = document.getElementById('fixCategoryProductsLogContainer');
|
|
const logContent = document.getElementById('fixCategoryProductsLogContent');
|
|
|
|
if (!button) {
|
|
console.error('fixCategoryProductsBtn not found');
|
|
return;
|
|
}
|
|
|
|
if (!routes.fixCategoryProducts) {
|
|
console.error('routes.fixCategoryProducts not found', routes);
|
|
alert('Error: Route not configured. Please refresh the page.');
|
|
return;
|
|
}
|
|
|
|
const originalText = button.textContent;
|
|
button.disabled = true;
|
|
button.textContent = 'Fixing...';
|
|
logContainer.style.display = 'block';
|
|
logContent.innerHTML = '<div class="log-entry">Starting fix process...</div>';
|
|
|
|
fetch(routes.fixCategoryProducts, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
button.disabled = false;
|
|
button.textContent = originalText;
|
|
|
|
if (data.success) {
|
|
const successEntry = document.createElement('div');
|
|
successEntry.className = 'log-entry success';
|
|
successEntry.textContent = `✓ Fix completed! Added: ${data.added || 0}, Skipped: ${data.skipped || 0}, Errors: ${data.errors || 0}`;
|
|
logContent.appendChild(successEntry);
|
|
|
|
if (data.log && data.log.length > 0) {
|
|
data.log.forEach(log => {
|
|
const entry = document.createElement('div');
|
|
entry.className = 'log-entry ' + (log.includes('ERROR') ? 'error' : (log.includes('ADDED') ? 'success' : 'info'));
|
|
entry.textContent = log;
|
|
logContent.appendChild(entry);
|
|
});
|
|
}
|
|
|
|
logContent.scrollTop = logContent.scrollHeight;
|
|
} else {
|
|
const errorEntry = document.createElement('div');
|
|
errorEntry.className = 'log-entry error';
|
|
errorEntry.textContent = '✗ Fix failed: ' + (data.message || 'Unknown error');
|
|
logContent.appendChild(errorEntry);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
button.disabled = false;
|
|
button.textContent = originalText;
|
|
|
|
const errorEntry = document.createElement('div');
|
|
errorEntry.className = 'log-entry error';
|
|
errorEntry.textContent = '✗ Error: ' + error.message;
|
|
logContent.appendChild(errorEntry);
|
|
});
|
|
}
|
|
|
|
function loadM1CategoryTreeWithProducts() {
|
|
const container = document.getElementById('m1-category-products-tree-container');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = '<div class="tree-loading">Loading categories and products...</div>';
|
|
|
|
fetch(routes.magento1CategoryTreeWithProducts)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
container.innerHTML = '';
|
|
if (data.tree && data.tree.length > 0) {
|
|
renderCategoryTreeWithProducts(container, data.tree);
|
|
} else {
|
|
container.innerHTML = '<div class="tree-loading">No categories found</div>';
|
|
}
|
|
} else {
|
|
container.innerHTML = `<div class="tree-error">Error: ${data.message || 'Failed to load categories'}</div>`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
container.innerHTML = `<div class="tree-error">Error: ${error.message}</div>`;
|
|
});
|
|
}
|
|
|
|
function loadM2CategoryTreeWithProducts() {
|
|
const container = document.getElementById('m2-category-products-tree-container');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = '<div class="tree-loading">Loading categories and products...</div>';
|
|
|
|
fetch(routes.magento2CategoryTreeWithProducts)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
container.innerHTML = '';
|
|
if (data.tree && data.tree.length > 0) {
|
|
renderCategoryTreeWithProducts(container, data.tree);
|
|
} else {
|
|
container.innerHTML = '<div class="tree-loading">No categories found</div>';
|
|
}
|
|
} else {
|
|
container.innerHTML = `<div class="tree-error">Error: ${data.message || 'Failed to load categories'}</div>`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
container.innerHTML = `<div class="tree-error">Error: ${error.message}</div>`;
|
|
});
|
|
}
|
|
|
|
function renderCategoryTreeWithProducts(container, tree) {
|
|
tree.forEach(node => {
|
|
const nodeElement = createCategoryWithProductsNode(node);
|
|
container.appendChild(nodeElement);
|
|
});
|
|
}
|
|
|
|
function toggleNode(toggleElement) {
|
|
const nodeItem = toggleElement.parentElement;
|
|
const nodeDiv = nodeItem.parentElement;
|
|
const childrenDivs = nodeDiv.querySelectorAll('.tree-children');
|
|
|
|
if (childrenDivs.length > 0) {
|
|
let isExpanded = false;
|
|
childrenDivs.forEach(div => {
|
|
if (div.style.display !== 'none') {
|
|
isExpanded = true;
|
|
}
|
|
});
|
|
|
|
if (isExpanded) {
|
|
childrenDivs.forEach(div => {
|
|
div.style.display = 'none';
|
|
});
|
|
toggleElement.classList.remove('expanded');
|
|
toggleElement.classList.add('collapsed');
|
|
} else {
|
|
childrenDivs.forEach(div => {
|
|
div.style.display = 'block';
|
|
});
|
|
toggleElement.classList.remove('collapsed');
|
|
toggleElement.classList.add('expanded');
|
|
}
|
|
}
|
|
}
|
|
|
|
function createCategoryWithProductsNode(node) {
|
|
const nodeDiv = document.createElement('div');
|
|
nodeDiv.className = 'tree-node';
|
|
const hasChildren = node.children && node.children.length > 0;
|
|
const hasProducts = node.products && node.products.length > 0;
|
|
const productCount = node.product_count !== undefined ? node.product_count : (node.products ? node.products.length : 0);
|
|
|
|
const itemDiv = document.createElement('div');
|
|
itemDiv.className = 'tree-node-item';
|
|
|
|
const toggle = document.createElement('span');
|
|
toggle.className = (hasChildren || hasProducts) ? 'tree-toggle collapsed' : 'tree-toggle leaf';
|
|
if (hasChildren || hasProducts) {
|
|
toggle.onclick = function(e) {
|
|
e.stopPropagation();
|
|
toggleNode(this);
|
|
};
|
|
}
|
|
|
|
const label = document.createElement('div');
|
|
label.className = 'tree-label';
|
|
|
|
const labelText = document.createElement('span');
|
|
labelText.className = 'tree-label-text';
|
|
labelText.textContent = `[${node.id}] ${node.name || 'Unnamed Category'} (${productCount} products)`;
|
|
|
|
const badge = document.createElement('span');
|
|
badge.className = `tree-badge ${node.is_active ? 'active' : 'inactive'}`;
|
|
badge.textContent = node.is_active ? 'Active' : 'Inactive';
|
|
|
|
label.appendChild(labelText);
|
|
label.appendChild(badge);
|
|
|
|
itemDiv.appendChild(toggle);
|
|
itemDiv.appendChild(label);
|
|
nodeDiv.appendChild(itemDiv);
|
|
|
|
// Add products section if products exist
|
|
if (hasProducts) {
|
|
const productsDiv = document.createElement('div');
|
|
productsDiv.className = 'tree-children';
|
|
productsDiv.style.display = 'none';
|
|
|
|
const productsHeader = document.createElement('div');
|
|
productsHeader.style.cssText = 'padding: 8px 12px; font-weight: 600; color: #667eea; background: #f0f0f0; border-radius: 4px; margin: 5px 0;';
|
|
productsHeader.textContent = `Products (${node.products.length}):`;
|
|
productsDiv.appendChild(productsHeader);
|
|
|
|
node.products.forEach(product => {
|
|
const productDiv = document.createElement('div');
|
|
productDiv.style.cssText = 'padding: 6px 12px 6px 30px; font-size: 0.9em; color: #666; border-left: 2px solid #e0e0e0; margin-left: 20px;';
|
|
productDiv.textContent = `ID: ${product.id || product.product_id || 'N/A'} | SKU: ${product.sku || 'N/A'} | Name: ${product.name || 'Unnamed Product'}`;
|
|
productsDiv.appendChild(productDiv);
|
|
});
|
|
|
|
nodeDiv.appendChild(productsDiv);
|
|
}
|
|
|
|
// Add children
|
|
if (hasChildren) {
|
|
const childrenDiv = document.createElement('div');
|
|
childrenDiv.className = 'tree-children';
|
|
childrenDiv.style.display = 'none';
|
|
node.children.forEach(child => {
|
|
childrenDiv.appendChild(createCategoryWithProductsNode(child));
|
|
});
|
|
nodeDiv.appendChild(childrenDiv);
|
|
}
|
|
|
|
return nodeDiv;
|
|
}
|
|
|
|
// Make functions available globally
|
|
window.startProductMigration = startProductMigration;
|
|
window.deleteM2Product = deleteM2Product;
|
|
window.deleteProductsAboveM1Max = deleteProductsAboveM1Max;
|
|
window.syncProductCategories = syncProductCategories;
|
|
window.fixCategoryProducts = fixCategoryProducts;
|
|
window.loadM1CategoryTreeWithProducts = loadM1CategoryTreeWithProducts;
|
|
window.loadM2CategoryTreeWithProducts = loadM2CategoryTreeWithProducts;
|
|
|