diff --git a/app/Http/Controllers/MagentoMigrationController.php b/app/Http/Controllers/MagentoMigrationController.php index e764bdf..80ec00c 100644 --- a/app/Http/Controllers/MagentoMigrationController.php +++ b/app/Http/Controllers/MagentoMigrationController.php @@ -28,6 +28,7 @@ public function index() $m2CategoriesNotInM1 = $this->migrationService->getM2CategoriesNotInM1(); $m1Attributes = $this->migrationService->getMagento1Attributes(); $m2Attributes = $this->migrationService->getMagento2Attributes(); + $m1AttributesMissingInM2 = $this->migrationService->getM1AttributesMissingInM2(); $m1AttributeGroups = $this->migrationService->getMagento1AttributeGroups(); $m2AttributeGroups = $this->migrationService->getMagento2AttributeGroups(); @@ -40,6 +41,7 @@ public function index() 'm2CategoriesNotInM1' => $m2CategoriesNotInM1, 'm1Attributes' => $m1Attributes, 'm2Attributes' => $m2Attributes, + 'm1AttributesMissingInM2' => $m1AttributesMissingInM2, 'm1AttributeGroups' => $m1AttributeGroups, 'm2AttributeGroups' => $m2AttributeGroups, ]); @@ -176,6 +178,26 @@ public function renameCategory(Request $request, $categoryId) } } + /** + * Migrate an attribute from Magento 1 to Magento 2 + */ + public function migrateAttribute(Request $request, $attributeId) + { + try { + $result = $this->migrationService->migrateAttribute($attributeId); + + return response()->json($result, $result['success'] ? 200 : 400); + + } catch (\Exception $e) { + Log::error('Attribute migration error: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Migration failed: ' . $e->getMessage(), + ], 500); + } + } + /** * Execute the migration */ diff --git a/app/Services/MagentoCategoryMigrationService.php b/app/Services/MagentoCategoryMigrationService.php index 023e096..ae1a876 100644 --- a/app/Services/MagentoCategoryMigrationService.php +++ b/app/Services/MagentoCategoryMigrationService.php @@ -1030,6 +1030,154 @@ public function getMagento2Attributes() } } + /** + * Get attributes that exist in Magento 1 but are missing in Magento 2 + */ + public function getM1AttributesMissingInM2() + { + try { + $m1Attributes = $this->getMagento1Attributes(); + $m2Attributes = $this->getMagento2Attributes(); + + // Get all M2 attribute codes + $m2AttributeCodes = $m2Attributes->pluck('attribute_code')->toArray(); + + // Filter M1 attributes that don't exist in M2 + $missingAttributes = $m1Attributes->filter(function ($attr) use ($m2AttributeCodes) { + return !in_array($attr->attribute_code, $m2AttributeCodes); + }); + + return $missingAttributes->values(); + } catch (Exception $e) { + Log::error('Error fetching missing attributes: ' . $e->getMessage()); + return collect([]); + } + } + + /** + * Migrate an attribute from Magento 1 to Magento 2 + */ + public function migrateAttribute($m1AttributeId) + { + try { + // Get M1 entity type ID + $m1EntityTypeId = DB::connection($this->magento1Connection) + ->table($this->magento1Prefix . 'eav_entity_type') + ->where('entity_type_code', 'catalog_category') + ->value('entity_type_id'); + + if (!$m1EntityTypeId) { + return ['success' => false, 'message' => 'Magento 1 entity type not found']; + } + + // Get full attribute data from M1 + $m1Attribute = DB::connection($this->magento1Connection) + ->table($this->magento1Prefix . 'eav_attribute') + ->where('attribute_id', $m1AttributeId) + ->where('entity_type_id', $m1EntityTypeId) + ->first(); + + if (!$m1Attribute) { + return ['success' => false, 'message' => 'Attribute not found in Magento 1']; + } + + // Check if attribute already exists in M2 + $m2EntityTypeId = DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'eav_entity_type') + ->where('entity_type_code', 'catalog_category') + ->value('entity_type_id'); + + if (!$m2EntityTypeId) { + return ['success' => false, 'message' => 'Magento 2 entity type not found']; + } + + $existingAttribute = DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'eav_attribute') + ->where('entity_type_id', $m2EntityTypeId) + ->where('attribute_code', $m1Attribute->attribute_code) + ->first(); + + if ($existingAttribute) { + return ['success' => false, 'message' => 'Attribute already exists in Magento 2']; + } + + DB::connection($this->magento2Connection)->beginTransaction(); + + // Insert attribute into M2 + $m2AttributeId = DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'eav_attribute') + ->insertGetId([ + 'entity_type_id' => $m2EntityTypeId, + 'attribute_code' => $m1Attribute->attribute_code, + 'attribute_model' => $m1Attribute->attribute_model ?? null, + 'backend_model' => $m1Attribute->backend_model ?? null, + 'backend_type' => $m1Attribute->backend_type, + 'backend_table' => $m1Attribute->backend_table ?? null, + 'frontend_model' => $m1Attribute->frontend_model ?? null, + 'frontend_input' => $m1Attribute->frontend_input ?? null, + 'frontend_label' => $m1Attribute->frontend_label ?? null, + 'frontend_class' => $m1Attribute->frontend_class ?? null, + 'source_model' => $m1Attribute->source_model ?? null, + 'is_required' => $m1Attribute->is_required ?? 0, + 'is_user_defined' => $m1Attribute->is_user_defined ?? 1, + 'default_value' => $m1Attribute->default_value ?? null, + 'is_unique' => $m1Attribute->is_unique ?? 0, + 'note' => $m1Attribute->note ?? null, + ]); + + // Add attribute to default attribute set if it exists + $defaultAttributeSet = DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'eav_attribute_set') + ->where('entity_type_id', $m2EntityTypeId) + ->where('attribute_set_name', 'Default') + ->first(); + + if ($defaultAttributeSet) { + $defaultGroup = DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'eav_attribute_group') + ->where('attribute_set_id', $defaultAttributeSet->attribute_set_id) + ->orderBy('sort_order') + ->first(); + + if ($defaultGroup) { + // Get max sort order for this group + $maxSortOrder = DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'eav_entity_attribute') + ->where('attribute_set_id', $defaultAttributeSet->attribute_set_id) + ->where('attribute_group_id', $defaultGroup->attribute_group_id) + ->max('sort_order') ?? 0; + + // Add attribute to entity_attribute table + DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'eav_entity_attribute') + ->insert([ + 'entity_type_id' => $m2EntityTypeId, + 'attribute_set_id' => $defaultAttributeSet->attribute_set_id, + 'attribute_group_id' => $defaultGroup->attribute_group_id, + 'attribute_id' => $m2AttributeId, + 'sort_order' => $maxSortOrder + 10, + ]); + } + } + + DB::connection($this->magento2Connection)->commit(); + + return [ + 'success' => true, + 'message' => 'Attribute migrated successfully', + 'attribute_code' => $m1Attribute->attribute_code, + 'm2_attribute_id' => $m2AttributeId, + ]; + + } catch (Exception $e) { + if (isset($this->magento2Connection)) { + DB::connection($this->magento2Connection)->rollBack(); + } + Log::error("Error migrating attribute {$m1AttributeId}: " . $e->getMessage()); + return ['success' => false, 'message' => 'Failed to migrate attribute: ' . $e->getMessage()]; + } + } + /** * Get attribute groups from Magento 1 */ diff --git a/resources/views/migration/index.blade.php b/resources/views/migration/index.blade.php index efd9e50..0582247 100644 --- a/resources/views/migration/index.blade.php +++ b/resources/views/migration/index.blade.php @@ -821,6 +821,71 @@ + +
+

⚠️ Magento 1 Attributes Missing in Magento 2

+

These attributes exist in Magento 1 but do not have a matching attribute code in Magento 2:

+ @if($m1AttributesMissingInM2->count() > 0) +
+ + + + + + + + + + + + + + + @foreach($m1AttributesMissingInM2 as $attr) + + + + + + + + + + + @endforeach + +
IDAttribute CodeLabelTypeInputRequiredUser DefinedActions
{{ $attr->attribute_id }}{{ $attr->attribute_code }}{{ $attr->frontend_label ?? 'N/A' }}{{ $attr->backend_type ?? 'N/A' }}{{ $attr->frontend_input ?? 'N/A' }} + @if($attr->is_required ?? 0) + Yes + @else + No + @endif + + @if($attr->is_user_defined ?? 0) + Yes + @else + No + @endif + + +
+
+
+ Total: {{ $m1AttributesMissingInM2->count() }} {{ Str::plural('attribute', $m1AttributesMissingInM2->count()) }} found in Magento 1 but not in Magento 2. +
+ @else +
+ ✓ All Magento 1 attributes have matching attribute codes in Magento 2. +
+ @endif +
+

📋 Category Attributes

@@ -1182,6 +1247,75 @@ function deleteCategory(categoryId, categoryName, source) { }); } + function migrateAttribute(attributeId, attributeCode) { + if (!confirm(`Are you sure you want to migrate the attribute "${attributeCode}" from Magento 1 to Magento 2?`)) { + return; + } + + const button = document.getElementById(`migrate-btn-${attributeId}`); + const originalText = button.textContent; + button.disabled = true; + button.textContent = 'Migrating...'; + button.style.background = '#999'; + button.style.cursor = 'not-allowed'; + + const url = `{{ route("migration.migrate-attribute", ["attributeId" => ":id"]) }}`.replace(':id', attributeId); + + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + // Remove the row from the table + const row = document.getElementById(`attr-row-${attributeId}`); + if (row) { + row.style.opacity = '0.5'; + row.style.background = '#d4edda'; + + // Update button to show success + button.textContent = 'Migrated ✓'; + button.style.background = '#28a745'; + button.disabled = true; + + // Update count + const totalSpan = document.getElementById('missingAttributesTotal'); + if (totalSpan) { + const currentCount = parseInt(totalSpan.textContent); + if (currentCount > 0) { + totalSpan.textContent = currentCount - 1; + const countDiv = document.getElementById('missingAttributesCount'); + if (countDiv && currentCount - 1 === 0) { + countDiv.innerHTML = 'Total: 0 attributes found in Magento 1 but not in Magento 2.'; + } else if (countDiv) { + const plural = (currentCount - 1) === 1 ? 'attribute' : 'attributes'; + countDiv.innerHTML = `Total: ${currentCount - 1} ${plural} found in Magento 1 but not in Magento 2.`; + } + } + } + } + alert('Attribute migrated successfully!'); + } else { + button.disabled = false; + button.textContent = originalText; + button.style.background = '#1976D2'; + button.style.cursor = 'pointer'; + alert('Error: ' + (data.message || 'Failed to migrate attribute')); + } + }) + .catch(error => { + button.disabled = false; + button.textContent = originalText; + button.style.background = '#1976D2'; + button.style.cursor = 'pointer'; + alert('Error: ' + error.message); + }); + } + // Render tree structure function renderTree(container, nodes, source = 'm2') { nodes.forEach(node => { diff --git a/routes/web.php b/routes/web.php index 4125db1..1aa93dd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -16,4 +16,5 @@ 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'); + Route::post('/attribute/{attributeId}/migrate', [MagentoMigrationController::class, 'migrateAttribute'])->name('migration.migrate-attribute'); });