diff --git a/app/Http/Controllers/CustomersController.php b/app/Http/Controllers/CustomersController.php index b95bec3..4324a82 100644 --- a/app/Http/Controllers/CustomersController.php +++ b/app/Http/Controllers/CustomersController.php @@ -116,6 +116,37 @@ public function getMigrationProgress(Request $request) } } + /** + * Get customer statistics + */ + public function getStatistics() + { + try { + $m1Customers = $this->migrationService->getMagento1Customers(); + $m2Customers = $this->migrationService->getMagento2Customers(); + $m1CustomersNotInM2 = $this->migrationService->getM1CustomersNotInM2(); + $m2CustomersNotInM1 = $this->migrationService->getM2CustomersNotInM1(); + + return response()->json([ + 'success' => true, + 'statistics' => [ + 'm1Customers' => $m1Customers->count(), + 'm2Customers' => $m2Customers->count(), + 'm1CustomersNotInM2' => $m1CustomersNotInM2->count(), + 'm2CustomersNotInM1' => $m2CustomersNotInM1->count(), + ] + ]); + + } catch (\Exception $e) { + Log::error('Get customer statistics error: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to get statistics: ' . $e->getMessage() + ], 500); + } + } + /** * Delete a single customer from Magento 2 */ diff --git a/app/Http/Controllers/OrdersController.php b/app/Http/Controllers/OrdersController.php new file mode 100644 index 0000000..a11c642 --- /dev/null +++ b/app/Http/Controllers/OrdersController.php @@ -0,0 +1,169 @@ +migrationService = $migrationService; + } + + /** + * Show the orders page + */ + public function index() + { + $m1Orders = $this->migrationService->getMagento1Orders(); + $m2Orders = $this->migrationService->getMagento2Orders(); + $m1OrdersNotInM2 = $this->migrationService->getM1OrdersNotInM2(); + $m2OrdersNotInM1 = $this->migrationService->getM2OrdersNotInM1(); + + return view('orders.index', [ + 'm1Orders' => $m1Orders, + 'm2Orders' => $m2Orders, + 'm1OrdersNotInM2' => $m1OrdersNotInM2, + 'm2OrdersNotInM1' => $m2OrdersNotInM1, + ]); + } + + /** + * Migrate all orders from Magento 1 to Magento 2 + */ + public function migrateOrders(Request $request) + { + try { + // 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->migrateOrders($dryRun, $progressKey); + + return response()->json($result, $result['success'] ? 200 : 400); + + } catch (\Exception $e) { + Log::error('Order migration error: ' . $e->getMessage()); + Log::error('Stack trace: ' . $e->getTraceAsString()); + + return response()->json([ + 'success' => false, + 'message' => 'Migration failed: ' . $e->getMessage(), + 'added' => 0, + 'updated' => 0, + 'errors' => 0, + 'log' => [] + ], 500); + } + } + + /** + * Get order 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 order migration progress error: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to get progress: ' . $e->getMessage() + ], 500); + } + } + + /** + * Get order statistics + */ + public function getStatistics() + { + try { + $m1Orders = $this->migrationService->getMagento1Orders(); + $m2Orders = $this->migrationService->getMagento2Orders(); + $m1OrdersNotInM2 = $this->migrationService->getM1OrdersNotInM2(); + $m2OrdersNotInM1 = $this->migrationService->getM2OrdersNotInM1(); + + return response()->json([ + 'success' => true, + 'statistics' => [ + 'm1Orders' => $m1Orders->count(), + 'm2Orders' => $m2Orders->count(), + 'm1OrdersNotInM2' => $m1OrdersNotInM2->count(), + 'm2OrdersNotInM1' => $m2OrdersNotInM1->count(), + ] + ]); + + } catch (\Exception $e) { + Log::error('Get order statistics error: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Failed to get statistics: ' . $e->getMessage() + ], 500); + } + } + + /** + * Delete a single order from Magento 2 + */ + public function deleteM2Order(Request $request, $orderId) + { + try { + $result = $this->migrationService->deleteM2Order($orderId); + + return response()->json($result, $result['success'] ? 200 : 400); + + } catch (\Exception $e) { + Log::error('Delete M2 order error: ' . $e->getMessage()); + + return response()->json([ + 'success' => false, + 'message' => 'Deletion failed: ' . $e->getMessage() + ], 500); + } + } +} diff --git a/app/Services/MagentoCategoryMigrationService.php b/app/Services/MagentoCategoryMigrationService.php index dc41551..1d806ac 100644 --- a/app/Services/MagentoCategoryMigrationService.php +++ b/app/Services/MagentoCategoryMigrationService.php @@ -6581,5 +6581,345 @@ protected function updateM2Record($m2Table, $rowArray) ->update($cleanData); } } + + /** + * Get all orders from Magento 1 + */ + public function getMagento1Orders() + { + try { + $orders = DB::connection($this->magento1Connection) + ->table($this->magento1Prefix . 'sales_flat_order') + ->select('entity_id', 'increment_id', 'customer_email', 'status', 'grand_total', 'created_at', 'updated_at') + ->orderBy('entity_id') + ->get(); + + return $orders; + } catch (Exception $e) { + Log::error('Error fetching Magento 1 orders: ' . $e->getMessage()); + Log::error('Stack trace: ' . $e->getTraceAsString()); + return collect([]); + } + } + + /** + * Get all orders from Magento 2 + */ + public function getMagento2Orders() + { + try { + $orders = DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'sales_order') + ->select('entity_id', 'increment_id', 'customer_email', 'status', 'grand_total', 'created_at', 'updated_at') + ->orderBy('entity_id') + ->get(); + + return $orders; + } catch (Exception $e) { + Log::error('Error fetching Magento 2 orders: ' . $e->getMessage()); + Log::error('Stack trace: ' . $e->getTraceAsString()); + return collect([]); + } + } + + /** + * Get Magento 1 orders that don't exist in Magento 2 + */ + public function getM1OrdersNotInM2() + { + try { + $m1Orders = $this->getMagento1Orders(); + $m2Orders = $this->getMagento2Orders(); + + // Get all M2 increment IDs - create a set for faster lookup + $m2IncrementIdsSet = []; + foreach ($m2Orders as $m2Order) { + $incrementId = $m2Order->increment_id ?? null; + if (!empty($incrementId) && is_string($incrementId)) { + $normalizedIncrementId = trim($incrementId); + if (!empty($normalizedIncrementId)) { + $m2IncrementIdsSet[$normalizedIncrementId] = true; + } + } + } + + // Filter M1 orders that don't exist in M2 + $missingOrders = collect(); + foreach ($m1Orders as $m1Order) { + $incrementId = $m1Order->increment_id ?? null; + if (!empty($incrementId) && is_string($incrementId)) { + $normalizedIncrementId = trim($incrementId); + if (!empty($normalizedIncrementId) && !isset($m2IncrementIdsSet[$normalizedIncrementId])) { + $missingOrders->push($m1Order); + } + } + } + + return $missingOrders->values(); + } catch (Exception $e) { + Log::error('Error fetching missing orders: ' . $e->getMessage()); + Log::error('Stack trace: ' . $e->getTraceAsString()); + return collect([]); + } + } + + /** + * Get Magento 2 orders that don't exist in Magento 1 + */ + public function getM2OrdersNotInM1() + { + try { + $m1Orders = $this->getMagento1Orders(); + $m2Orders = $this->getMagento2Orders(); + + // Get all M1 increment IDs - create a set for faster lookup + $m1IncrementIdsSet = []; + foreach ($m1Orders as $m1Order) { + $incrementId = $m1Order->increment_id ?? null; + if (!empty($incrementId) && is_string($incrementId)) { + $normalizedIncrementId = trim($incrementId); + if (!empty($normalizedIncrementId)) { + $m1IncrementIdsSet[$normalizedIncrementId] = true; + } + } + } + + // Filter M2 orders that don't exist in M1 + $missingOrders = collect(); + foreach ($m2Orders as $m2Order) { + $incrementId = $m2Order->increment_id ?? null; + if (!empty($incrementId) && is_string($incrementId)) { + $normalizedIncrementId = trim($incrementId); + if (!empty($normalizedIncrementId) && !isset($m1IncrementIdsSet[$normalizedIncrementId])) { + $missingOrders->push($m2Order); + } + } + } + + return $missingOrders->values(); + } catch (Exception $e) { + Log::error('Error fetching M2 orders not in M1: ' . $e->getMessage()); + Log::error('Stack trace: ' . $e->getTraceAsString()); + return collect([]); + } + } + + /** + * Migrate all orders from Magento 1 to Magento 2 + */ + public function migrateOrders($dryRun = false, $progressKey = null) + { + try { + $this->migrationLog = []; + $addedCount = 0; + $updatedCount = 0; + $errorCount = 0; + + // Get all M1 orders + $m1Orders = $this->getMagento1Orders(); + $totalOrders = $m1Orders->count(); + + // Initialize progress tracking + if ($progressKey && !$dryRun) { + Cache::put($progressKey, [ + 'total' => $totalOrders, + 'current' => 0, + 'added' => 0, + 'updated' => 0, + 'errors' => 0, + 'status' => 'running', + 'current_increment_id' => '' + ], 3600); + } + + if (!$dryRun) { + DB::connection($this->magento2Connection)->beginTransaction(); + } + + $currentIndex = 0; + foreach ($m1Orders as $m1Order) { + $currentIndex++; + try { + $m1IncrementId = !empty($m1Order->increment_id) ? trim($m1Order->increment_id) : null; + + // Update progress if tracking enabled + if ($progressKey && !$dryRun) { + Cache::put($progressKey, [ + 'total' => $totalOrders, + 'current' => $currentIndex, + 'added' => $addedCount, + 'updated' => $updatedCount, + 'errors' => $errorCount, + 'status' => 'running', + 'current_increment_id' => $m1IncrementId ?? 'N/A' + ], 3600); + } + + if (empty($m1IncrementId)) { + $this->migrationLog[] = "SKIPPED: Order ID {$m1Order->entity_id} - no increment_id"; + continue; + } + + // Check if order exists in M2 by increment_id + $m2Order = DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'sales_order') + ->where('increment_id', $m1IncrementId) + ->first(); + + $m2OrderId = null; + $isNew = false; + + if ($m2Order) { + // Order exists, update + $m2OrderId = $m2Order->entity_id; + if (!$dryRun) { + // Update order data + DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'sales_order') + ->where('entity_id', $m2OrderId) + ->update([ + 'customer_email' => $m1Order->customer_email ?? null, + 'status' => $m1Order->status ?? null, + 'grand_total' => $m1Order->grand_total ?? 0, + 'updated_at' => $m1Order->updated_at ?? now(), + ]); + $this->migrationLog[] = "Updating existing order: {$m1IncrementId} (ID: {$m2OrderId})"; + } else { + $this->migrationLog[] = "Would update existing order: {$m1IncrementId} (ID: {$m2OrderId})"; + } + $updatedCount++; + } else { + // Order doesn't exist, create + if (!$dryRun) { + // Insert order entity + $m2OrderId = DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'sales_order') + ->insertGetId([ + 'increment_id' => $m1IncrementId, + 'customer_email' => $m1Order->customer_email ?? null, + 'status' => $m1Order->status ?? 'pending', + 'grand_total' => $m1Order->grand_total ?? 0, + 'created_at' => $m1Order->created_at ?? now(), + 'updated_at' => $m1Order->updated_at ?? now(), + ]); + $this->migrationLog[] = "Added new order: {$m1IncrementId} (ID: {$m2OrderId})"; + } else { + $this->migrationLog[] = "Would add new order: {$m1IncrementId}"; + $m2OrderId = 0; // Placeholder for dry run + } + $addedCount++; + $isNew = true; + } + + // Note: Full order migration would also migrate: + // - Order items (sales_order_item) + // - Order addresses (sales_order_address) + // - Order payment (sales_order_payment) + // - Order status history (sales_order_status_history) + // This is a simplified version that only migrates the main order record + + } catch (Exception $e) { + $errorCount++; + $m1IncrementId = $m1Order->increment_id ?? 'N/A'; + $this->migrationLog[] = "ERROR: Failed to migrate order {$m1IncrementId}: " . $e->getMessage(); + Log::error("Error migrating order {$m1IncrementId}: " . $e->getMessage()); + } + } + + if (!$dryRun) { + DB::connection($this->magento2Connection)->commit(); + } + + // Update progress to completed + if ($progressKey && !$dryRun) { + Cache::put($progressKey, [ + 'total' => $totalOrders, + 'current' => $totalOrders, + 'added' => $addedCount, + 'updated' => $updatedCount, + 'errors' => $errorCount, + 'status' => 'completed', + 'current_increment_id' => '' + ], 3600); + } + + return [ + 'success' => true, + 'message' => $dryRun ? 'Dry run completed' : 'Order migration completed', + 'added' => $addedCount, + 'updated' => $updatedCount, + 'errors' => $errorCount, + 'log' => $this->migrationLog + ]; + + } catch (Exception $e) { + if (!$dryRun) { + DB::connection($this->magento2Connection)->rollBack(); + } + + // Update progress to failed + if ($progressKey && !$dryRun) { + Cache::put($progressKey, [ + 'total' => isset($totalOrders) ? $totalOrders : 0, + 'current' => isset($currentIndex) ? $currentIndex : 0, + 'added' => $addedCount, + 'updated' => $updatedCount, + 'errors' => $errorCount, + 'status' => 'failed', + 'current_increment_id' => '' + ], 3600); + } + + Log::error('Error migrating orders: ' . $e->getMessage()); + return [ + 'success' => false, + 'message' => 'Migration failed: ' . $e->getMessage(), + 'added' => 0, + 'updated' => 0, + 'errors' => 0, + 'log' => [] + ]; + } + } + + /** + * Delete a single order from Magento 2 + */ + public function deleteM2Order($orderId) + { + try { + $order = DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'sales_order') + ->where('entity_id', $orderId) + ->first(); + + if (!$order) { + return [ + 'success' => false, + 'message' => 'Order not found' + ]; + } + + $incrementId = $order->increment_id ?? 'N/A'; + + // Delete order (cascade deletes should handle related records) + DB::connection($this->magento2Connection) + ->table($this->magento2Prefix . 'sales_order') + ->where('entity_id', $orderId) + ->delete(); + + return [ + 'success' => true, + 'message' => "Order {$incrementId} deleted successfully" + ]; + + } catch (Exception $e) { + Log::error('Error deleting M2 order: ' . $e->getMessage()); + return [ + 'success' => false, + 'message' => 'Deletion failed: ' . $e->getMessage() + ]; + } + } } diff --git a/resources/js/customers.js b/resources/js/customers.js index a8beb7c..26a864a 100644 --- a/resources/js/customers.js +++ b/resources/js/customers.js @@ -115,6 +115,9 @@ function startCustomerMigration() { updateProgressBar(100, data.added + data.updated, data.added + data.updated, data.added, data.updated, data.errors, 'completed', ''); } + // Update customer statistics + updateCustomerStatistics(); + const successEntry = document.createElement('div'); successEntry.className = 'log-entry success'; successEntry.textContent = `✓ Migration completed! Added: ${data.added || 0}, Updated: ${data.updated || 0}, Errors: ${data.errors || 0}`; @@ -211,6 +214,40 @@ function pollMigrationProgress(progressKey, button, originalText, logContent) { }, 500); // Poll every 500ms for smoother updates } +function updateCustomerStatistics() { + if (!routes.statistics) { + console.error('Statistics route not available'); + return; + } + + fetch(routes.statistics, { + method: 'GET', + headers: { + 'X-CSRF-TOKEN': csrfToken, + 'Accept': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + if (data.success && data.statistics) { + const stats = data.statistics; + + const m1CustomersEl = document.getElementById('statM1Customers'); + const m2CustomersEl = document.getElementById('statM2Customers'); + const m1NotInM2El = document.getElementById('statM1NotInM2'); + const m2NotInM1El = document.getElementById('statM2NotInM1'); + + if (m1CustomersEl) m1CustomersEl.textContent = stats.m1Customers; + if (m2CustomersEl) m2CustomersEl.textContent = stats.m2Customers; + if (m1NotInM2El) m1NotInM2El.textContent = stats.m1CustomersNotInM2; + if (m2NotInM1El) m2NotInM1El.textContent = stats.m2CustomersNotInM1; + } + }) + .catch(error => { + console.error('Error updating customer statistics:', error); + }); +} + function updateProgressBar(percentage, current, total, added, updated, errors, status, currentEmail) { const progressBar = document.getElementById('progressBar'); const progressPercentage = document.getElementById('progressPercentage'); diff --git a/resources/js/orders.js b/resources/js/orders.js new file mode 100644 index 0000000..80297b7 --- /dev/null +++ b/resources/js/orders.js @@ -0,0 +1,318 @@ +// Orders page JavaScript + +let routes = {}; +let csrfToken = ''; + +// Initialize on page load +document.addEventListener('DOMContentLoaded', function() { + if (window.orderRoutes) { + routes = window.orderRoutes; + csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || ''; + } + + // Attach event listener to the button instead of using inline onclick + const startButton = document.getElementById('startOrderMigrationBtn'); + if (startButton) { + startButton.addEventListener('click', function(e) { + e.preventDefault(); + startOrderMigration(); + }); + } +}); + +function startOrderMigration() { + try { + const button = document.getElementById('startOrderMigrationBtn'); + if (!button) { + console.error('Start order migration button not found'); + alert('Error: Button not found. Please refresh the page.'); + return; + } + + const logContent = document.getElementById('orderMigrationLogContent'); + if (!logContent) { + console.error('Order migration log content not found'); + alert('Error: Log container not found. Please refresh the page.'); + return; + } + + const originalText = button.textContent || 'Start Order Migration'; + if (button) { + button.disabled = true; + button.textContent = 'Migrating...'; + button.style.cursor = 'not-allowed'; + } + + const progressContainer = document.getElementById('migrationProgressContainer'); + + // Show progress bar + if (progressContainer) { + progressContainer.style.display = 'block'; + updateProgressBar(0, 0, 0, 0, 0, 'Starting migration...', ''); + } + + logContent.innerHTML = '
The order migration process will:
+⚠️ Warning: This will modify your Magento 2 database. Make sure you have a backup before proceeding.
+Detailed logs showing which orders were added, updated, or encountered errors during migration:
+These orders exist in Magento 1 but are missing in Magento 2:
+| ID | +Increment ID | +Customer Email | +Status | +Grand Total | +Created At | +
|---|---|---|---|---|---|
| {{ $order->entity_id }} | +{{ $order->increment_id ?? 'N/A' }} | +{{ $order->customer_email ?? 'N/A' }} | +{{ $order->status ?? 'N/A' }} | +{{ $order->grand_total ?? 'N/A' }} | +{{ $order->created_at ?? 'N/A' }} | +
Showing first 50 of {{ $m1OrdersNotInM2->count() }} orders.
+ @endif +These orders exist in Magento 2 but are missing in Magento 1:
+| ID | +Increment ID | +Customer Email | +Status | +Grand Total | +Created At | +Actions | +
|---|---|---|---|---|---|---|
| {{ $order->entity_id }} | +{{ $order->increment_id ?? 'N/A' }} | +{{ $order->customer_email ?? 'N/A' }} | +{{ $order->status ?? 'N/A' }} | +{{ $order->grand_total ?? 'N/A' }} | +{{ $order->created_at ?? 'N/A' }} | ++ + | +
Showing first 50 of {{ $m2OrdersNotInM1->count() }} orders.
+ @endif +