diff --git a/.gitignore b/.gitignore index 01681dd..a9a3290 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ retailcrm/views/css/less/*.map retailcrm/views/css/*.css retailcrm/views/css/*.map !retailcrm/views/css/*.min.css -retailcrm/config_*.xml \ No newline at end of file +retailcrm/config_*.xml +coverage.xml \ No newline at end of file diff --git a/retailcrm/lib/RetailcrmAbstractBuilder.php b/retailcrm/lib/RetailcrmAbstractBuilder.php new file mode 100644 index 0000000..b9d472f --- /dev/null +++ b/retailcrm/lib/RetailcrmAbstractBuilder.php @@ -0,0 +1,52 @@ + + * @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. + */ +abstract class RetailcrmAbstractBuilder +{ + /** + * Checks if there is a key in the array and returns if there is an element array if not the default value + * + * @param string $key + * @param string $default + * + * @return mixed + */ + protected function arrayValue($key, $default = '--') + { + return isset($this->dataCrm[$key]) && !empty($this->dataCrm[$key]) ? $this->dataCrm[$key] : $default; + } +} diff --git a/retailcrm/lib/RetailcrmBuilderInterface.php b/retailcrm/lib/RetailcrmBuilderInterface.php new file mode 100644 index 0000000..04bca43 --- /dev/null +++ b/retailcrm/lib/RetailcrmBuilderInterface.php @@ -0,0 +1,70 @@ + + * @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. + */ +interface RetailcrmBuilderInterface +{ + /** + * Get result build + * + * @return mixed + */ + public function getData(); + + /** + * Set data array customerHistory + * + * @param array $dataCrm + * + * @return RetailcrmBuilderInterface + */ + public function setDataCrm($dataCrm); + + /** + * Build result + * + * @return \RetailcrmBuilderInterface + */ + public function build(); + + /** + * Create new object + * + * @return mixed + */ + public function reset(); +} + diff --git a/retailcrm/lib/RetailcrmCli.php b/retailcrm/lib/RetailcrmCli.php index 292b4c8..9c2a6b9 100644 --- a/retailcrm/lib/RetailcrmCli.php +++ b/retailcrm/lib/RetailcrmCli.php @@ -129,16 +129,24 @@ class RetailcrmCli } } + /** + * Shutdown handler. Moved here in order to keep compatibility with older PHP versions. + * + * @param mixed $error + */ + public function cleanupOnShutdown($error) + { + if (null !== $error) { + self::clearCurrentJob(null); + } + } + /** * This will register shutdown handler which will clean lock before shutdown */ private function setCleanupOnShutdown() { - RetailcrmJobManager::setCustomShutdownHandler(function ($error) { - if (null !== $error) { - self::clearCurrentJob(null); - } - }); + RetailcrmJobManager::setCustomShutdownHandler(array($this, 'cleanupOnShutdown')); } /** @@ -156,17 +164,17 @@ class RetailcrmCli $result ? 'true' : 'false' )); } catch (\Exception $exception) { - if ($exception instanceof RetailcrmJobManagerException && !empty($exception->getPrevious())) { + if ($exception instanceof RetailcrmJobManagerException && $exception->getPrevious() instanceof \Exception) { $this->printStack($exception->getPrevious()); } else { $this->printStack($exception); } self::clearCurrentJob($jobName); - } finally { - if (isset($result) && $result) { - self::clearCurrentJob($jobName); - } + } + + if (isset($result) && $result) { + self::clearCurrentJob($jobName); } } diff --git a/retailcrm/lib/RetailcrmCorporateCustomerBuilder.php b/retailcrm/lib/RetailcrmCorporateCustomerBuilder.php new file mode 100644 index 0000000..580c5e9 --- /dev/null +++ b/retailcrm/lib/RetailcrmCorporateCustomerBuilder.php @@ -0,0 +1,207 @@ + + * @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. + */ +class RetailcrmCorporateCustomerBuilder extends RetailcrmAbstractBuilder implements RetailcrmBuilderInterface +{ + /** + * @var Customer|CustomerCore $corporateCustomer Corporate customer + */ + private $corporateCustomer; + + /** + * @var RetailcrmBuilderInterface $customerBuilder Customer builder + */ + private $customerBuilder; + + /** + * @var array $dataCrm customerHistory + */ + protected $dataCrm; + + /** + * @var string $companyName + */ + private $companyName; + + /** + * @var string $companyInn + */ + protected $companyInn; + + /** + * @var Address|AddressCore $corporateAddress + */ + private $corporateAddress; + + /** + * RetailcrmCorporateCustomerBuilder constructor. + */ + public function __construct() + { + $this->reset(); + } + + /** + * @param RetailcrmBuilderInterface $customerBuilder + * @return RetailcrmCorporateCustomerBuilder + */ + public function setCustomerBuilder($customerBuilder) + { + $this->customerBuilder = $customerBuilder; + return $this; + } + + /** + * @param Customer $corporateCustomer Corporate customer + * @return RetailcrmCorporateCustomerBuilder + */ + public function setCustomer($corporateCustomer) + { + $this->corporateCustomer = $corporateCustomer; + return $this; + } + + /** + * @param string $companyName + * @return RetailcrmCorporateCustomerBuilder + */ + public function setCompanyName($companyName) + { + $this->companyName = $companyName; + return $this; + } + + /** + * @param string $companyInn + * @return RetailcrmCorporateCustomerBuilder + */ + public function setCompanyInn($companyInn) + { + $this->companyInn = $companyInn; + return $this; + } + + /** + * @param Address|AddressCore $corporateAddress + * @return RetailcrmCorporateCustomerBuilder + */ + public function setCorporateAddress($corporateAddress) + { + $this->corporateAddress = $corporateAddress; + return $this; + } + + /** + * Set data in address, name and inn company corporate customer + * + * @param array $dataCrm + * @return RetailcrmCorporateCustomerBuilder + */ + public function extractCompanyDataFromOrder($dataCrm) + { + $this->setCompanyName(isset($dataCrm['company']['name']) ? $dataCrm['company']['name'] : ''); + + if (isset($dataCrm['company']['contragent']) && !empty($dataCrm['company']['contragent']['INN'])) { + $this->setCompanyInn($dataCrm['company']['contragent']['INN']); + } + + return $this; + } + + public function setDataCrm($dataCrm) + { + $this->dataCrm = $dataCrm; + return $this; + } + + public function getData() + { + return new RetailcrmCustomerBuilderResult($this->corporateCustomer, $this->corporateAddress); + } + + public function reset() + { + $this->corporateCustomer = new Customer(); + $this->customerBuilder = null; + $this->corporateAddress = null; + + return $this; + } + + /** + * Build customer for corporate customer + */ + private function buildCustomer() + { + if (empty($this->customerBuilder)) { + $this->customerBuilder = new RetailcrmCustomerBuilder(); + } + + if (!empty($this->dataCrm)) { + $this->customerBuilder->setDataCrm($this->dataCrm); + } + + if (isset($this->dataCrm['address'])) { + $this->customerBuilder->build(); + } + + $this->corporateCustomer = $this->customerBuilder->getData()->getCustomer(); + $this->corporateAddress = $this->customerBuilder->getData()->getCustomerAddress(); + } + + public function build() + { + $this->buildCustomer(); + + if (!empty($this->corporateAddress)) { + + if (empty($this->corporateAddress->alias) || $this->corporateAddress->alias == 'default') { + $this->corporateAddress->alias = '--'; + } + + $this->corporateAddress->vat_number = !empty($this->companyInn) ? $this->companyInn : ''; + $this->corporateAddress->company = !empty($this->companyName) ? $this->companyName : ''; + + if (!empty($this->companyName) && (empty($this->corporateCustomer->firstname) || $this->corporateCustomer->firstname == '--')) { + $this->corporateCustomer->firstname = $this->companyName; + } + } + + return $this; + } +} + diff --git a/retailcrm/lib/RetailcrmCustomerAddressBuilder.php b/retailcrm/lib/RetailcrmCustomerAddressBuilder.php new file mode 100644 index 0000000..4b67091 --- /dev/null +++ b/retailcrm/lib/RetailcrmCustomerAddressBuilder.php @@ -0,0 +1,196 @@ + + * @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. + */ +class RetailcrmCustomerAddressBuilder extends RetailcrmAbstractBuilder implements RetailcrmBuilderInterface +{ + /** + * @var Address|AddressCore customerAddress + */ + private $customerAddress; + + /** + * @var array $dataCrm + */ + private $dataCrm; + + /** + * @var int $idCustomer + */ + private $idCustomer; + + /** + * @var string $firstName + */ + private $firstName; + + /** + * @var string $lastName + */ + private $lastName; + + /** + * @var string $phone + */ + private $phone; + + /** + * @var string $alias + */ + private $alias; + + /** + * RetailcrmCustomerAddressBuilder constructor. + */ + public function __construct() + { + $this->reset(); + } + + /** + * @param Address|AddressCore $customerAddress + * @return RetailcrmCustomerAddressBuilder + */ + public function setCustomerAddress($customerAddress) + { + $this->customerAddress = $customerAddress; + return $this; + } + + public function setDataCrm($dataCrm) + { + $this->dataCrm = $dataCrm; + return $this; + } + + /** + * @param int $idCustomer + * @return RetailcrmCustomerAddressBuilder + */ + public function setIdCustomer($idCustomer) + { + $this->idCustomer = $idCustomer; + return $this; + } + + /** + * @param string $alias + * @return RetailcrmCustomerAddressBuilder + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * @param string $firstName + * @return RetailcrmCustomerAddressBuilder + */ + public function setFirstName($firstName) + { + $this->firstName = $firstName; + return $this; + } + + /** + * @param string $lastName + * @return RetailcrmCustomerAddressBuilder + */ + public function setLastName($lastName) + { + $this->lastName = $lastName; + return $this; + } + + /** + * @param string $phone + * @return RetailcrmCustomerAddressBuilder + */ + public function setPhone($phone) + { + $this->phone = $phone; + return $this; + } + + public function getData() + { + if (!empty($this->customerAddress)) { + return $this->customerAddress; + } + + return array(); + } + + public function reset() + { + $this->customerAddress = new Address(); + + return $this; + } + + public function build() + { + if (empty($this->customerAddress)) { + $this->customerAddress = new Address(); + } + + $this->customerAddress->id_customer = $this->idCustomer; + $this->customerAddress->alias = !empty($this->alias) ? $this->alias : 'default'; + $this->customerAddress->lastname = $this->lastName; + $this->customerAddress->firstname = $this->firstName; + $this->customerAddress->address1 = isset($this->dataCrm['text']) ? $this->dataCrm['text'] : '--'; + + $this->customerAddress->id_country = isset($this->dataCrm['countryIso']) + ? Country::getByIso($this->dataCrm['countryIso']) + : Configuration::get('PS_COUNTRY_DEFAULT'); + + if (isset($this->dataCrm['region'])) { + $state = State::getIdByName($this->dataCrm['region']); + + if (!empty($state)) { + $this->customerAddress->id_state = $state; + } + } + + $this->customerAddress->city = isset($this->dataCrm['city']) ? $this->dataCrm['city'] : '--'; + $this->customerAddress->postcode = isset($this->dataCrm['index']) ? $this->dataCrm['index'] : ''; + $this->customerAddress->phone = !empty($this->phone) ? $this->phone : ''; + + return $this; + } +} + diff --git a/retailcrm/lib/RetailcrmCustomerBuilder.php b/retailcrm/lib/RetailcrmCustomerBuilder.php new file mode 100644 index 0000000..3b16026 --- /dev/null +++ b/retailcrm/lib/RetailcrmCustomerBuilder.php @@ -0,0 +1,161 @@ + + * @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. + */ +class RetailcrmCustomerBuilder extends RetailcrmAbstractBuilder implements RetailcrmBuilderInterface +{ + /** @var Customer|CustomerCore $customer Customer */ + private $customer; + + /** @var Address|AddressCore|null $customerAddress Address */ + private $customerAddress; + + /** @var array $dataCrm customerHistory */ + protected $dataCrm; + + /** @var RetailcrmBuilderInterface $addressBuilder Address builder */ + private $addressBuilder; + + public function __construct() + { + $this->reset(); + } + + /** + * @param Customer|CustomerCore $customer + * @return RetailcrmCustomerBuilder + */ + public function setCustomer($customer) + { + $this->customer = $customer; + return $this; + } + + /** + * @param RetailcrmBuilderInterface $addressBuilder + * @return RetailcrmCustomerBuilder + */ + public function setAddressBuilder($addressBuilder) + { + $this->addressBuilder = $addressBuilder; + return $this; + } + + public function setDataCrm($dataCrm) + { + $this->dataCrm = $dataCrm; + return $this; + } + + public function getData() + { + return new RetailcrmCustomerBuilderResult($this->customer, $this->customerAddress); + } + + public function reset() + { + $this->customer = new Customer(); + $this->customerAddress = null; + $this->addressBuilder = null; + + return $this; + } + + /** + * Build address for customer + */ + public function buildAddress() + { + if (empty($this->addressBuilder)) { + $this->addressBuilder = new RetailcrmCustomerAddressBuilder(); + } + + if (isset($this->dataCrm['address'])) { + $this->addressBuilder + ->setIdCustomer($this->arrayValue('externalId', 0)) + ->setDataCrm($this->dataCrm['address']) + ->setFirstName($this->arrayValue('firstName')) + ->setLastName($this->arrayValue('lastName')) + ->setPhone( isset($this->dataCrm['phones'][0]['number']) + && !empty($this->dataCrm['phones'][0]['number']) + ? $this->dataCrm['phones'][0]['number'] : '') + ->build(); + + $this->customerAddress = $this->addressBuilder->getData(); + } else { + $this->customerAddress = null; + } + } + + public function build() + { + if (isset($this->dataCrm['externalId'])) { + $this->customer->id = $this->dataCrm['externalId']; + } + + $this->customer->firstname = $this->arrayValue('firstName'); + $this->customer->lastname = $this->arrayValue('lastName'); + + if (isset($this->dataCrm['subscribed']) && $this->dataCrm['subscribed'] == false) { + $this->customer->newsletter = false; + } + + if (empty($this->customer->id_shop)) { + $this->customer->id_shop = Context::getContext()->shop->id; + } + + $this->customer->birthday = $this->arrayValue('birthday', ''); + + if (isset($this->dataCrm['sex'])) { + $this->customer->id_gender = $this->dataCrm['sex'] == 'male' ? 1 : 2; + } + + $this->buildAddress(); + + if (isset($this->dataCrm['email']) && Validate::isEmail($this->dataCrm['email'])) { + $this->customer->email = $this->dataCrm['email']; + } else { + $this->customer->email = RetailcrmTools::createPlaceholderEmail($this->arrayValue('firstName', microtime())); + } + + if (empty($this->customer->passwd )) { + $this->customer->passwd = Tools::substr(str_shuffle(Tools::strtolower(sha1(rand() . time()))), 0, 5); + } + + return $this; + } +} + diff --git a/retailcrm/lib/RetailcrmCustomerBuilderResult.php b/retailcrm/lib/RetailcrmCustomerBuilderResult.php new file mode 100644 index 0000000..c7972d2 --- /dev/null +++ b/retailcrm/lib/RetailcrmCustomerBuilderResult.php @@ -0,0 +1,74 @@ + + * @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. + */ +class RetailcrmCustomerBuilderResult +{ + /** @var Customer|CustomerCore $customer */ + private $customer; + + /**@var Address|AddressCore|null $customerAddress */ + private $customerAddress; + + /** + * RetailcrmCustomerBuilderResult constructor. + * + * @param Customer|CustomerCore $customer + * @param Address|AddressCore|null $customerAddress + */ + public function __construct($customer, $customerAddress) + { + $this->customer = $customer; + $this->customerAddress = $customerAddress; + } + + /** + * @return Customer|CustomerCore + */ + public function getCustomer() + { + return $this->customer; + } + + /** + * @return Address|AddressCore|null + */ + public function getCustomerAddress() + { + return $this->customerAddress; + } +} + diff --git a/retailcrm/lib/RetailcrmCustomerSwitcher.php b/retailcrm/lib/RetailcrmCustomerSwitcher.php new file mode 100644 index 0000000..3764b90 --- /dev/null +++ b/retailcrm/lib/RetailcrmCustomerSwitcher.php @@ -0,0 +1,447 @@ + + * @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. + */ +class RetailcrmCustomerSwitcher implements RetailcrmBuilderInterface +{ + /** + * @var \RetailcrmCustomerSwitcherState $data + */ + private $data; + + /** + * @var \RetailcrmCustomerSwitcherResult|null $result + */ + private $result; + + /** @var bool $isContact */ + private $isContact; + + /** + * RetailcrmCustomerSwitcher constructor. + */ + public function __construct() + { + $this->reset(); + } + + /** + * In fact, this will execute customer change in provided order. + * + * @return $this|\RetailcrmBuilderInterface + */ + public function build() + { + $this->data->validate(); + $this->debugLogState(); + $newCustomer = $this->data->getNewCustomer(); + $newContact = $this->data->getNewContact(); + $newCompany = $this->data->getNewCompanyName(); + $companyAddress = $this->data->getCompanyAddress(); + + if (!empty($newCustomer)) { + RetailcrmLogger::writeDebugArray( + __METHOD__, + array( + 'Changing to individual customer for order', + $this->data->getOrder()->id + ) + ); + $this->isContact = false; + $this->processChangeToRegular($this->data->getOrder(), $newCustomer); + + if (!empty($this->result)) { + $this->result->getAddress()->company = ''; + } + } else { + if (!empty($newContact)) { + RetailcrmLogger::writeDebugArray( + __METHOD__, + array( + 'Changing to contact person customer for order', + $this->data->getOrder()->id + ) + ); + $this->isContact = true; + $this->processChangeToRegular($this->data->getOrder(), $newContact); + } + + if (!empty($newCompany)) { + RetailcrmLogger::writeDebug( + __METHOD__, + sprintf( + 'Replacing old order id=`%d` company `%s` with new company `%s`', + $this->data->getOrder()->id, + self::getOrderCompany($this->data->getOrder()), + $newCompany + ) + ); + $this->processCompanyChange(); + } + + if (!empty($companyAddress)) { + $this->processCompanyAddress(); + } + } + + return $this; + } + + /** + * Change order customer to regular one + * + * @param \Order $order + * @param array $newCustomer + */ + protected function processChangeToRegular($order, $newCustomer) + { + $customer = null; + $address = null; + $orderShippingAddress = $this->data->getCrmOrderShippingAddress(); + + if ((!isset($newCustomer['address']) || empty($newCustomer['address'])) && !empty($orderShippingAddress)) { + $newCustomer['address'] = $orderShippingAddress; + } + + $builder = new RetailcrmCustomerBuilder(); + $builder->setDataCrm($newCustomer); + + RetailcrmLogger::writeDebugArray( + __METHOD__, + array( + 'Switching in order', + $order->id, + 'to', + $newCustomer + ) + ); + + if (isset($newCustomer['externalId'])) { + RetailcrmLogger::writeDebug(__METHOD__, sprintf( + 'Switching to existing customer id=%s', + $newCustomer['externalId'] + )); + $customer = new Customer($newCustomer['externalId']); + + if (!empty($customer->id)) { + $order->id_customer = $customer->id; + $address = $this->getCustomerAddress($customer, $builder->build()->getData()->getCustomerAddress()); + $this->processCustomerAddress($customer, $address); + } else { + RetailcrmLogger::writeDebug(__METHOD__, sprintf( + 'Customer id=%s was not found, skipping...', + $newCustomer['externalId'] + )); + } + } + + if (empty($customer) || empty($customer->id)) { + RetailcrmLogger::writeDebug(__METHOD__, "Customer wasn't found, generating new..."); + + $result = $builder->build()->getData(); + $customer = $result->getCustomer(); + $address = $this->getCustomerAddress($customer, $result->getCustomerAddress()); + + RetailcrmLogger::writeDebugArray(__METHOD__, array('Result:', array( + 'customer' => RetailcrmTools::dumpEntity($customer), + 'address' => RetailcrmTools::dumpEntity($address) + ))); + } + + $this->result = new RetailcrmCustomerSwitcherResult($customer, $address, $order); + } + + /** + * Process company address. + */ + protected function processCompanyAddress() + { + $companyAddress = $this->data->getCompanyAddress(); + $customer = $this->data->getOrder()->getCustomer(); + $address = new Address($this->data->getOrder()->id_address_invoice); + + if (!empty($companyAddress)) { + $firstName = '--'; + $lastName = '--'; + $billingPhone = $address->phone; + + if (empty($billingPhone)) { + $billingPhone = $address->phone_mobile; + } + + if (!empty($this->result)) { + $customer = $this->result->getCustomer(); + } + + if (empty($customer)) { + $customer = $this->data->getOrder()->getCustomer(); + } + + if (!empty($customer)) { + $firstName = !empty($customer->firstname) ? $customer->firstname : '--'; + $lastName = !empty($customer->lastname) ? $customer->lastname : '--'; + } + + $builder = new RetailcrmCustomerAddressBuilder(); + $address = $builder + ->setDataCrm($companyAddress) + ->setFirstName($firstName) + ->setLastName($lastName) + ->setPhone($billingPhone) + ->setAlias('--') + ->build() + ->getData(); + $address->company = $this->data->getNewCompanyName(); + RetailcrmTools::assignAddressIdsByFields($customer, $address); + } + + if (empty($this->result)) { + $this->result = new RetailcrmCustomerSwitcherResult($customer, $address, $this->data->getOrder()); + } else { + $this->result->setAddress($address); + } + } + + /** + * This will update company in the address. + */ + protected function processCompanyChange() + { + $customer = $this->data->getOrder()->getCustomer(); + $address = new Address($this->data->getOrder()->id_address_invoice); + + if (!empty($this->result)) { + $newAddress = $this->result->getAddress(); + $newCustomer = $this->result->getCustomer(); + + if (!empty($newAddress) && empty($companyAddress)) { + $address = $newAddress; + } + + if (!empty($newCustomer)) { + $customer = $newCustomer; + } + } + + $oldId = $address->id; + $address->alias = '--'; + $address->company = $this->data->getNewCompanyName(); + RetailcrmTools::assignAddressIdsByFields($customer, $address); + + if (empty($oldId) || $oldId == $address->id) { + $address->id = 0; + } + + if (empty($this->result)) { + $this->result = new RetailcrmCustomerSwitcherResult($customer, $address, $this->data->getOrder()); + } + } + + /** + * @return $this|\RetailcrmBuilderInterface + */ + public function reset() + { + $this->data = new RetailcrmCustomerSwitcherState(); + $this->result = null; + return $this; + } + + /** + * Interface method implementation + * + * @param array $data + * + * @return $this|\RetailcrmBuilderInterface + */ + public function setDataCrm($data) + { + if (is_array($data)) { + $data = reset($data); + } + + return $this->setData($data); + } + + /** + * Set initial state into component + * + * @param \RetailcrmCustomerSwitcherState $data + * + * @return $this|\RetailcrmBuilderInterface + */ + public function setData($data) + { + if (!($data instanceof RetailcrmCustomerSwitcherState)) { + throw new \InvalidArgumentException('Invalid data type'); + } + + $this->data = $data; + return $this; + } + + /** + * @return \RetailcrmCustomerSwitcherResult|null + */ + public function getResult() + { + return $this->result; + } + + /** + * @return \RetailcrmCustomerSwitcherState + */ + public function getData() + { + return $this->data; + } + + /** + * Debug log for component state + */ + private function debugLogState() + { + if (RetailcrmTools::isDebug()) { + RetailcrmLogger::writeDebugArray(__METHOD__, array('state', array( + 'newCustomer' => $this->data->getNewCustomer(), + 'newContact' => $this->data->getNewContact(), + 'newCompanyName' => $this->data->getNewCompanyName(), + 'companyAddress' => $this->data->getCompanyAddress(), + 'order' => RetailcrmTools::dumpEntity($this->data->getOrder()), + ))); + } + } + + /** + * Returns placeholder address if customer hasn't one; returns address without any changes otherwise. + * + * @param \Customer|\CustomerCore $customer + * @param Address|\AddressCore|null $address + * + * @return \Address|\AddressCore|array|mixed + */ + private function getCustomerAddress($customer, $address) + { + if (empty($address)) { + if ($this->isContact) { + $address = $this->createPlaceholderAddress($customer, '--'); + } else { + $address = $this->createPlaceholderAddress($customer); + } + } + + return $address; + } + + /** + * Process address fields for existing customer. + * + * @param Customer|\CustomerCore $customer + * @param Address|\AddressCore $address + */ + private function processCustomerAddress($customer, $address) + { + if ($this->isContact) { + $newCompany = $this->data->getNewCompanyName(); + RetailcrmLogger::writeDebug(__METHOD__, 'Processing address for a contact person'); + + if ($address->alias == '' || $address->alias == 'default') { + $address->alias = '--'; + } + + if (!empty($newCompany)) { + $address->company = $newCompany; + } else { + $oldAddress = new Address($this->data->getOrder()->id_address_invoice); + + if ($oldAddress->company) { + $address->company = $oldAddress->company; + } + } + + RetailcrmTools::assignAddressIdsByFields($customer, $address); + RetailcrmLogger::writeDebug(__METHOD__, sprintf('Address id=%d', $address->id)); + } else { + RetailcrmLogger::writeDebug(__METHOD__, 'Processing address for an individual'); + RetailcrmTools::searchIndividualAddress($customer); + + if (empty($address->id)) { + $address->alias = 'default'; + RetailcrmTools::assignAddressIdsByFields($customer, $address); + } + + RetailcrmLogger::writeDebug(__METHOD__, sprintf('Address id=%d', $address->id)); + } + } + + /** + * Builds placeholder address for customer if he doesn't have address. + * + * @param \Customer $customer + * @param string $alias + * + * @return \Address|\AddressCore|array|mixed + */ + private function createPlaceholderAddress($customer, $alias = 'default') + { + $addressBuilder = new RetailcrmCustomerAddressBuilder(); + return $addressBuilder + ->setIdCustomer($customer->id) + ->setFirstName($customer->firstname) + ->setLastName($customer->lastname) + ->setAlias($alias) + ->build() + ->getData(); + } + + /** + * Returns company name from order. + * + * @param \Order $order + * + * @return string + */ + private static function getOrderCompany($order) + { + if (!empty($order) && !empty($order->id_address_invoice)) { + $address = new Address($order->id_address_invoice); + + return $address->company; + } + + return ''; + } +} diff --git a/retailcrm/lib/RetailcrmHistory.php b/retailcrm/lib/RetailcrmHistory.php index 0ca5a51..ca6fef6 100644 --- a/retailcrm/lib/RetailcrmHistory.php +++ b/retailcrm/lib/RetailcrmHistory.php @@ -76,137 +76,70 @@ class RetailcrmHistory Configuration::updateValue('RETAILCRM_LAST_CUSTOMERS_SYNC', $end['id']); $customersHistory = RetailcrmHistoryHelper::assemblyCustomer($historyChanges); + RetailcrmLogger::writeDebugArray(__METHOD__, array('Assembled history:', $customersHistory)); foreach ($customersHistory as $customerHistory) { - $syncAddressFIO = false; - if (isset($customerHistory['deleted']) && $customerHistory['deleted']) { continue; } + $customerBuilder = new RetailcrmCustomerBuilder(); + $customerBuilder->setDataCrm($customerHistory); + if (isset($customerHistory['externalId'])) { - $customer = new Customer($customerHistory['externalId']); + $customerData = self::$api->customersGet($customerHistory['externalId'], 'externalId'); + $foundCustomer = new Customer($customerHistory['externalId']); + $customerAddress = new Address(RetailcrmTools::searchIndividualAddress($foundCustomer)); + $addressBuilder = new RetailcrmCustomerAddressBuilder(); - if (isset($customerHistory['firstName'])) { - $customer->firstname = $customerHistory['firstName']; - } + $addressBuilder + ->setCustomerAddress($customerAddress); - if (isset($customerHistory['lastName'])) { - $customer->lastname = $customerHistory['lastName']; - } + $customerBuilder + ->setCustomer($foundCustomer) + ->setAddressBuilder($addressBuilder) + ->setDataCrm($customerData['customer']) + ->build(); - if (isset($customerHistory['birthday'])) { - $customer->birthday = $customerHistory['birthday']; - } - - if (isset($customerHistory['email']) && Validate::isEmail($customerHistory['email'])) { - $customer->email = $customerHistory['email']; - } - - if (empty($customer->passwd)) { - $customer->passwd = md5(time()); - } - - // Only sync subscription status if customer was marked as unsubscribed in retailCRM. - if (isset($customerHistory['subscribed']) && $customerHistory['subscribed'] == false) { - $customer->newsletter = false; - } + $customer = $customerBuilder->getData()->getCustomer(); + $address = $customerBuilder->getData()->getCustomerAddress(); if (self::loadInCMS($customer, 'update') === false) { continue; } - if ($syncAddressFIO) { - $customerAddresses = $customer->getAddresses(static::$default_lang); + if (!empty($address)) { + RetailcrmTools::assignAddressIdsByFields($customer, $address); - foreach ($customerAddresses as $addressArray) { - if (isset($addressArray['alias']) && $addressArray['alias'] == 'default') { - $cmsAddress = new Address($addressArray['id_address']); - $cmsAddress->firstname = $customer->firstname; - $cmsAddress->lastname = $customer->lastname; - - self::loadInCMS($cmsAddress, 'update'); - - break; - } + if (self::loadInCMS($address, 'update') === false) { + continue; } } - } else { - if (!isset($customerHistory['firstName'])) { - continue; - } + $customerBuilder->build(); - $customer = new Customer(); - - $customer->firstname = $customerHistory['firstName']; - $customer->lastname = isset($customerHistory['lastName']) ? $customerHistory['lastName'] : '--'; - $customer->id_lang = self::$default_lang; - $customer->newsletter = false; - - if (isset($customerHistory['birthday'])) { - $customer->birthday = $customerHistory['birthday']; - } - - if (isset($customerHistory['sex'])) { - $customer->id_gender = $customerHistory['sex'] == 'male' ? 1 : 2; - } - - if (isset($customerHistory['email']) && Validate::isEmail($customerHistory['email'])) { - $customer->email = $customerHistory['email']; - } else { - $customer->email = RetailcrmTools::createPlaceholderEmail($customerHistory['firstName']); - } - - $customer->passwd = Tools::substr(str_shuffle(Tools::strtolower(sha1(rand() . time()))), 0, 5); + $customer = $customerBuilder->getData()->getCustomer(); if (self::loadInCMS($customer, 'add') === false) { continue; } - if (isset($customerHistory['address'])) { - $customerAddress = new Address(); - $customerAddress->id_customer = $customer->id; - $customerAddress->alias = 'default'; - $customerAddress->lastname = $customer->lastname; - $customerAddress->firstname = $customer->firstname; - - if (isset($customerHistory['address']['countryIso'])) { - $customerAddress->id_country = Country::getByIso($customerHistory['address']['countryIso']); - } - - if (isset($customerHistory['address']['region'])) { - $customerAddress->id_state = State::getIdByName($customerHistory['address']['region']); - } - - $customerAddress->city = isset($customerHistory['address']['city']) ? $customerHistory['address']['city'] : '--'; - - $customerAddress->address1 = isset($customerHistory['address']['text']) ? $customerHistory['address']['text'] : '--'; - - if (isset($customerHistory['phones'])) { - // Can be returned as string, beware! - if (is_array($customerHistory['phones'])) { - $phone = reset($customerHistory['phones']); - } else { - $phone = $customerHistory['phones']; - } - - $customerAddress->phone = $phone['number']; - } - - if (isset($customerHistory['address']['index'])) { - $customerAddress->postcode = $customerHistory['address']['index']; - } - - if (self::loadInCMS($customerAddress, 'add') === false) { - continue; - } - } - $customerFix[] = array( 'id' => $customerHistory['id'], 'externalId' => $customer->id ); + + $customer->update(); + + if (isset($customerHistory['address'])) { + $address = $customerBuilder->getData()->getCustomerAddress(); + + $address->id_customer = $customer->id; + + if (self::loadInCMS($address, 'add') === false) { + continue; + } + } } } @@ -230,7 +163,6 @@ class RetailcrmHistory public static function ordersHistory() { $default_currency = (int) Configuration::get('PS_CURRENCY_DEFAULT'); - $default_country = (int) Configuration::get('PS_COUNTRY_DEFAULT'); $lastSync = Configuration::get('RETAILCRM_LAST_ORDERS_SYNC'); $lastDate = Configuration::get('RETAILCRM_LAST_SYNC'); @@ -279,6 +211,7 @@ class RetailcrmHistory $deliveryDefault = json_decode(Configuration::get('RETAILCRM_API_DELIVERY_DEFAULT'), true); $paymentDefault = json_decode(Configuration::get('RETAILCRM_API_PAYMENT_DEFAULT'), true); $orders = RetailcrmHistoryHelper::assemblyOrder($historyChanges); + RetailcrmLogger::writeDebugArray(__METHOD__, array('Assembled history:', $orders)); foreach ($orders as $order_history) { if (isset($order_history['deleted']) && $order_history['deleted'] == true) { @@ -374,8 +307,9 @@ class RetailcrmHistory } } + $address = new Address(); + $customer = null; $customerId = null; - $builtFromContact = false; if ($order['customer']['type'] == 'customer_corporate' && RetailcrmTools::isCorporateEnabled() @@ -387,126 +321,93 @@ class RetailcrmHistory } if (empty($customerId) && !empty($order['contact']['email'])) { - $customerData = Customer::getCustomersByEmail($order['contact']['email']); - $customerData = is_array($customerData) ? reset($customerData) : array(); + $customer = Customer::getCustomersByEmail($order['contact']['email']); + $customer = is_array($customer) ? reset($customer) : array(); - if (array_key_exists('id_customer', $customerData)) { - $customerId = $customerData['id_customer']; + if (array_key_exists('id_customer', $customer)) { + $customerId = $customer['id_customer']; } } } elseif (array_key_exists('externalId', $order['customer'])) { $customerId = Customer::customerIdExistsStatic($order['customer']['externalId']); + $customerBuilder = new RetailcrmCustomerBuilder(); + + $customerBuilder->setCustomer(new Customer($customerId)); } - if (empty($customerId)) { - $firstName = ''; - $lastName = ''; - $email = ''; + if (!empty($order['company']) && RetailcrmTools::isCorporateEnabled()) { + $corporateCustomerBuilder = new RetailcrmCorporateCustomerBuilder(); + $dataOrder = array_merge( + $order['contact'], + array('address' => $order['company']['address']) + ); - if ($order['customer']['type'] == 'customer_corporate') { - if (!empty($order['contact'])) { - $contact = $order['contact']; - $firstName = $contact['firstName']; - $lastName = !empty($contact['lastName']) ? $contact['lastName'] : '--'; - $email = - Validate::isEmail(isset($contact['email']) ? $contact['email'] : '') - ? $contact['email'] - : RetailcrmTools::createPlaceholderEmail($contact['firstName']); - $builtFromContact = true; - } elseif (!empty($order['customer']['nickName'])) { - $firstName = $order['customer']['nickName']; - $lastName = '--'; - $email = Validate::isEmail( - isset($order['customer']['email']) ? $order['customer']['email'] : '' + $corporateCustomerBuilder + ->setCustomer(new Customer($customerId)) + ->setDataCrm($dataOrder) + ->extractCompanyDataFromOrder($order) + ->build(); + + $customer = $corporateCustomerBuilder->getData()->getCustomer(); + $address = $corporateCustomerBuilder->getData()->getCustomerAddress(); + } else { + $customerBuilder = new RetailcrmCustomerBuilder(); + + $customerBuilder + ->setDataCrm($order['customer']) + ->build(); + + $customer = $customerBuilder->getData()->getCustomer(); + + if (!empty($address)) { + $address = $customerBuilder->getData()->getCustomerAddress(); + } + } + + if (!empty($customer->email)) { + $customer->id = self::getCustomerIdByEmail($customer->email); + } + + if (self::loadInCMS($customer, 'save') === false) { + continue; + } + + if (!empty($address)) { + $address->id_customer = $customer->id; + RetailcrmTools::assignAddressIdsByFields($customer, $address); + + if (empty($address->id)) { + RetailcrmLogger::writeDebug( + __METHOD__, + sprintf( + ' %s::%s', + $address->id_customer, + get_class($address), + 'add' ) - ? $order['customer']['email'] - : RetailcrmTools::createPlaceholderEmail($order['customer']['nickName']); + ); + + $address->add(); + + if (!empty($order['company']) && RetailcrmTools::isCorporateEnabled()) { + self::$api->customersCorporateAddressesEdit( + $order['customer']['id'], + $order['company']['address']['id'], + array_merge($order['company']['address'], array('externalId' => $address->id)), + 'id', + 'id' + ); + + time_nanosleep(0, 20000000); } } else { - $firstName = $order['customer']['firstName']; - $lastName = !empty($order['customer']['lastName']) - ? $order['customer']['lastName'] - : '--'; - $email = Validate::isEmail( - isset($order['customer']['email']) ? $order['customer']['email'] : '' - ) - ? $order['customer']['email'] - : RetailcrmTools::createPlaceholderEmail($order['customer']['firstName']); + RetailcrmLogger::writeDebug( + __METHOD__, + sprintf('<%d> %s::%s', $address->id, get_class($address), 'save') + ); + + $address->save(); } - - /** @var Customer|\CustomerCore $customer */ - $customer = new Customer(); - $customer->firstname = $firstName; - $customer->lastname = $lastName; - $customer->email = $email; - $customer->passwd = Tools::substr( - str_shuffle(Tools::strtolower(sha1(rand() . time()))), - 0, - 5 - ); - - if (self::loadInCMS($customer, 'add') === false) { - continue; - } - - array_push( - $customerFix, - array( - 'id' => $builtFromContact ? $order['contact']['id'] : $order['customer']['id'], - 'externalId' => $customer->id - ) - ); - } else { - /** @var Customer|\CustomerCore $customer */ - $customer = new Customer($customerId); - } - - /** @var Address|\AddressCore $address */ - $address = new Address(); - $address->id_customer = $customer->id; - $address->id_country = $default_country; - $address->lastname = $customer->lastname; - $address->firstname = $customer->firstname; - $address->alias = 'default'; - $address->postcode = isset($order['delivery']['address']['index']) ? $order['delivery']['address']['index'] : '--'; - $address->city = !empty($order['delivery']['address']['city']) ? - $order['delivery']['address']['city'] : '--'; - $address->address1 = !empty($order['delivery']['address']['text']) ? - $order['delivery']['address']['text'] : '--'; - $address->phone = isset($order['phone']) ? $order['phone'] : ''; - - if (!empty($order['company']) - && !empty($order['company']['contragent']) - && !empty($order['company']['contragent']['legalName']) - ) { - $address->company = $order['company']['contragent']['legalName']; - - if (!empty($order['company']['contragent']['INN'])) { - $address->vat_number = $order['company']['contragent']['INN']; - } - } - - static::assignAddressIdByFields($customer, $address); - - if (empty($address->id)) { - RetailcrmLogger::writeDebug( - __METHOD__, - sprintf( - ' %s::%s', - $address->id_customer, - get_class($address), - 'add' - ) - ); - - $address->add(); - } else { - RetailcrmLogger::writeDebug( - __METHOD__, - sprintf('<%d> %s::%s', $address->id, get_class($address), 'save') - ); - - $address->save(); } $cart = new Cart(); @@ -538,7 +439,6 @@ class RetailcrmHistory } $productId = explode('#', $item['offer']['externalId']); - $product = array(); $product['id_product'] = (int) $productId[0]; $product['id_product_attribute'] = !empty($productId[1]) ? $productId[1] : 0; @@ -576,13 +476,16 @@ class RetailcrmHistory $newOrder->id_currency = $default_currency; $newOrder->id_lang = self::$default_lang; $newOrder->id_customer = (int) $customer->id; + if (isset($deliveryType)) { $newOrder->id_carrier = (int) $deliveryType; } + if (isset($paymentType)) { $newOrder->payment = $paymentType; $newOrder->module = $paymentId; } + $newOrder->total_paid = $order['summ'] + $order['delivery']['cost']; $newOrder->total_paid_tax_incl = $order['summ'] + $order['delivery']['cost']; $newOrder->total_paid_tax_excl = $order['summ'] + $order['delivery']['cost']; @@ -593,6 +496,7 @@ class RetailcrmHistory $newOrder->total_shipping_tax_incl = $order['delivery']['cost']; $newOrder->total_shipping_tax_excl = $order['delivery']['cost']; $newOrder->conversion_rate = 1.000000; + if (isset($orderStatus)) { $newOrder->current_state = (int) $orderStatus; $newOrderHistoryRecord = new OrderHistory( @@ -601,9 +505,11 @@ class RetailcrmHistory Context::getContext()->shop->id ); } + if (!empty($order['delivery']['date'])) { $newOrder->delivery_date = $order['delivery']['date']; } + $newOrder->date_add = $order['createdAt']; $newOrder->date_upd = $order['createdAt']; $newOrder->invoice_date = $order['createdAt']; @@ -705,6 +611,7 @@ class RetailcrmHistory __METHOD__, sprintf('Error adding order id=%d: %s', $order['id'], $e->getMessage()) ); + RetailcrmLogger::writeNoCaller($e->getTraceAsString()); } @@ -716,6 +623,7 @@ class RetailcrmHistory ) { $ptype = $payment['type']; $ptypes = $references->getSystemPaymentModules(); + if ($payments[$ptype] != null) { foreach ($ptypes as $pay) { if ($pay['code'] == $payments[$ptype]) { @@ -763,7 +671,6 @@ class RetailcrmHistory ); $carrier->add(false, false); - /* * collect order ids for single fix request */ @@ -773,11 +680,13 @@ class RetailcrmHistory * Create order details */ $orderDetail = new OrderDetail(); + $orderDetail->createList($newOrder, $cart, $newOrder->current_state, $product_list); if (!empty($customerFix)) { self::$api->customersFixExternalIds($customerFix); } + if (!empty($orderFix)) { self::$api->ordersFixExternalIds($orderFix); } @@ -793,6 +702,38 @@ class RetailcrmHistory } $orderToUpdate = new Order((int) $order['externalId']); + self::handleCustomerDataChange($orderToUpdate, $order); + + /* + * check delivery changes + */ + if (isset($order['status']) && isset($order['delivery'])) { + if ($order['status'] == 'new') { + $customerData = self::$api->customersGet($orderToUpdate->id_customer, 'externalId'); + $customerForAddress = $customerData['customer']; + + $customerBuilder = new RetailcrmCustomerBuilder(); + + if (isset($customerData['customer']['address'])) { + $customerForAddress['address'] = array_replace( + $customerData['customer']['address'], + $order['delivery']['address'] + ); + } else { + $customerForAddress['address'] = $order['delivery']['address']; + } + + $customerBuilder + ->setDataCrm($customerForAddress) + ->build(); + + $address = $customerBuilder->getData()->getCustomerAddress(); + $address->id = $orderToUpdate->id_address_delivery; + + $address->update(); + $orderToUpdate->update(); + } + } /* * check delivery type @@ -946,6 +887,175 @@ class RetailcrmHistory } } + /** + * Returns retailCRM order by id or by externalId. + * It returns only order data, not ApiResponse or something. + * + * @param string $id Order identifier + * @param string $by Search field (default: 'externalId') + * + * @return array + */ + protected static function getCRMOrder($id, $by = 'externalId') + { + $crmOrderResponse = self::$api->ordersGet($id, $by); + + if (!empty($crmOrderResponse) + && $crmOrderResponse->isSuccessful() + && $crmOrderResponse->offsetExists('order') + ) { + return (array) $crmOrderResponse['order']; + } + + return array(); + } + + /** + * Sets all needed data for customer switch to switcher state + * + * @param array $crmCustomer + * @param \RetailcrmCustomerSwitcherState $data + * @param bool $isContact + */ + protected static function prepareChangeToIndividual($crmCustomer, $data, $isContact = false) + { + RetailcrmLogger::writeDebugArray( + __METHOD__, + array( + 'Using this individual person data in order to set it into order,', + $data->getOrder()->id, + ': ', + $crmCustomer + ) + ); + + if ($isContact) { + $data->setNewContact($crmCustomer); + } else { + $data->setNewCustomer($crmCustomer); + } + } + + /** + * Handle customer data change (from individual to corporate, company change, etc) + * + * @param \Order $order + * @param array $historyOrder + * + * @return bool True if customer change happened; false otherwise. + */ + private static function handleCustomerDataChange($order, $historyOrder) + { + $handled = false; + $crmOrder = array(); + $newCustomerId = null; + $switcher = new RetailcrmCustomerSwitcher(); + $data = new RetailcrmCustomerSwitcherState(); + $data->setOrder($order); + + RetailcrmLogger::writeDebug( + __METHOD__, + $historyOrder + ); + + if (isset($historyOrder['customer'])) { + $crmOrder = self::getCRMOrder($historyOrder['id'], 'id'); + + if (empty($crmOrder)) { + RetailcrmLogger::writeCaller(__METHOD__, sprintf( + 'Cannot get order data from retailCRM. Skipping customer change. History data: %s', + print_r($historyOrder, true) + )); + + return false; + } + + $newCustomerId = $historyOrder['customer']['id']; + $isChangedToRegular = RetailcrmTools::isCustomerChangedToRegular($historyOrder); + $isChangedToCorporate = RetailcrmTools::isCustomerChangedToLegal($historyOrder); + + if (!$isChangedToRegular && !$isChangedToCorporate) { + $isChangedToCorporate = RetailcrmTools::isCrmOrderCorporate($crmOrder); + $isChangedToRegular = !$isChangedToCorporate; + } + + if ($isChangedToRegular) { + self::prepareChangeToIndividual( + RetailcrmTools::arrayValue($crmOrder, 'customer', array()), + $data + ); + } + } + + if (isset($historyOrder['contact'])) { + $newCustomerId = $historyOrder['contact']['id']; + + if (empty($crmOrder)) { + $crmOrder = self::getCRMOrder($historyOrder['id'], 'id'); + } + + if (empty($crmOrder)) { + RetailcrmLogger::writeCaller(__METHOD__, sprintf( + 'Cannot get order data from retailCRM. Skipping customer change. History data: %s', + print_r($historyOrder, true) + )); + + return false; + } + + if (RetailcrmTools::isCrmOrderCorporate($crmOrder)) { + self::prepareChangeToIndividual( + RetailcrmTools::arrayValue($crmOrder, 'contact', array()), + $data, + true + ); + + $data->setNewCustomer(array()); + } + } + + if (isset($historyOrder['company'])) { + if (!empty($crmOrder['company'])) { + $data->setNewCompany($crmOrder['company']); + } else { + $data->setNewCompany($historyOrder['company']); + } + } + + if (!empty($crmOrder) && !empty($crmOrder['delivery']) && !empty($crmOrder['delivery']['address'])) { + $data->setCrmOrderShippingAddress($crmOrder['delivery']['address']); + } + + if ($data->feasible()) { + try { + $result = $switcher->setData($data) + ->build() + ->getResult(); + $result->save(); + $handled = true; + } catch (\Exception $exception) { + $errorMessage = sprintf( + 'Error switching order externalId=%s to customer id=%s (new company: id=%s %s). Reason: %s', + $historyOrder['externalId'], + $newCustomerId, + isset($historyOrder['company']) ? $historyOrder['company']['id'] : '', + isset($historyOrder['company']) ? $historyOrder['company']['name'] : '', + $exception->getMessage() + ); + RetailcrmLogger::writeCaller(__METHOD__, $errorMessage); + RetailcrmLogger::writeDebug(__METHOD__, sprintf( + '%s%s%s', + $errorMessage, + PHP_EOL, + $exception->getTraceAsString() + )); + $handled = false; + } + } + + return $handled; + } + /** * Updates items in order via history * @@ -1191,6 +1301,7 @@ class RetailcrmHistory ); $orderdb = new Order($orderDetail->id_order); + foreach ($orderdb->getProducts() as $item) { if (isset($item['product_attribute_id']) && $item['product_attribute_id'] > 0) { $productId = $item['product_id'] . '#' . $item['product_attribute_id']; @@ -1321,29 +1432,28 @@ class RetailcrmHistory */ private static function loadInCMS($object, $action) { - $prefix = $object->id; - - if (empty($object->id)) { - if (property_exists(get_class($object), 'id_customer')) { - $prefix = sprintf('Customer ID: %d', $object->id_customer); - } - - if (property_exists(get_class($object), 'id_order')) { - $prefix = sprintf('Order ID: %d', $object->id_order); - } - } - - RetailcrmLogger::writeDebug( - __METHOD__, - sprintf( - '<%s> %s::%s', - $prefix, - get_class($object), - $action - ) - ); - try { + $prefix = $object->id; + + if (empty($object->id)) { + if (property_exists(get_class($object), 'id_customer')) { + $prefix = sprintf('Customer ID: %d', $object->id_customer); + } + + if (property_exists(get_class($object), 'id_order')) { + $prefix = sprintf('Order ID: %d', $object->id_order); + } + } + + RetailcrmLogger::writeDebug( + __METHOD__, + sprintf( + '<%s> %s::%s', + $prefix, + get_class($object), + $action + ) + ); $object->$action(); } catch (PrestaShopException $e) { RetailcrmLogger::writeCaller( @@ -1432,42 +1542,25 @@ class RetailcrmHistory } /** - * Assign address ID from customer addresses + * Returns customer ID by email if such customer was found in the DB. * - * @param Customer|CustomerCore $customer - * @param Address|\AddressCore $address + * @param string $customerEmail + * + * @return int */ - private static function assignAddressIdByFields($customer, &$address) + private static function getCustomerIdByEmail($customerEmail) { - $checkMapping = array( - 'id_customer', - 'id_country', - 'lastname', - 'firstname', - 'alias', - 'postcode', - 'city', - 'address1', - 'phone', - 'company', - 'vat_number' - ); + if (!empty($customerEmail)) { + $item = Customer::getCustomersByEmail($customerEmail); - // Assigns id to $address if same address was found in customer - foreach ($customer->getAddresses(static::$default_lang) as $customerInnerAddress) { - /** @var Address|\AddressCore $customerAddress */ - $customerAddress = new Address($customerInnerAddress['id_address']); + if (is_array($item) && count($item) > 0) { + $item = reset($item); - foreach ($checkMapping as $field) { - if ($customerAddress->$field != $address->$field) { - continue 2; - } + return (int) $item['id_customer']; } - - $address->id = $customerInnerAddress['id_address']; - - break; } + + return 0; } /** @@ -1582,3 +1675,4 @@ class RetailcrmHistory } } } + diff --git a/retailcrm/lib/RetailcrmHistoryHelper.php b/retailcrm/lib/RetailcrmHistoryHelper.php index 14cf024..9377957 100644 --- a/retailcrm/lib/RetailcrmHistoryHelper.php +++ b/retailcrm/lib/RetailcrmHistoryHelper.php @@ -204,6 +204,11 @@ class RetailcrmHistoryHelper { $customers[$change['customer']['id']]['subscribed'] = true; } } + + // Sometimes address can be found in this key. + if (isset($change['address'])) { + $customers[$change['customer']['id']]['address'] = $change['address']; + } } return $customers; diff --git a/retailcrm/lib/RetailcrmJobManager.php b/retailcrm/lib/RetailcrmJobManager.php index 7aea05b..5d2cbf1 100644 --- a/retailcrm/lib/RetailcrmJobManager.php +++ b/retailcrm/lib/RetailcrmJobManager.php @@ -172,7 +172,9 @@ class RetailcrmJobManager $lastRuns[$job] = new \DateTime('now'); } } catch (\Exception $exception) { - if ($exception instanceof RetailcrmJobManagerException && !empty($exception->getPrevious())) { + if ($exception instanceof RetailcrmJobManagerException + && $exception->getPrevious() instanceof \Exception + ) { static::handleError( $exception->getPrevious()->getFile(), $exception->getPrevious()->getMessage(), @@ -189,10 +191,10 @@ class RetailcrmJobManager } self::clearCurrentJob($job); - } finally { - if (isset($result) && $result) { - self::clearCurrentJob($job); - } + } + + if (isset($result) && $result) { + self::clearCurrentJob($job); } } @@ -393,20 +395,25 @@ class RetailcrmJobManager } } + /** + * Wrapper for shutdown handler. Moved here in order to keep compatibility with older PHP versions. + */ + public static function shutdownHandlerWrapper() + { + $error = error_get_last(); + + if(null !== $error && $error['type'] === E_ERROR) { + self::defaultShutdownHandler($error); + } + } + /** * Register default shutdown handler (should be be called before any job execution) */ private static function registerShutdownHandler() { if (!self::$shutdownHandlerRegistered) { - register_shutdown_function(function() { - $error = error_get_last(); - - if(null !== $error && $error['type'] === E_ERROR) { - self::defaultShutdownHandler($error); - } - }); - + register_shutdown_function(array('RetailcrmJobManager', 'shutdownHandlerWrapper')); self::$shutdownHandlerRegistered = true; } } diff --git a/retailcrm/lib/RetailcrmLogger.php b/retailcrm/lib/RetailcrmLogger.php index 42188cd..edcb41b 100644 --- a/retailcrm/lib/RetailcrmLogger.php +++ b/retailcrm/lib/RetailcrmLogger.php @@ -139,6 +139,32 @@ class RetailcrmLogger } } + /** + * Debug log record with multiple entries + * + * @param string $caller + * @param array|string $messages + */ + public static function writeDebugArray($caller, $messages) + { + if (RetailcrmTools::isDebug()) { + if (!empty($caller) && !empty($messages)) { + $result = is_array($messages) ? substr( + array_reduce( + $messages, + function ($carry, $item) { + $carry .= ' ' . print_r($item, true); + return $carry; + } + ), + 1 + ) : $messages; + + self::writeDebug($caller, $result); + } + } + } + /** * Returns log file path * diff --git a/retailcrm/lib/RetailcrmTools.php b/retailcrm/lib/RetailcrmTools.php index f939629..102a69f 100644 --- a/retailcrm/lib/RetailcrmTools.php +++ b/retailcrm/lib/RetailcrmTools.php @@ -42,6 +42,21 @@ class RetailcrmTools */ static $currentStatusCode; + /** @var int */ + public static $default_lang; + + /** + * @return int + */ + public static function defaultLang() + { + if (empty(self::$default_lang)) { + self::$default_lang = (int) Configuration::get('PS_LANG_DEFAULT'); + } + + return self::$default_lang; + } + /** * Returns true if corporate customers are enabled in settings * @@ -59,7 +74,7 @@ class RetailcrmTools * * @return bool */ - public static function isCustomerCorporate(Customer $customer) + public static function isCustomerCorporate($customer) { $addresses = $customer->getAddresses((int)Configuration::get('PS_LANG_DEFAULT')); @@ -92,6 +107,38 @@ class RetailcrmTools return $address instanceof Address && !empty($address->company); } + /** + * Returns true if provided crm order is corporate + * + * @param array $order + * + * @return bool + */ + public static function isCrmOrderCorporate($order) + { + return isset($order['customer']['type']) && $order['customer']['type'] == 'customer_corporate'; + } + + /** + * Search address for individual customer (not corporate one) + * + * @param Customer|CustomerCore $customer + * + * @return int + */ + public static function searchIndividualAddress($customer) + { + if (!empty($customer->id)) { + foreach ($customer->getAddresses(self::defaultLang()) as $addressArray) { + if ($addressArray['alias'] == 'default') { + return (int) $addressArray['id_address']; + } + } + } + + return 0; + } + /** * Returns 'true' if provided date string is valid * @@ -132,6 +179,44 @@ class RetailcrmTools return $ids; } + /** + * Dumps entity using it's definition mapping. + * + * @param \ObjectModel $object + * + * @return array|string + */ + public static function dumpEntity($object) + { + if (empty($object)) { + ob_start(); + var_dump($object); + return (string) ob_get_clean(); + } + + $data = array(); + $type = get_class($object); + + if (property_exists($type, 'definition')) { + $defs = $type::$definition; + + if (!empty($defs['fields'])) { + if (property_exists($object, 'id')) { + $data['id'] = $object->id; + } + + foreach (array_keys($defs['fields']) as $field) { + if (property_exists($object, $field)) { + $data[$field] = $object->$field; + } + } + + } + } + + return $data; + } + /** * Converts CMS address to CRM address * @@ -466,6 +551,78 @@ class RetailcrmTools return $code; } + /** + * This assertion returns true if customer was changed from legal entity to individual person. + * It doesn't return true if customer was changed from one individual person to another. + * + * @param array $assembledOrder Order data, assembled from history + * + * @return bool True if customer in order was changed from corporate to regular + */ + public static function isCustomerChangedToRegular($assembledOrder) + { + return isset($assembledOrder['contragentType']) && $assembledOrder['contragentType'] == 'individual'; + } + + /** + * This assertion returns true if customer was changed from individual person to a legal entity. + * It doesn't return true if customer was changed from one legal entity to another. + * + * @param array $assembledOrder Order data, assembled from history + * + * @return bool True if customer in order was changed from corporate to regular + */ + public static function isCustomerChangedToLegal($assembledOrder) + { + return isset($assembledOrder['contragentType']) && $assembledOrder['contragentType'] == 'legal-entity'; + } + + /** + * Get value by key from array if it exists, returns default value otherwise. + * + * @param array|\ArrayObject|\ArrayAccess $arr + * @param string $key + * @param string $def + * + * @return mixed|string + */ + public static function arrayValue($arr, $key, $def = '') + { + if (!is_array($arr) && !($arr instanceof ArrayObject) && !($arr instanceof ArrayAccess)) { + return $def; + } + + if (!array_key_exists($key, $arr) && !empty($arr[$key])) { + return $def; + } + + return isset($arr[$key]) ? $arr[$key] : $def; + } + + /** + * Assign address ID and customer ID from customer addresses. + * Customer ID in the address isn't checked (it will be set to id from provided customer, even if it doesn't have ID yet). + * + * @param Customer|CustomerCore $customer + * @param Address|\AddressCore $address + */ + public static function assignAddressIdsByFields($customer, $address) + { + RetailcrmLogger::writeDebugArray(__METHOD__, array('Called with customer', $customer->id, 'and address', self::dumpEntity($address))); + + foreach ($customer->getAddresses(self::defaultLang()) as $customerInnerAddress) { + $customerAddress = new Address($customerInnerAddress['id_address']); + + if (self::isAddressesEqualByFields($address, $customerAddress)) { + if ($address->id_customer != $customerAddress->id_customer) { + $address->id_customer = $customerAddress->id_customer; + } + + $address->id = $customerAddress->id; + } + } + } + /** * Starts JobManager with list of pre-registered jobs * @@ -480,4 +637,47 @@ class RetailcrmTools 'RetailcrmInventoriesEvent' => new \DateInterval('PT15M') )); } + + /** + * Returns true if mapped fields in address are equal. Returns false otherwise. + * + * @param \Address $first + * @param \Address $second + * + * @return bool + */ + protected static function isAddressesEqualByFields($first, $second) + { + $equal = true; + $checkMapping = array( + 'alias', + 'id_country', + 'lastname', + 'firstname', + 'postcode', + 'city', + 'address1', + 'phone', + 'company', + 'vat_number' + ); + + foreach ($checkMapping as $field) { + if ($first->$field != $second->$field) { + $equal = false; + RetailcrmLogger::writeDebugArray(__METHOD__, array( + 'first' => self::dumpEntity($first), + 'second' => self::dumpEntity($second), + 'field' => array( + 'name' => $field, + 'firstValue' => $first->$field, + 'secondValue' => $second->$field + ) + )); + break; + } + } + + return $equal; + } } diff --git a/retailcrm/lib/models/RetailcrmCustomerSwitcherResult.php b/retailcrm/lib/models/RetailcrmCustomerSwitcherResult.php new file mode 100644 index 0000000..13bd722 --- /dev/null +++ b/retailcrm/lib/models/RetailcrmCustomerSwitcherResult.php @@ -0,0 +1,142 @@ + + * @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. + */ +class RetailcrmCustomerSwitcherResult +{ + /** @var \Customer */ + private $customer; + + /** @var \Address */ + private $address; + + /** @var \Order $order */ + private $order; + + /** + * RetailcrmCustomerSwitcherResult constructor. + * + * @param \Customer $customer + * @param \Address $address + * @param \Order $order + */ + public function __construct($customer, $address, $order) + { + $this->customer = $customer; + $this->order = $order; + $this->address = $address; + + if (!($this->customer instanceof Customer) || !($this->order instanceof Order)) { + throw new \InvalidArgumentException(sprintf('Incorrect data provided to %s', __CLASS__)); + } + } + + /** + * @return \Customer + */ + public function getCustomer() + { + return $this->customer; + } + + /** + * @return \Address + */ + public function getAddress() + { + return $this->address; + } + + /** + * @param \Address $address + * + * @return RetailcrmCustomerSwitcherResult + */ + public function setAddress($address) + { + $this->address = $address; + return $this; + } + + /** + * @return \Order + */ + public function getOrder() + { + return $this->order; + } + + /** + * Save customer (if exists) and order. + * + * @return $this + * @throws \PrestaShopException + */ + public function save() + { + RetailcrmLogger::writeDebugArray( + __METHOD__, + array( + 'Saving customer, address and order:', + array( + 'customer' => RetailcrmTools::dumpEntity($this->customer), + 'address' => RetailcrmTools::dumpEntity($this->address), + 'order' => RetailcrmTools::dumpEntity($this->order) + ) + ) + ); + + if (!empty($this->customer)) { + $this->customer->save(); + + if (!empty($this->address) && !empty($this->customer->id)) { + $this->address->id_customer = $this->customer->id; + $this->address->save(); + + if (!empty($this->order) && !empty($this->address->id)) { + $this->order->id_customer = $this->customer->id; + $this->order->id_address_invoice = $this->address->id; + $this->order->id_address_delivery = $this->address->id; + $this->order->save(); + } else { + $this->address->delete(); + } + } + } + + return $this; + } +} diff --git a/retailcrm/lib/models/RetailcrmCustomerSwitcherState.php b/retailcrm/lib/models/RetailcrmCustomerSwitcherState.php new file mode 100644 index 0000000..cbb677d --- /dev/null +++ b/retailcrm/lib/models/RetailcrmCustomerSwitcherState.php @@ -0,0 +1,231 @@ + + * @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. + */ +class RetailcrmCustomerSwitcherState +{ + /** @var \Order $order */ + private $order; + + /** @var array */ + private $newCustomer; + + /** @var array */ + private $newContact; + + /** @var string $newCompanyName */ + private $newCompanyName; + + /** @var array $companyAddress */ + private $companyAddress; + + /** @var array $crmOrderShippingAddress */ + private $crmOrderShippingAddress; + + /** + * @return \Order + */ + public function getOrder() + { + return $this->order; + } + + /** + * @param \Order $order + * + * @return RetailcrmCustomerSwitcherState + */ + public function setOrder($order) + { + $this->order = $order; + return $this; + } + + /** + * @return array + */ + public function getNewCustomer() + { + return $this->newCustomer; + } + + /** + * @param array $newCustomer + * + * @return RetailcrmCustomerSwitcherState + */ + public function setNewCustomer($newCustomer) + { + $this->newCustomer = $newCustomer; + return $this; + } + + /** + * @return array + */ + public function getNewContact() + { + return $this->newContact; + } + + /** + * @param array $newContact + * + * @return RetailcrmCustomerSwitcherState + */ + public function setNewContact($newContact) + { + $this->newContact = $newContact; + return $this; + } + + /** + * @return string + */ + public function getNewCompanyName() + { + return $this->newCompanyName; + } + + /** + * @param string $newCompanyName + * + * @return RetailcrmCustomerSwitcherState + */ + public function setNewCompanyName($newCompanyName) + { + $this->newCompanyName = $newCompanyName; + return $this; + } + + /** + * @return array + */ + public function getCompanyAddress() + { + return $this->companyAddress; + } + + /** + * @param array $companyAddress + * + * @return RetailcrmCustomerSwitcherState + */ + public function setCompanyAddress($companyAddress) + { + $this->companyAddress = $companyAddress; + return $this; + } + + /** + * @param array $newCompany + * + * @return RetailcrmCustomerSwitcherState + */ + public function setNewCompany($newCompany) + { + if (isset($newCompany['name'])) { + $this->setNewCompanyName($newCompany['name']); + } + + if (isset($newCompany['address']) && !empty($newCompany['address'])) { + $this->setCompanyAddress($newCompany['address']); + } + + return $this; + } + + /** + * @return array + */ + public function getCrmOrderShippingAddress() + { + return $this->crmOrderShippingAddress; + } + + /** + * @param array $crmOrderShippingAddress + * + * @return RetailcrmCustomerSwitcherState + */ + public function setCrmOrderShippingAddress($crmOrderShippingAddress) + { + $this->crmOrderShippingAddress = $crmOrderShippingAddress; + return $this; + } + + /** + * Returns true if current state may be processable (e.g. when customer or related data was changed). + * It doesn't guarantee state validity. + * + * @return bool + */ + public function feasible() + { + return !(empty($this->newCustomer) && empty($this->newContact) && empty($this->newCompanyName)); + } + + /** + * Throws an exception if state is not valid + * + * @throws \InvalidArgumentException + * @return void + */ + public function validate() + { + if (empty($this->order)) { + throw new \InvalidArgumentException('Empty Order.'); + } + + if (empty($this->newCustomer) && empty($this->newContact) && empty($this->newCompanyName)) { + throw new \InvalidArgumentException('New customer, new contact and new company is empty.'); + } + + if (!empty($this->newCustomer) && !empty($this->newContact)) { + RetailcrmLogger::writeDebugArray( + __METHOD__, + array( + 'State data (customer and contact):' . PHP_EOL, + $this->getNewCustomer(), + $this->getNewContact() + ) + ); + throw new \InvalidArgumentException( + 'Too much data in state - cannot determine which customer should be used.' + ); + } + } +} diff --git a/retailcrm/lib/models/index.php b/retailcrm/lib/models/index.php new file mode 100644 index 0000000..6a1c957 --- /dev/null +++ b/retailcrm/lib/models/index.php @@ -0,0 +1,8 @@ +orderMethod site status + customer manager firstName lastName @@ -75,6 +76,8 @@ managerComment shipmentStore shipmentDate + contact + company shipped diff --git a/tests/RetailcrmCorporateCustomerBuilderTest.php b/tests/RetailcrmCorporateCustomerBuilderTest.php new file mode 100644 index 0000000..83365e0 --- /dev/null +++ b/tests/RetailcrmCorporateCustomerBuilderTest.php @@ -0,0 +1,144 @@ +corporateCustomer = new RetailcrmCorporateCustomerBuilder(); + + $this->corporateCustomer + ->setDataCrm($this->getDataCrm()) + ->build(); + + $result = new RetailcrmCustomerBuilderResult(null, null); + + $this->assertInstanceOf(get_class($result), $this->corporateCustomer->getData()); + } + + public function testGetData() + { + $this->corporateCustomer = new RetailcrmCorporateCustomerBuilder(); + + $this->corporateCustomer + ->setDataCrm($this->getDataCrm()) + ->build(); + + $builtCustomer = $this->corporateCustomer->getData()->getCustomer(); + $builtAddress = $this->corporateCustomer->getData()->getCustomerAddress(); + + $this->assertTrue($builtCustomer instanceof Customer + || $builtCustomer instanceof CustomerCore); + + $this->assertTrue($builtAddress instanceof Address + || $builtAddress instanceof AddressCore); + } + + public function testCorrectDataCorporateCustomer() + { + $this->corporateCustomer = new RetailcrmCorporateCustomerBuilder(); + + $this->corporateCustomer + ->setDataCrm($this->getDataCrm()) + ->setCustomer($this->getDataBuilder()) + ->setCompanyName('Test') + ->setCompanyInn(5666) + ->build(); + + $customerResult = $this->corporateCustomer->getData()->getCustomer(); + $this->assertEquals('April', $customerResult->firstname); + $this->assertEquals('Iphone', $customerResult->lastname); + $this->assertEquals(false, $customerResult->newsletter); + $this->assertEquals('1997-04-09', $customerResult->birthday); + $this->assertEquals(2, $customerResult->id_gender); + $this->assertEquals('hello@world.ru', $customerResult->email); + + $addressResult = $this->corporateCustomer->getData()->getCustomerAddress(); + + $this->assertEquals(Country::getByIso('RU'), $addressResult->id_country); + $this->assertEquals('г. Москва', $addressResult->city); + $this->assertEquals('Test', $addressResult->company); + $this->assertEquals(5666, $addressResult->vat_number); + } + + private function getDataBuilder() { + return array( + 'type' => 'customer_corporate', + 'id' => 9090, + 'nickName' => 'TestName', + 'mainAddress' => array( + 'id' => 4001, + 'name' => 'Test' + ), + 'createdAt' => '2020-02-17 07:44:31', + 'vip' => false, + 'bad' => false, + 'site' => 'opencart', + 'tags' => array(), + 'marginSumm' => 0, + 'totalSumm' => 0, + 'averageSumm' => 0, + 'ordersCount' => 0, + 'costSumm' => 0, + 'customFields' => array(), + 'personalDiscount' => 0, + 'mainCustomerContact' => array( + 'id' => 37, + 'customer' => array( + 'id' => 9089 + ), + 'companies' => array() + ), + 'mainCompany' => array( + 'id' => 34, + 'name' => 'Test', + 'contragentInn' => 5666 + ) + ); + } + + private function getDataCrm() { + return array( + 'type' => 'customer', + 'id' => 9000, + 'externalId' => '1777754', + 'isContact' => false, + 'createdAt' => '2020-04-09 16:55:59', + 'vip' => true, + 'bad' => true, + 'site' => '127-0-0-1-8080', + 'contragent' => array( + 'contragentType' => 'individual' + ), + 'tags' => array(), + 'marginSumm' => 0, + 'totalSumm' => 0, + 'averageSumm' => 0, + 'ordersCount' => 0, + 'costSumm' => 0, + 'customFields' => array(), + 'personalDiscount' => 0, + 'address' => array( + 'id' => 9718, + 'countryIso' => 'RU', + 'region' => 'Moscow', + 'city' => 'г. Москва', + 'index' => '344004', + 'text' => 'MAY' + ), + 'segments' => array(), + 'firstName' => 'April', + 'lastName' => 'Iphone', + 'email' => 'hello@world.ru', + 'sex' => 'female', + 'birthday' =>'1997-04-09' + ); + } +} + diff --git a/tests/RetailcrmCustomerAddressBuilderTest.php b/tests/RetailcrmCustomerAddressBuilderTest.php new file mode 100644 index 0000000..c660099 --- /dev/null +++ b/tests/RetailcrmCustomerAddressBuilderTest.php @@ -0,0 +1,67 @@ +customerAddress = new RetailcrmCustomerAddressBuilder(); + + $this->customerAddress + ->setDataCrm($this->getDataBuilder()) + ->build(); + + $this->assertNotEmpty($this->customerAddress->getData()); + } + + public function setCustomerAddress() + { + $this->customerAddress = new RetailcrmCustomerAddressBuilder(); + + $this->customerAddress + ->setCustomerAddress(new AddressCore(9999)) + ->build(); + + $this->assertNotEmpty($this->customerAddress->getData()); + } + + public function testCorrectDataAddress() + { + $this->customerAddress = new RetailcrmCustomerAddressBuilder(); + + $this->customerAddress + ->setDataCrm($this->getDataBuilder()) + ->setFirstName('Test') + ->setLastName('Test2') + ->setPhone('+7999999999') + ->build(); + + $addressResult = $this->customerAddress->getData(); + $this->assertEquals('Test', $addressResult->firstname); + $this->assertEquals('Test2', $addressResult->lastname); + $this->assertEquals(Country::getByIso('RU'), $addressResult->id_country); + $this->assertEquals('г. Москва', $addressResult->city); + $this->assertEquals(State::getIdByName('Moscow'), $addressResult->id_state); + $this->assertEquals('344004', $addressResult->postcode); + $this->assertEquals('+7999999999', $addressResult->phone); + } + + private function getDataBuilder() { + return array( + 'id' => 9718, + 'countryIso' => 'RU', + 'region' => 'Moscow', + 'city' => 'г. Москва', + 'index' => '344004', + 'text' => 'MAY' + ); + } +} + diff --git a/tests/RetailcrmCustomerBuilderTest.php b/tests/RetailcrmCustomerBuilderTest.php new file mode 100644 index 0000000..aad9acb --- /dev/null +++ b/tests/RetailcrmCustomerBuilderTest.php @@ -0,0 +1,124 @@ +customer = new RetailcrmCustomerBuilder(); + + $this->customer + ->setDataCrm($this->getDataCrm()) + ->build(); + + $result = new RetailcrmCustomerBuilderResult(null, null); + + $this->assertInstanceOf(get_class($result), $this->customer->getData()); + } + + public function testSetCustomer() + { + $this->customer = new RetailcrmCustomerBuilder(); + + $this->customer + ->setCustomer(new Customer(9719)) + ->build(); + + $this->assertNotEmpty($this->customer->getData()); + } + + public function testGetData() + { + $this->customer = new RetailcrmCustomerBuilder(); + + $this->customer + ->setDataCrm($this->getDataCrm()) + ->build(); + + $builtCustomer = $this->customer->getData()->getCustomer(); + $builtAddress = $this->customer->getData()->getCustomerAddress(); + + $this->assertTrue($builtCustomer instanceof Customer + || $builtCustomer instanceof CustomerCore); + + $this->assertTrue($builtAddress instanceof Address + || $builtAddress instanceof AddressCore); + } + + public function testBuildAddress() + { + $this->customer = new RetailcrmCustomerBuilder(); + + $this->customer->buildAddress(); + + $this->assertEquals(null, $this->customer->getData()->getCustomerAddress()); + } + + public function testCorrectDataCustomer() + { + $this->customer = new RetailcrmCustomerBuilder(); + + $this->customer + ->setDataCrm($this->getDataCrm()) + ->build(); + + $customerResult = $this->customer->getData()->getCustomer(); + + $this->assertEquals('April', $customerResult->firstname); + $this->assertEquals('Iphone', $customerResult->lastname); + $this->assertEquals(false, $customerResult->newsletter); + $this->assertEquals('1997-04-09', $customerResult->birthday); + $this->assertEquals(2, $customerResult->id_gender); + $this->assertEquals('hello@world.ru', $customerResult->email); + + $addressResult = $this->customer->getData()->getCustomerAddress(); + + $this->assertEquals(Country::getByIso('RU'), $addressResult->id_country); + $this->assertEquals('г. Москва', $addressResult->city); + } + + private function getDataCrm() { + return array( + 'type' => 'customer', + 'id' => 9000, + 'externalId' => '1777754', + 'isContact' => false, + 'createdAt' => '2020-04-09 16:55:59', + 'vip' => true, + 'bad' => true, + 'site' => '127-0-0-1-8080', + 'contragent' => array( + 'contragentType' => 'individual' + ), + 'tags' => array(), + 'marginSumm' => 0, + 'totalSumm' => 0, + 'averageSumm' => 0, + 'ordersCount' => 0, + 'costSumm' => 0, + 'customFields' => array(), + 'personalDiscount' => 0, + 'address' => array( + 'id' => 9718, + 'countryIso' => 'RU', + 'region' => 'Moscow', + 'city' => 'г. Москва', + 'index' => '344004', + 'text' => 'MAY' + ), + 'segments' => array(), + 'firstName' => 'April', + 'lastName' => 'Iphone', + 'email' => 'hello@world.ru', + 'sex' => 'female', + 'birthday' =>'1997-04-09' + ); + } +} + diff --git a/tests/helpers/RetailcrmTestHelper.php b/tests/helpers/RetailcrmTestHelper.php index d24cbe5..7143e3e 100644 --- a/tests/helpers/RetailcrmTestHelper.php +++ b/tests/helpers/RetailcrmTestHelper.php @@ -30,4 +30,11 @@ class RetailcrmTestHelper 'SELECT MAX(id_order) FROM `' . _DB_PREFIX_ . 'orders`' ); } + + public static function getMaxCustomerId() + { + return Db::getInstance()->getValue( + 'SELECT MAX(id_customer) FROM `' . _DB_PREFIX_ . 'customer`' + ); + } } diff --git a/tests/lib/RetailcrmHistoryTest.php b/tests/lib/RetailcrmHistoryTest.php index b8191ea..0c885ce 100644 --- a/tests/lib/RetailcrmHistoryTest.php +++ b/tests/lib/RetailcrmHistoryTest.php @@ -16,8 +16,10 @@ class RetailcrmHistoryTest extends RetailcrmTestCase 'customersHistory', 'ordersHistory', 'ordersGet', + 'customersGet', 'customersFixExternalIds', - 'ordersFixExternalIds' + 'ordersFixExternalIds', + 'customersCorporateAddressesEdit' ) ) ->getMock(); @@ -32,7 +34,75 @@ class RetailcrmHistoryTest extends RetailcrmTestCase $this->setConfig(); } - public function testOrderCreate() + public function testCustomersHistory() + { + $this->apiMock->expects($this->any()) + ->method('customersHistory') + ->willReturn( + new RetailcrmApiResponse( + '200', + json_encode( + $this->getHistoryDataNewCustomer() + ) + ) + ); + + RetailcrmHistory::$default_lang = (int)Configuration::get('PS_LANG_DEFAULT'); + RetailcrmHistory::$api = $this->apiMock; + + $externalId = isset($this->getApiCustomer()['externalId']) ? $this->getApiCustomer()['externalId'] : null; + + if (!empty($externalId)) + { + $oldCustomer = new Customer($externalId); + RetailcrmHistory::customersHistory(); + $newCustomer = new Customer($externalId); + + $this->assertNotEquals($oldCustomer, $newCustomer); + } else { + $oldLastId = RetailcrmTestHelper::getMaxCustomerId(); + RetailcrmHistory::customersHistory(); + $newLastId = RetailcrmTestHelper::getMaxCustomerId(); + + $this->assertTrue($newLastId > $oldLastId); + } + + $this->assertEquals(true, RetailcrmHistory::customersHistory()); + } + + public function orderCreate($apiMock) + { + RetailcrmHistory::$default_lang = (int)Configuration::get('PS_LANG_DEFAULT'); + RetailcrmHistory::$api = $apiMock; + + $oldLastId = RetailcrmTestHelper::getMaxOrderId(); + RetailcrmHistory::ordersHistory(); + $newLastId = RetailcrmTestHelper::getMaxOrderId(); + + $this->assertTrue($newLastId > $oldLastId); + + $order = new Order($newLastId); + + $this->assertInstanceOf('Order', $order); + } + + public function switchCustomer() + { + RetailcrmHistory::$default_lang = (int)Configuration::get('PS_LANG_DEFAULT'); + RetailcrmHistory::$api = $this->apiMock; + + $history = $this->getHistoryExistOrder(); + + $newId = $history['history'][0]['newValue']['externalId']; + + RetailcrmHistory::ordersHistory(); + + $order = new Order((int) $history['history'][0]['order']['externalId']); + + $this->assertTrue($newId == $order->id_customer); + } + + public function testOrderSwitchCustomer() { $this->apiMock->expects($this->any()) ->method('ordersHistory') @@ -40,10 +110,11 @@ class RetailcrmHistoryTest extends RetailcrmTestCase new RetailcrmApiResponse( '200', json_encode( - $this->getHistoryDataNewOrder() + $this->getHistoryExistOrder() ) ) ); + $this->apiMock->expects($this->any()) ->method('ordersGet') ->willReturn( @@ -57,19 +128,95 @@ class RetailcrmHistoryTest extends RetailcrmTestCase ) ); - RetailcrmHistory::$default_lang = (int)Configuration::get('PS_LANG_DEFAULT'); - RetailcrmHistory::$api = $this->apiMock; + $this->switchCustomer(); + } - $oldLastId = RetailcrmTestHelper::getMaxOrderId(); - RetailcrmHistory::ordersHistory(); - $newLastId = RetailcrmTestHelper::getMaxOrderId(); - $this->assertNotEquals($oldLastId, $newLastId); - $this->assertTrue($newLastId > $oldLastId); + public function testOrderSwitchCorporateCustomer() + { + $this->apiMock->expects($this->any()) + ->method('ordersHistory') + ->willReturn( + new RetailcrmApiResponse( + '200', + json_encode( + $this->getHistoryExistOrder() + ) + ) + ); - $order = new Order($newLastId); + $this->apiMock->expects($this->any()) + ->method('ordersGet') + ->willReturn( + new RetailcrmApiResponse( + '200', + json_encode( + array( + 'order' => $this->getApiOrderWitchCorporateCustomer() + ) + ) + ) + ); - $this->assertInstanceOf('Order', $order); + $this->switchCustomer(); + } + + public function testOrderCreate() + { + $this->apiMock->expects($this->any()) + ->method('ordersHistory') + ->willReturn( + new RetailcrmApiResponse( + '200', + json_encode( + $this->getHistoryDataNewOrder($this->getApiOrder()) + ) + ) + ); + + $this->apiMock->expects($this->any()) + ->method('ordersGet') + ->willReturn( + new RetailcrmApiResponse( + '200', + json_encode( + array( + 'order' => $this->getApiOrder() + ) + ) + ) + ); + + $this->orderCreate($this->apiMock); + } + + public function testOrderCreateWithCorporateCustomer() + { + $this->apiMock->expects($this->any()) + ->method('ordersHistory') + ->willReturn( + new RetailcrmApiResponse( + '200', + json_encode( + $this->getHistoryDataNewOrder($this->getApiOrderWitchCorporateCustomer()) + ) + ) + ); + + $this->apiMock->expects($this->any()) + ->method('ordersGet') + ->willReturn( + new RetailcrmApiResponse( + '200', + json_encode( + array( + 'order' => $this->getApiOrderWitchCorporateCustomer() + ) + ) + ) + ); + + $this->orderCreate($this->apiMock); } public function testPaymentStatusUpdate() @@ -93,7 +240,45 @@ class RetailcrmHistoryTest extends RetailcrmTestCase RetailcrmHistory::ordersHistory(); } - private function getHistoryDataNewOrder() + private function getHistoryExistOrder() + { + return array( + 'success' => true, + 'history' => array( + array( + 'id' => 19752, + 'createdAt' => '2018-01-01 00:00:00', + 'source' => 'api', + 'field' => 'customer', + 'apiKey' => array('current' => false), + 'oldValue' => array( + 'id' => 7778, + 'externalId' => '1', + 'site' => '127.0.0.1:8000' + ), + 'newValue' => array( + 'id' => 7777, + 'externalId' => '777', + 'site' => '127.0.0.1:8000' + ), + 'order' => array( + 'id' => 6025, + 'externalId' => '1', + 'site' => '127.0.0.1:8000', + 'status' => 'new' + ) + ) + ), + 'pagination' => array( + 'limit' => 20, + 'totalCount' => 1, + 'currentPage' => 1, + 'totalPageCount' => 1 + ) + ); + } + + private function getHistoryDataNewOrder($orderData) { return array( 'success' => true, @@ -111,14 +296,14 @@ class RetailcrmHistoryTest extends RetailcrmTestCase 'newValue' => array( 'code' => 'new' ), - 'order' => $this->getApiOrder() + 'order' => $orderData ) ), - "pagination" => array( - "limit" => 20, - "totalCount" => 1, - "currentPage" => 1, - "totalPageCount" => 1 + 'pagination' => array( + 'limit' => 20, + 'totalCount' => 1, + 'currentPage' => 1, + 'totalPageCount' => 1 ) ); } @@ -147,6 +332,7 @@ class RetailcrmHistoryTest extends RetailcrmTestCase 'customer' => array( 'segments' => array(), 'id' => 1, + 'externalId' => '777', 'type' => 'customer', 'firstName' => 'Test', 'lastName' => 'Test', @@ -160,6 +346,7 @@ class RetailcrmHistoryTest extends RetailcrmTestCase ) ), 'address' => array( + 'id_customer' => 2222, 'index' => '111111', 'countryIso' => 'RU', 'region' => 'Test region', @@ -191,6 +378,7 @@ class RetailcrmHistoryTest extends RetailcrmTestCase 'cost' => 100, 'netCost' => 0, 'address' => array( + 'id_customer' => 2222, 'index' => '111111', 'countryIso' => 'RU', 'region' => 'Test region', @@ -238,15 +426,159 @@ class RetailcrmHistoryTest extends RetailcrmTestCase return $order; } + private function getApiOrderWitchCorporateCustomer() + { + $orderWithCorporateCustomer = array( + 'slug' => 1, + 'id' => 2, + 'number' => '1C', + 'orderType' => 'eshop-individual', + 'orderMethod' => 'phone', + 'countryIso' => 'RU', + 'createdAt' => '2018-01-01 00:00:00', + 'statusUpdatedAt' => '2018-01-01 00:00:00', + 'summ' => 100, + 'totalSumm' => 100, + 'prepaySum' => 0, + 'purchaseSumm' => 50, + 'markDatetime' => '2018-01-01 00:00:00', + 'firstName' => 'Test', + 'lastName' => 'Test', + 'phone' => '80000000000', + 'call' => false, + 'expired' => false, + 'customer' => array( + 'segments' => array(), + 'id' => 1, + 'externalId' => '777', + 'type' => 'customer_corporate', + 'firstName' => 'Test', + 'lastName' => 'Test', + 'email' => 'email@test.ru', + 'phones' => array( + array( + 'number' => '111111111111111' + ), + array( + 'number' => '+7111111111' + ) + ), + 'address' => array( + 'id' => 2345, + 'id_customer' => 2222, + 'index' => '111111', + 'countryIso' => 'RU', + 'region' => 'Test region', + 'city' => 'Test', + 'text' => 'Test text address' + ), + 'createdAt' => '2018-01-01 00:00:00', + 'managerId' => 1, + 'vip' => false, + 'bad' => false, + 'site' => 'test-com', + 'contragent' => array( + 'contragentType' => 'individual' + ), + 'personalDiscount' => 0, + 'cumulativeDiscount' => 0, + 'marginSumm' => 58654, + 'totalSumm' => 61549, + 'averageSumm' => 15387.25, + 'ordersCount' => 4, + 'costSumm' => 101, + 'customFields' => array( + 'custom' => 'test' + ) + ), + 'contact' => array( + 'id' => 1, + 'externalId' => '7777', + 'type' => 'customer_corporate', + 'managerId' => 23, + 'isContact' => true, + 'vip' => false, + 'bad' => false, + ), + 'contragent' => array(), + 'delivery' => array( + 'code' => 'delivery', + 'cost' => 100, + 'netCost' => 0, + 'address' => array( + 'id_customer' => 2222, + 'index' => '111111', + 'countryIso' => 'RU', + 'region' => 'Test region', + 'city' => 'Test', + 'text' => 'Test text address' + ) + ), + 'company' => array( + 'id' => 7777, + 'contragent' => array( + 'legalName' => 'test', + 'INN' => '255222' + ), + 'address' => array( + 'id' => 1, + 'id_customer' => 2222, + 'index' => '111111', + 'countryIso' => 'RU', + 'region' => 'Test region', + 'city' => 'Test', + 'text' => 'Test text address' + ) + ), + 'site' => 'test-com', + 'status' => 'new', + 'items' => array( + array( + 'id' => 160, + 'initialPrice' => 100, + 'createdAt' => '2018-01-01 00:00:00', + 'quantity' => 1, + 'status' => 'new', + 'offer' => array( + 'id' => 1, + 'externalId' => $this->product['id'], + 'xmlId' => '1', + 'name' => 'Test name', + 'vatRate' => 'none' + ), + 'properties' => array(), + 'purchasePrice' => 50 + ), + array_merge(RetailcrmOrderBuilder::getGiftItem(10), array('id' => 25919)) + ), + 'fromApi' => false, + 'length' => 0, + 'width' => 0, + 'height' => 0, + 'shipmentStore' => 'main', + 'shipped' => false, + 'customFields' => array(), + 'uploadedToExternalStoreSystem' => false + ); + + $orderWithCorporateCustomer['payments'][] = array( + 'id' => 97, + 'type' => 'cheque', + 'amount' => 210 + ); + + return $orderWithCorporateCustomer; + } + private function getUpdatePaymentStatus($orderId) { return array( 'success' => true, - "pagination" => array( - "limit" => 20, - "totalCount" => 1, - "currentPage" => 1, - "totalPageCount" => 1 + 'pagination' => array( + 'limit' => 20, + 'totalCount' => 1, + 'currentPage' => 1, + 'totalPageCount' => 1 ), 'history' => array( array( @@ -317,4 +649,72 @@ class RetailcrmHistoryTest extends RetailcrmTestCase ) ); } + + private function getHistoryDataNewCustomer() + { + return array( + 'success' => true, + 'history' => array( + array( + 'id' => 1, + 'createdAt' => '2018-01-01 00:00:00', + 'created' => true, + 'source' => 'api', + 'field' => 'id', + 'oldValue' => null, + 'newValue' => 4949, + 'customer' => $this->getApiCustomer() + ) + ), + 'pagination' => array( + 'limit' => 20, + 'totalCount' => 1, + 'currentPage' => 1, + 'totalPageCount' => 1 + ) + ); + } + + private function getApiCustomer() + { + return array( + 'type' => 'customer', + 'id' => 1, + 'externalId' => '1', + 'isContact' => false, + 'createdAt' => '2020-05-08 03:00:38', + 'vip' => false, + 'bad' => false, + 'site' => 'example.com', + 'contragent'=> array( + 'contragentType'=> 'individual' + ), + 'tags' => array(), + 'marginSumm' => 0, + 'totalSumm' => 0, + 'averageSumm' => 0, + 'ordersCount' => 0, + 'costSumm' => 0, + 'customFields' => [], + 'personalDiscount' => 0, + 'address' => array( + 'id_customer' => 2222, + 'id' => 4053, + 'countryIso' => 'RU', + 'index' => '2170', + 'city' => 'Moscow', + 'street' => 'Good', + 'building' => '17', + 'text' => 'Good, д. 17' + ), + 'segments' => array(), + 'email' => 'test@example.com', + 'firstName' => 'Test', + 'lastName' => 'Test', + 'phones' => array( + 'number' => '+79999999999' + ) + ); + } } +