diff --git a/CHANGELOG.md b/CHANGELOG.md index 12d60f2..1b5af5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2023-06-27 4.6.8 +* Added the ability to select CRM warehouses to synchronize the balance of offers + ## 2023-06-27 4.6.7 * Fixed customer phone sending to crm when order will create by guest diff --git a/VERSION b/VERSION index 16f8758..45fc36e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.6.7 \ No newline at end of file +4.6.8 \ No newline at end of file diff --git a/doc/1.Setup/Setting stock.md b/doc/1.Setup/Setting stock.md index 688f2a3..3a9a1f7 100644 --- a/doc/1.Setup/Setting stock.md +++ b/doc/1.Setup/Setting stock.md @@ -6,19 +6,25 @@ Анализ работы по списанию остатков был произведен в задаче [#75178](https://redmine.retailcrm.tech/issues/75178). +**В версии 4.6.8** появилась возможность указать, с каких складов CRM выгружать остатки для торговых предложений. В блоке "Настройка управления остатками" добавлен мультисписок с доступными складами CRM. Для выбора нескольких складов зажмите CTRL (для Windows и Linux) или ⌘ Command (для MacOS). + +Если в CRM несколько складов, а в настройках не выбрали склады для выгрузки, в таком случае остатки будут передаваться по всем складам. + #### На стороне WooCommerce Если остатки **ведут на сайте**, то в этом случае, при оформлении заказа на сайте, остаток по товару сразу же списывается в WooCommerce. В CRM обновление остатков по товарам происходит в момент синхронизации ICML-файла каталога *(~ 4 часа)*. -| Кейс | Результат | -|--|--| -| Оформить заказ на сайте, после указать статус Отменен заказу на стороне WooCommerce | При оформлении заказа остатки сразу списываются, как в WooCommerce, так и в CRM;
После указания отмены заказу, в WooCommerce остатки сразу возвращаются, в CRM, если настроена автоматическая отмена товара при отмене заказа, остатки возвращаются, иначе нужно ждать обновления каталога | -| Оформить заказ в CRM, после указать статус из группы Выполнен в CRM | В CRM по товарам остатки не списываются;
В WooCommerce остаток по товару списывается, когда приходит статус Выполнен | -| Оформить заказ в CRM, после указать статус из группы Отмена в CRM | В CRM остатки не списываются *(в т.ч. и при установлении отмены статусу товара)*, в WooCommerce остатки по товару также не возвращаются | + +| Кейс | Результат | +|----------|-------------| +| Оформить заказ на сайте, после указать статус Отменен заказу на стороне WooCommerce | При оформлении заказа остатки сразу списываются, как в WooCommerce, так и в CRM;
После указания отмены заказу, в WooCommerce остатки сразу возвращаются, в CRM, если настроена автоматическая отмена товара при отмене заказа, остатки возвращаются, иначе нужно ждать обновления каталога | +| Оформить заказ в CRM, после указать статус из группы Выполнен в CRM | В CRM по товарам остатки не списываются;
В WooCommerce остаток по товару списывается, когда приходит статус Выполнен | +| Оформить заказ в CRM, после указать статус из группы Отмена в CRM | В CRM остатки не списываются *(в т.ч. и при установлении отмены статусу товара)*, в WooCommerce остатки по товару также не возвращаются | #### На стороне CRM При ведении стока **на стороне CRM**, остаток по товару сразу списывается в CRM. На стороне WooCommerce остатки по товарам обновляются **раз в 15 минут**. -| Кейс | Результат | -|--|--| -| Оформить заказ в CRM, после указать статус в CRM, соответствующий в маппинге статусу Отмены заказа | Если данный статус в CRM не выставляет автоматически статус отмены товару, то в CRM остатки не возвращаются, также, как и на стороне WooCommerce
Если будет выставлен статус отмены товару, остатки возвратятся в CRM и спустя 15 минут в WooCommerce | -| Оформить заказ в CRM, после указать в CRM **статус товару** из группы Отмена | В CRM остаток по товару возвращается, в WooCommerce сток обновляется через 15 минут | -| Оформить заказ на сайте в WooCommerce | Заказ выгружается в CRM, по нему списываются остатки по товару, данное списание передается на сторону WooCommerce | -| Оформить заказ на сайте в WooCommerce, после указать статус Отменен в админке сайта WooCommerce | В CRM приходит статус отмены заказа, если в CRM настроена опция автоматической отмены товара при отмене заказа, то остатки возвращаются в CRM, спустя время и в WooCommerce | +Если данный статус в CRM не выставляет автоматически статус отмены товару, то в CRM остатки не возвращаются, также, как и на стороне WooCommerce
Если будет выставлен статус отмены товару, остатки возвратятся в CRM и спустя 15 минут в WooCommerce + +| Кейс | Результат | +|------------------------------------------------------------------------------|-----------| +| Оформить заказ в CRM, после указать в CRM **статус товару** из группы Отмена | В CRM остаток по товару возвращается, в WooCommerce сток обновляется через 15 минут | +| Оформить заказ на сайте в WooCommerce | Заказ выгружается в CRM, по нему списываются остатки по товару, данное списание передается на сторону WooCommerce | +| Оформить заказ на сайте в WooCommerce, после указать статус Отменен в админке сайта WooCommerce | В CRM приходит статус отмены заказа, если в CRM настроена опция автоматической отмены товара при отмене заказа, то остатки возвращаются в CRM, спустя время и в WooCommerce | \ No newline at end of file diff --git a/resources/pot/retailcrm-es_ES.pot b/resources/pot/retailcrm-es_ES.pot index e50bb7c..d03c92c 100644 --- a/resources/pot/retailcrm-es_ES.pot +++ b/resources/pot/retailcrm-es_ES.pot @@ -414,3 +414,10 @@ msgstr "Región" msgid "Standard CRM fields" msgstr "Los campos del CRM por defecto" + +msgid "Warehouses available in CRM" +msgstr "Almacenes disponibles en CRM" + +msgid "Select warehouses to receive balances from CRM. To select multiple warehouses, hold CTRL (for Windows and Linux) or ⌘ Command (for MacOS)" +msgstr "Selecciona los almacenes para recibir el stock desde CRM. Para seleccionar varios mantén pulsado CTRL (para Windows y Linux) o ⌘ Command (para MacOS)" + diff --git a/resources/pot/retailcrm-ru_RU.pot b/resources/pot/retailcrm-ru_RU.pot index 9923dcc..76566e5 100644 --- a/resources/pot/retailcrm-ru_RU.pot +++ b/resources/pot/retailcrm-ru_RU.pot @@ -423,3 +423,9 @@ msgstr "Регион" msgid "Standard CRM fields" msgstr "Стандартные поля CRM" + +msgid "Warehouses available in CRM" +msgstr "Склады, доступные в CRM" + +msgid "Select warehouses to receive balances from CRM. To select multiple warehouses, hold CTRL (for Windows and Linux) or ⌘ Command (for MacOS)" +msgstr "Выберите склады для получения остатков из CRM. Для выбора нескольких складов зажмите CTRL (для Windows и Linux) или ⌘ Command (для MacOS)" diff --git a/src/include/abstracts/class-wc-retailcrm-abstracts-settings.php b/src/include/abstracts/class-wc-retailcrm-abstracts-settings.php index 678a590..eb69216 100644 --- a/src/include/abstracts/class-wc-retailcrm-abstracts-settings.php +++ b/src/include/abstracts/class-wc-retailcrm-abstracts-settings.php @@ -157,9 +157,9 @@ abstract class WC_Retailcrm_Abstracts_Settings extends WC_Integration $order_methods_option = []; $order_methods_list = $this->apiClient->orderMethodsList(); - if (!empty($order_methods_list) && $order_methods_list->isSuccessful()) { + if ($order_methods_list->isSuccessful() && !empty($order_methods_list['orderMethods'])) { foreach ($order_methods_list['orderMethods'] as $order_method) { - if ($order_method['active'] == false) { + if (!$order_method['active']) { continue; } @@ -379,6 +379,32 @@ abstract class WC_Retailcrm_Abstracts_Settings extends WC_Integration 'description' => __('Enable this setting if you would like to get information on leftover stocks from Simla.com to the website', 'retailcrm') ]; + $crmStores = []; + $crmStoresList = $this->apiClient->storesList(); + + if ($crmStoresList->isSuccessful() && !empty($crmStoresList['stores'])) { + foreach ($crmStoresList['stores'] as $store) { + if (!$store['active']) { + continue; + } + + $crmStores[$store['code']] = $store['name']; + } + } + + $this->form_fields['stores_for_uploading'] = [ + 'label' => ' ', + 'title' => __('Warehouses available in CRM', 'retailcrm'), + 'class' => '', + 'type' => 'multiselect', + 'options' => $crmStores, + 'css' => 'min-height:100px;', + 'select_buttons' => true, + 'description' => __('Select warehouses to receive balances from CRM. To select multiple warehouses, hold CTRL (for Windows and Linux) or ⌘ Command (for MacOS)', + 'retailcrm' + ), + ]; + /** * UA options */ diff --git a/src/include/class-wc-retailcrm-base.php b/src/include/class-wc-retailcrm-base.php index fcb6506..6688077 100644 --- a/src/include/class-wc-retailcrm-base.php +++ b/src/include/class-wc-retailcrm-base.php @@ -301,6 +301,7 @@ if (!class_exists('WC_Retailcrm_Base')) { public function load_stocks() { $inventories = new WC_Retailcrm_Inventories($this->apiClient); + $inventories->updateQuantity(); } diff --git a/src/include/class-wc-retailcrm-history.php b/src/include/class-wc-retailcrm-history.php index f1739d7..86c9595 100644 --- a/src/include/class-wc-retailcrm-history.php +++ b/src/include/class-wc-retailcrm-history.php @@ -55,6 +55,11 @@ if (!class_exists('WC_Retailcrm_History')) : unset($this->retailcrmSettings['order_methods']); } + // Because the orderHistory method uses array_flip, and the option with array called error + if (isset($this->retailcrmSettings['stores_for_uploading'])) { + unset($this->retailcrmSettings['stores_for_uploading']); + } + $this->retailcrm = $retailcrm; $this->startDate = new DateTime('-1 days'); } diff --git a/src/include/class-wc-retailcrm-inventories.php b/src/include/class-wc-retailcrm-inventories.php index 5d0fa77..95d85cc 100644 --- a/src/include/class-wc-retailcrm-inventories.php +++ b/src/include/class-wc-retailcrm-inventories.php @@ -18,10 +18,10 @@ if (!class_exists('WC_Retailcrm_Inventories')) : protected $retailcrm; /** @var array */ - protected $retailcrm_settings; + protected $crmSettings; /** @var string */ - protected $bind_field = 'externalId'; + protected $bindField = 'externalId'; /** * WC_Retailcrm_Inventories constructor. @@ -29,13 +29,11 @@ if (!class_exists('WC_Retailcrm_Inventories')) : */ public function __construct($retailcrm = false) { - $this->retailcrm_settings = get_option(WC_Retailcrm_Base::$option_key); + $this->crmSettings = get_option(WC_Retailcrm_Base::$option_key); $this->retailcrm = $retailcrm; - if (isset($this->retailcrm_settings['bind_by_sku']) - && $this->retailcrm_settings['bind_by_sku'] == WC_Retailcrm_Base::YES - ) { - $this->bind_field = 'xmlId'; + if (!empty($this->crmSettings['bind_by_sku']) && $this->crmSettings['bind_by_sku'] === WC_Retailcrm_Base::YES) { + $this->bindField = 'xmlId'; } } @@ -46,20 +44,19 @@ if (!class_exists('WC_Retailcrm_Inventories')) : */ protected function load_stocks() { - $success = array(); - if (!$this->retailcrm instanceof WC_Retailcrm_Proxy) { return null; } $page = 1; - $variationProducts = array(); + $availableStores = $this->crmSettings['stores_for_uploading'] ?? null; + $variationProducts = []; do { /** @var WC_Retailcrm_Response $response */ - $response = $this->retailcrm->storeInventories(array(), $page, 250); + $response = $this->retailcrm->storeInventories(['details' => true], $page, 250); - if (empty($response) || !$response->isSuccessful()) { + if (empty($response['offers']) || !$response->isSuccessful()) { return null; } @@ -67,11 +64,23 @@ if (!class_exists('WC_Retailcrm_Inventories')) : $page++; foreach ($response['offers'] as $offer) { - if (isset($offer[$this->bind_field])) { - $product = retailcrm_get_wc_product($offer[$this->bind_field], $this->retailcrm_settings); + $offerQuantity = $offer['quantity']; + + if (!empty($availableStores) && count($offer['stores']) > 1) { + $offerQuantity = 0; + + foreach ($offer['stores'] as $store) { + if (in_array($store['store'], $availableStores, true)) { + $offerQuantity += $store['quantity']; + } + } + } + + if (isset($offer[$this->bindField])) { + $product = retailcrm_get_wc_product($offer[$this->bindField], $this->crmSettings); if ($product instanceof WC_Product) { - if ($product->get_type() == 'external') { + if ($product->get_type() === 'external') { continue; } @@ -80,15 +89,15 @@ if (!class_exists('WC_Retailcrm_Inventories')) : if (!empty($parentId)) { if (isset($variationProducts[$parentId])) { - $variationProducts[$parentId] += $offer['quantity']; + $variationProducts[$parentId] += $offerQuantity; } else { - $variationProducts[$parentId] = $offer['quantity']; + $variationProducts[$parentId] = $offerQuantity; } } } $product->set_manage_stock(true); - $product->set_stock_quantity($offer['quantity']); + $product->set_stock_quantity($offerQuantity); $product->save(); } } @@ -124,7 +133,7 @@ if (!class_exists('WC_Retailcrm_Inventories')) : */ public function updateQuantity() { - if ($this->retailcrm_settings['sync'] == WC_Retailcrm_Base::YES) { + if ($this->crmSettings['sync'] === WC_Retailcrm_Base::YES) { $this->load_stocks(); } } diff --git a/src/languages/retailcrm-es_ES.mo b/src/languages/retailcrm-es_ES.mo index e638036..a748462 100644 Binary files a/src/languages/retailcrm-es_ES.mo and b/src/languages/retailcrm-es_ES.mo differ diff --git a/src/languages/retailcrm-ru_RU.mo b/src/languages/retailcrm-ru_RU.mo index c8531f5..c9ca1f9 100644 Binary files a/src/languages/retailcrm-ru_RU.mo and b/src/languages/retailcrm-ru_RU.mo differ diff --git a/src/readme.txt b/src/readme.txt index 060db64..02f7b30 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -4,8 +4,8 @@ Donate link: https://www.simla.com Tags: Интеграция, Simla.com, simla Requires PHP: 7.0 Requires at least: 5.3 -Tested up to: 6.0 -Stable tag: 4.6.7 +Tested up to: 6.2 +Stable tag: 4.6.8 License: GPLv1 or later License URI: http://www.gnu.org/licenses/gpl-1.0.html @@ -82,6 +82,9 @@ Asegúrate de tener una clave API específica para cada tienda. Las siguientes i == Changelog == += 4.6.8 = +* Added the ability to select CRM warehouses to synchronize the balance of offers + = 4.6.7 = * Fixed customer phone sending to crm when order will create by guest diff --git a/src/retailcrm.php b/src/retailcrm.php index 9608790..ddc9bc8 100644 --- a/src/retailcrm.php +++ b/src/retailcrm.php @@ -5,10 +5,10 @@ * Description: Integration plugin for WooCommerce & Simla.com * Author: RetailDriver LLC * Author URI: http://retailcrm.pro/ - * Version: 4.6.7 - * Tested up to: 6.0 + * Version: 4.6.8 + * Tested up to: 6.2 * WC requires at least: 5.4 - * WC tested up to: 6.9 + * WC tested up to: 7.8 * Text Domain: retailcrm */ diff --git a/src/uninstall.php b/src/uninstall.php index 814e67b..11dcd40 100644 --- a/src/uninstall.php +++ b/src/uninstall.php @@ -16,7 +16,7 @@ * * @link https://wordpress.org/plugins/woo-retailcrm/ * - * @version 4.6.7 + * @version 4.6.8 * * @package RetailCRM */ diff --git a/tests/datasets/data-base-retailcrm.php b/tests/datasets/data-base-retailcrm.php index f38523c..b8e3f78 100644 --- a/tests/datasets/data-base-retailcrm.php +++ b/tests/datasets/data-base-retailcrm.php @@ -140,4 +140,28 @@ class DataBaseRetailCrm ] ]; } + + public static function getResponseStoreList() + { + return [ + 'success' => true, + 'stores' => [ + [ + 'name' => 'main', + 'code' => 'main', + 'active' => true + ], + [ + 'name' => 'woocommerce', + 'code' => 'woocommerce', + 'active' => true + ], + [ + 'name' => 'prestashop', + 'code' => 'prestashop', + 'active' => true + ] + ] + ]; + } } diff --git a/tests/datasets/data-inventories-retailcrm.php b/tests/datasets/data-inventories-retailcrm.php index 4925e2a..946d4cf 100644 --- a/tests/datasets/data-inventories-retailcrm.php +++ b/tests/datasets/data-inventories-retailcrm.php @@ -16,21 +16,39 @@ namespace datasets; class DataInventoriesRetailCrm { public static function getResponseData() { - return array( + return [ 'success' => true, - 'pagination' => array( + 'pagination' => [ 'limit' => 250, 'totalCount' => 1, 'currentPage' => 1, 'totalPageCount' => 1 - ), - 'offers' => array( - array( + ], + 'offers' => [ + [ 'id' => 1, 'xmlId' => 'xmlId', - 'quantity' => 10 - ) - ) - ); + 'quantity' => 100, + 'stores' => [ + [ + 'quantity' => 25, + 'purchasePrice' => 0, + 'store' => 'main' + ], + [ + 'quantity' => 25, + 'purchasePrice' => 0, + 'store' => 'woocommerce' + ], + [ + 'quantity' => 50, + 'purchasePrice' => 0, + 'store' => 'prestashop' + ], + + ] + ] + ] + ]; } -} \ No newline at end of file +} diff --git a/tests/helpers/class-wc-retailcrm-test-case-helper.php b/tests/helpers/class-wc-retailcrm-test-case-helper.php index 786d556..b64a301 100644 --- a/tests/helpers/class-wc-retailcrm-test-case-helper.php +++ b/tests/helpers/class-wc-retailcrm-test-case-helper.php @@ -51,7 +51,7 @@ class WC_Retailcrm_Test_Case_Helper extends WC_Unit_Test_Case 'whatsapp_active' => 'yes', 'whatsapp_location_icon' => 'yes', 'whatsapp_number' => '+79184567234', - 'icml' => 'yes', + 'icml' => 'yes', 'corporate_enabled' => 'yes', 'abandoned_carts_enabled' => 'yes', 'single_order' => '123', @@ -61,7 +61,7 @@ class WC_Retailcrm_Test_Case_Helper extends WC_Unit_Test_Case 'update_number' => 'yes', 'debug_mode' => 'yes', 'debug-info' => '', - 'order-meta-data-retailcrm' => json_encode( + 'order-meta-data-retailcrm' => json_encode( [ 'woo_order' => 'crm_order', 'crm_phone' => 'default-crm-field#phone', @@ -78,6 +78,7 @@ class WC_Retailcrm_Test_Case_Helper extends WC_Unit_Test_Case ] ), 'product_description' => 'full', + 'stores_for_uploading' => ['woocommerce', 'main'], ]; update_option(WC_Retailcrm_Base::$option_key, $options); @@ -140,7 +141,7 @@ class WC_Retailcrm_Test_Case_Helper extends WC_Unit_Test_Case protected function setMockResponse($mock, $method, $response) { $mock->expects($this->any()) - ->method($method) - ->willReturn($response); + ->method($method) + ->willReturn($response); } } diff --git a/tests/test-wc-retailcrm-base.php b/tests/test-wc-retailcrm-base.php index 89e4173..a9370f8 100644 --- a/tests/test-wc-retailcrm-base.php +++ b/tests/test-wc-retailcrm-base.php @@ -16,6 +16,7 @@ use datasets\DataBaseRetailCrm; class WC_Retailcrm_Base_Test extends WC_Retailcrm_Test_Case_Helper { protected $apiMock; + protected $responseMockStoresList; protected $responseMockOrderMethods; protected $responseMockDeliveryTypes; protected $responseMockPaymentTypes; @@ -31,6 +32,7 @@ class WC_Retailcrm_Base_Test extends WC_Retailcrm_Test_Case_Helper ->disableOriginalConstructor() ->setMethods( [ + 'storesList', 'orderMethodsList', 'deliveryTypesList', 'paymentTypesList', @@ -47,6 +49,7 @@ class WC_Retailcrm_Base_Test extends WC_Retailcrm_Test_Case_Helper $this->setMockPaymentTypes(); $this->setMockStatuses(); $this->setMockCustomFields(); + $this->setMockStoresList(); $_GET['page'] = 'wc-settings'; $_GET['tab'] = 'integration'; @@ -120,6 +123,7 @@ class WC_Retailcrm_Base_Test extends WC_Retailcrm_Test_Case_Helper //Other settings $this->assertArrayHasKey('corporate_enabled', $this->baseRetailcrm->form_fields); + $this->assertArrayHasKey('stores_for_uploading', $this->baseRetailcrm->form_fields); $this->assertArrayHasKey('abandoned_carts_enabled', $this->baseRetailcrm->form_fields); $this->assertArrayHasKey('online_assistant', $this->baseRetailcrm->form_fields); $this->assertArrayHasKey('deactivate_update_order', $this->baseRetailcrm->form_fields); @@ -395,6 +399,20 @@ class WC_Retailcrm_Base_Test extends WC_Retailcrm_Test_Case_Helper return json_decode($matches[0], true); } + private function setMockStoresList() + { + $this->responseMockStoresList = $this + ->getMockBuilder('\WC_Retailcrm_Response_Helper') + ->disableOriginalConstructor() + ->setMethods(['isSuccessful']) + ->getMock(); + + $this->setMockResponse($this->responseMockStoresList, 'isSuccessful', true); + + $this->responseMockStoresList->setResponse(DataBaseRetailCrm::getResponseStoreList()); + $this->setMockResponse($this->apiMock, 'storesList', $this->responseMockStoresList); + } + private function setMockOrderMethods() { $this->responseMockOrderMethods = $this diff --git a/tests/test-wc-retailcrm-inventories.php b/tests/test-wc-retailcrm-inventories.php index 4cdda9f..bda9485 100644 --- a/tests/test-wc-retailcrm-inventories.php +++ b/tests/test-wc-retailcrm-inventories.php @@ -21,16 +21,16 @@ class WC_Retailcrm_Inventories_Test extends WC_Retailcrm_Test_Case_Helper public function setUp() { $this->responseMock = $this->getMockBuilder('\WC_Retailcrm_Response_Helper') - ->disableOriginalConstructor() - ->setMethods(array('isSuccessful')) - ->getMock(); + ->disableOriginalConstructor() + ->setMethods(['isSuccessful']) + ->getMock(); $this->setMockResponse($this->responseMock, 'isSuccessful', true); $this->apiMock = $this->getMockBuilder('\WC_Retailcrm_Proxy') - ->disableOriginalConstructor() - ->setMethods(array('storeInventories')) - ->getMock(); + ->disableOriginalConstructor() + ->setMethods(['storeInventories']) + ->getMock(); parent::setUp(); } @@ -111,9 +111,9 @@ class WC_Retailcrm_Inventories_Test extends WC_Retailcrm_Test_Case_Helper if ($retailcrm && null !== $response) { $this->assertInstanceOf('WC_Product', $product); $this->assertEquals($entity, $product->get_type()); - $this->assertEquals(10, $product->get_stock_quantity()); + $this->assertEquals(50, $product->get_stock_quantity()); } else { - $this->assertNotEquals(10, $product->get_stock_quantity()); + $this->assertNotEquals(50, $product->get_stock_quantity()); } }