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.'
);