option('id'); if ($importId) { $import = DB::table('neblogistic_import')->where('id', $importId)->first(); if (!$import) { $this->error("Import record with ID {$importId} not found."); return 1; } $this->processImport($import); } else { // Process all pending imports $imports = DB::table('neblogistic_import') ->where('status', 'pending') ->orderBy('created_at', 'asc') ->get(); if ($imports->isEmpty()) { $this->info('No pending imports found.'); return 0; } $this->info("Found {$imports->count()} pending import(s)."); foreach ($imports as $import) { $this->processImport($import); } } return 0; } private function processImport($import) { $this->info("Processing import ID: {$import->id} - {$import->file_name}"); // Update status to processing DB::table('neblogistic_import') ->where('id', $import->id) ->update([ 'status' => 'processing', 'updated_at' => now() ]); try { $filePath = $import->file_path; $extension = $import->file_extension; // Download file from S3 to temporary location $tempPath = $this->downloadFileFromS3($filePath); if (!$tempPath) { throw new \Exception("Failed to download file from S3: {$filePath}"); } $updatedCount = 0; $notFoundCount = 0; $errorCount = 0; $errors = []; $processedRows = 0; if (in_array($extension, ['xlsx', 'xls'])) { $result = $this->processExcelFile($tempPath, $errors); $updatedCount = $result['updated']; $notFoundCount = $result['not_found']; $errorCount = $result['errors']; $processedRows = $result['processed']; } elseif ($extension === 'csv') { $result = $this->processCsvFile($tempPath, $errors); $updatedCount = $result['updated']; $notFoundCount = $result['not_found']; $errorCount = $result['errors']; $processedRows = $result['processed']; } // Clean up temporary file if (file_exists($tempPath)) { unlink($tempPath); } // Update import record $errorLog = !empty($errors) ? implode("\n", array_slice($errors, 0, 100)) : null; DB::table('neblogistic_import') ->where('id', $import->id) ->update([ 'status' => 'completed', 'processed_rows' => $processedRows, 'updated_count' => $updatedCount, 'not_found_count' => $notFoundCount, 'error_count' => $errorCount, 'error_log' => $errorLog, 'processed_at' => now(), 'updated_at' => now() ]); $this->info("Import completed. Updated: {$updatedCount}, Not Found: {$notFoundCount}, Errors: {$errorCount}"); } catch (\Exception $e) { Log::error('Import processing error', [ 'import_id' => $import->id, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); DB::table('neblogistic_import') ->where('id', $import->id) ->update([ 'status' => 'failed', 'error_log' => $e->getMessage(), 'updated_at' => now() ]); $this->error("Failed to process import ID {$import->id}: " . $e->getMessage()); } } private function downloadFileFromS3($filePath) { try { // Extract the S3 key from the full URL $urlParts = parse_url($filePath); $s3Key = ltrim($urlParts['path'] ?? '', '/'); // Remove AWS_URL prefix if present $awsUrl = env('AWS_URL'); if ($awsUrl && strpos($filePath, $awsUrl) === 0) { $s3Key = str_replace($awsUrl . '/', '', $filePath); } // Download from S3 $contents = Storage::disk('s3')->get($s3Key); if (!$contents) { return null; } // Save to temporary file $tempPath = sys_get_temp_dir() . '/' . uniqid('neblogistic_import_') . '.tmp'; file_put_contents($tempPath, $contents); return $tempPath; } catch (\Exception $e) { Log::error('Error downloading file from S3', [ 'file_path' => $filePath, 'error' => $e->getMessage() ]); return null; } } private function processExcelFile($filePath, &$errors) { $updatedCount = 0; $notFoundCount = 0; $errorCount = 0; $processedRows = 0; try { $data = Excel::toArray([], $filePath); if (empty($data) || empty($data[0])) { throw new \Exception('The file is empty or invalid.'); } $rows = $data[0]; $headers = array_shift($rows); // Find the index of "Connect od Order" column $connectOrderIndex = null; foreach ($headers as $index => $header) { if (trim(strtolower($header)) === 'connect od order') { $connectOrderIndex = $index; break; } } if ($connectOrderIndex === null) { throw new \Exception('Column "Connect od Order" not found in the file.'); } // Map all other columns for update $columnMapping = []; foreach ($headers as $index => $header) { $columnMapping[$index] = trim($header); } // Process each row foreach ($rows as $rowIndex => $row) { try { $connectOrderId = trim($row[$connectOrderIndex] ?? ''); if (empty($connectOrderId)) { continue; } $processedRows++; // Check if record exists $record = DB::table('neblogistic')->where('id', $connectOrderId)->first(); if (!$record) { $notFoundCount++; $errors[] = "Row " . ($rowIndex + 2) . ": Record with ID {$connectOrderId} not found."; continue; } // Build update data from the row $updateData = []; foreach ($row as $colIndex => $value) { if ($colIndex === $connectOrderIndex) { continue; } $columnName = $columnMapping[$colIndex] ?? null; if ($columnName && !empty(trim($value))) { $dbField = $this->mapColumnToDbField($columnName); if ($dbField) { $trimmedValue = trim($value); // Convert date format for lr_date field from d.m.Y to Y-m-d if ($dbField === 'lr_date') { try { $date = \DateTime::createFromFormat('d.m.Y', $trimmedValue); if ($date) { $trimmedValue = $date->format('Y-m-d'); } } catch (\Exception $e) { // If parsing fails, keep original value } } $updateData[$dbField] = $trimmedValue; } } } // Update the record if there's data to update if (!empty($updateData)) { // Get old data before update for audit trail $oldRecord = DB::table('neblogistic')->where('id', $connectOrderId)->first(); $oldDataArray = $oldRecord ? json_decode(json_encode($oldRecord), true) : null; DB::table('neblogistic') ->where('id', $connectOrderId) ->update($updateData); // Log UPDATE operation (console command - no request object) \App\Services\NeblogisticAuditService::logUpdate( $connectOrderId, $oldDataArray, $updateData, null, null // No request object in console command ); $updatedCount++; } } catch (\Exception $e) { $errorCount++; $errors[] = "Row " . ($rowIndex + 2) . ": " . $e->getMessage(); Log::error('Import row error', [ 'row' => $rowIndex + 2, 'error' => $e->getMessage() ]); } } } catch (\Exception $e) { throw new \Exception('Error processing Excel file: ' . $e->getMessage()); } return [ 'updated' => $updatedCount, 'not_found' => $notFoundCount, 'errors' => $errorCount, 'processed' => $processedRows ]; } private function processCsvFile($filePath, &$errors) { $updatedCount = 0; $notFoundCount = 0; $errorCount = 0; $processedRows = 0; try { $csv = Reader::createFromPath($filePath, 'r'); $csv->setHeaderOffset(0); $headers = $csv->getHeader(); $records = $csv->getRecords(); // Find "Connect od Order" in headers $connectOrderKey = null; foreach ($headers as $header) { if (trim(strtolower($header)) === 'connect od order') { $connectOrderKey = $header; break; } } if (!$connectOrderKey) { throw new \Exception('Column "Connect od Order" not found in the file.'); } // Process each record $rowIndex = 0; foreach ($records as $row) { $rowIndex++; try { $connectOrderId = trim($row[$connectOrderKey] ?? ''); if (empty($connectOrderId)) { continue; } $processedRows++; // Check if record exists $record = DB::table('neblogistic')->where('id', $connectOrderId)->first(); if (!$record) { $notFoundCount++; $errors[] = "Row " . ($rowIndex + 1) . ": Record with ID {$connectOrderId} not found."; continue; } // Build update data $updateData = []; foreach ($row as $columnName => $value) { if ($columnName === $connectOrderKey) { continue; } if (!empty(trim($value))) { $dbField = $this->mapColumnToDbField($columnName); if ($dbField) { $trimmedValue = trim($value); // Convert date format for lr_date field from d.m.Y to Y-m-d if ($dbField === 'lr_date') { try { $date = \DateTime::createFromFormat('d.m.Y', $trimmedValue); if ($date) { $trimmedValue = $date->format('Y-m-d'); } } catch (\Exception $e) { // If parsing fails, keep original value } } $updateData[$dbField] = $trimmedValue; } } } // Update the record if there's data to update if (!empty($updateData)) { DB::table('neblogistic') ->where('id', $connectOrderId) ->update($updateData); $updatedCount++; } } catch (\Exception $e) { $errorCount++; $errors[] = "Row " . ($rowIndex + 1) . ": " . $e->getMessage(); Log::error('Import row error', [ 'row' => $rowIndex + 1, 'error' => $e->getMessage() ]); } } } catch (\Exception $e) { throw new \Exception('Error processing CSV file: ' . $e->getMessage()); } return [ 'updated' => $updatedCount, 'not_found' => $notFoundCount, 'errors' => $errorCount, 'processed' => $processedRows ]; } private function mapColumnToDbField($columnName) { $mapping = [ 'Connect od Order' => 'id', 'ORDER NO' => 'order_no', 'INVOICE NO' => 'invoice_no', 'COURIER NAME' => 'courier_name', 'LR NO' => 'lr_no', 'LR DATE' => 'lr_date', ]; $normalizedColumn = trim($columnName); return $mapping[$normalizedColumn] ?? null; } }