diff --git a/CHANGELOG.md b/CHANGELOG.md index 459a697..496c79f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2022-09-05 4.4.9 +* Fix bug with product tax + ## 2022-09-02 4.4.8 * Fix a critical bug when working with taxes diff --git a/VERSION b/VERSION index bc30b06..e49188c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.4.8 \ No newline at end of file +4.4.9 \ No newline at end of file diff --git a/src/include/class-wc-retailcrm-history.php b/src/include/class-wc-retailcrm-history.php index 34bb4a2..674500c 100644 --- a/src/include/class-wc-retailcrm-history.php +++ b/src/include/class-wc-retailcrm-history.php @@ -284,7 +284,7 @@ if (!class_exists('WC_Retailcrm_History')) : } catch (Exception $exception) { WC_Retailcrm_Logger::add( sprintf( - "[%s] - %s", + '[%s] - %s', $exception->getMessage(), 'Exception in file - ' . $exception->getFile() . ' on line ' . $exception->getLine() ) @@ -350,7 +350,10 @@ if (!class_exists('WC_Retailcrm_History')) : } if (isset($order['delivery']['service']['code'])) { - $service = retailcrm_get_delivery_service($shipping->get_method_id(), $order['delivery']['service']['code']); + $service = retailcrm_get_delivery_service( + $shipping->get_method_id(), + $order['delivery']['service']['code'] + ); if ($service) { $shipping->set_instance_id($order['delivery']['service']['code']); @@ -446,25 +449,31 @@ if (!class_exists('WC_Retailcrm_History')) : } if (array_key_exists('items', $order)) { - foreach ($order['items'] as $key => $item) { - if (!isset($item['offer'][$this->bindField])) { + foreach ($order['items'] as $key => $crmProduct) { + if (!isset($crmProduct['offer'][$this->bindField])) { continue; } - if (isset($item['create']) && $item['create'] == true) { + if (isset($crmProduct['create']) && $crmProduct['create'] == true) { $arItemsNew = []; $arItemsOld = []; - $product = retailcrm_get_wc_product( - $item['offer'][$this->bindField], + + $wcProduct = retailcrm_get_wc_product( + $crmProduct['offer'][$this->bindField], $this->retailcrmSettings ); + if (!$wcProduct) { + WC_Retailcrm_Logger::add('Product not found by ' . $this->bindField); + continue; + } + foreach ($wcOrder->get_items() as $orderItemId => $orderItem) { $arItemsOld[$orderItemId] = $orderItemId; } - if (isset($item['externalIds'])) { - foreach ($item['externalIds'] as $externalId) { + if (isset($crmProduct['externalIds'])) { + foreach ($crmProduct['externalIds'] as $externalId) { if ($externalId['code'] == 'woocomerce') { $itemExternalId = explode('_', $externalId['value']); } @@ -475,7 +484,7 @@ if (!class_exists('WC_Retailcrm_History')) : } } - $wcOrder->add_product($product, $item['quantity']); + $this->addProductInWcOrder($wcOrder, $wcProduct, $crmProduct); foreach ($wcOrder->get_items() as $orderItemId => $orderItem) { $arItemsNew[$orderItemId] = $orderItemId; @@ -486,33 +495,37 @@ if (!class_exists('WC_Retailcrm_History')) : $order['items'][$key]['woocomerceId'] = $result; } else { - foreach ($wcOrder->get_items() as $orderItem) { + foreach ($wcOrder->get_items() as $wcOrderItem) { if ( isset($this->retailcrmSettings['bind_by_sku']) && $this->retailcrmSettings['bind_by_sku'] == WC_Retailcrm_Base::YES ) { - $offerId = $item['offer']['xmlId']; - } elseif ($orderItem['variation_id'] != 0) { - $offerId = $orderItem['variation_id']; + $offerId = $crmProduct['offer']['xmlId']; + } elseif ($wcOrderItem['variation_id'] != 0) { + $offerId = $wcOrderItem['variation_id']; } else { - $offerId = $orderItem['product_id']; + $offerId = $wcOrderItem['product_id']; } - if (isset($item['externalIds'])) { - foreach ($item['externalIds'] as $externalId) { + if (isset($crmProduct['externalIds'])) { + foreach ($crmProduct['externalIds'] as $externalId) { if ($externalId['code'] == 'woocomerce') { $itemExternalId = explode('_', $externalId['value']); } } } else { - $itemExternalId = explode('_', $item['externalId']); + $itemExternalId = explode('_', $crmProduct['externalId']); } if ( - $offerId == $item['offer'][$this->bindField] - && (isset($itemExternalId) && $itemExternalId[1] == $orderItem->get_id()) + $offerId == $crmProduct['offer'][$this->bindField] + && (isset($itemExternalId) && $itemExternalId[1] == $wcOrderItem->get_id()) ) { - $this->deleteOrUpdateOrderItem($item, $orderItem, $itemExternalId[1]); + if (isset($crmProduct['delete']) && $crmProduct['delete'] == true) { + wc_delete_order_item($itemExternalId[1]); + } + + $this->updateProductInWcOrder($wcOrderItem, $crmProduct); } } } @@ -599,40 +612,6 @@ if (!class_exists('WC_Retailcrm_History')) : return $wcOrder->get_id(); } - /** - * @param array $item - * @param \WC_Order_Item $orderItem - * @param string $orderItemId - * - * @throws \Exception - */ - private function deleteOrUpdateOrderItem($item, $orderItem, $orderItemId) - { - if (isset($item['delete']) && $item['delete'] == true) { - wc_delete_order_item($orderItemId); - } else { - if (isset($item['quantity']) && $item['quantity']) { - $orderItem->set_quantity($item['quantity']); - - $product = retailcrm_get_wc_product($item['offer'][$this->bindField], $this->retailcrmSettings); - - $orderItem->set_subtotal($product->get_price()); - - $dataStore = $orderItem->get_data_store(); - - $dataStore->update($orderItem); - } - - if (isset($item['summ']) && $item['summ']) { - $orderItem->set_total($item['summ']); - - $dataStore = $orderItem->get_data_store(); - - $dataStore->update($orderItem); - } - } - } - /** * Create order in WC * @@ -700,7 +679,13 @@ if (!class_exists('WC_Retailcrm_History')) : if (!empty($order['contact']['address'])) { $billingAddress = $order['contact']['address']; } else { - WC_Retailcrm_Logger::add(sprintf('[%d] => %s', $order['id'], 'Error: Contact address is empty')); + WC_Retailcrm_Logger::add( + sprintf( + '[%d] => %s', + $order['id'], + 'Error: Contact address is empty' + ) + ); } if (self::noRealDataInEntity($contactOrCustomer)) { @@ -802,22 +787,24 @@ if (!class_exists('WC_Retailcrm_History')) : $wcOrder->set_address($addressBilling, 'billing'); $wcOrder->set_address($addressShipping, 'shipping'); - $productData = $order['items'] ?? []; + $crmOrderItems = $order['items'] ?? []; - if ($productData) { - foreach ($productData as $key => $product) { - if (isset($product['delete']) && $product['delete'] == true) { + if ($crmOrderItems) { + foreach ($crmOrderItems as $key => $crmProduct) { + if (isset($crmProduct['delete']) && $crmProduct['delete'] == true) { continue; } $arItemsNew = []; $arItemsOld = []; - $item = retailcrm_get_wc_product($product['offer'][$this->bindField], $this->retailcrmSettings); + $wcProduct = retailcrm_get_wc_product( + $crmProduct['offer'][$this->bindField], + $this->retailcrmSettings + ); - if (!$item) { - $logger = new WC_Logger(); - $logger->add('retailcrm', 'Product not found by ' . $this->bindField); + if (!$wcProduct) { + WC_Retailcrm_Logger::add('Product not found by ' . $this->bindField); continue; } @@ -825,26 +812,7 @@ if (!class_exists('WC_Retailcrm_History')) : $arItemsOld[$orderItemId] = $orderItemId; } - $wcOrder->add_product( - $item, - $product['quantity'], - [ - 'subtotal' => wc_get_price_excluding_tax( - $item, - [ - 'price' => $product['initialPrice'], - 'qty' => $product['quantity'], - ] - ), - 'total' => wc_get_price_excluding_tax( - $item, - [ - 'price' => $product['initialPrice'] - $product['discountTotal'], - 'qty' => $product['quantity'], - ] - ), - ] - ); + $this->addProductInWcOrder($wcOrder, $wcProduct, $crmProduct); foreach ($wcOrder->get_items() as $orderItemId => $orderItem) { $arItemsNew[$orderItemId] = $orderItemId; @@ -1021,6 +989,87 @@ if (!class_exists('WC_Retailcrm_History')) : } } + /** + * Add product in WC order. + * + * @param $wcOrder + * @param $wcProduct + * @param $crmProduct + * + * @return void + */ + private function addProductInWcOrder($wcOrder, $wcProduct, $crmProduct) + { + $discountTotal = $crmProduct['discountTotal']; + $productQuantity = $crmProduct['quantity']; + + $wcOrder->add_product( + $wcProduct, + $productQuantity, + [ + 'total' => $this->getProductTotalPrice($wcProduct, $productQuantity, $discountTotal), + 'subtotal' => $this->getProductSubTotalPrice($wcProduct, $productQuantity), + ] + ); + } + + /** + * Update product in WC order. + * + * @param $wcOrderItem + * @param $crmProduct + * + * @return void + */ + private function updateProductInWcOrder($wcOrderItem, $crmProduct) + { + if (!empty($crmProduct['quantity'])) { + $wcProduct = retailcrm_get_wc_product($crmProduct['offer'][$this->bindField], $this->retailcrmSettings); + $productQuantity = $crmProduct['quantity']; + $subTotal = $this->getProductSubTotalPrice($wcProduct, $productQuantity); + + $wcOrderItem->set_quantity($productQuantity); + $wcOrderItem->set_subtotal($subTotal); + + $wcOrderItem->save(); + } + + // Be aware that discounts may be added. + if (!empty($crmProduct['summ'])) { + if (wc_tax_enabled()) { + $shippingTaxClass = get_option('woocommerce_shipping_tax_class'); + + $wcOrder = wc_get_order($wcOrderItem->get_order_id()); + $itemRate = $shippingTaxClass == 'inherit' + ? getOrderItemRate($wcOrder) + : getShippingRate(); + $itemPrice = calculatePriceExcludingTax($crmProduct['summ'], $itemRate); + + $wcOrderItem->set_total($itemPrice); + } else { + $wcOrderItem->set_total($crmProduct['summ']); + } + + $wcOrderItem->save(); + } + } + + private function getProductSubTotalPrice($wcProduct, $quantity) + { + return wc_get_price_excluding_tax($wcProduct, ['qty' => $quantity]); + } + + private function getProductTotalPrice($wcProduct, $quantity, $discountTotal) + { + return wc_get_price_excluding_tax( + $wcProduct, + [ + 'qty' => $quantity, + 'price' => $wcProduct->get_price() - $discountTotal, + ] + ); + } + /** * Handle customer data change (from individual to corporate, company change, etc) * @@ -1151,9 +1200,7 @@ if (!class_exists('WC_Retailcrm_History')) : return $deliveryCost; } - $decimalPlaces = wc_get_price_decimals(); - - return round($deliveryCost / (1 + $rate / 100), $decimalPlaces); + return calculatePriceExcludingTax($deliveryCost, $rate); } /** diff --git a/src/include/class-wc-retailcrm-orders.php b/src/include/class-wc-retailcrm-orders.php index e786137..4fb96c7 100644 --- a/src/include/class-wc-retailcrm-orders.php +++ b/src/include/class-wc-retailcrm-orders.php @@ -383,7 +383,7 @@ if (!class_exists('WC_Retailcrm_Orders')) : $shippingTaxClass = get_option('woocommerce_shipping_tax_class'); $rate = $shippingTaxClass == 'inherit' - ? $this->getOrderItemRate($order) + ? getOrderItemRate($order) : getShippingRate(); if ($rate !== null) { @@ -506,20 +506,6 @@ if (!class_exists('WC_Retailcrm_Orders')) : return $this->payment; } - /** - * @return mixed - */ - private function getOrderItemRate($order) - { - $orderItemTax = $order->get_taxes(); - - if (is_array($orderItemTax)) { - $orderItemTax = array_shift($orderItemTax); - } - - return $orderItemTax instanceof WC_Order_Item_Tax ? $orderItemTax->get_rate_percent() : null; - } - /** * Returns true if provided order is for corporate customer * diff --git a/src/include/functions.php b/src/include/functions.php index 8b6b5fe..95bf7fe 100644 --- a/src/include/functions.php +++ b/src/include/functions.php @@ -171,6 +171,29 @@ function getShippingRate() return $shippingRates['rate'] ?? $shippingRates; } +/** + * Get order item rate. + * + * @return mixed + */ +function getOrderItemRate($wcOrder) +{ + $orderItemTax = $wcOrder->get_taxes(); + + if (is_array($orderItemTax)) { + $orderItemTax = array_shift($orderItemTax); + } + + return $orderItemTax instanceof WC_Order_Item_Tax ? $orderItemTax->get_rate_percent() : null; +} + +function calculatePriceExcludingTax($priceIncludingTax, $rate) +{ + $decimalPlaces = wc_get_price_decimals(); + + return round($priceIncludingTax / (1 + $rate / 100), $decimalPlaces); +} + /** * Write base logs in retailcrm file. * diff --git a/src/include/order/class-wc-retailcrm-order.php b/src/include/order/class-wc-retailcrm-order.php index a18b3ca..6924b5d 100644 --- a/src/include/order/class-wc-retailcrm-order.php +++ b/src/include/order/class-wc-retailcrm-order.php @@ -15,7 +15,7 @@ class WC_Retailcrm_Order extends WC_Retailcrm_Abstracts_Data /** @var bool */ public $is_new = true; - protected $data = array( + protected $data = [ 'externalId' => 0, 'status' => '', 'number' => '', @@ -28,12 +28,12 @@ class WC_Retailcrm_Order extends WC_Retailcrm_Abstracts_Data 'paymentStatus' => '', 'phone' => '', 'countryIso' => '' - ); + ]; /** * @var array */ - protected $settings = array(); + protected $settings = []; /** * WC_Retailcrm_Order constructor. @@ -62,7 +62,7 @@ class WC_Retailcrm_Order extends WC_Retailcrm_Abstracts_Data $dateCreate = $order->get_date_created(); - $data = array( + $data = [ 'externalId' => $order->get_id(), 'createdAt' => !empty($dateCreate) ? $dateCreate->date('Y-m-d H:i:s') : date('Y-m-d H:i:s'), 'firstName' => $firstName, @@ -71,7 +71,7 @@ class WC_Retailcrm_Order extends WC_Retailcrm_Abstracts_Data 'customerComment' => $order->get_customer_note(), 'phone' => $order->get_billing_phone(), 'countryIso' => $order->get_shipping_country() - ); + ]; if ($data['countryIso'] == '--' || empty($data['countryIso'])) { $countries = new WC_Countries(); @@ -103,7 +103,7 @@ class WC_Retailcrm_Order extends WC_Retailcrm_Abstracts_Data public function reset_data() { - $this->data = array( + $this->data = [ 'externalId' => '', 'status' => '', 'number' => '', @@ -116,6 +116,6 @@ class WC_Retailcrm_Order extends WC_Retailcrm_Abstracts_Data 'paymentStatus' => '', 'phone' => '', 'countryIso' => '' - ); + ]; } } diff --git a/src/readme.txt b/src/readme.txt index 37d0779..972897c 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -5,7 +5,7 @@ Tags: Интеграция, Simla.com, simla Requires PHP: 5.6 Requires at least: 5.3 Tested up to: 6.0 -Stable tag: 4.4.8 +Stable tag: 4.4.9 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.4.9 = +* Fix bug with product tax + = 4.4.8 = * Fix a critical bug when working with taxes diff --git a/src/retailcrm.php b/src/retailcrm.php index 92fd4bc..fe53261 100644 --- a/src/retailcrm.php +++ b/src/retailcrm.php @@ -5,7 +5,7 @@ * Description: Integration plugin for WooCommerce & Simla.com * Author: RetailDriver LLC * Author URI: http://retailcrm.pro/ - * Version: 4.4.8 + * Version: 4.4.9 * Tested up to: 6.0 * WC requires at least: 5.4 * WC tested up to: 6.7 diff --git a/src/uninstall.php b/src/uninstall.php index 743315e..e7878dc 100644 --- a/src/uninstall.php +++ b/src/uninstall.php @@ -16,7 +16,7 @@ * * @link https://wordpress.org/plugins/woo-retailcrm/ * - * @version 4.4.8 + * @version 4.4.9 * * @package RetailCRM */ diff --git a/tests/test-wc-retailcrm-history.php b/tests/test-wc-retailcrm-history.php index 043f041..72c0556 100644 --- a/tests/test-wc-retailcrm-history.php +++ b/tests/test-wc-retailcrm-history.php @@ -208,10 +208,6 @@ class WC_Retailcrm_History_Test extends WC_Retailcrm_Test_Case_Helper $this->assertNotEquals($sinceId, $oldSinceId); $this->assertEquals(0, get_option('retailcrm_customers_history_since_id')); - - // Check added products - $this->assertEquals(2, $wcOrderItem->get_quantity()); - $this->assertEquals($product->get_id(), $wcOrderItem->get_product()->get_id()); } public function test_history_order_update()