fix for missing product columns

This commit is contained in:
DDEV User 2025-11-23 09:32:23 -07:00
parent e77f4447b4
commit d195fd62ab
5 changed files with 1201 additions and 15 deletions

View File

@ -147,5 +147,30 @@ public function fixCategoryProducts(Request $request)
], 500);
}
}
/**
* Migrate product options from M1 mageworx_custom_option_ tables to M2 mageworx_optiontemplates_ tables
*/
public function migrateProductOptions(Request $request)
{
try {
$dryRun = $request->input('dry_run', false);
$result = $this->migrationService->migrateProductOptions($dryRun);
return response()->json($result, $result['success'] ? 200 : 400);
} catch (\Exception $e) {
Log::error('Product options migration error: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Migration failed: ' . $e->getMessage(),
'migrated' => 0,
'skipped' => 0,
'errors' => 0,
'log' => []
], 500);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -452,8 +452,103 @@ function createCategoryWithProductsNode(node) {
return nodeDiv;
}
function startProductOptionsMigration(dryRun) {
// Check if routes are available
if (!routes || !routes.migrateProductOptions) {
console.error('Product options routes not available', routes);
alert('Error: Routes not initialized. Please refresh the page.');
return;
}
const button = dryRun ? document.getElementById('dryRunProductOptionsMigrationBtn') : document.getElementById('startProductOptionsMigrationBtn');
const otherButton = dryRun ? document.getElementById('startProductOptionsMigrationBtn') : document.getElementById('dryRunProductOptionsMigrationBtn');
const logContainer = document.getElementById('productOptionsMigrationLogContainer');
const logContent = document.getElementById('productOptionsMigrationLogContent');
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';
logContainer.style.display = 'block';
logContent.innerHTML = '<div class="log-entry">' + (dryRun ? 'Running dry run...' : 'Starting migration...') + '</div>';
fetch(routes.migrateProductOptions, {
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;
if (otherButton) {
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! Migrated: ${data.migrated || 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('WARNING') ? 'warning' : (log.includes('INFO') ? 'info' : 'success')));
entry.textContent = log;
logContent.appendChild(entry);
});
}
logContent.scrollTop = logContent.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);
if (data.log && data.log.length > 0) {
data.log.forEach(log => {
const entry = document.createElement('div');
entry.className = 'log-entry ' + (log.includes('ERROR') ? 'error' : 'warning');
entry.textContent = log;
logContent.appendChild(entry);
});
}
}
})
.catch(error => {
button.disabled = false;
if (otherButton) {
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);
});
}
// Make functions available globally
window.startProductMigration = startProductMigration;
window.startProductOptionsMigration = startProductOptionsMigration;
window.deleteM2Product = deleteM2Product;
window.deleteProductsAboveM1Max = deleteProductsAboveM1Max;
window.syncProductCategories = syncProductCategories;

View File

