* @copyright 2021 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. */ 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 int */ static $ordersOffset; /** * @var int */ 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 * * @return int */ public static function getOrdersCount() { $sql = 'SELECT count(o.id_order) FROM `' . _DB_PREFIX_ . 'orders` o WHERE 1 ' . Shop::addSqlRestriction(false, 'o'); return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql); } /** * Get orders ids from the database * * @param int $start Sets the LIMIT start parameter for sql query * @param int|null $count Sets the count of orders to get from database * * @return Generator * * @throws PrestaShopDatabaseException */ public static function getOrdersIds($start = 0, $count = null) { if (null === $count) { $to = static::getOrdersCount(); $count = $to - $start; } else { $to = $start + $count; } if (0 < $count) { $predefinedSql = 'SELECT o.`id_order` FROM `' . _DB_PREFIX_ . 'orders` o WHERE 1 ' . Shop::addSqlRestriction(false, 'o') . ' ORDER BY o.`id_order` ASC'; while ($start < $to) { $offset = ($start + static::$ordersOffset > $to) ? $to - $start : static::$ordersOffset; if (0 >= $offset) { break; } $sql = $predefinedSql . ' LIMIT ' . (int) $start . ', ' . (int) $offset; $orders = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql); if (empty($orders)) { break; } foreach ($orders as $order) { yield $order; } $start += $offset; } } } /** * @param int $from * @param int|null $count */ public static function exportOrders($from = 0, $count = null) { if (!static::validateState()) { return; } $orders = []; $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 (50 == count($orders)) { static::$api->ordersUpload($orders); $orders = []; } } 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($withOrders = true) { $sql = 'SELECT count(c.id_customer) FROM `' . _DB_PREFIX_ . 'customer` c WHERE 1 ' . Shop::addSqlRestriction(false, 'c'); if (!$withOrders) { $sql .= ' 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 )'; } return (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql); } /** * Get customers ids from database * * @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 $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, $withOrders = true, $returnAddressId = true) { if (null === $count) { $to = static::getCustomersCount($withOrders); $count = $to - $start; } else { $to = $start + $count; } if (0 < $count) { $predefinedSql = 'SELECT c.`id_customer` ' . ($returnAddressId ? ', a.`id_address`' : '') . ' FROM `' . _DB_PREFIX_ . 'customer` c ' . ($returnAddressId ? ' LEFT JOIN ( SELECT ad.`id_customer`, ad.`id_address` FROM `' . _DB_PREFIX_ . 'address` ad INNER JOIN ( SELECT `id_customer`, MAX(`date_add`) AS `date_add` FROM `' . _DB_PREFIX_ . 'address` GROUP BY id_customer ) ad2 ON ad2.`id_customer` = ad.`id_customer` AND ad2.`date_add` = ad.`date_add` ORDER BY ad.`id_customer` ASC ) a ON a.`id_customer` = c.`id_customer` ' : '') . ' WHERE 1 ' . 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 < $to) { $offset = ($start + static::$customersOffset > $to) ? $to - $start : static::$customersOffset; if (0 >= $offset) { break; } $sql = $predefinedSql . ' LIMIT ' . (int) $start . ', ' . (int) $offset; $customers = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql); if (empty($customers)) { break; } foreach ($customers as $customer) { yield $customer; } $start += $offset; } } } /** * @param int $from * @param int|null $count */ public static function exportCustomers($from = 0, $count = null) { if (!static::validateState()) { return; } $customers = []; $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 = []; } 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 (50 == count($customers)) { static::$api->customersUpload($customers); $customers = []; time_nanosleep(0, 250000000); } } } if (count($customers)) { static::$api->customersUpload($customers); } } /** * @param int $id * * @return bool * * @throws PrestaShopObjectNotFoundExceptionCore * @throws PrestaShopDatabaseException * @throws PrestaShopException * @throws Exception */ public static function exportOrder($id) { if (!static::$api) { return false; } $object = new Order($id); $customer = new Customer($object->id_customer); $apiResponse = static::$api->ordersGet($object->id); $existingOrder = []; if ($apiResponse->isSuccessful() && $apiResponse->offsetExists('order')) { $existingOrder = $apiResponse['order']; } if (!Validate::isLoadedObject($object)) { throw new PrestaShopObjectNotFoundExceptionCore('Order not found'); } $orderBuilder = new RetailcrmOrderBuilder(); $crmOrder = $orderBuilder ->defaultLangFromConfiguration() ->setApi(static::$api) ->setCmsOrder($object) ->setCmsCustomer($customer) ->buildOrderWithPreparedCustomer() ; if (empty($crmOrder)) { return false; } if (empty($existingOrder)) { $response = static::$api->ordersCreate($crmOrder); } else { $response = static::$api->ordersEdit($crmOrder); if (empty($existingOrder['payments']) && !empty($crmOrder['payments'])) { $payment = array_merge(reset($crmOrder['payments']), [ 'order' => ['externalId' => $crmOrder['externalId']], ]); static::$api->ordersPaymentCreate($payment); } } if (!$response->isSuccessful()) { $errorMsg = ''; if ($response->offsetExists('errorMsg')) { $errorMsg = $response['errorMsg'] . ': '; } if ($response->offsetExists('errors')) { $errorMsg .= implode('; ', $response['errors']); } throw new Exception($errorMsg); } return $response->isSuccessful(); } /** * 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; } }