diff --git a/retailcrm/lib/RetailcrmExport.php b/retailcrm/lib/RetailcrmExport.php index 8d2d3b0..4745e7f 100644 --- a/retailcrm/lib/RetailcrmExport.php +++ b/retailcrm/lib/RetailcrmExport.php @@ -38,6 +38,35 @@ */ class RetailcrmExport { + const RETAILCRM_EXPORT_ORDERS_STEP_SIZE_CLI = 5000; + const RETAILCRM_EXPORT_CUSTOMERS_STEP_SIZE_CLI = 5000; + const RETAILCRM_EXPORT_ORDERS_STEP_SIZE_WEB = 50; + const RETAILCRM_EXPORT_CUSTOMERS_STEP_SIZE_WEB = 300; + + /** + * @var \RetailcrmProxy|\RetailcrmApiClientV5 + */ + static $api; + + /** + * @var integer + */ + static $ordersOffset; + + /** + * @var integer + */ + static $customersOffset; + + /** + * Initialize inner state + */ + public static function init() + { + static::$api = null; + static::$ordersOffset = self::RETAILCRM_EXPORT_ORDERS_STEP_SIZE_CLI; + static::$customersOffset = self::RETAILCRM_EXPORT_CUSTOMERS_STEP_SIZE_CLI; + } /** * Get total count of orders for context shop @@ -66,19 +95,23 @@ class RetailcrmExport public static function getOrdersIds($start = 0, $count = null) { if (is_null($count)) { - $count = static::getOrdersCount(); + $to = static::getOrdersCount(); + $count = $to - $start; + } else { + $to = $start + $count; } if ($count > 0) { - $loadSize = 5000; $predefinedSql = 'SELECT o.`id_order` FROM `' . _DB_PREFIX_ . 'orders` o WHERE 1 ' . Shop::addSqlRestriction(false, 'o') . ' ORDER BY o.`id_order` ASC'; - while ($start <= $count) { - $offset = ($start + $loadSize > $count) ? $count - $start : $loadSize; + while ($start < $to) { + $offset = ($start + static::$ordersOffset > $to) ? $to - $start : static::$ordersOffset; + if ($offset <= 0) + break; $sql = $predefinedSql . ' LIMIT ' . (int)$start . ', ' . (int)$offset; @@ -98,16 +131,82 @@ class RetailcrmExport } /** - * Get total count of customers without orders for context shop + * @param int $from + * @param int|null $count + */ + public static function exportOrders($from = 0, $count = null) + { + if (!static::validateState()) { + return; + } + + $orders = array(); + $orderRecords = static::getOrdersIds($from, $count); + $orderBuilder = new RetailcrmOrderBuilder(); + $orderBuilder->defaultLangFromConfiguration()->setApi(static::$api); + + foreach ($orderRecords as $record) { + $orderBuilder->reset(); + + $order = new Order($record['id_order']); + + $orderCart = new Cart($order->id_cart); + $orderCustomer = new Customer($order->id_customer); + + $orderBuilder->setCmsOrder($order); + + if (!empty($orderCart->id)) { + $orderBuilder->setCmsCart($orderCart); + } else { + $orderBuilder->setCmsCart(null); + } + + if (!empty($orderCustomer->id)) { + $orderBuilder->setCmsCustomer($orderCustomer); + } else { + //TODO + // Caused crash before because of empty RetailcrmOrderBuilder::cmsCustomer. + // Current version *shouldn't* do this, but I suggest more tests for guest customers. + $orderBuilder->setCmsCustomer(null); + } + + try { + $orders[] = $orderBuilder->buildOrderWithPreparedCustomer(); + } catch (\InvalidArgumentException $exception) { + RetailcrmLogger::writeCaller('export', sprintf('Error while building %s: %s', $record['id_order'], $exception->getMessage())); + RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); + RetailcrmLogger::output($exception->getMessage()); + } + + time_nanosleep(0, 250000000); + + if (count($orders) == 50) { + static::$api->ordersUpload($orders); + $orders = array(); + } + } + + if (count($orders)) { + static::$api->ordersUpload($orders); + } + } + + /** + * Get total count of customers for context shop + * @param bool $withOrders If set to true, then return total count of customers. + * If set to false, then return count of customers without orders * * @return int */ - public static function getCustomersCount() + public static function getCustomersCount($withOrders = true) { $sql = 'SELECT count(c.id_customer) FROM `' . _DB_PREFIX_ . 'customer` c WHERE 1 - ' . Shop::addSqlRestriction(false, 'c') . ' + ' . Shop::addSqlRestriction(false, 'c'); + + if (!$withOrders) { + $sql .= ' AND c.id_customer not in ( select o.id_customer from `' . _DB_PREFIX_ . 'orders` o @@ -115,6 +214,7 @@ class RetailcrmExport ' . Shop::addSqlRestriction(false, 'o') . ' group by o.id_customer )'; + } return (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql); } @@ -124,23 +224,27 @@ class RetailcrmExport * * @param int $start Sets the LIMIT start parameter for sql query * @param null $count Sets the count of customers to get from database - * @param bool $withAddressId If set to true, then also return address id in `id_address` + * @param bool $withOrders If set to true, then return all customers ids. + * If set to false, then return ids only of customers without orders + * @param bool $returnAddressId If set to true, then also return address id in `id_address` * * @return Generator * @throws PrestaShopDatabaseException */ - public static function getCustomersIds($start = 0, $count = null, $withAddressId = true) + public static function getCustomersIds($start = 0, $count = null, $withOrders = true, $returnAddressId = true) { if (is_null($count)) { - $count = static::getCustomersCount(); + $to = static::getCustomersCount($withOrders); + $count = $to - $start; + } else { + $to = $start + $count; } if ($count > 0) { - $loadSize = 500; $predefinedSql = 'SELECT c.`id_customer` - ' . ($withAddressId ? ', a.`id_address`' : '') . ' + ' . ($returnAddressId ? ', a.`id_address`' : '') . ' FROM `' . _DB_PREFIX_ . 'customer` c - ' . ($withAddressId ? ' + ' . ($returnAddressId ? ' LEFT JOIN ( SELECT @@ -167,19 +271,22 @@ class RetailcrmExport a.`id_customer` = c.`id_customer` ' : '') . ' WHERE 1 - ' . Shop::addSqlRestriction(false, 'c') . ' + ' . Shop::addSqlRestriction(false, 'c') . + ($withOrders ? '' : ' AND c.`id_customer` not in ( select o.`id_customer` from `' . _DB_PREFIX_ . 'orders` o WHERE 1 ' . Shop::addSqlRestriction(false, 'o') . ' group by o.`id_customer` - ) + )') . ' ORDER BY c.`id_customer` ASC'; - while ($start <= $count) { - $offset = ($start + $loadSize > $count) ? $count - $start : $loadSize; + while ($start < $to) { + $offset = ($start + static::$customersOffset > $to) ? $to - $start : static::$customersOffset; + if ($offset <= 0) + break; $sql = $predefinedSql . ' LIMIT ' . (int)$start . ', ' . (int)$offset; @@ -198,4 +305,74 @@ class RetailcrmExport } } + /** + * @param int $from + * @param int|null $count + */ + public static function exportCustomers($from = 0, $count = null) + { + if (!static::validateState()) { + return; + } + + $customers = array(); + $customersRecords = RetailcrmExport::getCustomersIds($from, $count, false); + + foreach ($customersRecords as $record) { + $customerId = $record['id_customer']; + $addressId = $record['id_address']; + + $cmsCustomer = new Customer($customerId); + + if (Validate::isLoadedObject($cmsCustomer)) { + if ($addressId) { + $cmsAddress = new Address($addressId); + + $addressBuilder = new RetailcrmAddressBuilder(); + $address = $addressBuilder + ->setAddress($cmsAddress) + ->build() + ->getDataArray(); + } else { + $address = array(); + } + + try { + $customers[] = RetailcrmOrderBuilder::buildCrmCustomer($cmsCustomer, $address); + } catch (\Exception $exception) { + RetailcrmLogger::writeCaller('export', sprintf('Error while building %s: %s', $customerId, $exception->getMessage())); + RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); + RetailcrmLogger::output($exception->getMessage()); + } + + if (count($customers) == 50) { + static::$api->customersUpload($customers); + $customers = array(); + + time_nanosleep(0, 250000000); + } + } + } + + if (count($customers)) { + static::$api->customersUpload($customers); + } + } + + /** + * Returns false if inner state is not correct + * + * @return bool + */ + private static function validateState() + { + if (!static::$api || + !static::$ordersOffset || + !static::$customersOffset + ) { + return false; + } + + return true; + } } diff --git a/retailcrm/lib/events/RetailcrmExportEvent.php b/retailcrm/lib/events/RetailcrmExportEvent.php index b4e90d1..7d2b540 100644 --- a/retailcrm/lib/events/RetailcrmExportEvent.php +++ b/retailcrm/lib/events/RetailcrmExportEvent.php @@ -64,98 +64,10 @@ class RetailcrmExportEvent extends RetailcrmAbstractEvent implements RetailcrmEv continue; } - $orders = array(); - $orderRecords = RetailcrmExport::getOrdersIds(); - $orderBuilder = new RetailcrmOrderBuilder(); - $orderBuilder->defaultLangFromConfiguration()->setApi($api); - - foreach ($orderRecords as $record) { - $orderBuilder->reset(); - - $order = new Order($record['id_order']); - - $orderCart = new Cart($order->id_cart); - $orderCustomer = new Customer($order->id_customer); - - $orderBuilder->setCmsOrder($order); - - if (!empty($orderCart->id)) { - $orderBuilder->setCmsCart($orderCart); - } else { - $orderBuilder->setCmsCart(null); - } - - if (!empty($orderCustomer->id)) { - $orderBuilder->setCmsCustomer($orderCustomer); - } else { - //TODO - // Caused crash before because of empty RetailcrmOrderBuilder::cmsCustomer. - // Current version *shouldn't* do this, but I suggest more tests for guest customers. - $orderBuilder->setCmsCustomer(null); - } - - try { - $orders[] = $orderBuilder->buildOrderWithPreparedCustomer(); - } catch (\InvalidArgumentException $exception) { - RetailcrmLogger::writeCaller('export', sprintf('Error while building %s: %s', $record['id_order'], $exception->getMessage())); - RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); - RetailcrmLogger::output($exception->getMessage()); - } - - time_nanosleep(0, 250000000); - - if (count($orders) == 50) { - $api->ordersUpload($orders); - $orders = array(); - } - } - - if (count($orders)) { - $api->ordersUpload($orders); - } - - $customers = array(); - $customersRecords = RetailcrmExport::getCustomersIds(); - - foreach ($customersRecords as $record) { - $customerId = $record['id_customer']; - $addressId = $record['id_address']; - - $cmsCustomer = new Customer($customerId); - - if (Validate::isLoadedObject($cmsCustomer)) { - if ($addressId) { - $cmsAddress = new Address($addressId); - - $addressBuilder = new RetailcrmAddressBuilder(); - $address = $addressBuilder - ->setAddress($cmsAddress) - ->build() - ->getDataArray(); - } else { - $address = array(); - } - - try { - $customers[] = RetailcrmOrderBuilder::buildCrmCustomer($cmsCustomer, $address); - } catch (\Exception $exception) { - RetailcrmLogger::writeCaller('export', sprintf('Error while building %s: %s', $customerId, $exception->getMessage())); - RetailcrmLogger::writeNoCaller($exception->getTraceAsString()); - RetailcrmLogger::output($exception->getMessage()); - } - - if (count($customers) == 50) { - $api->customersUpload($customers); - $customers = array(); - - time_nanosleep(0, 250000000); - } - } - } - - if (count($customers)) { - $api->customersUpload($customers); - } + RetailcrmExport::init(); + RetailcrmExport::$api = $api; + RetailcrmExport::exportOrders(); + RetailcrmExport::exportCustomers(); } return true; diff --git a/retailcrm/lib/templates/RetailcrmSettingsTemplate.php b/retailcrm/lib/templates/RetailcrmSettingsTemplate.php index fdc5893..168e6ad 100644 --- a/retailcrm/lib/templates/RetailcrmSettingsTemplate.php +++ b/retailcrm/lib/templates/RetailcrmSettingsTemplate.php @@ -74,6 +74,11 @@ class RetailcrmSettingsTemplate extends RetailcrmAbstractTemplate $params['methodsForDefault'] = $this->module->reference->getPaymentAndDeliveryForDefault( array($this->module->translate('Delivery method'), $this->module->translate('Payment type')) ); + $params['ordersCount'] = RetailcrmExport::getOrdersCount(); + $params['customersCount'] = RetailcrmExport::getCustomersCount(); + $params['exportCustomersCount'] = RetailcrmExport::getCustomersCount(false); + $params['exportOrdersStepSize'] = RetailcrmExport::RETAILCRM_EXPORT_ORDERS_STEP_SIZE_WEB; + $params['exportCustomersStepSize'] = RetailcrmExport::RETAILCRM_EXPORT_CUSTOMERS_STEP_SIZE_WEB; } return $params; diff --git a/retailcrm/retailcrm.php b/retailcrm/retailcrm.php index 3db54dd..a964e1f 100644 --- a/retailcrm/retailcrm.php +++ b/retailcrm/retailcrm.php @@ -63,6 +63,8 @@ class RetailCRM extends Module const SYNC_CARTS_STATUS = 'RETAILCRM_API_SYNCHRONIZED_CART_STATUS'; const SYNC_CARTS_DELAY = 'RETAILCRM_API_SYNCHRONIZED_CART_DELAY'; const UPLOAD_ORDERS = 'RETAILCRM_UPLOAD_ORDERS_ID'; + const EXPORT_ORDERS = 'RETAILCRM_EXPORT_ORDERS_STEP'; + const EXPORT_CUSTOMERS = 'RETAILCRM_EXPORT_CUSTOMERS_STEP'; const MODULE_LIST_CACHE_CHECKSUM = 'RETAILCRM_MODULE_LIST_CACHE_CHECKSUM'; const ENABLE_CORPORATE_CLIENTS = 'RETAILCRM_ENABLE_CORPORATE_CLIENTS'; const ENABLE_HISTORY_UPLOADS = 'RETAILCRM_ENABLE_HISTORY_UPLOADS'; @@ -261,9 +263,15 @@ class RetailCRM extends Module if (Tools::isSubmit('submit' . $this->name)) { $ordersIds = (string)(Tools::getValue(static::UPLOAD_ORDERS)); + $exportOrders = (int)(Tools::getValue(static::EXPORT_ORDERS)); + $exportCustomers = (int)(Tools::getValue(static::EXPORT_CUSTOMERS)); if (!empty($ordersIds)) { $output .= $this->uploadOrders(RetailcrmTools::partitionId($ordersIds)); + } elseif (!empty($exportOrders)) { + return $this->export($exportOrders); + } elseif (!empty($exportCustomers)) { + return $this->export($exportCustomers, 'customer'); } else { $output .= $this->saveSettings(); } @@ -357,6 +365,45 @@ class RetailCRM extends Module } } + /** + * @param int $step + * @param string $entity + * @return bool + */ + public function export($step, $entity = 'order') + { + $step--; + if ($step < 0) + return false; + + $api = RetailcrmTools::getApiClient(); + + if (empty($api)) { + RetailcrmLogger::writeCaller(__METHOD__, 'Set API key & URL first'); + + return false; + } + + RetailcrmExport::init(); + RetailcrmExport::$api = $api; + + if ($entity === 'order') { + $stepSize = RetailcrmExport::RETAILCRM_EXPORT_ORDERS_STEP_SIZE_WEB; + + RetailcrmExport::$ordersOffset = $stepSize; + RetailcrmExport::exportOrders($step * $stepSize, $stepSize); + // todo maybe save current step to database + } elseif ($entity === 'customer') { + $stepSize = RetailcrmExport::RETAILCRM_EXPORT_CUSTOMERS_STEP_SIZE_WEB; + + RetailcrmExport::$customersOffset = $stepSize; + RetailcrmExport::exportCustomers($step * $stepSize, $stepSize); + // todo maybe save current step to database + } + + return true; + } + public function hookActionCustomerAccountAdd($params) { if ($this->api) { diff --git a/retailcrm/translations/es.php b/retailcrm/translations/es.php index 6d4979b..a9c13eb 100644 --- a/retailcrm/translations/es.php +++ b/retailcrm/translations/es.php @@ -47,10 +47,17 @@ $_MODULE['<{retailcrm}prestashop>settings_27096e1243f98e1b3300f57ff1c76456'] = ' $_MODULE['<{retailcrm}prestashop>settings_5b385947acf10ac0c5521161ce96aaa7'] = 'Elige la entrega'; $_MODULE['<{retailcrm}prestashop>settings_7dcc1208fa03381346955c6732d9ea85'] = 'Elige el tipo'; $_MODULE['<{retailcrm}prestashop>settings_acfa058ec9e6e4745eddc0cae3f0f881'] = 'Identificador del pedido'; -$_MODULE['<{retailcrm}prestashop>settings_91412465ea9169dfd901dd5e7c96dd99'] = 'Exportar'; +$_MODULE['<{retailcrm}prestashop>settings_91412465ea9169dfd901dd5e7c96dd99'] = 'Subir'; $_MODULE['<{retailcrm}prestashop>settings_4d3d769b812b6faa6b76e1a8abaece2d'] = 'Active'; $_MODULE['<{retailcrm}prestashop>settings_f75d8fa5c89351544d372cf90528ccf2'] = 'Clave de la página web'; $_MODULE['<{retailcrm}prestashop>settings_c9cc8cce247e49bae79f15173ce97354'] = 'Guardar'; +$_MODULE['<{retailcrm}prestashop>settings_30a8be02b0e6e45988617eca94301966'] = 'Ingrese los ID de los pedidos para cargar en RetailCRM, divididos por una coma. También puede especificar rangos, como "1-10". Se permite subir hasta 10 pedidos a la vez.'; +$_MODULE['<{retailcrm}prestashop>settings_f4af7f6987dfee28741ce77ff2d09d46'] = 'Exportar pedidos y clientes'; +$_MODULE['<{retailcrm}prestashop>settings_06d94a1c5dfe9d54ef201d2217ca477c'] = 'Puede exportar todos los pedidos y clientes de CMS a RetailCRM presionando el botón "Exportar". Este proceso puede llevar mucho tiempo y es necesario que mantenga la pestaña abierta hasta que termine.'; +$_MODULE['<{retailcrm}prestashop>settings_7442e29d7d53e549b78d93c46b8cdcfc'] = 'Pedidos'; +$_MODULE['<{retailcrm}prestashop>settings_e6d0e1c8fc6a4fcf47869df87e04cd88'] = 'Clientes'; +$_MODULE['<{retailcrm}prestashop>settings_f8f36c02fa6f370808135c66cfc788aa'] = 'Clientes sin pedidos'; +$_MODULE['<{retailcrm}prestashop>settings_0095a9fa74d1713e43e370a7d7846224'] = 'Exportar'; $_MODULE['<{retailcrm}prestashop>settings_4f18e3f1c9941a6ec5a38bc716c521b4'] = 'Código que necesita insertar en la web'; $_MODULE['<{retailcrm}prestashop>settings_9082f68bc90113d8950e4ed7fe8fa0a4'] = 'Administrador de tareas'; $_MODULE['<{retailcrm}prestashop>settings_9194de58ce560c095f02cefc1c1c61e6'] = 'Nombre de la tarea'; diff --git a/retailcrm/translations/ru.php b/retailcrm/translations/ru.php index d20dc43..1ea2893 100644 --- a/retailcrm/translations/ru.php +++ b/retailcrm/translations/ru.php @@ -52,6 +52,13 @@ $_MODULE['<{retailcrm}prestashop>settings_4d3d769b812b6faa6b76e1a8abaece2d'] = ' $_MODULE['<{retailcrm}prestashop>settings_f75d8fa5c89351544d372cf90528ccf2'] = 'Ключ сайта'; $_MODULE['<{retailcrm}prestashop>settings_c9cc8cce247e49bae79f15173ce97354'] = 'Сохранить'; $_MODULE['<{retailcrm}prestashop>settings_4f18e3f1c9941a6ec5a38bc716c521b4'] = 'Код для вставки на сайт'; +$_MODULE['<{retailcrm}prestashop>settings_30a8be02b0e6e45988617eca94301966'] = 'Введите идентификаторы заказов для загрузки в RetailCRM, разделив их запятыми. Вы также можете указать диапазоны, например "1-10". Одновременно можно загружать до 10 заказов.'; +$_MODULE['<{retailcrm}prestashop>settings_f4af7f6987dfee28741ce77ff2d09d46'] = 'Экспортировать заказы и клиентов'; +$_MODULE['<{retailcrm}prestashop>settings_06d94a1c5dfe9d54ef201d2217ca477c'] = 'Вы можете экспортировать все заказы и клиентов из CMS в RetailCRM, нажав кнопку «Экспорт». Этот процесс может занять много времени, и до его завершения необходимо держать вкладку открытой.'; +$_MODULE['<{retailcrm}prestashop>settings_7442e29d7d53e549b78d93c46b8cdcfc'] = 'Заказы'; +$_MODULE['<{retailcrm}prestashop>settings_e6d0e1c8fc6a4fcf47869df87e04cd88'] = 'Клиенты'; +$_MODULE['<{retailcrm}prestashop>settings_f8f36c02fa6f370808135c66cfc788aa'] = 'Клиенты без заказов'; +$_MODULE['<{retailcrm}prestashop>settings_0095a9fa74d1713e43e370a7d7846224'] = 'Экспортировать'; $_MODULE['<{retailcrm}prestashop>settings_9082f68bc90113d8950e4ed7fe8fa0a4'] = 'Менеджер задач'; $_MODULE['<{retailcrm}prestashop>settings_9194de58ce560c095f02cefc1c1c61e6'] = 'Имя задачи'; $_MODULE['<{retailcrm}prestashop>settings_05a3a24340b7b9cc8d4e08f0ef4f4dd9'] = 'Последний запуск'; diff --git a/retailcrm/views/css/less/retailcrm-export.less b/retailcrm/views/css/less/retailcrm-export.less new file mode 100644 index 0000000..39d63b8 --- /dev/null +++ b/retailcrm/views/css/less/retailcrm-export.less @@ -0,0 +1,45 @@ +.retail { + &-circle { + float: left; + width: 50%; + text-align: center; + padding: 20px; + margin: 20px 0; + &__title { + font-size: 18px; + margin-bottom: 20px; + } + input&__content { + width: 120px; + height: 120px; + border-radius: 120px; + text-align: center; + font-size: 20px; + line-height: 60px; + } + } + &-progress { + border-radius: 60px; + border: 1px solid rgba(122, 122, 122, 0.15); + width: 100%; + height: 60px; + overflow: hidden; + transition: height 0.25s ease; + &__loader { + width: 0; + border-radius: 60px; + background: #0068FF; + color: white; + text-align: center; + padding: 0 30px; + font-size: 18px; + font-weight: 600; + transition: width 0.4s ease-in; + line-height: 60px; + } + } + &-hidden { + visibility: hidden; + height: 0!important; + } +} diff --git a/retailcrm/views/css/retailcrm-export.min.css b/retailcrm/views/css/retailcrm-export.min.css new file mode 100644 index 0000000..2a9c514 --- /dev/null +++ b/retailcrm/views/css/retailcrm-export.min.css @@ -0,0 +1 @@ +.retail-circle{float:left;width:50%;text-align:center;padding:20px;margin:20px 0}.retail-circle__title{font-size:18px;margin-bottom:20px}input.retail-circle__content{width:120px;height:120px;border-radius:120px;text-align:center;font-size:20px;line-height:60px}.retail-progress{border-radius:60px;border:1px solid rgba(122,122,122,0.15);width:100%;height:60px;overflow:hidden;transition:height .25s ease}.retail-progress__loader{width:0;border-radius:60px;background:#0068FF;color:white;text-align:center;padding:0 30px;font-size:18px;font-weight:600;transition:width .4s ease-in;line-height:60px}.retail-hidden{visibility:hidden;height:0!important} \ No newline at end of file diff --git a/retailcrm/views/css/styles.min.css b/retailcrm/views/css/styles.min.css index 1ffc9e9..4820beb 100644 --- a/retailcrm/views/css/styles.min.css +++ b/retailcrm/views/css/styles.min.css @@ -1,611 +1 @@ -@font-face { - font-family: 'OpenSans'; - src: url('../fonts/OpenSans/opensans-regular.eot'); - src: url('../fonts/OpenSans/opensans-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/OpenSans/opensans-regular.woff2') format('woff2'), url('../fonts/OpenSans/opensans-regular.woff') format('woff'), url('../fonts/OpenSans/opensans-regular.ttf') format('truetype'), url('../fonts/OpenSans/opensans-regular.svg#open_sansregular') format('svg'); - font-weight: normal; - font-style: normal; -} -@font-face { - font-family: 'OpenSans'; - src: url('../fonts/OpenSansBold/opensans-bold.eot'); - src: url('../fonts/OpenSansBold/opensans-bold.eot?#iefix') format('embedded-opentype'), url('../fonts/OpenSansBold/opensans-bold.woff2') format('woff2'), url('../fonts/OpenSansBold/opensans-bold.woff') format('woff'), url('../fonts/OpenSansBold/opensans-bold.ttf') format('truetype'), url('../fonts/OpenSansBold/opensans-bold.svg#open_sansbold') format('svg'); - font-weight: 600; - font-style: normal; -} -body, -html { - margin: 0; - padding: 0; - height: 100%; -} -.hidden { - visibility: hidden; -} -.retail-wrap { - font-family: OpenSans, Arial, sans-serif; - padding: 0 15px; - height: 100%; -} -.retail-wrap *, -.retail-wrap *::before, -.retail-wrap *::after { - box-sizing: border-box; -} -.retail-container { - margin: 0 auto; - width: 100%; - max-width: 950px; -} -.retail-title { - margin: 60px 0 0; - font-weight: 400; - text-align: center; - font-size: 28px; - line-height: 38px; -} -.retail-title_content { - text-align: left; - margin-top: 40px; -} -.retail-txt { - color: #7A7A7A; - font-size: 18px; - line-height: 26px; -} -.retail-descript { - margin-top: 45px; - text-align: center; -} -.retail-tab__enabled { - display: block; -} -.retail-tab__disabled { - display: none !important; -} -.retail-video { - margin: 57px auto 0; - max-width: 442px; - position: relative; -} -.retail-video-trigger { - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 0; - width: 100%; - height: 100%; - cursor: pointer; -} -.retail-video iframe { - pointer-events: none; -} -.retail-video__btn { - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - margin: auto; - width: 100px; - height: 100px; - cursor: pointer; - opacity: 0.4; - transition: 0.25s ease; -} -.retail-video__btn svg { - width: 100%; -} -.retail-video__btn:hover { - opacity: 0.6; -} -.retail-btns { - margin: 56px auto 0; - display: flex; - justify-content: space-between; - max-width: 815px; - transition: 0.05s ease; -} -.retail-btns__separate { - padding: 0 20px; - display: flex; - align-items: center; - color: #7A7A7A; - font-size: 16px; -} -.retail-btns_hide { - opacity: 0; -} -.retail-form { - margin-top: 60px; -} -.retail-form__title { - font-size: 16px; - font-weight: 600; - line-height: 24px; - margin-bottom: 22px; -} -.retail-form__label { - width: 100% !important; - text-align: left !important; - margin: 15px 12px; - font-size: 15px; -} -.retail-form__row { - margin-top: 15px; -} -.retail-form__row_submit { - margin-top: 23px; -} -.retail-form__message-warning { - padding: 13px 18px; - margin: 1px 13px; - border-radius: 8px; - border: 1px solid #fcf3b5; - font-size: 1rem; - box-shadow: 0px 0px 6px 0px #fdd0d0; -} -.retail-form__checkbox { - display: flex; - flex-direction: row; - align-items: center; - padding: 4px 12px; -} -.retail-form__checkbox input[type=checkbox] { - width: 24px; - height: 24px; -} -.retail-form__checkbox label { - width: auto; - margin-left: 8px; - font-size: 16px; -} -.retail-form__area { - display: inline-block !important; - vertical-align: top; - width: 430px !important; - height: 60px !important; - border: 1px solid rgba(122, 122, 122, 0.15) !important; - box-shadow: none !important; - border-radius: 58px !important; - padding: 0 28px !important; - line-height: normal; - color: #7A7A7A !important; - font-size: 16px !important; - appearance: none; -} -.retail-form__area:focus { - color: #363A41; -} -.retail-form__area:focus::-webkit-input-placeholder { - color: #363A41; -} -.retail-form__area:focus::-moz-placeholder { - color: #363A41; -} -.retail-form__area:focus:-moz-placeholder { - color: #363A41; -} -.retail-form__area:focus:-ms-input-placeholder { - color: #363A41; -} -.retail-form__area_txt { - padding: 20px 28px !important; - line-height: 24px !important; - height: 487px !important; - border-radius: 20px !important; - resize: none !important; - font-family: OpenSans, Arial, sans-serif !important; -} -.retail-form input:focus, -.retail-form textarea:focus { - outline: none !important; -} -.retail-form_main { - margin-top: 34px; - max-width: 900px; - width: 100%; -} -.retail-form_main .retail-form__area { - width: 100% !important; -} -.retail-tabs { - margin-top: 60px; -} -.retail-tabs__btn { - display: inline-block; - vertical-align: top; - padding: 19px 30px; - font-size: 16px; - font-weight: 600; - line-height: 22px; - color: #7A7A7A; - text-align: center; - min-width: 152px; - text-decoration: none !important; - position: relative; - transition: 0.25s ease; -} -.retail-tabs__btn:hover { - color: #363A41; -} -.retail-tabs__btn::after { - content: ""; - height: 3px; - width: 100%; - position: absolute; - bottom: -1px; - left: 0; - right: 0; - opacity: 0; - visibility: hidden; - background: #0068FF; - transition: 0.25s ease; -} -.retail-tabs__btn_active { - color: #363A41; -} -.retail-tabs__btn_active::after { - opacity: 1; - visibility: visible; -} -.retail-tabs__head { - display: flex; - justify-content: space-between; - border-bottom: 1px solid #DFDFDF; -} -.retail-tabs__body { - padding-top: 18px; -} -.retail-tabs__body p { - margin-top: 23px; - margin-bottom: 0; - color: #7A7A7A; - font-size: 16px; - line-height: 24px; -} -.retail-tabs__item { - display: none; -} -.retail-list { - margin: 0; - padding: 0; - list-style: none; -} -.retail-list__item { - padding-left: 2px; - position: relative; - color: #7A7A7A; - font-size: 16px; - line-height: 24px; -} -.retail-list__item::before { - content: "-"; - display: inline-block; - vertical-align: top; - color: #7A7A7A; - font-size: 16px; - line-height: 24px; - margin-right: 3px; -} -.retail-tile { - display: flex; - flex-wrap: wrap; -} -.retail-tile__col { - width: 48%; - padding-right: 35px; -} -.retail-tile__col:nth-child(1) { - width: 52%; -} -.retail-tile__col_contacts { - padding-right: 0; -} -.retail-tile__row { - display: flex; - justify-content: center; - margin-bottom: 30px; -} -.retail-tile__row:nth-last-child(1) { - margin-bottom: 0; -} -.retail-tile__item { - margin-top: 34px; -} -.retail-tile__item:nth-child(1) { - margin-top: 20px; -} -.retail-tile__title { - color: #363A41; - font-size: 16px; - font-weight: 600; - line-height: 24px; -} -.retail-tile__descript { - color: #7A7A7A; - font-size: 16px; - line-height: 24px; - margin-top: 10px; -} -.retail-tile__link { - color: #0068FF; - font-size: 16px; - font-weight: 600; - line-height: 24px; - transition: 0.25s ease; -} -.retail-tile__link:hover { - color: #005add; -} -.retail-popup { - position: absolute; - width: 90%; - height: 90%; - background: white; - left: 0; - right: 0; - top: 0; - bottom: 0; - margin: auto; - transform: translate3d(0, -1000%, 0) scale(0.1); - transition: 0.25s ease; -} -.retail-popup__close { - position: absolute; - top: -30px; - right: -30px; - width: 30px; - height: 30px; - cursor: pointer; -} -.retail-popup__close::before, -.retail-popup__close::after { - content: ""; - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - margin: auto; - height: 30px; - width: 2px; - background: white; -} -.retail-popup__close::before { - transform: rotate(45deg); -} -.retail-popup__close::after { - transform: rotate(-45deg); -} -.retail-popup.open { - transform: translate3d(0, 0, 0) scale(1); -} -.retail-popup-wrap { - position: fixed; - left: 0; - right: 0; - top: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.6); - z-index: 1000; - display: none; -} -.retail-column { - display: flex; - max-width: 1389px; - height: 100%; -} -.retail-column__aside { - width: 253px; - background: #EAEBEC; - min-height: 300px; -} -.retail-column__content { - flex: 1 0 auto; - padding: 0 58px; - min-height: 300px; -} -.retail-menu { - padding: 8px 20px 8px 15px; -} -.retail-menu__btn { - display: inline-flex; - align-items: center; - justify-content: center; - vertical-align: top; - width: 100%; - height: 60px; - background: rgba(122, 122, 122, 0.1) !important; - font-weight: bold; - font-size: 16px; - color: #363A41 !important; - text-decoration: none !important; - padding: 0 30px; - margin-top: 20px; - border-radius: 5px; - text-align: center; - transition: 0.25s ease; -} -.retail-menu__btn span { - display: block; -} -.retail-menu__btn:hover { - background: rgba(122, 122, 122, 0.15) !important; -} -.retail-menu__btn:nth-child(1) { - margin-top: 0; -} -.retail-menu__btn_active { - color: white !important; - background: #0068FF !important; -} -.retail-menu__btn_active:hover { - background: #005add !important; -} -.retail-menu__btn_big { - font-size: 18px; -} -.retail-full-height { - height: 100%; -} -.retail .btn { - display: inline-block; - vertical-align: top; - background: rgba(122, 122, 122, 0.1); - border-radius: 58px; - height: 60px; - line-height: 60px; - padding: 0 30px; - font-size: 18px; - font-weight: 600; - text-align: center; - color: #0068FF; - text-decoration: none; - cursor: pointer; - appearance: none; - border: none; - box-shadow: none; - transition: 0.25s ease; -} -.retail .btn:hover { - background: rgba(122, 122, 122, 0.15); -} -.retail .btn:active { - background: rgba(122, 122, 122, 0.25); -} -.retail .btn_max { - min-width: 356px; -} -.retail .btn_invert { - background: #0068FF; - color: white; -} -.retail .btn_invert:hover { - background: #005add; -} -.retail .btn_invert:active { - background: #0045aa; -} -.retail .btn_whatsapp { - background: #33D16B; - color: white; - padding: 0 62px; -} -.retail .btn_whatsapp:hover { - background: #22CA5D; -} -.retail .btn_submit { - min-width: 218px; -} -.retail .toggle-box { - display: none; -} -.retail-table { - width: 100%; - border: none; - border-radius: 20px; -} -.retail-table thead th { - background: rgba(122, 122, 122, 0.15); - font-size: 16px; -} -.retail-table tbody tr td { - border-bottom: 1px solid #ccc; -} -.retail-table td, -.retail-table th { - color: #333; - font-size: 14px; - line-height: 60px; - padding: 4px 4px 4px 6px; - max-width: 300px; - vertical-align: middle; -} -.retail-table-no-wrap { - white-space: nowrap; -} -.retail-table-center { - text-align: center; -} -.retail-table-sort__btn, -.retail-table-sort__switch { - cursor: pointer; -} -.retail-table-sort__btn:hover, -.retail-table-sort__switch:hover { - color: #005add; -} -.retail-table-sort__btn { - float: left; - clear: both; - line-height: 15px; - font-size: 20px; - padding-left: 10px; - padding-right: 10px; - opacity: 0; - transition: opacity 0.2s ease-out; -} -.retail-table-sort__btn-wrap { - position: absolute; -} -.retail-table-sort__asc { - padding-top: 15px; - padding-bottom: 0; -} -.retail-table-sort__desc { - padding-top: 0; - padding-bottom: 15px; -} -.retail-table-sort thead th:hover .retail-table-sort__btn, -.retail-table-sort thead td:hover .retail-table-sort__btn { - opacity: 1; -} -.retail-collapsible__input { - display: none; -} -label.retail-collapsible__title { - cursor: pointer; - font-weight: normal; - text-align: center; - padding: 0; - position: relative; - line-height: 1; - width: 100%; -} -label.retail-collapsible__title:before { - content: '\25B6'; - opacity: 0; - transition: opacity 0.2s ease-out; -} -label.retail-collapsible__title:hover:before { - opacity: 1; -} -.retail-collapsible__content { - text-align: left; - font-size: 12px; - line-height: 1.5; - background: #fff; - border: 1px solid rgba(122, 122, 122, 0.15); - position: absolute; - top: 20px; - left: 0; - padding: 18px; - margin: 0; - border-radius: 20px; - overflow: hidden; - max-height: 0; - transition-duration: 0.2s; - transition-timing-function: ease-out; - transition-property: max-height, visibility; - visibility: hidden; -} -.retail-collapsible__input:checked + .retail-collapsible__title .retail-collapsible__content { - max-height: 100vh; - visibility: visible; -} -.retail-error-msg, -.retail-error-msg:after, -.retail-error-msg:before { - color: #dd2e44; -} +@font-face{font-family:'OpenSans';src:url('../fonts/OpenSans/opensans-regular.eot');src:url('../fonts/OpenSans/opensans-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/OpenSans/opensans-regular.woff2') format('woff2'),url('../fonts/OpenSans/opensans-regular.woff') format('woff'),url('../fonts/OpenSans/opensans-regular.ttf') format('truetype'),url('../fonts/OpenSans/opensans-regular.svg#open_sansregular') format('svg');font-weight:normal;font-style:normal}@font-face{font-family:'OpenSans';src:url('../fonts/OpenSansBold/opensans-bold.eot');src:url('../fonts/OpenSansBold/opensans-bold.eot?#iefix') format('embedded-opentype'),url('../fonts/OpenSansBold/opensans-bold.woff2') format('woff2'),url('../fonts/OpenSansBold/opensans-bold.woff') format('woff'),url('../fonts/OpenSansBold/opensans-bold.ttf') format('truetype'),url('../fonts/OpenSansBold/opensans-bold.svg#open_sansbold') format('svg');font-weight:600;font-style:normal}body,html{margin:0;padding:0;height:100%}.hidden{visibility:hidden}.retail-wrap{font-family:OpenSans,Arial,sans-serif;padding:0 15px;height:100%}.retail-wrap *,.retail-wrap *::before,.retail-wrap *::after{box-sizing:border-box}.retail-container{margin:0 auto;width:100%;max-width:950px}.retail-title{margin:60px 0 0;font-weight:400;text-align:center;font-size:28px;line-height:38px}.retail-title_content{text-align:left;margin-top:40px}.retail-txt{color:#7A7A7A;font-size:18px;line-height:26px}.retail-descript{margin-top:45px;text-align:center}.retail-tab__enabled{display:block}.retail-tab__disabled{display:none !important}.retail-video{margin:57px auto 0;max-width:442px;position:relative}.retail-video-trigger{position:absolute;top:0;bottom:0;right:0;left:0;width:100%;height:100%;cursor:pointer}.retail-video iframe{pointer-events:none}.retail-video__btn{position:absolute;left:0;right:0;top:0;bottom:0;margin:auto;width:100px;height:100px;cursor:pointer;opacity:.4;transition:.25s ease}.retail-video__btn svg{width:100%}.retail-video__btn:hover{opacity:.6}.retail-btns{margin:56px auto 0;display:flex;justify-content:space-between;max-width:815px;transition:.05s ease}.retail-btns__separate{padding:0 20px;display:flex;align-items:center;color:#7A7A7A;font-size:16px}.retail-btns_hide{opacity:0}.retail-form{margin-top:60px}.retail-form__title{font-size:16px;font-weight:600;line-height:24px;margin-bottom:22px}.retail-form__label{width:100% !important;text-align:left !important;margin:15px 12px;font-size:15px}.retail-form__row{margin-top:15px}.retail-form__row_submit{margin-top:23px}.retail-form__message-warning{padding:13px 18px;margin:1px 13px;border-radius:8px;border:1px solid #fcf3b5;font-size:1rem;box-shadow:0 0 6px 0 #fdd0d0}.retail-form__checkbox{display:flex;flex-direction:row;align-items:center;padding:4px 12px}.retail-form__checkbox input[type=checkbox]{width:24px;height:24px}.retail-form__checkbox label{width:auto;margin-left:8px;font-size:16px}.retail-form__area{display:inline-block !important;vertical-align:top;width:430px !important;height:60px !important;border:1px solid rgba(122,122,122,0.15) !important;box-shadow:none !important;border-radius:58px !important;padding:0 28px !important;line-height:normal;color:#7A7A7A !important;font-size:16px !important;appearance:none}.retail-form__area:focus{color:#363A41}.retail-form__area:focus::-webkit-input-placeholder{color:#363A41}.retail-form__area:focus::-moz-placeholder{color:#363A41}.retail-form__area:focus:-moz-placeholder{color:#363A41}.retail-form__area:focus:-ms-input-placeholder{color:#363A41}.retail-form__area_txt{padding:20px 28px !important;line-height:24px !important;height:487px !important;border-radius:20px !important;resize:none !important;font-family:OpenSans,Arial,sans-serif !important}.retail-form input:focus,.retail-form textarea:focus{outline:none !important}.retail-form_main{margin-top:34px;max-width:900px;width:100%}.retail-form_main .retail-form__area{width:100% !important}.retail-tabs{margin-top:60px}.retail-tabs__btn{display:inline-block;vertical-align:top;padding:19px 30px;font-size:16px;font-weight:600;line-height:22px;color:#7A7A7A;text-align:center;min-width:152px;text-decoration:none !important;position:relative;transition:.25s ease}.retail-tabs__btn:hover{color:#363A41}.retail-tabs__btn::after{content:"";height:3px;width:100%;position:absolute;bottom:-1px;left:0;right:0;opacity:0;visibility:hidden;background:#0068FF;transition:.25s ease}.retail-tabs__btn_active{color:#363A41}.retail-tabs__btn_active::after{opacity:1;visibility:visible}.retail-tabs__head{display:flex;justify-content:space-between;border-bottom:1px solid #DFDFDF}.retail-tabs__body{padding-top:18px}.retail-tabs__body p{margin-top:23px;margin-bottom:0;color:#7A7A7A;font-size:16px;line-height:24px}.retail-tabs__item{display:none}.retail-list{margin:0;padding:0;list-style:none}.retail-list__item{padding-left:2px;position:relative;color:#7A7A7A;font-size:16px;line-height:24px}.retail-list__item::before{content:"-";display:inline-block;vertical-align:top;color:#7A7A7A;font-size:16px;line-height:24px;margin-right:3px}.retail-tile{display:flex;flex-wrap:wrap}.retail-tile__col{width:48%;padding-right:35px}.retail-tile__col:nth-child(1){width:52%}.retail-tile__col_contacts{padding-right:0}.retail-tile__row{display:flex;justify-content:center;margin-bottom:30px}.retail-tile__row:nth-last-child(1){margin-bottom:0}.retail-tile__item{margin-top:34px}.retail-tile__item:nth-child(1){margin-top:20px}.retail-tile__title{color:#363A41;font-size:16px;font-weight:600;line-height:24px}.retail-tile__descript{color:#7A7A7A;font-size:16px;line-height:24px;margin-top:10px}.retail-tile__link{color:#0068FF;font-size:16px;font-weight:600;line-height:24px;transition:.25s ease}.retail-tile__link:hover{color:#005add}.retail-popup{position:absolute;width:90%;height:90%;background:white;left:0;right:0;top:0;bottom:0;margin:auto;transform:translate3d(0, -1000%, 0) scale(.1);transition:.25s ease}.retail-popup__close{position:absolute;top:-30px;right:-30px;width:30px;height:30px;cursor:pointer}.retail-popup__close::before,.retail-popup__close::after{content:"";position:absolute;left:0;right:0;top:0;bottom:0;margin:auto;height:30px;width:2px;background:white}.retail-popup__close::before{transform:rotate(45deg)}.retail-popup__close::after{transform:rotate(-45deg)}.retail-popup.open{transform:translate3d(0, 0, 0) scale(1)}.retail-popup-wrap{position:fixed;left:0;right:0;top:0;bottom:0;background:rgba(0,0,0,0.6);z-index:1000;display:none}.retail-column{display:flex;max-width:1389px;height:100%}.retail-column__aside{width:253px;background:#EAEBEC;min-height:300px}.retail-column__content{flex:1 0 auto;padding:0 58px;min-height:300px}.retail-menu{padding:8px 20px 8px 15px}.retail-menu__btn{display:inline-flex;align-items:center;justify-content:center;vertical-align:top;width:100%;height:60px;background:rgba(122,122,122,0.1) !important;font-weight:bold;font-size:16px;color:#363A41 !important;text-decoration:none !important;padding:0 30px;margin-top:20px;border-radius:5px;text-align:center;transition:.25s ease}.retail-menu__btn span{display:block}.retail-menu__btn:hover{background:rgba(122,122,122,0.15) !important}.retail-menu__btn:nth-child(1){margin-top:0}.retail-menu__btn_active{color:white !important;background:#0068FF !important}.retail-menu__btn_active:hover{background:#005add !important}.retail-menu__btn_big{font-size:18px}.retail-full-height{height:100%}.retail .btn{display:inline-block;vertical-align:top;background:rgba(122,122,122,0.1);border-radius:58px;height:60px;line-height:60px;padding:0 30px;font-size:18px;font-weight:600;text-align:center;color:#0068FF;text-decoration:none;cursor:pointer;appearance:none;border:none;box-shadow:none;transition:.25s ease}.retail .btn:hover{background:rgba(122,122,122,0.15)}.retail .btn:active{background:rgba(122,122,122,0.25)}.retail .btn_max{min-width:356px}.retail .btn_invert{background:#0068FF;color:white}.retail .btn_invert:hover{background:#005add}.retail .btn_invert:active{background:#0045aa}.retail .btn_whatsapp{background:#33D16B;color:white;padding:0 62px}.retail .btn_whatsapp:hover{background:#22CA5D}.retail .btn_submit{min-width:218px}.retail .toggle-box{display:none}.retail-table{width:100%;border:none;border-radius:20px}.retail-table thead th{background:rgba(122,122,122,0.15);font-size:16px}.retail-table tbody tr td{border-bottom:1px solid #ccc}.retail-table td,.retail-table th{color:#333;font-size:14px;line-height:60px;padding:4px 4px 4px 6px;max-width:300px;vertical-align:middle}.retail-table-no-wrap{white-space:nowrap}.retail-table-center{text-align:center}.retail-table-sort__btn,.retail-table-sort__switch{cursor:pointer}.retail-table-sort__btn:hover,.retail-table-sort__switch:hover{color:#005add}.retail-table-sort__btn{float:left;clear:both;line-height:15px;font-size:20px;padding-left:10px;padding-right:10px;opacity:0;transition:opacity .2s ease-out}.retail-table-sort__btn-wrap{position:absolute}.retail-table-sort__asc{padding-top:15px;padding-bottom:0}.retail-table-sort__desc{padding-top:0;padding-bottom:15px}.retail-table-sort thead th:hover .retail-table-sort__btn,.retail-table-sort thead td:hover .retail-table-sort__btn{opacity:1}.retail-collapsible__input{display:none}label.retail-collapsible__title{cursor:pointer;font-weight:normal;text-align:center;padding:0;position:relative;line-height:1;width:100%}label.retail-collapsible__title:before{content:'\25B6';opacity:0;transition:opacity .2s ease-out}label.retail-collapsible__title:hover:before{opacity:1}.retail-collapsible__content{text-align:left;font-size:12px;line-height:1.5;background:#fff;border:1px solid rgba(122,122,122,0.15);position:absolute;top:20px;left:0;padding:18px;margin:0;border-radius:20px;overflow:hidden;max-height:0;transition-duration:.2s;transition-timing-function:ease-out;transition-property:max-height,visibility;visibility:hidden}.retail-collapsible__input:checked+.retail-collapsible__title .retail-collapsible__content{max-height:100vh;visibility:visible}.retail-error-msg,.retail-error-msg:after,.retail-error-msg:before{color:#dd2e44} \ No newline at end of file diff --git a/retailcrm/views/js/retailcrm-export.js b/retailcrm/views/js/retailcrm-export.js new file mode 100644 index 0000000..f7d0da3 --- /dev/null +++ b/retailcrm/views/js/retailcrm-export.js @@ -0,0 +1,143 @@ +/** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +$(function () { + function RetailcrmExportForm() { + this.form = $('input[name=RETAILCRM_EXPORT_ORDERS_COUNT]').closest('form').get(0); + + if (typeof this.form === 'undefined') { + return false; + } + + this.ordersCount = parseInt($(this.form).find('input[name="RETAILCRM_EXPORT_ORDERS_COUNT"]').val()); + this.customersCount = parseInt($(this.form).find('input[name="RETAILCRM_EXPORT_CUSTOMERS_COUNT"]').val()); + this.ordersStepSize = parseInt($(this.form).find('input[name="RETAILCRM_EXPORT_ORDERS_STEP_SIZE"]').val()); + this.customersStepSize = parseInt($(this.form).find('input[name="RETAILCRM_EXPORT_CUSTOMERS_STEP_SIZE"]').val()); + this.ordersStep = 0; + this.customersStep = 0; + + this.submitButton = $(this.form).find('button[id="export-orders-submit"]').get(0); + this.progressBar = $(this.form).find('div[id="export-orders-progress"]').get(0); + + this.submitAction = this.submitAction.bind(this); + this.exportAction = this.exportAction.bind(this); + this.exportDone = this.exportDone.bind(this); + this.initializeProgressBar = this.initializeProgressBar.bind(this); + this.updateProgressBar = this.updateProgressBar.bind(this); + + $(this.submitButton).click(this.submitAction); + } + + RetailcrmExportForm.prototype.submitAction = function (event) { + event.preventDefault(); + + this.initializeProgressBar(); + this.exportAction(); + }; + + RetailcrmExportForm.prototype.exportAction = function () { + let data = {}; + if (this.ordersStep * this.ordersStepSize < this.ordersCount) { + this.ordersStep++; + data = { + submitretailcrm: 1, + RETAILCRM_EXPORT_ORDERS_STEP: this.ordersStep + } + } else { + if (this.customersStep * this.customersStepSize < this.customersCount) { + this.customersStep++; + data = { + submitretailcrm: 1, + RETAILCRM_EXPORT_CUSTOMERS_STEP: this.customersStep + } + } else { + return this.exportDone(); + } + } + + let _this = this; + + $.ajax({ + url: this.form.action, + method: this.form.method, + timeout: 0, + data: data + }) + .done(function (response) { + _this.updateProgressBar(); + _this.exportAction(); + }) + }; + + RetailcrmExportForm.prototype.initializeProgressBar = function () { + $(this.submitButton).addClass('retail-hidden'); + $(this.progressBar) + .removeClass('retail-hidden') + .append($('
', {class: 'retail-progress__loader', text: '0%'})) + + window.addEventListener('beforeunload', this.confirmLeave); + }; + + RetailcrmExportForm.prototype.updateProgressBar = function () { + let processedOrders = this.ordersStep * this.ordersStepSize; + if (processedOrders > this.ordersCount) + processedOrders = this.ordersCount; + + let processedCustomers = this.customersStep * this.customersStepSize; + if (processedCustomers > this.customersCount) + processedCustomers = this.customersCount; + + const processed = processedOrders + processedCustomers; + const total = this.ordersCount + this.customersCount; + const percents = Math.round(100 * processed / total); + + $(this.progressBar).find('.retail-progress__loader').text(percents + '%'); + $(this.progressBar).find('.retail-progress__loader').css('width', percents + '%'); + $(this.progressBar).find('.retail-progress__loader').attr('alt', processed + '/' + total); + }; + + RetailcrmExportForm.prototype.confirmLeave = function (event) { + event.preventDefault(); + e.returnValue = 'Export process has been started'; + } + + RetailcrmExportForm.prototype.exportDone = function () { + window.removeEventListener('beforeunload', this.confirmLeave); + alert('Export is done') + } + + window.RetailcrmExportForm = RetailcrmExportForm; +}); diff --git a/retailcrm/views/js/retailcrm-export.min.js b/retailcrm/views/js/retailcrm-export.min.js new file mode 100644 index 0000000..54c4d06 --- /dev/null +++ b/retailcrm/views/js/retailcrm-export.min.js @@ -0,0 +1,37 @@ +/** + * MIT License + * + * Copyright (c) 2020 DIGITAL RETAIL TECHNOLOGIES SL + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade PrestaShop to newer + * versions in the future. If you wish to customize PrestaShop for your + * needs please refer to http://www.prestashop.com for more information. + * + * @author DIGITAL RETAIL TECHNOLOGIES SL + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +$(function(){function t(){if(this.form=$("input[name=RETAILCRM_EXPORT_ORDERS_COUNT]").closest("form").get(0),void 0===this.form)return!1;this.ordersCount=parseInt($(this.form).find('input[name="RETAILCRM_EXPORT_ORDERS_COUNT"]').val()),this.customersCount=parseInt($(this.form).find('input[name="RETAILCRM_EXPORT_CUSTOMERS_COUNT"]').val()),this.ordersStepSize=parseInt($(this.form).find('input[name="RETAILCRM_EXPORT_ORDERS_STEP_SIZE"]').val()),this.customersStepSize=parseInt($(this.form).find('input[name="RETAILCRM_EXPORT_CUSTOMERS_STEP_SIZE"]').val()),this.ordersStep=0,this.customersStep=0,this.submitButton=$(this.form).find('button[id="export-orders-submit"]').get(0),this.progressBar=$(this.form).find('div[id="export-orders-progress"]').get(0),this.submitAction=this.submitAction.bind(this),this.exportAction=this.exportAction.bind(this),this.exportDone=this.exportDone.bind(this),this.initializeProgressBar=this.initializeProgressBar.bind(this),this.updateProgressBar=this.updateProgressBar.bind(this),$(this.submitButton).click(this.submitAction)}t.prototype.submitAction=function(t){t.preventDefault(),this.initializeProgressBar(),this.exportAction()},t.prototype.exportAction=function(){let t={};if(this.ordersStep*this.ordersStepSize",{class:"retail-progress__loader",text:"0%"})),window.addEventListener("beforeunload",this.confirmLeave)},t.prototype.updateProgressBar=function(){let t=this.ordersStep*this.ordersStepSize;t>this.ordersCount&&(t=this.ordersCount);let e=this.customersStep*this.customersStepSize;e>this.customersCount&&(e=this.customersCount);var s=t+e,i=this.ordersCount+this.customersCount,r=Math.round(100*s/i);$(this.progressBar).find(".retail-progress__loader").text(r+"%"),$(this.progressBar).find(".retail-progress__loader").css("width",r+"%"),$(this.progressBar).find(".retail-progress__loader").attr("alt",s+"/"+i)},t.prototype.confirmLeave=function(t){t.preventDefault(),e.returnValue="Export process has been started"},t.prototype.exportDone=function(){window.removeEventListener("beforeunload",this.confirmLeave),alert("Export is done")},window.RetailcrmExportForm=t}); \ No newline at end of file diff --git a/retailcrm/views/js/retailcrm.js b/retailcrm/views/js/retailcrm.js index a504d4b..640f7d1 100644 --- a/retailcrm/views/js/retailcrm.js +++ b/retailcrm/views/js/retailcrm.js @@ -43,6 +43,7 @@ $(function(){ this.player.init(); this.tabs.init(); this.uploadForm.init(this.settingsTabs.init()); + this.exportForm.init() this.popup.init(); this.toggleBox(); this.trimConsultant(); @@ -233,6 +234,13 @@ $(function(){ } } }, + exportForm: { + init: function () { + if (!(typeof RetailcrmExportForm === 'undefined')) { + new RetailcrmExportForm(); + } + } + }, tabs: { init: function () { $('.retail-tabs__btn').on('click', this.swithTab); diff --git a/retailcrm/views/js/retailcrm.min.js b/retailcrm/views/js/retailcrm.min.js index 7bb64a9..3fedc14 100644 --- a/retailcrm/views/js/retailcrm.min.js +++ b/retailcrm/views/js/retailcrm.min.js @@ -1 +1,37 @@ -$(function(){var Main={init:function(){this.selects.init();this.tableSort.init();this.player.init();this.tabs.init();this.uploadForm.init(this.settingsTabs.init());this.popup.init();this.toggleBox();this.trimConsultant();this.showSettings()},selects:{init:function(){var _this=this;try{$(".jq-select").SumoSelect();$("li.opt").each((_,el)=>{if($(el).find("label").html().length===0){let select=$(el).closest("ul").closest("div").parent().find("select");$(el).find("label").html(select.attr("placeholder"));$(el).addClass("disabled")}});_this.update();$(document).on("change",".jq-select",function(){_this.update()})}catch(e){console.warn("Cannot initialize select: "+e.message)}},update:function(){var selected={};let selects=$(".retail-tab__enabled").find("select:not(#RETAILCRM_API_DELIVERY_DEFAULT, #RETAILCRM_API_PAYMENT_DEFAULT)");selects.each((i,select)=>{var value=$(select).val();if(value&&value.length){selected[i]=$('option[value="'+$(select).val()+'"]',$(select)).index()}});let values=Object.values(selected);selects.each((i,select)=>{$("option",select).each((o,option)=>{if($.inArray(o,values)===-1||typeof selected[i]!=="undefined"&&selected[i]==o){select.sumo.enableItem(o)}else{select.sumo.disableItem(o)}})})}},tableSort:{init:function(){var _this=this;$(".retail-table-sort").each((i,table)=>{$(table).find(".retail-table-sort__switch").each((j,header)=>{const column=$(header).closest("th,td").index();$(header).click(e=>{e.preventDefault();_this.sort(table,column)})});$(table).find(".retail-table-sort__asc").each((j,header)=>{const column=$(header).closest("th,td").index();$(header).click(e=>{e.preventDefault();_this.sort(table,column,"asc")})});$(table).find(".retail-table-sort__desc").each((j,header)=>{const column=$(header).closest("th,td").index();$(header).click(e=>{e.preventDefault();_this.sort(table,column,"desc")})});$(table).find(".retail-table-sort__initial").click()})},sort:function(table,column,direction=undefined){let rows,switching,i,x,y,shouldSwitch,dir,switchcount=0;switching=true;dir=direction?direction:"asc";while(switching){switching=false;rows=table.rows;for(i=1;iy.innerHTML.toLowerCase()){shouldSwitch=true;break}}else if(dir==="desc"){if(x.innerHTML.toLowerCase() + * @copyright 2020 DIGITAL RETAIL TECHNOLOGIES SL + * @license https://opensource.org/licenses/MIT The MIT License + * + * Don't forget to prefix your containers with your own identifier + * to avoid any conflicts with others containers. + */ +$(function(){var a={init:function(){this.selects.init(),this.tableSort.init(),this.player.init(),this.tabs.init(),this.uploadForm.init(this.settingsTabs.init()),this.exportForm.init(),this.popup.init(),this.toggleBox(),this.trimConsultant(),this.showSettings()},selects:{init:function(){var t=this;try{$(".jq-select").SumoSelect(),$("li.opt").each((t,e)=>{if(0===$(e).find("label").html().length){let t=$(e).closest("ul").closest("div").parent().find("select");$(e).find("label").html(t.attr("placeholder")),$(e).addClass("disabled")}}),t.update(),$(document).on("change",".jq-select",function(){t.update()})}catch(t){console.warn("Cannot initialize select: "+t.message)}},update:function(){var n={};let t=$(".retail-tab__enabled").find("select:not(#RETAILCRM_API_DELIVERY_DEFAULT, #RETAILCRM_API_PAYMENT_DEFAULT)");t.each((t,e)=>{var i=$(e).val();i&&i.length&&(n[t]=$('option[value="'+$(e).val()+'"]',$(e)).index())});let s=Object.values(n);t.each((i,a)=>{$("option",a).each((t,e)=>{-1===$.inArray(t,s)||void 0!==n[i]&&n[i]==t?a.sumo.enableItem(t):a.sumo.disableItem(t)})})}},tableSort:{init:function(){var n=this;$(".retail-table-sort").each((t,a)=>{$(a).find(".retail-table-sort__switch").each((t,e)=>{const i=$(e).closest("th,td").index();$(e).click(t=>{t.preventDefault(),n.sort(a,i)})}),$(a).find(".retail-table-sort__asc").each((t,e)=>{const i=$(e).closest("th,td").index();$(e).click(t=>{t.preventDefault(),n.sort(a,i,"asc")})}),$(a).find(".retail-table-sort__desc").each((t,e)=>{const i=$(e).closest("th,td").index();$(e).click(t=>{t.preventDefault(),n.sort(a,i,"desc")})}),$(a).find(".retail-table-sort__initial").click()})},sort:function(t,e,i=void 0){let a,n,s,o,r,l,c,d=0;for(n=!0,c=i||"asc";n;){for(n=!1,a=t.rows,s=1;sr.innerHTML.toLowerCase()){l=!0;break}}else if("desc"===c&&o.innerHTML.toLowerCase() + RetailCRM
{l s='Upload orders' mod='retailcrm'}
+
{l s='Enter order IDs to upload to RetailCRM, divided by a comma. You can also specify ranges, like "1-10". It\'s allowed to upload to 10 orders at a time.' mod='retailcrm'}
- +
- + +
+
{l s='Export orders & customers' mod='retailcrm'}
+
{l s='You can export all the orders and customers from CMS to RetailCRM by pressing "Export" button. This process can take a long time, and it\'s required that you keep the tab opened until it\'s done.' mod='retailcrm'}
+
+
+
{l s='Orders' mod='retailcrm'}
+ + +
+
+
+ {l s='Customers' mod='retailcrm'} +
+ + + +
+
+
+ +
@@ -286,4 +320,5 @@ +