Merge pull request #51 from Neur0toxine/master

[feature] send abandoned carts to retailCRM as orders
This commit is contained in:
Alex Lushpai 2019-10-29 10:54:39 +03:00 committed by GitHub
commit f6f8d1302b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 853 additions and 24 deletions

View File

@ -1,3 +1,6 @@
## v2.4.0
* Добавлена возможность выгрузки брошенных корзин
## v2.3.4
* Добавлена поддержка передачи одинаковых товаров в заказе как разных товарных позиций

View File

@ -1 +1 @@
2.3.4
2.4.0

0
retailcrm/bootstrap.php Normal file → Executable file
View File

0
retailcrm/index.php Normal file → Executable file
View File

View File

@ -0,0 +1,92 @@
<?php
/**
* @author Retail Driver LCC
* @copyright RetailCRM
* @license GPL
* @version 2.2.9
* @link https://retailcrm.ru
*
*/
$_SERVER['HTTPS'] = 1;
require_once(dirname(__FILE__) . '/../../../config/config.inc.php');
require_once(dirname(__FILE__) . '/../../../init.php');
require_once(dirname(__FILE__) . '/../bootstrap.php');
if (empty(Configuration::get('RETAILCRM_API_SYNCHRONIZE_CARTS'))) {
return;
}
$apiUrl = Configuration::get('RETAILCRM_ADDRESS');
$apiKey = Configuration::get('RETAILCRM_API_TOKEN');
$apiVersion = Configuration::get('RETAILCRM_API_VERSION');
$api = null;
if (!empty($apiUrl) && !empty($apiKey)) {
$api = new RetailcrmProxy($apiUrl, $apiKey, _PS_ROOT_DIR_ . '/retailcrm.log', $apiVersion);
} else {
error_log('abandonedCarts: set api key & url first', 3, _PS_ROOT_DIR_ . '/retailcrm.log');
return;
}
$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(\''.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));
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();
}
}
}

0
retailcrm/job/export.php Normal file → Executable file
View File

0
retailcrm/job/icml.php Normal file → Executable file
View File

0
retailcrm/job/index.php Normal file → Executable file
View File

0
retailcrm/job/inventories.php Normal file → Executable file
View File

18
retailcrm/job/jobs.php Executable file
View File

@ -0,0 +1,18 @@
<?php
/**
* @author Retail Driver LCC
* @copyright RetailCRM
* @license GPL
* @version 2.2.9
* @link https://retailcrm.ru
*/
require_once(dirname(__FILE__) . '/../../../config/config.inc.php');
require_once(dirname(__FILE__) . '/../../../init.php');
require_once(dirname(__FILE__) . '/../bootstrap.php');
JobManager::startJobs(
array(
'abandonedCarts' => null
),
true
);

0
retailcrm/job/missing.php Normal file → Executable file
View File

0
retailcrm/job/sync.php Normal file → Executable file
View File

0
retailcrm/lib/CurlException.php Normal file → Executable file
View File

0
retailcrm/lib/InvalidJsonException.php Normal file → Executable file
View File

444
retailcrm/lib/JobManager.php Executable file
View File

