mirror of
https://github.com/retailcrm/prestashop-module.git
synced 2025-04-17 15:30:54 +00:00
abandoned carts upload timeout & JobManager rework
This commit is contained in:
parent
a4473d488d
commit
1edd12bc80
8 changed files with 213 additions and 58 deletions
19
retailcrm/job/abandonedCarts.php
Normal file → Executable file
19
retailcrm/job/abandonedCarts.php
Normal file → Executable 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
2
retailcrm/job/jobs.php
Normal file → Executable 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
99
retailcrm/lib/JobManager.php
Normal file → Executable 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
0
retailcrm/public/js/exec-jobs.js
Normal file → Executable file
1
retailcrm/public/js/retailcrm-upload.min.js
vendored
Executable file
1
retailcrm/public/js/retailcrm-upload.min.js
vendored
Executable 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});
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
|
@ -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 час';
|
Loading…
Add table
Reference in a new issue