@ -12,6 +12,7 @@
<li><strong>Update existing products:</strong> If a product with the same SKU already exists in Magento 2, it will be updated with the latest data from Magento 1</li>
<li><strong>Migrate product attributes:</strong> All product attributes including SKU, name, description, price, weight, status, visibility, and tax class will be migrated</li>
<li><strong>Assign to categories:</strong> Products will be assigned to the corresponding categories in Magento 2</li>
<li><strong>Migrate product options:</strong> All <code>catalog_product_option</code> tables will be migrated, including options, prices, titles, and type values</li>
<li><strong>Generate logs:</strong> Detailed migration logs showing which products 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>
@ -30,6 +31,52 @@
</div>
</div>
<!-- Migration Logs Section -->
<div class="section" style="margin-top: 30px;">
<h2>📋 Product Migration Logs</h2>
<p style="margin-bottom: 15px; color: #666;">Detailed logs showing which products were added, updated, or encountered errors during migration:</p>
<div class="log-container" id="productMigrationLogContainer" style="display: block;">
<div id="productMigrationLogContent">
<div class="log-entry" style="color: #999; font-style: italic;">
No product migration logs yet. Click "Run Dry Run" or "Start Product Migration" to begin.
</div>
</div>
</div>
</div>
<!-- Migrate Product Options Section -->
<div class="section" style="margin-top: 30px;">
<h2>📦 Migrate Product Options</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 Product Options Migration"?</h3>
<p style="margin: 5px 0; color: #1976D2;">The product options migration process will:</p>
<ul style="margin: 10px 0 0 20px; color: #1976D2; line-height: 1.8;">
<li><strong>Find mageworx tables:</strong> Automatically discovers all <code>mageworx_custom_option_*</code> tables in Magento 1</li>
<li><strong>Map to M2 tables:</strong> Maps M1 tables to corresponding <code>mageworx_optiontemplates_*</code> tables in Magento 2</li>
<li><strong>Migrate data:</strong> Migrates all records from M1 mageworx custom option tables to M2 option templates tables</li>
<li><strong>Update existing records:</strong> If a record already exists in M2 (by primary key), it will be updated</li>
<li><strong>Insert new records:</strong> New records will be inserted into M2 tables</li>
<li><strong>Generate logs:</strong> Detailed migration logs showing which tables and records were migrated</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 style="margin-top: 20px; display: flex; gap: 15px; flex-wrap: wrap;">
<button id="dryRunProductOptionsMigrationBtn" class="btn btn-secondary" onclick="startProductOptionsMigration(true)">
Run Dry Run (Check for Errors)
</button>
<button id="startProductOptionsMigrationBtn" class="btn btn-primary" onclick="startProductOptionsMigration(false)">
Start Product Options Migration
</button>
</div>
<div class="log-container" id="productOptionsMigrationLogContainer" style="display: none; margin-top: 20px; overflow: visible; max-height: none;">
<h3 style="margin-bottom: 10px; color: white;">Migration Logs</h3>
<div id="productOptionsMigrationLogContent" style="max-height: 400px; overflow-y: auto; background: #000000; color: #ffffff; padding: 15px; border-radius: 4px; font-family: monospace; font-size: 0.9em;">
</div>
</div>
</div>
<!-- Missing Attributes Section -->
<div class="section" id="missingAttributesSection" style="margin-top: 30px; display: none;">
<h2>⚠️ Missing Attributes</h2>
@ -49,19 +96,6 @@
</div>
</div>
<!-- Migration Logs Section -->
<div class="section" style="margin-top: 30px;">
<h2>📋 Product Migration Logs</h2>
<p style="margin-bottom: 15px; color: #666;">Detailed logs showing which products were added, updated, or encountered errors during migration:</p>
<div class="log-container" id="productMigrationLogContainer" style="display: block;">
<div id="productMigrationLogContent">
<div class="log-entry" style="color: #999; font-style: italic;">
No product migration logs yet. Click "Run Dry Run" or "Start Product Migration" to begin.
</div>
</div>
</div>
</div>
<!-- Product Statistics -->
<div class="section" style="margin-top: 30px;">
<h2>📊 Product Statistics</h2>
@ -247,6 +281,7 @@ class="btn btn-danger"
<script>
window.productRoutes = {
migrateProducts: '{{ route("products.migrate-products") }}',
migrateProductOptions: '{{ route("products.migrate-product-options") }}',
deleteProduct: '{{ route("products.delete-product", ["productId" => ":id"]) }}',
deleteProductsAboveM1Max: '{{ route("products.delete-products-above-m1-max") }}',
syncProductCategories: '{{ route("products.sync-product-categories") }}',

View File

@ -50,6 +50,7 @@
Route::prefix('products')->name('products.')->group(function () {
Route::get('/', [ProductsController::class, 'index'])->name('index');
Route::post('/migrate', [ProductsController::class, 'migrateProducts'])->name('migrate-products');
Route::post('/migrate-options', [ProductsController::class, 'migrateProductOptions'])->name('migrate-product-options');
Route::post('/sync-categories', [ProductsController::class, 'syncProductCategories'])->name('sync-product-categories');
Route::post('/fix-category-products', [ProductsController::class, 'fixCategoryProducts'])->name('fix-category-products');
Route::delete('/{productId}', [ProductsController::class, 'deleteM2Product'])->name('delete-product');