diff --git a/src/config/objects.xml b/src/config/objects.xml index 466b020..11e27b9 100644 --- a/src/config/objects.xml +++ b/src/config/objects.xml @@ -59,6 +59,7 @@ orderMethod site status + customer manager firstName lastName diff --git a/src/include/class-wc-retailcrm-history.php b/src/include/class-wc-retailcrm-history.php index 4bbcbc5..c127c2c 100644 --- a/src/include/class-wc-retailcrm-history.php +++ b/src/include/class-wc-retailcrm-history.php @@ -371,7 +371,6 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) : */ protected function orderUpdate($order, $options) { - $crmOrder = array(); $wc_order = wc_get_order($order['externalId']); if (!$wc_order instanceof WC_Order) { @@ -398,6 +397,7 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) : $wc_order->set_shipping_last_name($order['lastName']); } +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD if (isset($order['phone'])) { @@ -412,11 +412,13 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) : >>>>>>> fixes & more fields for sync ======= +======= + $this->handleCustomerDataChange($wc_order, $order); +>>>>>>> Fixes, customer change logic for legal entities & individual persons (contact person will be used in the second case) >>>>>>> WIP: Logic for company replacement via component (which was surprisingly easy to implement) if (array_key_exists('items', $order)) { foreach ($order['items'] as $key => $item) { - if (!isset($item['offer'][$this->bind_field])) { continue; } @@ -904,6 +906,154 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) : } } + /** + * Handle customer data change (from individual to corporate, company change, etc) + * + * @param \WC_Order $wc_order + * @param array $order + */ + protected function handleCustomerDataChange($wc_order, $order) + { + $crmOrder = array(); + $newCustomerId = null; + $switcher = new WC_Retailcrm_Customer_Switcher(); + $data = new WC_Retailcrm_Customer_Switcher_State(); + $data->setWcOrder($wc_order); + + WC_Retailcrm_Logger::debug( + __METHOD__, + 'processing order', + $order + ); + + if (isset($order['customer'])) { + $crmOrder = $this->getCRMOrder($order['id'], 'id'); + + if (empty($crmOrder)) { + WC_Retailcrm_Logger::addCaller(__METHOD__, sprintf( + 'Cannot get order data from retailCRM. Skipping customer change. History data: %s', + print_r($order, true) + )); + + return; + } + + $newCustomerId = $order['customer']['id']; + $isChangedToRegular = self::isCustomerChangedToRegular($order); + $isChangedToCorporate = self::isCustomerChangedToLegal($order); + + if (!$isChangedToRegular && !$isChangedToCorporate) { + $isChangedToCorporate = self::isOrderCorporate($crmOrder); + $isChangedToRegular = !$isChangedToCorporate; + } + + if ($isChangedToRegular) { + $this->prepareChangeToIndividual( + self::arrayValue($crmOrder, 'customer', array()), + $data + ); + } + } + + if (isset($order['contact'])) { + $newCustomerId = $order['contact']['id']; + + if (empty($crmOrder)) { + $crmOrder = $this->getCRMOrder($order['id'], 'id'); + } + + if (empty($crmOrder)) { + WC_Retailcrm_Logger::addCaller(__METHOD__, sprintf( + 'Cannot get order data from retailCRM. Skipping customer change. History data: %s', + print_r($order, true) + )); + + return; + } + + $this->prepareChangeToIndividual( + self::arrayValue($crmOrder, 'contact', array()), + $data, + true + ); + + if (isset($order['customer']) && $order['customer']['id'] == $order['contact']['id']) { + $data->setNewCustomer(array()); + } + } + + if (isset($order['company'])) { + $data->setNewCompany($order['company']); + } + + try { + $result = $switcher->setData($data) + ->build() + ->getResult(); + + $result->save(); + } catch (\Exception $exception) { + WC_Retailcrm_Logger::addCaller( + __METHOD__, + sprintf( + 'Error switching order externalId=%s to customer id=%s (new company: id=%s %s). Reason: %s', + $order['externalId'], + $newCustomerId, + isset($order['company']) ? $order['company']['id'] : '', + isset($order['company']) ? $order['company']['name'] : '', + $exception->getMessage() + ) + ); + } + } + + /** + * 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 function getCRMOrder($id, $by = 'externalId') + { + $crmOrderResponse = $this->retailcrm->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 \WC_Retailcrm_Customer_Switcher_State $data + * @param bool $isContact + */ + protected function prepareChangeToIndividual($crmCustomer, $data, $isContact = false) + { + WC_Retailcrm_Logger::debug( + __METHOD__, + 'Using this individual person data in order to set it into order,', + $data->getWcOrder()->get_id(), + ': ', + $crmCustomer + ); + + if ($isContact) { + $data->setNewContact($crmCustomer); + } else { + $data->setNewCustomer($crmCustomer); + } + } + /** * @param array $itemData * @@ -942,6 +1092,52 @@ if ( ! class_exists( 'WC_Retailcrm_History' ) ) : { return isset($order['customer']['type']) && $order['customer']['type'] == 'customer_corporate'; } + + /** + * 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 + */ + private 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 + */ + private static function isCustomerChangedToLegal($assembledOrder) + { + return isset($assembledOrder['contragentType']) && $assembledOrder['contragentType'] == 'legal-entity'; + } + + /** + * @param array|\ArrayObject|\ArrayAccess $arr + * @param string $key + * @param string $def + * + * @return mixed|string + */ + private 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 $arr[$key]; + } } endif; \ No newline at end of file diff --git a/src/include/class-wc-retailcrm-plugin.php b/src/include/class-wc-retailcrm-plugin.php index 5bb3232..3769a7e 100644 --- a/src/include/class-wc-retailcrm-plugin.php +++ b/src/include/class-wc-retailcrm-plugin.php @@ -180,6 +180,18 @@ class WC_Retailcrm_Plugin { } } + /** + * Generates placeholder email + * + * @param string $name + * + * @return string + */ + public static function createPlaceholderEmail($name) + { + return substr(md5($name), 0, 15) . '@example.com'; + } + /** * Check running history * diff --git a/src/include/components/class-wc-retailcrm-customer-switcher.php b/src/include/components/class-wc-retailcrm-customer-switcher.php index 2ee037d..0cd8500 100644 --- a/src/include/components/class-wc-retailcrm-customer-switcher.php +++ b/src/include/components/class-wc-retailcrm-customer-switcher.php @@ -36,22 +36,37 @@ class WC_Retailcrm_Customer_Switcher implements WC_Retailcrm_Builder_Interface { $this->data->validate(); - $wcOrder = $this->data->getWcOrder(); - $newCustomer = $this->data->getNewCustomer(); - $newContact = $this->data->getNewContact(); - $newCompany = $this->data->getNewCompanyName(); + WC_Retailcrm_Logger::debug(__METHOD__, 'state', $this->data); - if (!empty($newCustomer)) { - $this->processChangeToRegular($wcOrder, $newCustomer); - return $this; - } + if (!empty($this->data->getNewCustomer())) { + WC_Retailcrm_Logger::debug( + __METHOD__, + 'Changing to individual customer for order', + $this->data->getWcOrder()->get_id() + ); + $this->processChangeToRegular($this->data->getWcOrder(), $this->data->getNewCustomer()); + } else { + if (!empty($this->data->getNewContact())) { + WC_Retailcrm_Logger::debug( + __METHOD__, + 'Changing to contact person customer for order', + $this->data->getWcOrder()->get_id() + ); + $this->processChangeToRegular($this->data->getWcOrder(), $this->data->getNewContact()); + } - if (!empty($newContact)) { - $this->processChangeToRegular($wcOrder, $newContact); - } - - if (!empty($newCompany)) { - $this->updateCompany($wcOrder, $newCompany); + if (!empty($this->data->getNewCompanyName())) { + WC_Retailcrm_Logger::debug( + __METHOD__, + sprintf( + 'Replacing old order id=`%d` company `%s` with new company `%s`', + $this->data->getWcOrder()->get_id(), + $this->data->getWcOrder()->get_billing_company(), + $this->data->getNewCompanyName() + ) + ); + $this->processCompanyChange(); + } } return $this; @@ -69,27 +84,34 @@ class WC_Retailcrm_Customer_Switcher implements WC_Retailcrm_Builder_Interface { $wcCustomer = null; + WC_Retailcrm_Logger::debug( + __METHOD__, + 'Switching in order', + $wcOrder->get_id(), + 'to', + $newCustomer + ); + if (isset($newCustomer['externalId'])) { $wcCustomer = WC_Retailcrm_Plugin::getWcCustomerById($newCustomer['externalId']); if (!empty($wcCustomer)) { $wcOrder->set_customer_id($wcCustomer->get_id()); + WC_Retailcrm_Logger::debug( + __METHOD__, + 'Set customer to', + $wcCustomer->get_id(), + 'in order', + $wcOrder->get_id() + ); } } else { - //TODO: - // 1. Too risky! Consider using default WooCommerce object. - // 2. Will it work as expected with such property name? Check that. - // 3. It will remove user from order directly, WC_Order logic is completely skipped here. - // It can cause these problems: - // 1) Order is changed and it's state in WC_Order is inconsistent, which can lead to problems - // and data inconsistency while saving. For example, order saving can overwrite `_customer_user` - // meta, which will revert this operation and we'll end up with a broken data (order is still - // attached to an old customer). Whichever, this last statement should be checked. - // 2) The second problem is a lifecycle in general. We're using builder interface, and code inside - // doesn't do anything which is not expected from builder. For example, besides this line, there's no - // CRUD operations. Such operation will not be expected here, so, it's better to remove it from here. - // The best solution would be to use WC_Order, and not modify it's data directly. - delete_post_meta($wcOrder->get_id(), '_customer_user'); + $wcOrder->set_customer_id(0); + WC_Retailcrm_Logger::debug( + __METHOD__, + 'Set customer to 0 (guest) in order', + $wcOrder->get_id() + ); } $fields = array( @@ -102,21 +124,50 @@ class WC_Retailcrm_Customer_Switcher implements WC_Retailcrm_Builder_Interface $wcOrder->{'set_' . $field}($value); } + if (isset($newCustomer['address'])) { + $address = $newCustomer['address']; + + if (isset($address['region'])) { + $wcOrder->set_billing_state($address['region']); + } + + if (isset($address['index'])) { + $wcOrder->set_billing_postcode($address['index']); + } + + if (isset($address['country'])) { + $wcOrder->set_billing_country($address['country']); + } + + if (isset($address['city'])) { + $wcOrder->set_billing_city($address['city']); + } + + if (isset($address['text'])) { + $wcOrder->set_billing_address_1($address['text']); + } + } + + if (!empty(self::singleCustomerPhone($newCustomer))) { + $wcOrder->set_billing_phone(self::singleCustomerPhone($newCustomer)); + } + $wcOrder->set_billing_company(''); $this->result = new WC_Retailcrm_Customer_Switcher_Result($wcCustomer, $wcOrder); } /** - * Update company in the order - * - * @param WC_Order $wcOrder - * @param string $company + * This will update company field in order and create result if it's not set (happens when only company was changed). * * @throws \WC_Data_Exception */ - public function updateCompany($wcOrder, $company) + public function processCompanyChange() { - $wcOrder->set_billing_company($company); + $this->data->getWcOrder()->set_billing_company($this->data->getNewCompanyName()); + + if (empty($this->result)) { + $this->result = new WC_Retailcrm_Customer_Switcher_Result(null, $this->data->getWcOrder()); + } } /** @@ -181,4 +232,31 @@ class WC_Retailcrm_Customer_Switcher implements WC_Retailcrm_Builder_Interface return $arr[$key]; } + + /** + * Returns first phone from order data or null + * + * @param array $customerData + * + * @return string|null + */ + private static function singleCustomerPhone($customerData) + { + if (!array_key_exists('phones', $customerData)) { + return null; + } + + if (empty($customerData['phones']) || !is_array($customerData['phones'])) { + return null; + } + + $phones = $customerData['phones']; + $phone = reset($phones); + + if (!isset($phone['number'])) { + return null; + } + + return (string) $phone['number']; + } } diff --git a/src/include/models/class-wc-retailcrm-customer-switcher-result.php b/src/include/models/class-wc-retailcrm-customer-switcher-result.php index fbf333b..0664db5 100644 --- a/src/include/models/class-wc-retailcrm-customer-switcher-result.php +++ b/src/include/models/class-wc-retailcrm-customer-switcher-result.php @@ -47,4 +47,27 @@ class WC_Retailcrm_Customer_Switcher_Result { return $this->wcOrder; } + + /** + * Save customer (if exists) and order. + * + * @return $this + */ + public function save() + { + WC_Retailcrm_Logger::debug( + __METHOD__, + 'Saving customer and order:', + $this->wcCustomer, + $this->wcOrder + ); + + if (!empty($this->wcCustomer)) { + $this->wcCustomer->save(); + } + + $this->wcOrder->save(); + + return $this; + } } diff --git a/src/include/models/class-wc-retailcrm-customer-switcher-state.php b/src/include/models/class-wc-retailcrm-customer-switcher-state.php index ee2e67e..256016b 100644 --- a/src/include/models/class-wc-retailcrm-customer-switcher-state.php +++ b/src/include/models/class-wc-retailcrm-customer-switcher-state.php @@ -102,7 +102,7 @@ class WC_Retailcrm_Customer_Switcher_State public function setNewCompany($newCompany) { if (isset($newCompany['name'])) { - $this->setNewCompany($newCompany['name']); + $this->setNewCompanyName($newCompany['name']); } return $this; @@ -120,16 +120,17 @@ class WC_Retailcrm_Customer_Switcher_State throw new \InvalidArgumentException('Empty WC_Order.'); } - if (empty($this->getNewCustomer()) - && empty($this->getNewContact()) - && empty($this->getNewCorporateCustomer()) - ) { - throw new \InvalidArgumentException('New customer, new contact and new corporate customer is empty.'); + if (empty($this->getNewCustomer()) && empty($this->getNewContact()) && empty($this->getNewCompanyName())) { + throw new \InvalidArgumentException('New customer, new contact and new company is empty.'); } - if (!empty($this->getNewCustomer()) - && (!empty($this->getNewContact()) || !empty($this->getNewCorporateCustomer())) - ) { + if (!empty($this->getNewCustomer()) && !empty($this->getNewContact())) { + WC_Retailcrm_Logger::debug( + __METHOD__, + '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.' );