em = $em; $this->upload_logger = $upload_logger; $this->load_logger = $load_logger; $this->project_dir = $kernel->getProjectDir(); $this->callback_url = $callback_url; $this->filesystem = $filesystem; parent::__construct(); } protected function configure() { $this->setName('warrantyserial:load') ->setDescription('Load warranty serials from file.') ->setHelp('Load warranty serials from file.'); } protected function execute(InputInterface $input, OutputInterface $output) { $em = $this->em; $this->log_data = []; $status = 'pending'; // get the filenames from the queue table with status pending $db = $em->getConnection(); $ws_query_sql = 'SELECT id, file_serial, file_id, api_user, orig_file_serial FROM warranty_serial_queue WHERE status = :status ORDER BY id LIMIT 1'; $ws_query_stmt = $db->prepare($ws_query_sql); $ws_query_stmt->bindValue('status', $status); $ws_results = $ws_query_stmt->executeQuery(); $output_info = []; while ($row = $ws_results->fetchAssociative()) { $filename = $row['file_serial']; $user_id = $row['api_user']; $id = $row['id']; $file_id = $row['file_id']; $orig_filename = $row['orig_file_serial']; $output_info[] = $this->processWarrantySerialFile($filename, $user_id, $file_id, $orig_filename); // remove entry from queue table $this->updateWarrantySerialQueue($id); // delete the uploaded csv file and directory $this->deleteDirectoryAndFile($file_id); } if (count($output_info) > 0) { // error_log(print_r($this->log_data, true)); // load log data into db $this->load_logger->logWarrantySerialLoadInfo($this->log_data); // send results back to third party $this->sendResults($output_info); } return 0; } protected function processWarrantySerialFile($filename, $user_id, $file_id, $orig_filename) { $csv_file = $this->project_dir . '/public/warranty_serial_uploads/' . $filename; $output_info = []; // attempt to open file try { $fh = fopen($csv_file, "r"); } catch (Exception $e) { $error = 'The file ' . $csv_file . 'could not be read.'; $log_data = [ 'user_id' => $user_id, 'is_uploaded' => false, 'error' => $error, ]; $this->upload_logger->logWarrantySerialUploadInfo($log_data); $output_info = $this->setOutputInfo($filename, $file_id, true, $error, $data, $orig_filename); return $output_info; } $data = []; while(($row = fgetcsv($fh)) !== false) { $validation_result = $this->validateRow($row, $user_id); if (!empty($validation_result)) { $data[] = $validation_result; continue; } // valid entry, we parse and insert $serial = trim(strtoupper($row[0])); // error_log('Processing ' . $serial); $sku = trim(strtoupper($row[1])); $dispatch_status = trim($row[2]); $str_date_create = trim($row[3]); $inventory_status = trim($row[4]); $cat_id = trim($row[5]); $cat_name = trim(strtoupper($row[6])); // we are sure that this is a valid date at this point $created_date = $this->convertDateCreate($str_date_create); $meta_info = [ 'dispatch_status' => $dispatch_status, 'inventory_status' => $inventory_status, 'category_id' => $cat_id, 'category_name' => $cat_name, ]; $info = json_encode($meta_info); // prepare the data $source = 'motiv'; if ($sku == 'N/A') $sku = null; // prepared statement $db = $this->em->getConnection(); $insert_stmt = $db->prepare('INSERT INTO warranty_serial (id, sku, date_create, source, meta_info) VALUES (:serial, :sku, :date_create, :source, :meta_info)'); $res = $insert_stmt->execute([ ':serial' => $serial, ':sku' => $sku, ':date_create' => $created_date, ':source' => $source, ':meta_info' => $info, ]); if (!$res) { // log the not successful insert $err = $insert_stmt->errorInfo(); $error = $err[2]; $this->logLoadInfo($user_id, false, $serial, $error); $data[] = [ 'serial' => $serial, 'status' => 'error', 'has_error' => true, 'error_message' => $error, ]; } else { // log the successful insert $this->logLoadInfo($user_id, true, $serial, ''); $data[] = [ 'serial' => $serial, 'status' => 'success', 'has_error' => false, 'error_message' => '', ]; } } // form what we output $output_info = $this->setOutputInfo($filename, $file_id, false, '', $data, $orig_filename, $orig_filename); return $output_info; } protected function validateRow($row, $user_id) { $data = []; // possible lines: // (1) header in csv file - ignore // SerialNumber,Sku,DispatchStatus,CreatedDate,InventoryStatus,CategoryID,CategoryName // (2) No available data - ignore // (3) CH2000012071,WCHD23BL-CPN00-LX,0,2020-08-11 04:05:27.090,0,4,CHAMPION MF - valid // (4) MG2000313690,N/A,1,2021-05-14T23:47:30.6430000+08:00,0,10,GOLD - valid // (5) Empty line - ignore // (6) empty sku - log // check if empty line if ($row == array(null)) { // no need to log, but send back error $error = 'Empty line'; $data = [ 'serial' => '', 'status' => 'error', 'has_error' => true, 'error_message' => $error, ]; return $data; } // check the number of fields if (count($row) != self::FIELD_COUNT) { $error = 'Invalid number of fields.'; $data = [ 'serial' => '', 'status' => 'error', 'has_error' => true, 'error_message' => $error, ]; return $data; } // check if the line is a header if ($row[0] == 'SerialNumber') { // no need to log, but send back error $error = 'Invalid information.'; $data = [ 'serial' => '', 'status' => 'error', 'has_error' => true, 'error_message' => $error, ]; return $data; } // check if empty serial if (empty($row[0])) { // this one, we log $error = 'Empty serial'; $this->logLoadInfo($user_id, false, '', $error); $data = [ 'serial' => '', 'status' => 'error', 'has_error' => true, 'error_message' => $error, ]; return $data; } // check length of serial $serial = trim($row[0]); if (strlen($serial) > SELF::SERIAL_LENGTH) { // log $error = 'Serial length too long'; $this->logLoadInfo($user_id, false, $serial, $error); $data = [ 'serial' => $serial, 'status' => 'error', 'has_error' => true, 'error_message' => $error, ]; return $data; } // validate the date created $str_date_create = trim($row[3]); $date_create = $this->convertDateCreate($str_date_create); if ($date_create == null) { // log $error = 'Invalid date create.'; $this->logLoadInfo($user_id, false, $serial, $error); $data = [ 'serial' => $serial, 'status' => 'error', 'has_error' => true, 'error_message' => $error, ]; return $data; } // check if serial is a dupe $existing_serial = $this->em->getRepository(WarrantySerial::class)->find($serial); if ($existing_serial != null) { // log $error = 'Serial already exists.'; $this->logLoadInfo($user_id, false, $serial, $error); $data = [ 'serial' => $serial, 'status' => 'error', 'has_error' => true, 'error_message' => $error, ]; return $data; } // valid entry, return empty return $data; } protected function convertDateCreate($str_date_create) { // since some people cannot follow simple instructions... // check the date format on the string // try 2021-05-15T08:35:46+08:00 format on str_date_create $date_create = DateTime::createFromFormat('Y-m-d\TH:i:sP', $str_date_create); if ($date_create == false) { // try this format: 2021-05-15T08:47:20.3330000+08:00 // get the date, time and timezone from str_date_create $str_date_time = substr($str_date_create, 0, 19); $str_timezone = substr($str_date_create, 27); $str_datetime_tz = $str_date_time . $str_timezone; // create DateTime object // sample: 2021-05-15T12:16:06+08:00 $date_create = DateTime::createFromFormat('Y-m-d\TH:i:sP', $str_datetime_tz); // check if datetime object was created // if not, someone f*cked up and we have no date create if ($date_create == false) { return null; } } // if you reach this part, then date string is valid. Since we'll be using // sql to insert the entries, we return the string $created_date = $date_create->format('Y-m-d H:i:s'); // $created_date = DateTime::createFromFormat('Y-m-d H:i:s', $str_created_date); return $created_date; } protected function logLoadInfo($user_id, $is_loaded, $serial, $error) { $date_create = new DateTime(); $str_date_create = $date_create->format('Y-m-d H:i:s'); $this->log_data[] = [ $str_date_create, $user_id, $serial, $is_loaded, $error, ]; } protected function updateWarrantySerialQueue($id) { // prepared statement $db = $this->em->getConnection(); // lock the warranty serial queue table $db->exec('LOCK TABLES warranty_serial_queue WRITE;'); $delete_stmt = $db->prepare('DELETE FROM warranty_serial_queue WHERE id = :id'); $res = $delete_stmt->execute([ ':id' => $id, ]); $db->exec('UNLOCK TABLES;'); } protected function deleteDirectoryAndFile($filedir) { $csv_filedir = $this->project_dir . '/public/warranty_serial_uploads/' . $filedir; $this->filesystem->remove($csv_filedir); } protected function setOutputInfo($filename, $file_id, $has_error, $error_message, $entries, $orig_filename) { $info = [ 'id' => $file_id, 'filename' => $orig_filename, 'has_error' => $has_error, 'error_message' => $error_message, 'data' => $entries, ]; return $info; } protected function sendResults($output_info) { $body = json_encode($output_info); // error_log(print_r($body, true)); // error_log('Sending json output to ' . $this->callback_url); $curl = curl_init(); $options = [ CURLOPT_URL => $this->callback_url, CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => $body, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', ], ]; curl_setopt_array($curl, $options); $res = curl_exec($curl); curl_close($curl); // check result error_log('Result ' . $res); } }