abandoned carts upload timeout & JobManager rework

This commit is contained in:
Pavel 2019-10-29 09:42:34 +03:00
parent a4473d488d
commit 1edd12bc80
8 changed files with 213 additions and 58 deletions

19
retailcrm/job/abandonedCarts.php Normal file → Executable file
View file

@ -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));

2
retailcrm/job/jobs.php Normal file → Executable file
View file

@ -12,7 +12,7 @@ require_once(dirname(__FILE__) . '/../bootstrap.php');
JobManager::startJobs(
array(
'abandonedCarts' => DateInterval::createFromDateString('1 hour')
'abandonedCarts' => null
),
true
);

99
retailcrm/lib/JobManager.php Normal file → Executable file
View file

@ -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 <prestashop directory>/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
*

0
retailcrm/public/js/exec-jobs.js Normal file → Executable file
View file

1
retailcrm/public/js/retailcrm-upload.min.js vendored Executable file
View file

@ -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<a.length;b++)c[b]=a[b];return c}}$(function(){function a(){return this.form=$("input[value=\"retailcrm_upload_form\"]").parent().get(0),"undefined"!=typeof this.form&&void(this.input=$(this.form).find("input[name=\"RETAILCRM_UPLOAD_ORDERS_ID\"]").get(0),this.submitButton=$(this.form).find("button[type=\"submit\"]").get(0),this.messageContainer=document.querySelector("#content > 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 <div id=\"retailcrm-loading-fade\">\n <div id=\"retailcrm-loader\"></div>\n </div>\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});

View file

@ -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;
}

View file

@ -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';
$_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';

View file

@ -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'] = 'Статус заказа для брошенных корзин не должен использоваться в других настройках';
$_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 час';