@ -0,0 +1,444 @@
<?php
/**
* @author Retail Driver LCC
* @copyright RetailCRM
* @license GPL
* @version 2.2.11
* @link https://retailcrm.ru
*
*/
if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) {
date_default_timezone_set(@date_default_timezone_get());
}
require_once(dirname(__FILE__) . '/../../../config/config.inc.php');
require_once(dirname(__FILE__) . '/../../../init.php');
require_once(dirname(__FILE__) . '/../bootstrap.php');
if (!defined('_PS_VERSION_')) {
exit;
}
/**
* Class JobManager
*
* @author Retail Driver LCC
* @license GPL
* @link https://retailcrm.ru
*/
class JobManager
{
const LAST_RUN_NAME = 'RETAILCRM_LAST_RUN';
const IN_PROGRESS_NAME = 'RETAILCRM_JOBS_IN_PROGRESS';
/**
* @var resource $lock
*/
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();
if ($inBackground) {
$cmdline = sprintf('%s "%s" %b', __FILE__, static::serializeJobs($jobs), $runOnceInContext);
static::execPHP($cmdline, true, $runOnceInContext);
} else {
static::execJobs($jobs, $runOnceInContext);
}
}
/**
* Run scheduled jobs with request
*
* @param array $jobs
* @param bool $runOnceInContext
*
* @throws \Exception
*/
public static function execJobs($jobs = array(), $runOnceInContext = false)
{
$current = date_create('now');
$lastRuns = static::getLastRuns();
if (!static::lock()) {
return;
}
foreach ($jobs as $job => $diff) {
try {
if (isset($lastRuns[$job]) && $lastRuns[$job] instanceof DateTime) {
$shouldRunAt = clone $lastRuns[$job];
} else {
$shouldRunAt = new DateTime();
}
if ($diff instanceof DateInterval) {
$shouldRunAt->add($diff);
}
if (!isset($shouldRunAt) || $shouldRunAt <= $current) {
JobManager::runJob($job, $runOnceInContext);
$lastRuns[$job] = new DateTime();
}
} catch (\Exception $exception) {
static::handleError($exception->getFile(), $exception->getMessage());
} catch (\Throwable $throwable) {
static::handleError($throwable->getFile(), $throwable->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
*
* @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(DATE_RFC3339, (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);
}

0
retailcrm/lib/RetailcrmApiClientV4.php Normal file → Executable file
View File

0
retailcrm/lib/RetailcrmApiClientV5.php Normal file → Executable file
View File

0
retailcrm/lib/RetailcrmApiErrors.php Normal file → Executable file
View File

0
retailcrm/lib/RetailcrmApiResponse.php Normal file → Executable file
View File

0
retailcrm/lib/RetailcrmCatalog.php Normal file → Executable file
View File

0
retailcrm/lib/RetailcrmDaemonCollector.php Normal file → Executable file
View File

View File

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

0
retailcrm/lib/RetailcrmHistoryHelper.php Normal file → Executable file
View File

0
retailcrm/lib/RetailcrmHttpClient.php Normal file → Executable file
View File

0
retailcrm/lib/RetailcrmIcml.php Normal file → Executable file
View File

0
retailcrm/lib/RetailcrmInventories.php Normal file → Executable file
View File

0
retailcrm/lib/RetailcrmProxy.php Normal file → Executable file
View File

0
retailcrm/lib/RetailcrmReferences.php Normal file → Executable file
View File

0
retailcrm/lib/RetailcrmService.php Normal file → Executable file
View File

0
retailcrm/lib/index.php Normal file → Executable file
View File

0
retailcrm/logo.gif Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 306 B

After

Width:  |  Height:  |  Size: 306 B

0
retailcrm/logo.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

0
retailcrm/objects.xml Normal file → Executable file
View File

1
retailcrm/public/css/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.min.css

0
retailcrm/public/css/retailcrm-upload.css Normal file → Executable file
View File

1
retailcrm/public/js/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.min.js

View File

@ -0,0 +1,6 @@
(function () {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/modules/retailcrm/job/jobs.php', true);
xhr.timeout = 0;
xhr.send(null);
})();

0
retailcrm/public/js/retailcrm-upload.js Normal file → Executable file
View File

View File

@ -31,6 +31,7 @@ class RetailCRM extends Module
public $log;
public $confirmUninstall;
public $reference;
public $assetsBase;
private $use_new_hooks = true;
@ -38,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');
@ -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,9 @@ 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_API_SYNCHRONIZED_CART_DELAY') &&
Configuration::deleteByName('RETAILCRM_LAST_ORDERS_SYNC');
}
@ -144,12 +156,18 @@ 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'));
$synchronizedCartDelay = (string)(Tools::getValue('RETAILCRM_API_SYNCHRONIZED_CART_DELAY'));
$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 +184,9 @@ 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);
Configuration::updateValue('RETAILCRM_API_SYNCHRONIZED_CART_DELAY', $synchronizedCartDelay);
$output .= $this->displayConfirmation($this->l('Settings updated'));
}
@ -189,14 +210,9 @@ class RetailCRM extends Module
"<a target=\"_blank\" href=\"$address/admin/settings#t-main\">$address/admin/settings#t-main</a>"
);
$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 +294,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 +343,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 +352,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 +413,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 +448,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 +484,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 +706,84 @@ class RetailCRM extends Module
);
if ($this->api) {
/*
* Synchronize carts form
*/
$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'
)
),
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')
),
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
*/
@ -698,6 +877,9 @@ 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_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');
@ -751,6 +933,24 @@ class RetailCRM extends Module
}
}
$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;
}
}
return $helper->generateForm($fields_form);
}
@ -1036,9 +1236,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 +1365,15 @@ class RetailCRM extends Module
return false;
}
private function validateStatuses($statuses, $statusExport, $cartStatus)
{
if ($cartStatus != '' && ($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 +1382,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
*

15
retailcrm/translations/es.php Normal file → Executable file
View File

@ -61,4 +61,17 @@ $_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';
$_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';
$_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';

0
retailcrm/translations/index.php Normal file → Executable file
View File

15
retailcrm/translations/ru.php Normal file → Executable file
View File

@ -61,4 +61,17 @@ $_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'] = 'Не все заказы загружены успешно';
$_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_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 час';