diff --git a/app/Http/Controllers/CustomersController.php b/app/Http/Controllers/CustomersController.php index b5587db..b95bec3 100644 --- a/app/Http/Controllers/CustomersController.php +++ b/app/Http/Controllers/CustomersController.php @@ -5,6 +5,7 @@ use App\Services\MagentoCategoryMigrationService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Cache; class CustomersController extends Controller { @@ -39,13 +40,30 @@ public function index() public function migrateCustomers(Request $request) { try { - $dryRun = $request->input('dry_run', false); - $result = $this->migrationService->migrateCustomers($dryRun); + // Handle both JSON and form data + $dryRun = false; + $progressKey = null; + + if ($request->isJson()) { + $dryRun = $request->json()->get('dry_run', false); + $progressKey = $request->json()->get('progress_key', null); + } else { + $dryRun = $request->input('dry_run', false); + $progressKey = $request->input('progress_key', null); + } + + // Convert string "true"/"false" to boolean if needed + if (is_string($dryRun)) { + $dryRun = filter_var($dryRun, FILTER_VALIDATE_BOOLEAN); + } + + $result = $this->migrationService->migrateCustomers($dryRun, $progressKey); return response()->json($result, $result['success'] ? 200 : 400); } catch (\Exception $e) { Log::error('Customer migration error: ' . $e->getMessage()); + Log::error('Stack trace: ' . $e->getTraceAsString()); return response()->json([ 'success' => false, @@ -58,6 +76,46 @@ public function migrateCustomers(Request $request) } } + /** + * Get customer migration progress + */ + public function getMigrationProgress(Request $request) + { + try { + $progressKey = $request->input('progress_key'); + + if (empty($progressKey)) { + return response()->json([ + 'success' => false, + 'message' => 'Progress key is required' + ], 400); + } + + $progress = Cache::get($progressKey); + + if (!$progress) { + return response()->json([ + 'success' => false, + 'message' => 'Progress not found', + 'progress' => null + ], 404); + } + + return response()->json([ + 'success' => true, + 'progress' => $progress + ]); + + } catch (\Exception $e) { + Log::error('Get customer migration progress error: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to get progress: ' . $e->getMessage() + ], 500); + } + } + /** * Delete a single customer from Magento 2 */ diff --git a/app/Services/MagentoCategoryMigrationService.php b/app/Services/MagentoCategoryMigrationService.php index eed59ae..dc41551 100644 --- a/app/Services/MagentoCategoryMigrationService.php +++ b/app/Services/MagentoCategoryMigrationService.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Cache; use Exception; class MagentoCategoryMigrationService @@ -5678,7 +5679,7 @@ public function getM2CustomersNotInM1() /** * Migrate all customers from Magento 1 to Magento 2 */ - public function migrateCustomers($dryRun = false) + public function migrateCustomers($dryRun = false, $progressKey = null) { try { $this->migrationLog = []; @@ -5710,6 +5711,20 @@ public function migrateCustomers($dryRun = false) // Get all M1 customers $m1Customers = $this->getMagento1Customers(); + $totalCustomers = $m1Customers->count(); + + // Initialize progress tracking + if ($progressKey && !$dryRun) { + Cache::put($progressKey, [ + 'total' => $totalCustomers, + 'current' => 0, + 'added' => 0, + 'updated' => 0, + 'errors' => 0, + 'status' => 'running', + 'current_email' => '' + ], 3600); + } // Get all customer attribute IDs from M1 $m1AttributeIds = DB::connection($this->magento1Connection) @@ -5729,10 +5744,25 @@ public function migrateCustomers($dryRun = false) DB::connection($this->magento2Connection)->beginTransaction(); } + $currentIndex = 0; foreach ($m1Customers as $m1Customer) { + $currentIndex++; try { $m1Email = !empty($m1Customer->email) ? strtolower(trim($m1Customer->email)) : null; + // Update progress if tracking enabled + if ($progressKey && !$dryRun) { + Cache::put($progressKey, [ + 'total' => $totalCustomers, + 'current' => $currentIndex, + 'added' => $addedCount, + 'updated' => $updatedCount, + 'errors' => $errorCount, + 'status' => 'running', + 'current_email' => $m1Email ?? 'N/A' + ], 3600); + } + if (empty($m1Email)) { $this->migrationLog[] = "SKIPPED: Customer ID {$m1Customer->entity_id} - no email address"; continue; @@ -5844,6 +5874,19 @@ public function migrateCustomers($dryRun = false) if (!$dryRun) { DB::connection($this->magento2Connection)->commit(); } + + // Update progress to completed + if ($progressKey && !$dryRun) { + Cache::put($progressKey, [ + 'total' => $totalCustomers, + 'current' => $totalCustomers, + 'added' => $addedCount, + 'updated' => $updatedCount, + 'errors' => $errorCount, + 'status' => 'completed', + 'current_email' => '' + ], 3600); + } return [ 'success' => true, @@ -5858,6 +5901,20 @@ public function migrateCustomers($dryRun = false) if (!$dryRun) { DB::connection($this->magento2Connection)->rollBack(); } + + // Update progress to failed + if ($progressKey && !$dryRun) { + Cache::put($progressKey, [ + 'total' => isset($totalCustomers) ? $totalCustomers : 0, + 'current' => isset($currentIndex) ? $currentIndex : 0, + 'added' => $addedCount, + 'updated' => $updatedCount, + 'errors' => $errorCount, + 'status' => 'failed', + 'current_email' => '' + ], 3600); + } + Log::error('Error migrating customers: ' . $e->getMessage()); return [ 'success' => false, diff --git a/app/Services/MagentoProductMigrationService.php b/app/Services/MagentoProductMigrationService.php index c827527..ae657dc 100644 --- a/app/Services/MagentoProductMigrationService.php +++ b/app/Services/MagentoProductMigrationService.php @@ -2744,13 +2744,40 @@ protected function migrateCatalogProductOptions() $m2ProductId = $productIdMapping[$m1ProductId]; - // Check if option already exists in M2 (by product_id and type) - $existingOption = DB::connection($this->magento2Connection) + // Check if option already exists in M2 + // Match by product_id, type, sort_order, and sku (if sku is not null/empty) + // This ensures we find the exact same option and update it instead of creating duplicates + $m1Sku = $m1Option->sku ?? null; + $m1SortOrder = $m1Option->sort_order ?? 0; + + $query = DB::connection($this->magento2Connection) ->table($this->magento2Prefix . 'catalog_product_option') ->where('product_id', $m2ProductId) ->where('type', $m1Option->type) - ->where('sku', $m1Option->sku ?? '') - ->first(); + ->where('sort_order', $m1SortOrder); + + // If SKU is provided and not empty, include it in the match + if (!empty($m1Sku)) { + $query->where('sku', $m1Sku); + } else { + // If SKU is null/empty, match options where SKU is also null/empty + $query->where(function($q) { + $q->whereNull('sku')->orWhere('sku', ''); + }); + } + + $existingOption = $query->first(); + + // If still no match, try matching by product_id, type, and sort_order only + // This catches cases where SKU might differ but it's the same option + if (!$existingOption) { + $existingOption = DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'catalog_product_option') + ->where('product_id', $m2ProductId) + ->where('type', $m1Option->type) + ->where('sort_order', $m1SortOrder) + ->first(); + } $optionData = [ 'product_id' => $m2ProductId, diff --git a/assets/Screenshot_From_2025-11-30_22-33-37-3161ad9d-3ff4-4a65-80fc-7e67b3e01c72.png b/assets/Screenshot_From_2025-11-30_22-33-37-3161ad9d-3ff4-4a65-80fc-7e67b3e01c72.png new file mode 100644 index 0000000..e69de29 diff --git a/assets/image-691a52f0-582a-4795-918b-7378da7c637d.png b/assets/image-691a52f0-582a-4795-918b-7378da7c637d.png new file mode 100644 index 0000000..e69de29 diff --git a/resources/js/customers.js b/resources/js/customers.js index d596c71..a8beb7c 100644 --- a/resources/js/customers.js +++ b/resources/js/customers.js @@ -9,39 +9,115 @@ document.addEventListener('DOMContentLoaded', function() { routes = window.customerRoutes; csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || ''; } + + // Attach event listener to the button instead of using inline onclick + const startButton = document.getElementById('startCustomerMigrationBtn'); + if (startButton) { + startButton.addEventListener('click', function(e) { + e.preventDefault(); + startCustomerMigration(); + }); + } }); -function startCustomerMigration(dryRun) { - const button = dryRun ? document.getElementById('dryRunCustomerMigrationBtn') : document.getElementById('startCustomerMigrationBtn'); - const otherButton = dryRun ? document.getElementById('startCustomerMigrationBtn') : document.getElementById('dryRunCustomerMigrationBtn'); - const originalText = button.textContent; - button.disabled = true; - otherButton.disabled = true; - button.textContent = dryRun ? 'Running Dry Run...' : 'Migrating...'; - button.style.cursor = 'not-allowed'; +function startCustomerMigration() { + try { + const button = document.getElementById('startCustomerMigrationBtn'); + if (!button) { + console.error('Start customer migration button not found'); + alert('Error: Button not found. Please refresh the page.'); + return; + } + + const logContent = document.getElementById('customerMigrationLogContent'); + if (!logContent) { + console.error('Customer migration log content not found'); + alert('Error: Log container not found. Please refresh the page.'); + return; + } + + const originalText = button.textContent || 'Start Customer Migration'; + if (button) { + button.disabled = true; + button.textContent = 'Migrating...'; + button.style.cursor = 'not-allowed'; + } - const logContent = document.getElementById('customerMigrationLogContent'); - logContent.innerHTML = '