From ec34a62f9f04c13115d902516a2c2828c5140f22 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 23 Oct 2019 17:52:23 +0300 Subject: [PATCH 1/6] [feature] send abandoned carts to retailCRM as orders --- retailcrm/bootstrap.php | 0 retailcrm/config_ru.xml | 13 + retailcrm/index.php | 0 retailcrm/job/abandonedCarts.php | 85 +++++ retailcrm/job/export.php | 0 retailcrm/job/icml.php | 0 retailcrm/job/index.php | 0 retailcrm/job/inventories.php | 0 retailcrm/job/jobs.php | 18 + retailcrm/job/missing.php | 0 retailcrm/job/sync.php | 0 retailcrm/lib/CurlException.php | 0 retailcrm/lib/InvalidJsonException.php | 0 retailcrm/lib/JobManager.php | 393 +++++++++++++++++++++ retailcrm/lib/RetailcrmApiClientV4.php | 0 retailcrm/lib/RetailcrmApiClientV5.php | 0 retailcrm/lib/RetailcrmApiErrors.php | 0 retailcrm/lib/RetailcrmApiResponse.php | 0 retailcrm/lib/RetailcrmCatalog.php | 0 retailcrm/lib/RetailcrmDaemonCollector.php | 0 retailcrm/lib/RetailcrmHistory.php | 5 + retailcrm/lib/RetailcrmHistoryHelper.php | 0 retailcrm/lib/RetailcrmHttpClient.php | 0 retailcrm/lib/RetailcrmIcml.php | 0 retailcrm/lib/RetailcrmInventories.php | 0 retailcrm/lib/RetailcrmProxy.php | 0 retailcrm/lib/RetailcrmReferences.php | 0 retailcrm/lib/RetailcrmService.php | 0 retailcrm/lib/index.php | 0 retailcrm/logo.gif | Bin retailcrm/logo.png | Bin retailcrm/objects.xml | 0 retailcrm/public/css/retailcrm-upload.css | 0 retailcrm/public/js/exec-jobs.js | 6 + retailcrm/public/js/retailcrm-upload.js | 0 retailcrm/retailcrm.php | 221 ++++++++++-- retailcrm/translations/es.php | 6 +- retailcrm/translations/index.php | 0 retailcrm/translations/ru.php | 6 +- 39 files changed, 731 insertions(+), 22 deletions(-) mode change 100644 => 100755 retailcrm/bootstrap.php create mode 100644 retailcrm/config_ru.xml mode change 100644 => 100755 retailcrm/index.php create mode 100644 retailcrm/job/abandonedCarts.php mode change 100644 => 100755 retailcrm/job/export.php mode change 100644 => 100755 retailcrm/job/icml.php mode change 100644 => 100755 retailcrm/job/index.php mode change 100644 => 100755 retailcrm/job/inventories.php create mode 100644 retailcrm/job/jobs.php mode change 100644 => 100755 retailcrm/job/missing.php mode change 100644 => 100755 retailcrm/job/sync.php mode change 100644 => 100755 retailcrm/lib/CurlException.php mode change 100644 => 100755 retailcrm/lib/InvalidJsonException.php create mode 100644 retailcrm/lib/JobManager.php mode change 100644 => 100755 retailcrm/lib/RetailcrmApiClientV4.php mode change 100644 => 100755 retailcrm/lib/RetailcrmApiClientV5.php mode change 100644 => 100755 retailcrm/lib/RetailcrmApiErrors.php mode change 100644 => 100755 retailcrm/lib/RetailcrmApiResponse.php mode change 100644 => 100755 retailcrm/lib/RetailcrmCatalog.php mode change 100644 => 100755 retailcrm/lib/RetailcrmDaemonCollector.php mode change 100644 => 100755 retailcrm/lib/RetailcrmHistoryHelper.php mode change 100644 => 100755 retailcrm/lib/RetailcrmHttpClient.php mode change 100644 => 100755 retailcrm/lib/RetailcrmIcml.php mode change 100644 => 100755 retailcrm/lib/RetailcrmInventories.php mode change 100644 => 100755 retailcrm/lib/RetailcrmProxy.php mode change 100644 => 100755 retailcrm/lib/RetailcrmReferences.php mode change 100644 => 100755 retailcrm/lib/RetailcrmService.php mode change 100644 => 100755 retailcrm/lib/index.php mode change 100644 => 100755 retailcrm/logo.gif mode change 100644 => 100755 retailcrm/logo.png mode change 100644 => 100755 retailcrm/objects.xml mode change 100644 => 100755 retailcrm/public/css/retailcrm-upload.css create mode 100644 retailcrm/public/js/exec-jobs.js mode change 100644 => 100755 retailcrm/public/js/retailcrm-upload.js mode change 100644 => 100755 retailcrm/translations/es.php mode change 100644 => 100755 retailcrm/translations/index.php mode change 100644 => 100755 retailcrm/translations/ru.php diff --git a/retailcrm/bootstrap.php b/retailcrm/bootstrap.php old mode 100644 new mode 100755 diff --git a/retailcrm/config_ru.xml b/retailcrm/config_ru.xml new file mode 100644 index 0000000..e87ab03 --- /dev/null +++ b/retailcrm/config_ru.xml @@ -0,0 +1,13 @@ + + + retailcrm + + + + + + + 1 + 1 + + \ No newline at end of file diff --git a/retailcrm/index.php b/retailcrm/index.php old mode 100644 new mode 100755 diff --git a/retailcrm/job/abandonedCarts.php b/retailcrm/job/abandonedCarts.php new file mode 100644 index 0000000..3f84007 --- /dev/null +++ b/retailcrm/job/abandonedCarts.php @@ -0,0 +1,85 @@ +executeS( + 'SELECT c.id_cart, c.date_upd + FROM '._DB_PREFIX_.'cart AS c + WHERE id_customer != 0 + AND TIME_TO_SEC(TIMEDIFF(now(), date_upd)) >= 86400 + AND c.id_cart NOT IN(SELECT id_cart from '._DB_PREFIX_.'orders);' +); + +$status = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS'); +$paymentTypes = array_keys(json_decode(Configuration::get('RETAILCRM_API_PAYMENT'), true)); + +if (empty($rows) + || empty($status) + || !$api + || (count($paymentTypes) < 1) +) { + return; +} + +foreach ($rows as $cartId) { + $cart = new Cart($cartId['id_cart']); + $cartExternalId = RetailCRM::getCartOrderExternalId($cart); + + $response = $api->ordersGet($cartExternalId); + + if ($response === false) { + $api->customersCreate(RetailCRM::buildCrmCustomer(new Customer($cart->id_customer))); + $order = RetailCRM::buildCrmOrderFromCart($cart, $cartExternalId, $paymentTypes[0], $status); + + if (empty($order)) { + continue; + } + + if ($api->ordersCreate($order) !== false) { + $cart->date_upd = date('Y-m-d H:i:s'); + $cart->save(); + } + + continue; + } + + if (isset($response['order']) && !empty($response['order'])) { + $order = RetailCRM::buildCrmOrderFromCart($cart, $response['order']['externalId'], $paymentTypes[0], $status); + + if (empty($order)) { + continue; + } + + if ($api->ordersEdit($order) !== false) { + $cart->date_upd = date('Y-m-d H:i:s'); + $cart->save(); + } + } +} diff --git a/retailcrm/job/export.php b/retailcrm/job/export.php old mode 100644 new mode 100755 diff --git a/retailcrm/job/icml.php b/retailcrm/job/icml.php old mode 100644 new mode 100755 diff --git a/retailcrm/job/index.php b/retailcrm/job/index.php old mode 100644 new mode 100755 diff --git a/retailcrm/job/inventories.php b/retailcrm/job/inventories.php old mode 100644 new mode 100755 diff --git a/retailcrm/job/jobs.php b/retailcrm/job/jobs.php new file mode 100644 index 0000000..aeeedbc --- /dev/null +++ b/retailcrm/job/jobs.php @@ -0,0 +1,18 @@ + DateInterval::createFromDateString('10 seconds') + ), + true +); diff --git a/retailcrm/job/missing.php b/retailcrm/job/missing.php old mode 100644 new mode 100755 diff --git a/retailcrm/job/sync.php b/retailcrm/job/sync.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/CurlException.php b/retailcrm/lib/CurlException.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/InvalidJsonException.php b/retailcrm/lib/InvalidJsonException.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/JobManager.php b/retailcrm/lib/JobManager.php new file mode 100644 index 0000000..ea5db29 --- /dev/null +++ b/retailcrm/lib/JobManager.php @@ -0,0 +1,393 @@ + $diff) { + try { + if (static::compareIntervals(date_diff($lastRun, $current), $diff, $current) == 1) { + JobManager::runJob($job, $runOnceInContext); + } + } catch (\Exception $exception) { + static::handleError($exception->getFile(), $exception->getMessage()); + } catch (\Throwable $throwable) { + static::handleError($throwable->getFile(), $throwable->getMessage()); + } catch (\Error $error) { + static::handleError($error->getFile(), $error->getMessage()); + } + } + + static::unlock(); + } + + /** + * Compare two date intervals + * + * @param $first + * @param $second + * @param null $now + * + * @return int + * @throws \Exception + */ + private static function compareIntervals($first, $second, $now = null) + { + $dateFirst = is_null($now) ? new DateTime() : $now; + $dateSecond = clone $dateFirst; + + $dateFirst->add($first); + $dateSecond->add($second); + + if($dateFirst < $dateSecond) { + return -1; + } elseif($dateFirst == $dateSecond) { + return 0; + } + + return 1; + } + + /** + * Runs job + * + * @param string $job + * @param bool $once + */ + public static function runJob($job, $once = false) + { + $jobFile = implode(DIRECTORY_SEPARATOR, array(_PS_ROOT_DIR_, 'modules', 'retailcrm', 'job', $job . '.php')); + + if (!file_exists($jobFile)) { + throw new \InvalidArgumentException('Cannot find job'); + } + + static::execPHP($jobFile, false, $once); + } + + /** + * Runs PHP file + * + * @param $fileCommandLine + * @param bool $fork + * @param bool $once + */ + private static function execPHP($fileCommandLine, $fork = true, $once = false) + { + if ($fork) { + static::execInBackground(sprintf('%s %s', static::getPhpBinary(), $fileCommandLine)); + } else { + static::execHere($fileCommandLine, $once); + } + } + + /** + * Serializes jobs to JSON + * + * @param $jobs + * + * @return string + */ + public static function serializeJobs($jobs) + { + foreach ($jobs as $name => $interval) { + $jobs[$name] = serialize($interval); + } + + return (string) base64_encode(json_encode($jobs)); + } + + /** + * Unserializes jobs + * + * @param $jobsJson + * + * @return array + */ + public static function deserializeJobs($jobsJson) + { + $jobs = json_decode(base64_decode($jobsJson), true); + + if (json_last_error() != JSON_ERROR_NONE) { + throw new \InvalidArgumentException(sprintf('Invalid JSON: %s', json_last_error_msg())); + } + + if (!is_array($jobs) || count($jobs) == 0) { + throw new \InvalidArgumentException('Empty or invalid data'); + } + + foreach ($jobs as $name => $interval) { + if (!is_string($name) || !is_string($interval)) { + throw new \InvalidArgumentException('Invalid job in array'); + } + + $intervalObj = unserialize($interval); + + if (!($intervalObj instanceof DateInterval)) { + throw new \InvalidArgumentException('Invalid job interval in array'); + } + + $jobs[$name] = $intervalObj; + } + + return (array) $jobs; + } + + /** + * Writes error to log and returns 500 + * + * @param $file + * @param $msg + */ + private static function handleError($file, $msg) + { + error_log(sprintf('%s: %s', $file, $msg), 3, _PS_ROOT_DIR_ . '/retailcrm.log'); + http_response_code(500); + } + + /** + * Run process in background without waiting + * + * @param $cmd + * + * @return void + */ + private static function execInBackground($cmd) { + if (substr(php_uname(), 0, 7) == "Windows"){ + pclose(popen("start /B ". $cmd, "r")); + } else { + $outputPos = strpos($cmd, '>'); + + if ($outputPos !== false) { + $cmd = substr($cmd, 0, $outputPos); + } + + $command = $cmd . " > /dev/null &"; + + if (function_exists('exec')) { + exec($command); + } else if (function_exists('shell_exec')) { + shell_exec($command); + } else if (function_exists('passthru')) { + passthru($command); + } + } + } + + /** + * Executes php script in this context, without hanging up request + * + * @param string $phpScript + * @param bool $once + */ + private static function execHere($phpScript, $once = false) + { + ignore_user_abort( true); + set_time_limit(static::getTimeLimit()); + + if (version_compare(phpversion(), '7.0.16', '>=') && + function_exists('fastcgi_finish_request') + ) { + if (!headers_sent()) { + header('Expires: Thu, 19 Nov 1981 08:52:00 GMT'); + header('Cache-Control: no-store, no-cache, must-revalidate'); + } + + fastcgi_finish_request(); + } + + if ($once) { + require_once($phpScript); + } else { + require($phpScript); + } + } + + /** + * Returns true if system support execution in background + * + * @return bool + */ + private static function canExecInBackground() + { + if (substr(php_uname(), 0, 7) == "Windows"){ + return function_exists('pclose') && function_exists('popen'); + } else { + return function_exists('exec') + || function_exists('shell_exec') + || function_exists('passthru'); + } + } + + /** + * Returns path to current PHP binary + * + * @return string + */ + private static function getPhpBinary() + { + if (defined('PHP_BINARY') && !empty(PHP_BINARY)) { + return PHP_BINARY; + } + + if (defined('PHP_BINDIR') && !empty(PHP_BINDIR)) { + $version = phpversion(); + $filePath = implode(DIRECTORY_SEPARATOR, array(PHP_BINDIR, 'php' . $version)); + + while (strlen($version) != 0 && !file_exists($filePath)) { + $dotPos = strrpos($version, '.'); + + if ($dotPos !== false) { + $version = substr($version, 0, strrpos($version, '.')); + } else { + $version = ''; + } + + $filePath = implode(DIRECTORY_SEPARATOR, array(PHP_BINDIR, 'php' . $version)); + } + + if (file_exists($filePath)) { + return $filePath; + } + } + + return 'php'; + } + + /** + * Returns script execution time limit + * + * @return int + */ + private static function getTimeLimit() + { + return 14400; + } + + /** + * Returns true if lock is present and it's not expired + * + * @return bool + */ + private static function isLocked() + { + $inProcess = (bool) Configuration::get(self::IN_PROGRESS_NAME); + $lastRan = date_create_from_format(self::DATE_FORMAT, (string) Configuration::get(self::LAST_RUN_NAME)); + $lastRanSeconds = $lastRan instanceof DateTime ? $lastRan->format('U') : time(); + + if (($lastRanSeconds + self::getTimeLimit()) < time()) { + static::unlock(); + + return false; + } + + return $inProcess; + } + + /** + * Installs lock + * + * @return bool + */ + private static function lock() + { + if (!static::isLocked()) { + Configuration::updateValue(self::IN_PROGRESS_NAME, true); + + return true; + } + + return false; + } + + /** + * Removes lock + * + * @return bool + */ + private static function unlock() + { + Configuration::updateValue(self::IN_PROGRESS_NAME, false); + + return false; + } +} + +if (PHP_SAPI == 'cli' && $argc == 3) { + try { + $jobs = JobManager::deserializeJobs($argv[1]); + $runOnce = (bool) $argv[2]; + } catch (InvalidArgumentException $exception) { + printf('Error: %s%s', $exception->getMessage(), PHP_EOL); + exit(0); + } + + JobManager::execJobs($jobs, $runOnce); +} \ No newline at end of file diff --git a/retailcrm/lib/RetailcrmApiClientV4.php b/retailcrm/lib/RetailcrmApiClientV4.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmApiClientV5.php b/retailcrm/lib/RetailcrmApiClientV5.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmApiErrors.php b/retailcrm/lib/RetailcrmApiErrors.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmApiResponse.php b/retailcrm/lib/RetailcrmApiResponse.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmCatalog.php b/retailcrm/lib/RetailcrmCatalog.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmDaemonCollector.php b/retailcrm/lib/RetailcrmDaemonCollector.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmHistory.php b/retailcrm/lib/RetailcrmHistory.php index 764388a..7aabe0c 100755 --- a/retailcrm/lib/RetailcrmHistory.php +++ b/retailcrm/lib/RetailcrmHistory.php @@ -173,6 +173,7 @@ class RetailcrmHistory $sinceId = $end['id']; $statuses = array_flip(array_filter(json_decode(Configuration::get('RETAILCRM_API_STATUS'), true))); + $cartStatus = (string)(Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS')); $deliveries = array_flip(array_filter(json_decode(Configuration::get('RETAILCRM_API_DELIVERY'), true))); $payments = array_flip(array_filter(json_decode(Configuration::get('RETAILCRM_API_PAYMENT'), true))); $deliveryDefault = json_decode(Configuration::get('RETAILCRM_API_DELIVERY_DEFAULT'), true); @@ -189,6 +190,10 @@ class RetailcrmHistory if ($responce) { $order = $responce['order']; + + if ($order['status'] == $cartStatus) { + continue; + } } else { continue; } diff --git a/retailcrm/lib/RetailcrmHistoryHelper.php b/retailcrm/lib/RetailcrmHistoryHelper.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmHttpClient.php b/retailcrm/lib/RetailcrmHttpClient.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmIcml.php b/retailcrm/lib/RetailcrmIcml.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmInventories.php b/retailcrm/lib/RetailcrmInventories.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmProxy.php b/retailcrm/lib/RetailcrmProxy.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmReferences.php b/retailcrm/lib/RetailcrmReferences.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/RetailcrmService.php b/retailcrm/lib/RetailcrmService.php old mode 100644 new mode 100755 diff --git a/retailcrm/lib/index.php b/retailcrm/lib/index.php old mode 100644 new mode 100755 diff --git a/retailcrm/logo.gif b/retailcrm/logo.gif old mode 100644 new mode 100755 diff --git a/retailcrm/logo.png b/retailcrm/logo.png old mode 100644 new mode 100755 diff --git a/retailcrm/objects.xml b/retailcrm/objects.xml old mode 100644 new mode 100755 diff --git a/retailcrm/public/css/retailcrm-upload.css b/retailcrm/public/css/retailcrm-upload.css old mode 100644 new mode 100755 diff --git a/retailcrm/public/js/exec-jobs.js b/retailcrm/public/js/exec-jobs.js new file mode 100644 index 0000000..cd2fc8d --- /dev/null +++ b/retailcrm/public/js/exec-jobs.js @@ -0,0 +1,6 @@ +(function () { + var xhr = new XMLHttpRequest(); + xhr.open('GET', '/modules/retailcrm/job/jobs.php', true); + xhr.timeout = 0; + xhr.send(null); +})(); \ No newline at end of file diff --git a/retailcrm/public/js/retailcrm-upload.js b/retailcrm/public/js/retailcrm-upload.js old mode 100644 new mode 100755 diff --git a/retailcrm/retailcrm.php b/retailcrm/retailcrm.php index b5758d5..727ad6d 100755 --- a/retailcrm/retailcrm.php +++ b/retailcrm/retailcrm.php @@ -31,6 +31,7 @@ class RetailCRM extends Module public $log; public $confirmUninstall; public $reference; + public $assetsBase; private $use_new_hooks = true; @@ -53,6 +54,12 @@ class RetailCRM extends Module $this->psVersion = Tools::substr(_PS_VERSION_, 0, 3); $this->log = _PS_ROOT_DIR_ . '/retailcrm.log'; $this->module_key = '149c765c6cddcf35e1f13ea6c71e9fa5'; + $this->assetsBase = + Tools::getShopDomainSsl(true, true) . + __PS_BASE_URI__ . + 'modules/' . + $this->name . + '/public'; if ($this->psVersion == '1.6') { $this->bootstrap = true; @@ -84,6 +91,8 @@ class RetailCRM extends Module public function hookHeader() { + $this->context->controller->addJS($this->assetsBase . '/js/exec-jobs.js'); + if (Configuration::get('RETAILCRM_DAEMON_COLLECTOR_ACTIVE') && Configuration::get('RETAILCRM_DAEMON_COLLECTOR_KEY') ) { @@ -116,6 +125,8 @@ class RetailCRM extends Module Configuration::deleteByName('RETAILCRM_LAST_SYNC') && Configuration::deleteByName('RETAILCRM_API_VERSION') && Configuration::deleteByName('RETAILCRM_LAST_CUSTOMERS_SYNC') && + Configuration::deleteByName('RETAILCRM_API_SYNCHRONIZE_CARTS') && + Configuration::deleteByName('RETAILCRM_API_SYNCHRONIZED_CART_STATUS') && Configuration::deleteByName('RETAILCRM_LAST_ORDERS_SYNC'); } @@ -144,12 +155,17 @@ class RetailCRM extends Module $collectorActive = (Tools::getValue('RETAILCRM_DAEMON_COLLECTOR_ACTIVE_1')); $collectorKey = (string)(Tools::getValue('RETAILCRM_DAEMON_COLLECTOR_KEY')); $clientId = Configuration::get('RETAILCRM_CLIENT_ID'); + $synchronizeCartsActive = (Tools::getValue('RETAILCRM_API_SYNCHRONIZE_CARTS_1')); + $synchronizedCartStatus = (string)(Tools::getValue('RETAILCRM_API_SYNCHRONIZED_CART_STATUS')); $settings = array( 'address' => $address, 'token' => $token, 'version' => $version, - 'clientId' => $clientId + 'clientId' => $clientId, + 'status' => $status, + 'statusExport' => $statusExport, + 'synchronizeCartStatus' => $synchronizedCartStatus ); $output .= $this->validateForm($settings, $output); @@ -166,6 +182,8 @@ class RetailCRM extends Module Configuration::updateValue('RETAILCRM_STATUS_EXPORT', $statusExport); Configuration::updateValue('RETAILCRM_DAEMON_COLLECTOR_ACTIVE', $collectorActive); Configuration::updateValue('RETAILCRM_DAEMON_COLLECTOR_KEY', $collectorKey); + Configuration::updateValue('RETAILCRM_API_SYNCHRONIZE_CARTS', $synchronizeCartsActive); + Configuration::updateValue('RETAILCRM_API_SYNCHRONIZED_CART_STATUS', $synchronizedCartStatus); $output .= $this->displayConfirmation($this->l('Settings updated')); } @@ -189,14 +207,9 @@ class RetailCRM extends Module "$address/admin/settings#t-main" ); - $assetsBase = - Tools::getShopDomainSsl(true, true) . - __PS_BASE_URI__ . - 'modules/' . - $this->name . - '/public'; - $this->context->controller->addCSS($assetsBase . '/css/retailcrm-upload.css'); - $this->context->controller->addJS($assetsBase . '/js/retailcrm-upload.js'); + $this->context->controller->addCSS($this->assetsBase . '/css/retailcrm-upload.css'); + $this->context->controller->addJS($this->assetsBase . '/js/retailcrm-upload.js'); + $this->context->controller->addJS($this->assetsBase . '/js/exec-jobs.js'); $this->display(__FILE__, 'retailcrm.tpl'); return $output . $this->displaySettingsForm() . $this->displayUploadOrdersForm(); @@ -278,7 +291,45 @@ class RetailCRM extends Module */ public static function verifyDate($date, $format = "Y-m-d") { - return (bool)date_create_from_format($format, $date); + return $date !== "0000-00-00" && (bool)date_create_from_format($format, $date); + } + + /** + * Build array with order data for retailCRM from PrestaShop cart data + * + * @param Cart $cart Cart with data + * @param string $externalId External ID for order + * @param string $paymentType Payment type (buildCrmOrder requires it) + * @param string $status Status for order + * + * @return array + */ + public static function buildCrmOrderFromCart(Cart $cart = null, $externalId = '', $paymentType = '', $status = '') + { + if (empty($cart) || empty($paymentType) || empty($status)) { + return array(); + } + + $order = new Order(); + $order->id_cart = $cart->id; + $order->id_customer = $cart->id_customer; + $order->total_discounts = 0; + $order->module = $paymentType; + $order->payment = $paymentType; + $orderData = static::buildCrmOrder( + $order, + new Customer($cart->id_customer), + $cart, + false, + true, + true + ); + $orderData['externalId'] = $externalId; + $orderData['status'] = $status; + + unset($orderData['payments']); + + return $orderData; } /** @@ -289,6 +340,7 @@ class RetailCRM extends Module * @param Cart $orderCart Cart for provided order. Optional * @param bool $isStatusExport Use status for export * @param bool $preferCustomerAddress Use customer address even if delivery address is provided + * @param bool $dataFromCart Prefer data from cart * * @return array retailCRM order data */ @@ -297,7 +349,8 @@ class RetailCRM extends Module Customer $customer = null, Cart $orderCart = null, $isStatusExport = false, - $preferCustomerAddress = false + $preferCustomerAddress = false, + $dataFromCart = false ) { $apiVersion = Configuration::get('RETAILCRM_API_VERSION'); $statusExport = Configuration::get('RETAILCRM_STATUS_EXPORT'); @@ -357,6 +410,10 @@ class RetailCRM extends Module return $v->id_customer == $customer->id; } ); + + if (is_array($address) && count($address) == 1) { + $address = reset($address); + } } $address = static::addressParse($address); @@ -388,16 +445,34 @@ class RetailCRM extends Module $crmOrder['payments'] = array(); } - if (array_key_exists($order->id_carrier, $delivery) && !empty($delivery[$order->id_carrier])) { - $crmOrder['delivery']['code'] = $delivery[$order->id_carrier]; + $idCarrier = $dataFromCart ? $cart->id_carrier : $order->id_carrier; + + if (empty($idCarrier)) { + $idCarrier = $order->id_carrier; + $totalShipping = $order->total_shipping; + $totalShippingWithoutTax = $order->total_shipping_tax_excl; + } else { + $totalShipping = $dataFromCart ? $cart->getCarrierCost($idCarrier) : $order->total_shipping; + + if (!empty($totalShipping) && $totalShipping != 0) { + $totalShippingWithoutTax = $dataFromCart + ? $totalShipping - $cart->getCarrierCost($idCarrier, false) + : $order->total_shipping_tax_excl; + } else { + $totalShippingWithoutTax = $order->total_shipping_tax_excl; + } } - if (isset($order->total_shipping) && ((int) $order->total_shipping) > 0) { - $crmOrder['delivery']['cost'] = round($order->total_shipping, 2); + if (array_key_exists($idCarrier, $delivery) && !empty($delivery[$idCarrier])) { + $crmOrder['delivery']['code'] = $delivery[$idCarrier]; } - if (isset($order->total_shipping_tax_excl) && $order->total_shipping_tax_excl > 0) { - $crmOrder['delivery']['netCost'] = round($order->total_shipping_tax_excl, 2); + if (isset($totalShipping) && ((int) $totalShipping) > 0) { + $crmOrder['delivery']['cost'] = round($totalShipping, 2); + } + + if (isset($totalShippingWithoutTax) && $totalShippingWithoutTax > 0) { + $crmOrder['delivery']['netCost'] = round($totalShippingWithoutTax, 2); } $comment = $order->getFirstMessage(); @@ -406,7 +481,30 @@ class RetailCRM extends Module $crmOrder['customerComment'] = $comment; } - foreach ($order->getProducts() as $product) { + if ($dataFromCart) { + $productStore = $cart; + $converter = function ($product) { + $product['product_attribute_id'] = $product['id_product_attribute']; + $product['product_quantity'] = $product['cart_quantity']; + $product['product_id'] = $product['id_product']; + $product['id_order_detail'] = $product['id_product']; + $product['product_name'] = $product['name']; + $product['product_price'] = $product['price']; + $product['purchase_supplier_price'] = $product['price']; + $product['product_price_wt'] = $product['price_wt']; + + return $product; + }; + } else { + $productStore = $order; + $converter = function ($product) { + return $product; + }; + } + + foreach ($productStore->getProducts() as $productData) { + $product = $converter($productData); + if (isset($product['product_attribute_id']) && $product['product_attribute_id'] > 0) { $productId = $product['product_id'] . '#' . $product['product_attribute_id']; } else { @@ -605,6 +703,43 @@ class RetailCRM extends Module ); if ($this->api) { + /* + * Synchronize carts form + */ + if ($this->use_new_hooks) { + $fields_form[]['form'] = array( + 'legend' => array( + 'title' => $this->l('Synchronization of buyer carts'), + ), + 'input' => array( + array( + 'type' => 'checkbox', + 'label' => $this->l('Create orders for abandoned carts of buyers'), + 'name' => 'RETAILCRM_API_SYNCHRONIZE_CARTS', + 'values' => array( + 'query' => array( + array( + 'id_option' => 1, + ) + ), + 'id' => 'id_option', + 'name' => 'name' + ) + ), + array( + 'type' => 'select', + 'name' => 'RETAILCRM_API_SYNCHRONIZED_CART_STATUS', + 'label' => $this->l('Order status for abandoned carts of buyers'), + 'options' => array( + 'query' => $this->reference->getStatuseDefaultExport(), + 'id' => 'id_option', + 'name' => 'name' + ) + ) + ) + ); + } + /* * Delivery */ @@ -698,6 +833,8 @@ class RetailCRM extends Module $helper->fields_value['RETAILCRM_API_VERSION'] = Configuration::get('RETAILCRM_API_VERSION'); $helper->fields_value['RETAILCRM_STATUS_EXPORT'] = Configuration::get('RETAILCRM_STATUS_EXPORT'); $helper->fields_value['RETAILCRM_DAEMON_COLLECTOR_ACTIVE_1'] = Configuration::get('RETAILCRM_DAEMON_COLLECTOR_ACTIVE'); + $helper->fields_value['RETAILCRM_API_SYNCHRONIZE_CARTS_1'] = Configuration::get('RETAILCRM_API_SYNCHRONIZE_CARTS'); + $helper->fields_value['RETAILCRM_API_SYNCHRONIZED_CART_STATUS'] = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS'); $helper->fields_value['RETAILCRM_DAEMON_COLLECTOR_KEY'] = Configuration::get('RETAILCRM_DAEMON_COLLECTOR_KEY'); $deliverySettings = Configuration::get('RETAILCRM_API_DELIVERY'); @@ -751,6 +888,17 @@ class RetailCRM extends Module } } + if ($this->use_new_hooks) { + $synchronizedCartsStatusDefault = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS'); + if (isset($synchronizedCartsStatusDefault) && $synchronizedCartsStatusDefault != '') { + $synchronizedCartsStatus = json_decode($synchronizedCartsStatusDefault); + if ($synchronizedCartsStatus) { + $name = 'RETAILCRM_API_SYNCHRONIZED_CART_STATUS'; + $helper->fields_value[$name] = $synchronizedCartsStatus; + } + } + } + return $helper->generateForm($fields_form); } @@ -1036,9 +1184,15 @@ class RetailCRM extends Module if (isset($params['orderStatus'])) { $cart = $params['cart']; - $order = static::buildCrmOrder($params['order'], $params['customer'], $params['cart'], false); + $response = $this->api->ordersGet(self::getCartOrderExternalId($cart)); + $order = static::buildCrmOrder($params['order'], $params['customer'], $cart, false); - $this->api->ordersCreate($order); + if (!empty($response) && isset($response['order'])) { + $order['id'] = $response['order']['id']; + $this->api->ordersEdit($order, 'id'); + } else { + $this->api->ordersCreate($order); + } return true; @@ -1159,6 +1313,15 @@ class RetailCRM extends Module return false; } + private function validateStatuses($statuses, $statusExport, $cartStatus) + { + if ($cartStatus == $statusExport || (stripos($statuses, $cartStatus) !== false)) { + return false; + } + + return true; + } + private function validateForm($settings, $output) { if (!$this->validateCrmAddress($settings['address']) || !Validate::isGenericName($settings['address'])) { @@ -1167,11 +1330,29 @@ class RetailCRM extends Module $output .= $this->displayError($this->l('Invalid or empty crm api token')); } elseif (!$this->validateApiVersion($settings)) { $output .= $this->displayError($this->l('The selected version of the API is unavailable')); + } elseif (!$this->validateStatuses( + $settings['status'], + $settings['statusExport'], + $settings['synchronizeCartStatus']) + ) { + $output .= $this->displayError($this->l('Order status for abandoned carts should not be used in other settings')); } return $output; } + /** + * Returns externalId for order + * + * @param Cart $cart + * + * @return string + */ + public static function getCartOrderExternalId(Cart $cart) + { + return sprintf('pscart_%d', $cart->id); + } + /** * Activate/deactivate module in marketplace retailCRM * diff --git a/retailcrm/translations/es.php b/retailcrm/translations/es.php old mode 100644 new mode 100755 index 6db2eca..43e037f --- a/retailcrm/translations/es.php +++ b/retailcrm/translations/es.php @@ -61,4 +61,8 @@ $_MODULE['<{retailcrm}prestashop>retailcrm_acfa058ec9e6e4745eddc0cae3f0f881'] = $_MODULE['<{retailcrm}prestashop>retailcrm_91412465ea9169dfd901dd5e7c96dd99'] = 'Exportar'; $_MODULE['<{retailcrm}prestashop>retailcrm_6bd461d1fc51b3294c6513cecc24758d'] = 'Los pedidos han sido cargados con éxito'; $_MODULE['<{retailcrm}prestashop>retailcrm_3518f7b8d79f91da4c91772b4c46db94'] = 'No se han podido cargar algunos pedidos'; -$_MODULE['<{retailcrm}prestashop>retailcrm_9a7fc06b4b2359f1f26f75fbbe27a3e8'] = 'No todos los pedidos se han cargado con existo'; \ No newline at end of file +$_MODULE['<{retailcrm}prestashop>retailcrm_9a7fc06b4b2359f1f26f75fbbe27a3e8'] = 'No todos los pedidos se han cargado con existo'; +$_MODULE['<{retailcrm}prestashop>retailcrm_917afe348e09163269225a89a825e634'] = 'Sincronización de carritos de compradores'; +$_MODULE['<{retailcrm}prestashop>retailcrm_d8e002d770b6f98af7b7ae9a0e5acfe9'] = 'Crear pedidos para carritos abandonados de compradores'; +$_MODULE['<{retailcrm}prestashop>retailcrm_35b5a9139a54caeb925556ceb2c38086'] = 'Estado del pedido para carritos abandonados de compradores'; +$_MODULE['<{retailcrm}prestashop>retailcrm_b9c4e8fe56eabcc4c7913ebb2f8eb388'] = 'Estado del pedido para carritos abandonados no debe ser utilizado en otros ajustes'; \ No newline at end of file diff --git a/retailcrm/translations/index.php b/retailcrm/translations/index.php old mode 100644 new mode 100755 diff --git a/retailcrm/translations/ru.php b/retailcrm/translations/ru.php old mode 100644 new mode 100755 index 1f0def2..a2136b1 --- a/retailcrm/translations/ru.php +++ b/retailcrm/translations/ru.php @@ -61,4 +61,8 @@ $_MODULE['<{retailcrm}prestashop>retailcrm_acfa058ec9e6e4745eddc0cae3f0f881'] = $_MODULE['<{retailcrm}prestashop>retailcrm_91412465ea9169dfd901dd5e7c96dd99'] = 'Выгрузить'; $_MODULE['<{retailcrm}prestashop>retailcrm_6bd461d1fc51b3294c6513cecc24758d'] = 'Все заказы успешно загружены'; $_MODULE['<{retailcrm}prestashop>retailcrm_3518f7b8d79f91da4c91772b4c46db94'] = 'Некоторые заказы не удалось загрузить'; -$_MODULE['<{retailcrm}prestashop>retailcrm_9a7fc06b4b2359f1f26f75fbbe27a3e8'] = 'Не все заказы загружены успешно'; \ No newline at end of file +$_MODULE['<{retailcrm}prestashop>retailcrm_9a7fc06b4b2359f1f26f75fbbe27a3e8'] = 'Не все заказы загружены успешно'; +$_MODULE['<{retailcrm}prestashop>retailcrm_917afe348e09163269225a89a825e634'] = 'Синхронизация корзин покупателей'; +$_MODULE['<{retailcrm}prestashop>retailcrm_d8e002d770b6f98af7b7ae9a0e5acfe9'] = 'Создавать заказы для брошенных корзин покупателей'; +$_MODULE['<{retailcrm}prestashop>retailcrm_35b5a9139a54caeb925556ceb2c38086'] = 'Статус заказа для брошенных корзин покупателей'; +$_MODULE['<{retailcrm}prestashop>retailcrm_b9c4e8fe56eabcc4c7913ebb2f8eb388'] = 'Статус заказа для брошенных корзин не должен использоваться в других настройках'; \ No newline at end of file From 1e20ca316adb415337ea31cf72e7df96e8022bfd Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 25 Oct 2019 10:09:48 +0300 Subject: [PATCH 2/6] run job every hour instead of 10 seconds --- retailcrm/job/jobs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/retailcrm/job/jobs.php b/retailcrm/job/jobs.php index aeeedbc..2ceb379 100644 --- a/retailcrm/job/jobs.php +++ b/retailcrm/job/jobs.php @@ -12,7 +12,7 @@ require_once(dirname(__FILE__) . '/../bootstrap.php'); JobManager::startJobs( array( - 'abandonedCarts' => DateInterval::createFromDateString('10 seconds') + 'abandonedCarts' => DateInterval::createFromDateString('1 hour') ), true ); From a4473d488dbb300032ee7e3cbff65f643e1505f7 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 25 Oct 2019 10:49:42 +0300 Subject: [PATCH 3/6] JobManager fix --- retailcrm/lib/JobManager.php | 40 +++++++----------------------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/retailcrm/lib/JobManager.php b/retailcrm/lib/JobManager.php index ea5db29..1b69c95 100644 --- a/retailcrm/lib/JobManager.php +++ b/retailcrm/lib/JobManager.php @@ -29,7 +29,6 @@ if (!defined('_PS_VERSION_')) { */ class JobManager { - const DATE_FORMAT = 'Y.m.d H:i:s'; const LAST_RUN_NAME = 'RETAILCRM_LAST_RUN'; const IN_PROGRESS_NAME = 'RETAILCRM_JOBS_IN_PROGRESS'; @@ -59,7 +58,7 @@ class JobManager public static function execJobs($jobs = array(), $runOnceInContext = false) { $current = date_create('now'); - $lastRun = date_create_from_format(self::DATE_FORMAT, (string) Configuration::get(self::LAST_RUN_NAME)); + $lastRun = date_create_from_format(DATE_RFC3339, (string) Configuration::get(self::LAST_RUN_NAME)); if ($lastRun === false) { $lastRun = $current; @@ -69,12 +68,14 @@ class JobManager return; } - Configuration::updateValue(self::LAST_RUN_NAME, date_format($current, self::DATE_FORMAT)); - foreach ($jobs as $job => $diff) { try { - if (static::compareIntervals(date_diff($lastRun, $current), $diff, $current) == 1) { + $shouldRunAt = clone $lastRun; + $shouldRunAt->add($diff); + + if ($shouldRunAt <= $current) { JobManager::runJob($job, $runOnceInContext); + Configuration::updateValue(self::LAST_RUN_NAME, date_format($current, DATE_RFC3339)); } } catch (\Exception $exception) { static::handleError($exception->getFile(), $exception->getMessage()); @@ -88,33 +89,6 @@ class JobManager static::unlock(); } - /** - * Compare two date intervals - * - * @param $first - * @param $second - * @param null $now - * - * @return int - * @throws \Exception - */ - private static function compareIntervals($first, $second, $now = null) - { - $dateFirst = is_null($now) ? new DateTime() : $now; - $dateSecond = clone $dateFirst; - - $dateFirst->add($first); - $dateSecond->add($second); - - if($dateFirst < $dateSecond) { - return -1; - } elseif($dateFirst == $dateSecond) { - return 0; - } - - return 1; - } - /** * Runs job * @@ -339,7 +313,7 @@ class JobManager private static function isLocked() { $inProcess = (bool) Configuration::get(self::IN_PROGRESS_NAME); - $lastRan = date_create_from_format(self::DATE_FORMAT, (string) Configuration::get(self::LAST_RUN_NAME)); + $lastRan = date_create_from_format(DATE_RFC3339, (string) Configuration::get(self::LAST_RUN_NAME)); $lastRanSeconds = $lastRan instanceof DateTime ? $lastRan->format('U') : time(); if (($lastRanSeconds + self::getTimeLimit()) < time()) { From 1edd12bc8082ac3c6aeb97a854cc28b182d870a7 Mon Sep 17 00:00:00 2001 From: Pavel Date: Tue, 29 Oct 2019 09:42:34 +0300 Subject: [PATCH 4/6] abandoned carts upload timeout & JobManager rework --- retailcrm/job/abandonedCarts.php | 19 ++- retailcrm/job/jobs.php | 2 +- retailcrm/lib/JobManager.php | 99 +++++++++++++-- retailcrm/public/js/exec-jobs.js | 0 retailcrm/public/js/retailcrm-upload.min.js | 1 + retailcrm/retailcrm.php | 128 ++++++++++++++------ retailcrm/translations/es.php | 11 +- retailcrm/translations/ru.php | 11 +- 8 files changed, 213 insertions(+), 58 deletions(-) mode change 100644 => 100755 retailcrm/job/abandonedCarts.php mode change 100644 => 100755 retailcrm/job/jobs.php mode change 100644 => 100755 retailcrm/lib/JobManager.php mode change 100644 => 100755 retailcrm/public/js/exec-jobs.js create mode 100755 retailcrm/public/js/retailcrm-upload.min.js diff --git a/retailcrm/job/abandonedCarts.php b/retailcrm/job/abandonedCarts.php old mode 100644 new mode 100755 index 3f84007..39c1a04 --- a/retailcrm/job/abandonedCarts.php +++ b/retailcrm/job/abandonedCarts.php @@ -29,14 +29,21 @@ if (!empty($apiUrl) && !empty($apiKey)) { return; } -$rows = Db::getInstance()->executeS( - 'SELECT c.id_cart, c.date_upd +$time = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_DELAY'); + +if (is_numeric($time) && $time > 0) { + $time = intval($time); +} else { + $time = 0; +} + +$now = new DateTime(); +$sql = 'SELECT c.id_cart, c.date_upd FROM '._DB_PREFIX_.'cart AS c WHERE id_customer != 0 - AND TIME_TO_SEC(TIMEDIFF(now(), date_upd)) >= 86400 - AND c.id_cart NOT IN(SELECT id_cart from '._DB_PREFIX_.'orders);' -); - + AND TIME_TO_SEC(TIMEDIFF(\''.pSQL($now->format('Y-m-d H:i:s')).'\', date_upd)) >= '.$time.' + AND c.id_cart NOT IN(SELECT id_cart from '._DB_PREFIX_.'orders);'; +$rows = Db::getInstance()->executeS($sql); $status = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS'); $paymentTypes = array_keys(json_decode(Configuration::get('RETAILCRM_API_PAYMENT'), true)); diff --git a/retailcrm/job/jobs.php b/retailcrm/job/jobs.php old mode 100644 new mode 100755 index 2ceb379..589da88 --- a/retailcrm/job/jobs.php +++ b/retailcrm/job/jobs.php @@ -12,7 +12,7 @@ require_once(dirname(__FILE__) . '/../bootstrap.php'); JobManager::startJobs( array( - 'abandonedCarts' => DateInterval::createFromDateString('1 hour') + 'abandonedCarts' => null ), true ); diff --git a/retailcrm/lib/JobManager.php b/retailcrm/lib/JobManager.php old mode 100644 new mode 100755 index 1b69c95..4ec9165 --- a/retailcrm/lib/JobManager.php +++ b/retailcrm/lib/JobManager.php @@ -37,6 +37,26 @@ class JobManager */ static $lock; + /** + * Entry point for all jobs. + * Jobs must be passed in this format: + * JobManager::startJobs( + * array( + * 'jobName' => DateInterval::createFromDateString('1 hour') + * ), + * true + * ); + * + * File `jobName.php` must exist in retailcrm/job and must contain everything to run job. + * Throwed errors will be logged in /retailcrm.log + * DateInterval must be positive. Pass `null` instead of DateInterval to remove + * any delay - in other words, jobs without interval will be executed every time. + * + * @param array $jobs + * @param bool $runOnceInContext + * + * @throws \Exception + */ public static function startJobs($jobs = array(), $runOnceInContext = false) { $inBackground = static::canExecInBackground(); @@ -54,15 +74,13 @@ class JobManager * * @param array $jobs * @param bool $runOnceInContext + * + * @throws \Exception */ public static function execJobs($jobs = array(), $runOnceInContext = false) { $current = date_create('now'); - $lastRun = date_create_from_format(DATE_RFC3339, (string) Configuration::get(self::LAST_RUN_NAME)); - - if ($lastRun === false) { - $lastRun = $current; - } + $lastRuns = static::getLastRuns(); if (!static::lock()) { return; @@ -70,25 +88,84 @@ class JobManager foreach ($jobs as $job => $diff) { try { - $shouldRunAt = clone $lastRun; - $shouldRunAt->add($diff); + if (isset($lastRuns[$job]) && $lastRuns[$job] instanceof DateTime) { + $shouldRunAt = clone $lastRuns[$job]; + } else { + $shouldRunAt = new DateTime(); + } - if ($shouldRunAt <= $current) { + if ($diff instanceof DateInterval) { + $shouldRunAt->add($diff); + } + + if (!isset($shouldRunAt) || $shouldRunAt <= $current) { JobManager::runJob($job, $runOnceInContext); - Configuration::updateValue(self::LAST_RUN_NAME, date_format($current, DATE_RFC3339)); + $lastRuns[$job] = new DateTime(); } } catch (\Exception $exception) { static::handleError($exception->getFile(), $exception->getMessage()); } catch (\Throwable $throwable) { static::handleError($throwable->getFile(), $throwable->getMessage()); - } catch (\Error $error) { - static::handleError($error->getFile(), $error->getMessage()); } } + static::setLastRuns($lastRuns); static::unlock(); } + /** + * Extracts jobs last runs from db + * + * @return array + * @throws \Exception + */ + private static function getLastRuns() + { + $lastRuns = json_decode((string) Configuration::get(self::LAST_RUN_NAME), true); + + if (json_last_error() != JSON_ERROR_NONE) { + $lastRuns = []; + } else { + foreach ($lastRuns as $job => $ran) { + $lastRan = DateTime::createFromFormat(DATE_RFC3339, $ran); + + if ($lastRan instanceof DateTime) { + $lastRuns[$job] = $lastRan; + } else { + $lastRuns[$job] = new DateTime(); + } + } + } + + return (array) $lastRuns; + } + + /** + * Updates jobs last runs in db + * + * @param array $lastRuns + * + * @throws \Exception + */ + private static function setLastRuns($lastRuns = []) + { + $now = new DateTime(); + + if (!is_array($lastRuns)) { + $lastRuns = []; + } + + foreach ($lastRuns as $job => $ran) { + if ($ran instanceof DateTime) { + $lastRuns[$job] = $ran->format(DATE_RFC3339); + } else { + $lastRuns[$job] = $now->format(DATE_RFC3339); + } + } + + Configuration::updateValue(self::LAST_RUN_NAME, (string) json_encode($lastRuns)); + } + /** * Runs job * diff --git a/retailcrm/public/js/exec-jobs.js b/retailcrm/public/js/exec-jobs.js old mode 100644 new mode 100755 diff --git a/retailcrm/public/js/retailcrm-upload.min.js b/retailcrm/public/js/retailcrm-upload.min.js new file mode 100755 index 0000000..ffbfed8 --- /dev/null +++ b/retailcrm/public/js/retailcrm-upload.min.js @@ -0,0 +1 @@ +function _toConsumableArray(a){return _arrayWithoutHoles(a)||_iterableToArray(a)||_nonIterableSpread()}function _nonIterableSpread(){throw new TypeError("Invalid attempt to spread non-iterable instance")}function _iterableToArray(a){if(Symbol.iterator in Object(a)||"[object Arguments]"===Object.prototype.toString.call(a))return Array.from(a)}function _arrayWithoutHoles(a){if(Array.isArray(a)){for(var b=0,c=Array(a.length);b div.bootstrap + div.bootstrap"),this.submitAction=this.submitAction.bind(this),this.partitionId=this.partitionId.bind(this),this.setLoading=this.setLoading.bind(this),$(this.submitButton).click(this.submitAction))}a.prototype.submitAction=function(a){a.preventDefault();let b=this.partitionId($(this.input).val().toString().replace(/\s+/g,""));return 0!==b.length&&void(this.setLoading(!0),$(this.form).submit())},a.prototype.setLoading=function(a){let b=$("div#retailcrm-loading-fade");0===b.length&&$("body").append("\n
\n
\n
\n ".trim()),b.css("visibility",a?"visible":"hidden")},a.prototype.partitionId=function(a){if(""===a)return[];let b=a.split(","),c=b.filter(a=>-1===a.toString().indexOf("-")),d=b.filter(a=>-1!==a.toString().indexOf("-")),e=[];return d.forEach(a=>{let b=a.split("-");e=[].concat(_toConsumableArray(e),_toConsumableArray(_toConsumableArray(Array(+b[1]+1).keys()).slice(+b[0],+b[1]+1)))}),[].concat(_toConsumableArray(c),_toConsumableArray(e)).map(a=>+a).sort((c,a)=>c-a)},new a}); diff --git a/retailcrm/retailcrm.php b/retailcrm/retailcrm.php index 727ad6d..f5ae3fa 100755 --- a/retailcrm/retailcrm.php +++ b/retailcrm/retailcrm.php @@ -127,6 +127,7 @@ class RetailCRM extends Module Configuration::deleteByName('RETAILCRM_LAST_CUSTOMERS_SYNC') && Configuration::deleteByName('RETAILCRM_API_SYNCHRONIZE_CARTS') && Configuration::deleteByName('RETAILCRM_API_SYNCHRONIZED_CART_STATUS') && + Configuration::deleteByName('RETAILCRM_API_SYNCHRONIZED_CART_DELAY') && Configuration::deleteByName('RETAILCRM_LAST_ORDERS_SYNC'); } @@ -157,6 +158,7 @@ class RetailCRM extends Module $clientId = Configuration::get('RETAILCRM_CLIENT_ID'); $synchronizeCartsActive = (Tools::getValue('RETAILCRM_API_SYNCHRONIZE_CARTS_1')); $synchronizedCartStatus = (string)(Tools::getValue('RETAILCRM_API_SYNCHRONIZED_CART_STATUS')); + $synchronizedCartDelay = (string)(Tools::getValue('RETAILCRM_API_SYNCHRONIZED_CART_DELAY')); $settings = array( 'address' => $address, @@ -184,6 +186,7 @@ class RetailCRM extends Module Configuration::updateValue('RETAILCRM_DAEMON_COLLECTOR_KEY', $collectorKey); Configuration::updateValue('RETAILCRM_API_SYNCHRONIZE_CARTS', $synchronizeCartsActive); Configuration::updateValue('RETAILCRM_API_SYNCHRONIZED_CART_STATUS', $synchronizedCartStatus); + Configuration::updateValue('RETAILCRM_API_SYNCHRONIZED_CART_DELAY', $synchronizedCartDelay); $output .= $this->displayConfirmation($this->l('Settings updated')); } @@ -706,39 +709,80 @@ class RetailCRM extends Module /* * Synchronize carts form */ - if ($this->use_new_hooks) { - $fields_form[]['form'] = array( - 'legend' => array( - 'title' => $this->l('Synchronization of buyer carts'), + $fields_form[]['form'] = array( + 'legend' => array( + 'title' => $this->l('Synchronization of buyer carts'), + ), + 'input' => array( + array( + 'type' => 'checkbox', + 'label' => $this->l('Create orders for abandoned carts of buyers'), + 'name' => 'RETAILCRM_API_SYNCHRONIZE_CARTS', + 'values' => array( + 'query' => array( + array( + 'id_option' => 1, + ) + ), + 'id' => 'id_option', + 'name' => 'name' + ) ), - 'input' => array( - array( - 'type' => 'checkbox', - 'label' => $this->l('Create orders for abandoned carts of buyers'), - 'name' => 'RETAILCRM_API_SYNCHRONIZE_CARTS', - 'values' => array( - 'query' => array( - array( - 'id_option' => 1, - ) + array( + 'type' => 'select', + 'name' => 'RETAILCRM_API_SYNCHRONIZED_CART_STATUS', + 'label' => $this->l('Order status for abandoned carts of buyers'), + 'options' => array( + 'query' => $this->reference->getStatuseDefaultExport(), + 'id' => 'id_option', + 'name' => 'name' + ) + ), + array( + 'type' => 'select', + 'name' => 'RETAILCRM_API_SYNCHRONIZED_CART_DELAY', + 'label' => $this->l('Upload abandoned carts'), + 'options' => array( + 'query' => array( + array( + 'id_option' => '0', + 'name' => $this->l('Immediately') ), - 'id' => 'id_option', - 'name' => 'name' - ) - ), - array( - 'type' => 'select', - 'name' => 'RETAILCRM_API_SYNCHRONIZED_CART_STATUS', - 'label' => $this->l('Order status for abandoned carts of buyers'), - 'options' => array( - 'query' => $this->reference->getStatuseDefaultExport(), - 'id' => 'id_option', - 'name' => 'name' - ) + array( + 'id_option' => '60', + 'name' => $this->l('After 1 minute') + ), + array( + 'id_option' => '300', + 'name' => $this->l('After 5 minutes') + ), + array( + 'id_option' => '600', + 'name' => $this->l('After 10 minutes') + ), + array( + 'id_option' => '900', + 'name' => $this->l('After 15 minutes') + ), + array( + 'id_option' => '1800', + 'name' => $this->l('After 30 minutes') + ), + array( + 'id_option' => '2700', + 'name' => $this->l('After 45 minute') + ), + array( + 'id_option' => '3600', + 'name' => $this->l('After 1 hour') + ), + ), + 'id' => 'id_option', + 'name' => 'name' ) ) - ); - } + ) + ); /* * Delivery @@ -835,6 +879,7 @@ class RetailCRM extends Module $helper->fields_value['RETAILCRM_DAEMON_COLLECTOR_ACTIVE_1'] = Configuration::get('RETAILCRM_DAEMON_COLLECTOR_ACTIVE'); $helper->fields_value['RETAILCRM_API_SYNCHRONIZE_CARTS_1'] = Configuration::get('RETAILCRM_API_SYNCHRONIZE_CARTS'); $helper->fields_value['RETAILCRM_API_SYNCHRONIZED_CART_STATUS'] = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS'); + $helper->fields_value['RETAILCRM_API_SYNCHRONIZED_CART_DELAY'] = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_DELAY'); $helper->fields_value['RETAILCRM_DAEMON_COLLECTOR_KEY'] = Configuration::get('RETAILCRM_DAEMON_COLLECTOR_KEY'); $deliverySettings = Configuration::get('RETAILCRM_API_DELIVERY'); @@ -888,14 +933,21 @@ class RetailCRM extends Module } } - if ($this->use_new_hooks) { - $synchronizedCartsStatusDefault = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS'); - if (isset($synchronizedCartsStatusDefault) && $synchronizedCartsStatusDefault != '') { - $synchronizedCartsStatus = json_decode($synchronizedCartsStatusDefault); - if ($synchronizedCartsStatus) { - $name = 'RETAILCRM_API_SYNCHRONIZED_CART_STATUS'; - $helper->fields_value[$name] = $synchronizedCartsStatus; - } + $synchronizedCartsStatusDefault = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_STATUS'); + if (isset($synchronizedCartsStatusDefault) && $synchronizedCartsStatusDefault != '') { + $synchronizedCartsStatus = json_decode($synchronizedCartsStatusDefault); + if ($synchronizedCartsStatus) { + $name = 'RETAILCRM_API_SYNCHRONIZED_CART_STATUS'; + $helper->fields_value[$name] = $synchronizedCartsStatus; + } + } + + $synchronizedCartsDelayDefault = Configuration::get('RETAILCRM_API_SYNCHRONIZED_CART_DELAY'); + if (isset($synchronizedCartsDelayDefault) && $synchronizedCartsDelayDefault != '') { + $synchronizedCartsDelay = json_decode($synchronizedCartsDelayDefault); + if ($synchronizedCartsDelay) { + $name = 'RETAILCRM_API_SYNCHRONIZED_CART_DELAY'; + $helper->fields_value[$name] = $synchronizedCartsDelay; } } @@ -1315,7 +1367,7 @@ class RetailCRM extends Module private function validateStatuses($statuses, $statusExport, $cartStatus) { - if ($cartStatus == $statusExport || (stripos($statuses, $cartStatus) !== false)) { + if ($cartStatus != '' && ($cartStatus == $statusExport || (stripos($statuses, $cartStatus) !== false))) { return false; } diff --git a/retailcrm/translations/es.php b/retailcrm/translations/es.php index 43e037f..f7b3285 100755 --- a/retailcrm/translations/es.php +++ b/retailcrm/translations/es.php @@ -65,4 +65,13 @@ $_MODULE['<{retailcrm}prestashop>retailcrm_9a7fc06b4b2359f1f26f75fbbe27a3e8'] = $_MODULE['<{retailcrm}prestashop>retailcrm_917afe348e09163269225a89a825e634'] = 'Sincronización de carritos de compradores'; $_MODULE['<{retailcrm}prestashop>retailcrm_d8e002d770b6f98af7b7ae9a0e5acfe9'] = 'Crear pedidos para carritos abandonados de compradores'; $_MODULE['<{retailcrm}prestashop>retailcrm_35b5a9139a54caeb925556ceb2c38086'] = 'Estado del pedido para carritos abandonados de compradores'; -$_MODULE['<{retailcrm}prestashop>retailcrm_b9c4e8fe56eabcc4c7913ebb2f8eb388'] = 'Estado del pedido para carritos abandonados no debe ser utilizado en otros ajustes'; \ No newline at end of file +$_MODULE['<{retailcrm}prestashop>retailcrm_b9c4e8fe56eabcc4c7913ebb2f8eb388'] = 'Estado del pedido para carritos abandonados no debe ser utilizado en otros ajustes'; +$_MODULE['<{retailcrm}prestashop>retailcrm_a0d135501a738c3c98de385dc28cda61'] = 'Cargar carritos abandonados'; +$_MODULE['<{retailcrm}prestashop>retailcrm_fd83e0ccb3e6312a62f888dd496dd0a5'] = 'Instantáneamente'; +$_MODULE['<{retailcrm}prestashop>retailcrm_6c70ac5cc9fe8dff4f3bfe765dfe8798'] = 'Tras 1 minuto'; +$_MODULE['<{retailcrm}prestashop>retailcrm_be76333ef50b23b6337d263fc87fe11a'] = 'Tras 5 minutos'; +$_MODULE['<{retailcrm}prestashop>retailcrm_6e74495fad332de9a8207b5416de3cce'] = 'Tras 10 minutos'; +$_MODULE['<{retailcrm}prestashop>retailcrm_d5bb7c2cb1565fb1568924b01847b330'] = 'Tras 15 minutos'; +$_MODULE['<{retailcrm}prestashop>retailcrm_9d3095e54f694bb41ef4a3e62ed90e7a'] = 'Tras 30 minutos'; +$_MODULE['<{retailcrm}prestashop>retailcrm_dfb403fd86851c7d9f97706dff5a2327'] = 'Tras 45 minutos'; +$_MODULE['<{retailcrm}prestashop>retailcrm_4b5e6470d5d85448fcd89c828352d25e'] = 'Tras 1 hora'; \ No newline at end of file diff --git a/retailcrm/translations/ru.php b/retailcrm/translations/ru.php index a2136b1..2e7be52 100755 --- a/retailcrm/translations/ru.php +++ b/retailcrm/translations/ru.php @@ -65,4 +65,13 @@ $_MODULE['<{retailcrm}prestashop>retailcrm_9a7fc06b4b2359f1f26f75fbbe27a3e8'] = $_MODULE['<{retailcrm}prestashop>retailcrm_917afe348e09163269225a89a825e634'] = 'Синхронизация корзин покупателей'; $_MODULE['<{retailcrm}prestashop>retailcrm_d8e002d770b6f98af7b7ae9a0e5acfe9'] = 'Создавать заказы для брошенных корзин покупателей'; $_MODULE['<{retailcrm}prestashop>retailcrm_35b5a9139a54caeb925556ceb2c38086'] = 'Статус заказа для брошенных корзин покупателей'; -$_MODULE['<{retailcrm}prestashop>retailcrm_b9c4e8fe56eabcc4c7913ebb2f8eb388'] = 'Статус заказа для брошенных корзин не должен использоваться в других настройках'; \ No newline at end of file +$_MODULE['<{retailcrm}prestashop>retailcrm_b9c4e8fe56eabcc4c7913ebb2f8eb388'] = 'Статус заказа для брошенных корзин не должен использоваться в других настройках'; +$_MODULE['<{retailcrm}prestashop>retailcrm_a0d135501a738c3c98de385dc28cda61'] = 'Выгружать брошенные корзины'; +$_MODULE['<{retailcrm}prestashop>retailcrm_fd83e0ccb3e6312a62f888dd496dd0a5'] = 'Мгновенно'; +$_MODULE['<{retailcrm}prestashop>retailcrm_6c70ac5cc9fe8dff4f3bfe765dfe8798'] = 'Через 1 минуту'; +$_MODULE['<{retailcrm}prestashop>retailcrm_be76333ef50b23b6337d263fc87fe11a'] = 'Через 5 минут'; +$_MODULE['<{retailcrm}prestashop>retailcrm_6e74495fad332de9a8207b5416de3cce'] = 'Через 10 минут'; +$_MODULE['<{retailcrm}prestashop>retailcrm_d5bb7c2cb1565fb1568924b01847b330'] = 'Через 15 минут'; +$_MODULE['<{retailcrm}prestashop>retailcrm_9d3095e54f694bb41ef4a3e62ed90e7a'] = 'Через 30 минут'; +$_MODULE['<{retailcrm}prestashop>retailcrm_dfb403fd86851c7d9f97706dff5a2327'] = 'Через 45 минут'; +$_MODULE['<{retailcrm}prestashop>retailcrm_4b5e6470d5d85448fcd89c828352d25e'] = 'Через 1 час'; \ No newline at end of file From 42e03ece81c383e10095fdb221eb0a069a31d71b Mon Sep 17 00:00:00 2001 From: Pavel Date: Tue, 29 Oct 2019 10:36:24 +0300 Subject: [PATCH 5/6] bump version --- CHANGELOG.md | 3 +++ VERSION | 2 +- retailcrm/config_ru.xml | 13 ------------- retailcrm/retailcrm.php | 2 +- 4 files changed, 5 insertions(+), 15 deletions(-) delete mode 100644 retailcrm/config_ru.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index a8edc66..d3170ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## v2.4.0 +* Добавлена возможность выгрузки брошенных корзин + ## v2.3.4 * Добавлена поддержка передачи одинаковых товаров в заказе как разных товарных позиций diff --git a/VERSION b/VERSION index 3f684d2..197c4d5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.3.4 +2.4.0 diff --git a/retailcrm/config_ru.xml b/retailcrm/config_ru.xml deleted file mode 100644 index e87ab03..0000000 --- a/retailcrm/config_ru.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - retailcrm - - - - - - - 1 - 1 - - \ No newline at end of file diff --git a/retailcrm/retailcrm.php b/retailcrm/retailcrm.php index f5ae3fa..6a74b3e 100755 --- a/retailcrm/retailcrm.php +++ b/retailcrm/retailcrm.php @@ -39,7 +39,7 @@ class RetailCRM extends Module { $this->name = 'retailcrm'; $this->tab = 'export'; - $this->version = '2.3.4'; + $this->version = '2.4.0'; $this->author = 'Retail Driver LCC'; $this->displayName = $this->l('RetailCRM'); $this->description = $this->l('Integration module for RetailCRM'); From 4407d2e55c3c8309cefab2c285880abdb7882125 Mon Sep 17 00:00:00 2001 From: Pavel Date: Tue, 29 Oct 2019 10:53:48 +0300 Subject: [PATCH 6/6] ignore minified assets for now --- retailcrm/public/css/.gitignore | 1 + retailcrm/public/js/.gitignore | 1 + retailcrm/public/js/retailcrm-upload.min.js | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 retailcrm/public/css/.gitignore create mode 100644 retailcrm/public/js/.gitignore delete mode 100755 retailcrm/public/js/retailcrm-upload.min.js diff --git a/retailcrm/public/css/.gitignore b/retailcrm/public/css/.gitignore new file mode 100644 index 0000000..6be0402 --- /dev/null +++ b/retailcrm/public/css/.gitignore @@ -0,0 +1 @@ +*.min.css \ No newline at end of file diff --git a/retailcrm/public/js/.gitignore b/retailcrm/public/js/.gitignore new file mode 100644 index 0000000..0baf809 --- /dev/null +++ b/retailcrm/public/js/.gitignore @@ -0,0 +1 @@ +*.min.js \ No newline at end of file diff --git a/retailcrm/public/js/retailcrm-upload.min.js b/retailcrm/public/js/retailcrm-upload.min.js deleted file mode 100755 index ffbfed8..0000000 --- a/retailcrm/public/js/retailcrm-upload.min.js +++ /dev/null @@ -1 +0,0 @@ -function _toConsumableArray(a){return _arrayWithoutHoles(a)||_iterableToArray(a)||_nonIterableSpread()}function _nonIterableSpread(){throw new TypeError("Invalid attempt to spread non-iterable instance")}function _iterableToArray(a){if(Symbol.iterator in Object(a)||"[object Arguments]"===Object.prototype.toString.call(a))return Array.from(a)}function _arrayWithoutHoles(a){if(Array.isArray(a)){for(var b=0,c=Array(a.length);b div.bootstrap + div.bootstrap"),this.submitAction=this.submitAction.bind(this),this.partitionId=this.partitionId.bind(this),this.setLoading=this.setLoading.bind(this),$(this.submitButton).click(this.submitAction))}a.prototype.submitAction=function(a){a.preventDefault();let b=this.partitionId($(this.input).val().toString().replace(/\s+/g,""));return 0!==b.length&&void(this.setLoading(!0),$(this.form).submit())},a.prototype.setLoading=function(a){let b=$("div#retailcrm-loading-fade");0===b.length&&$("body").append("\n
\n
\n
\n ".trim()),b.css("visibility",a?"visible":"hidden")},a.prototype.partitionId=function(a){if(""===a)return[];let b=a.split(","),c=b.filter(a=>-1===a.toString().indexOf("-")),d=b.filter(a=>-1!==a.toString().indexOf("-")),e=[];return d.forEach(a=>{let b=a.split("-");e=[].concat(_toConsumableArray(e),_toConsumableArray(_toConsumableArray(Array(+b[1]+1).keys()).slice(+b[0],+b[1]+1)))}),[].concat(_toConsumableArray(c),_toConsumableArray(e)).map(a=>+a).sort((c,a)=>c-a)},new a});