diff --git a/config/api_acl.yaml b/config/api_acl.yaml index c1c1067f..7d16fbf4 100644 --- a/config/api_acl.yaml +++ b/config/api_acl.yaml @@ -69,6 +69,11 @@ access_keys: acls: - id: dealer.list label: List + - id: warrantyserial + label: Warranty Serial + acls: + - id: warrantyserial.upload + label: Upload - id: tapi_vmanufacturer label: Third Party Vehicle Manufacturer Access diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 60518b75..1b0f6824 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -43,6 +43,10 @@ security: pattern: ^\/rapi\/ security: false + test_capi: + pattern: ^\/test_capi\/ + security: false + warranty_api: pattern: ^\/capi\/ stateless: true diff --git a/config/routes/capi.yaml b/config/routes/capi.yaml index 472da5c2..44f57c99 100644 --- a/config/routes/capi.yaml +++ b/config/routes/capi.yaml @@ -3,6 +3,10 @@ capi_test: path: /capi/test controller: App\Controller\CAPI\TestController::test +capi_test_warranty_serial: + path: /test_capi/test/warranty_serial + controller: App\Controller\CAPI\TestController::warrantySerial + # battery api @@ -184,3 +188,9 @@ capi_dealer_list: path: /capi/dealers controller: App\Controller\CAPI\DealerController::getAll methods: [GET] + +# warranty serial api +capi_warranty_serial_upload: + path: /capi/warranty_serial/upload + controller: App\Controller\CAPI\WarrantySerialController::uploadWarrantySerialFile + methods: [POST] diff --git a/config/services.yaml b/config/services.yaml index 9d1ee14e..c130b986 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -111,6 +111,10 @@ services: $cvu_mfg_id: "%env(CVU_MFG_ID)%" $cvu_brand_id: "%env(CVU_BRAND_ID)%" + App\Command\LoadWarrantySerialCommand: + arguments: + $callback_url: "%env(WARRANTY_SERIAL_CALLBACK_URL)%" + # rider tracker service App\Service\RiderTracker: arguments: @@ -306,3 +310,13 @@ services: App\Service\WarrantyBulkUploader: arguments: $em: "@doctrine.orm.entity_manager" + + # warranty serial file logger + App\Service\WarrantySerialUploadLogger: + arguments: + $em: "@doctrine.orm.entity_manager" + + # warranty serial load logger + App\Service\WarrantySerialLoadLogger: + arguments: + $em: "@doctrine.orm.entity_manager" diff --git a/src/Command/LoadWarrantySerialCommand.php b/src/Command/LoadWarrantySerialCommand.php new file mode 100644 index 00000000..07ba261a --- /dev/null +++ b/src/Command/LoadWarrantySerialCommand.php @@ -0,0 +1,432 @@ +em = $em; + $this->upload_logger = $upload_logger; + $this->load_logger = $load_logger; + $this->project_dir = $kernel->getProjectDir(); + $this->callback_url = $callback_url; + + 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 FROM warranty_serial_queue + WHERE status = :status'; + + $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']; + + $output_info[] = $this->processWarrantySerialFile($filename, $user_id, $file_id); + + $this->updateWarrantySerialQueue($id); + } + + // 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) + { + $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); + + 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); + + 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; + } + + // validate the date created + $serial = trim($row[0]); + $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(); + $update_stmt = $db->prepare('UPDATE warranty_serial_queue SET status = :status + WHERE id = :id'); + + $res = $update_stmt->execute([ + ':status' => 'done', + ':id' => $id, + ]); + } + + protected function setOutputInfo($filename, $file_id, $has_error, $error_message, $entries) + { + // need to get the original filename from warranty_serial_queue + // use the uploaded_file_serial which has the saved csv file + $upload_entry = $this->em->getRepository(WarrantySerialQueue::class)->findOneBy(['file_id' => $file_id]); + $original_filename = $upload_entry->getOrigFileSerial(); + + $info = [ + 'id' => $file_id, + 'filename' => $original_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); + } + +} diff --git a/src/Controller/APIUserController.php b/src/Controller/APIUserController.php index 302e97dd..122d3830 100644 --- a/src/Controller/APIUserController.php +++ b/src/Controller/APIUserController.php @@ -155,19 +155,20 @@ class APIUserController extends Controller // metadata $rider_id = $req->request->get('rider_id'); $rider = $em->getRepository(Rider::class)->find($rider_id); - // TODO: check for null rider - - $meta = ['rider_id' => $rider_id]; - - // set api user in rider if ($rider != null) + { + $meta = ['rider_id' => $rider_id]; + + // set api user in rider $rider->setAPIUser($obj); + $obj->setRider($rider) + ->setMetadata($meta); + } + // set and save values $obj->setName($req->request->get('name')) ->setEnabled($req->request->get('enabled') ? true : false) - ->setMetadata($meta) - ->setRider($rider) ->clearRoles(); // set roles diff --git a/src/Controller/CAPI/TestController.php b/src/Controller/CAPI/TestController.php index e1a5ea08..9604c056 100644 --- a/src/Controller/CAPI/TestController.php +++ b/src/Controller/CAPI/TestController.php @@ -17,4 +17,19 @@ class TestController extends APIController ]; return new APIResponse(true, 'Test successful.', $data); } + + public function warrantySerial(Request $req) + { + error_log('Got request'); + + $res = json_decode($req->getContent(), true); + + // return $res; + + $data = [ + 'result' => $res, + ]; + return new APIResponse(true, 'Test successful.', $data); + } + } diff --git a/src/Controller/CAPI/WarrantySerialController.php b/src/Controller/CAPI/WarrantySerialController.php new file mode 100644 index 00000000..c845e367 --- /dev/null +++ b/src/Controller/CAPI/WarrantySerialController.php @@ -0,0 +1,182 @@ +acl_gen = $acl_gen; + $this->upload_logger = $upload_logger; + } + + public function uploadWarrantySerialFile(Request $req, EntityManagerInterface $em, KernelInterface $kernel) + { + $this->denyAccessUnlessGranted('warrantyserial.upload', null, 'No access.'); + + $required_params = [ + 'serial_file', + ]; + + $user_id = $_SERVER['HTTP_X_CATA_API_KEY']; + + $res = $this->checkRequiredParamsForFiles($req, $required_params, $user_id); + + if ($res !== true) + return $res; + + // get the csv file + $csv_file = $req->files->get('serial_file'); + + // process file upload + $upload_dir = $kernel->getProjectDir() . '/public/warranty_serial_uploads'; + $serial_filename = $this->handleSerialFileUpload($csv_file, $upload_dir); + + // insert to warranty serial queue + $res = $this->registerWarrantySerialFile($em, $csv_file, $serial_filename, $user_id); + + // flush to db + $em->flush(); + + return $res; + } + + protected function registerWarrantySerialFile($em, $file, $serial_filename, $user_id) + { + // parse the serial filename to get the file id + $parts = explode('/', $serial_filename); + $file_id = $parts[0]; + $orig_filename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME) . '.' . $file->getClientOriginalExtension(); + + $ws_file = new WarrantySerialQueue(); + + $ws_file->setFileSerial($serial_filename) + ->setStatus('pending') + ->setOrigFileSerial($orig_filename) + ->setFileID($file_id) + ->setApiUser($user_id); + + $em->persist($ws_file); + + // log upload + $log_data = [ + 'user_id' => $user_id, + 'is_uploaded' => true, + 'orig_file_serial' => $orig_filename, + 'uploaded_file_serial' => $serial_filename, + ]; + + $this->upload_logger->logWarrantySerialUploadInfo($log_data); + + $data = [ + 'id' => $file_id, + ]; + + return new APIResponse(true, 'Warranty serial file uploaded.', $data); + } + + protected function handleSerialFileUpload($file, $target_dir) + { + // create target dir if it doesn't exist + if (!file_exists($target_dir)) + { + if (!mkdir($target_dir, 0744, true)) + { + $log_data = [ + 'user_id' => $user_id, + 'is_uploaded' => false, + 'error' => 'Failed to create folder for warranty serial files.' + ]; + + $this->upload_logger->logWarrantySerialUploadInfo($log_data); + + return null; + } + } + + // get current date + $curr_date = new DateTime(); + $str_curr_date = $curr_date->format('Ymd'); + $file_id = $str_curr_date . uniqid(); + + // move file + $filename = 'warranty_serial' . '.' . $file->getClientOriginalExtension(); + $file->move($target_dir . '/' . $file_id, $filename); + + return $file_id . '/' . $filename; + } + + protected function checkRequiredParamsForFiles(Request $req, $params, $user_id) + { + // check required parameters + $missing = $this->checkMissingParametersForFiles($req, $params); + if (count($missing) > 0) + { + // log the error + $miss_string = implode(', ', $missing); + $log_data = [ + 'user_id' => $user_id, + 'is_uploaded' => false, + 'error' => 'Missing parameter(s): ' . $miss_string + ]; + + $this->upload_logger->logWarrantySerialUploadInfo($log_data); + + return new APIResponse(false, 'Missing parameter(s): ' . $miss_string); + } + + return true; + } + + protected function checkMissingParametersForFiles(Request $req, $params = []) + { + $missing = []; + + // check if parameters are there + foreach ($params as $param) + { + if ($req->getMethod() == 'GET') + { + $check = $req->query->get($param); + if (empty($check)) + $missing[] = $param; + } + else if ($req->getMethod() == 'POST') + { + // get files from request. + $check = $req->files->get($param); + if (empty($check)) + { + $missing[] = $param; + } + } + else + return $params; + } + + return $missing; + } +} diff --git a/src/Entity/WarrantySerialLoadLog.php b/src/Entity/WarrantySerialLoadLog.php new file mode 100644 index 00000000..ece58ffb --- /dev/null +++ b/src/Entity/WarrantySerialLoadLog.php @@ -0,0 +1,120 @@ +date_create = new DateTime(); + $this->api_user = ''; + $this->serial = ''; + $this->flag_loaded = false; + $this->error = null; + } + + public function getID() + { + return $this->id; + } + + public function setDateCreate(DateTime $date_create) + { + $this->date_create = $date_create; + return $this; + } + + public function getDateCreate() + { + return $this->date_create; + } + + public function setApiUser($api_user) + { + $this->api_user = $api_user; + return $this; + } + + public function getApiUser() + { + return $this->api_user; + } + + public function setSerial($serial) + { + $this->serial = $serial; + return $this; + } + + public function getSerial() + { + return $this->serial; + } + + public function setLoaded($flag_loaded = true) + { + $this->flag_loaded = $flag_loaded; + return $this; + } + + public function isLoaded() + { + return $this->flag_loaded; + } + + public function setError($error) + { + $this->error = $error; + return $this; + } + + public function getError() + { + return $this->error; + } +} diff --git a/src/Entity/WarrantySerialQueue.php b/src/Entity/WarrantySerialQueue.php new file mode 100644 index 00000000..d139a173 --- /dev/null +++ b/src/Entity/WarrantySerialQueue.php @@ -0,0 +1,129 @@ +date_create = new DateTime(); + $this->file_serial = null; + $this->orig_file_serial = null; + $this->status = ''; + $this->api_user = ''; + $this->file_id = ''; + } + + public function getID() + { + return $this->id; + } + + public function setFileSerial($file = null) + { + $this->file_serial = $file; + return $this; + } + + public function getFileSerial() + { + return $this->file_serial; + } + + public function setStatus($status) + { + $this->status = $status; + return $this; + } + + public function getStatus() + { + return $this->status; + } + + public function setApiUser($api_user) + { + $this->api_user = $api_user; + return $this; + } + + public function getApiUser() + { + return $this->api_user; + } + + public function setFileID($file_id) + { + $this->file_id = $file_id; + return $this; + } + + public function getFileID() + { + return $this->file_id; + } + + public function setOrigFileSerial($file = null) + { + $this->orig_file_serial = $file; + return $this; + } + + public function getOrigFileSerial() + { + return $this->orig_file_serial; + } +} diff --git a/src/Entity/WarrantySerialUploadLog.php b/src/Entity/WarrantySerialUploadLog.php new file mode 100644 index 00000000..44f5b16a --- /dev/null +++ b/src/Entity/WarrantySerialUploadLog.php @@ -0,0 +1,138 @@ +date_create = new DateTime(); + $this->api_user = ''; + $this->orig_file_serial = ''; + $this->orig_file_serial = ''; + $this->flag_uploaded = false; + $this->error = null; + } + + public function getID() + { + return $this->id; + } + + public function setDateCreate(DateTime $date_create) + { + $this->date_create = $date_create; + return $this; + } + + public function getDateCreate() + { + return $this->date_create; + } + + public function setApiUser($api_user) + { + $this->api_user = $api_user; + return $this; + } + + public function getApiUser() + { + return $this->api_user; + } + + public function setOrigFileSerial($file = null) + { + $this->orig_file_serial = $file; + return $this; + } + + public function getOrigFileSerial() + { + return $this->orig_file_serial; + } + + public function setUploadedFileSerial($file = null) + { + $this->uploaded_file_serial = $file; + return $this; + } + + public function getUploadedFileSerial() + { + return $this->uploaded_file_serial; + } + + public function setUploaded($flag_uploaded = true) + { + $this->flag_uploaded = $flag_uploaded; + return $this; + } + + public function isUploaded() + { + return $this->flag_uploaded; + } + + public function setError($error) + { + $this->error = $error; + return $this; + } + + public function getError() + { + return $this->error; + } +} diff --git a/src/Service/WarrantySerialLoadLogger.php b/src/Service/WarrantySerialLoadLogger.php new file mode 100644 index 00000000..904a6535 --- /dev/null +++ b/src/Service/WarrantySerialLoadLogger.php @@ -0,0 +1,53 @@ +em = $em; + } + + public function logWarrantySerialLoadInfo($log_data) + { + // cache directory + $cache_dir = __DIR__ . '/../../var/cache'; + + $file = $cache_dir . '/warranty_serial_load_log.tab'; + error_log('opening file for warranty serial load log - ' . $file); + + $fp = fopen($file, 'w'); + if ($fp === false) + { + error_log('could not open file for load data infile - ' . $file); + } + else + { + foreach ($log_data as $key => $data) + { + $line = implode('|', $data) . "\r\n"; + fwrite($fp, $line); + } + } + + fclose($fp); + + // prepared statement + $db = $this->em->getConnection(); + $stmt = $db->prepare('LOAD DATA LOCAL INFILE \''. $file . '\' INTO TABLE warranty_serial_load_log FIELDS TERMINATED BY \'|\' LINES TERMINATED BY \'\\r\\n\' (date_create, api_user, serial, flag_loaded, error)'); + + $result = $stmt->execute(); + if (!$result) + error_log('Failed loading data.'); + + // TODO: delete file? + } + +} diff --git a/src/Service/WarrantySerialUploadLogger.php b/src/Service/WarrantySerialUploadLogger.php new file mode 100644 index 00000000..d708792c --- /dev/null +++ b/src/Service/WarrantySerialUploadLogger.php @@ -0,0 +1,47 @@ +em = $em; + } + + public function logWarrantySerialUploadInfo($log_data) + { + $log_entry = new WarrantySerialUploadLog(); + + $user_id = $log_data['user_id']; + $is_uploaded = $log_data['is_uploaded']; + + $error = ''; + $orig_file_serial = ''; + $uploaded_file_serial = ''; + + if (isset($log_data['error'])) + $error = $log_data['error']; + if (isset($log_data['orig_file_serial'])) + $orig_file_serial = $log_data['orig_file_serial']; + if (isset($log_data['uploaded_file_serial'])) + $uploaded_file_serial = $log_data['uploaded_file_serial']; + + $log_entry->setApiUser($user_id) + ->setUploaded($is_uploaded) + ->setOrigFileSerial($orig_file_serial) + ->setUploadedFileSerial($uploaded_file_serial) + ->setError($error); + + $this->em->persist($log_entry); + $this->em->flush(); + + } + +}