diff --git a/src/include/api/class-wc-retailcrm-client-v5.php b/src/include/api/class-wc-retailcrm-client-v5.php index 35b75b1..f877604 100644 --- a/src/include/api/class-wc-retailcrm-client-v5.php +++ b/src/include/api/class-wc-retailcrm-client-v5.php @@ -2992,12 +2992,12 @@ class WC_Retailcrm_Client_V5 } /** Maximum discount calculation */ - public function calculateDiscountLoyalty(string $site, array $order) + public function calculateDiscountLoyalty(string $site, array $order, $bonuses = 0) { return $this->client->makeRequest( "/loyalty/calculate", WC_Retailcrm_Request::METHOD_POST, - ['site' => $site, 'order' => json_encode($order)] + ['site' => $site, 'order' => json_encode($order), 'bonuses' => $bonuses] ); } @@ -3011,12 +3011,12 @@ class WC_Retailcrm_Client_V5 ); } - public function cancelBonusOrder(string $site, array $order) + public function cancelBonusOrder(array $order) { return $this->client->makeRequest( "/orders/loyalty/cancel-bonus-operations", WC_Retailcrm_Request::METHOD_POST, - ['site' => $site, 'order' => json_encode($order)] + ['order' => json_encode($order)] ); } diff --git a/src/include/class-wc-retailcrm-loyalty.php b/src/include/class-wc-retailcrm-loyalty.php index c6da194..67ab5d7 100644 --- a/src/include/class-wc-retailcrm-loyalty.php +++ b/src/include/class-wc-retailcrm-loyalty.php @@ -346,12 +346,29 @@ if (!class_exists('WC_Retailcrm_Loyalty')) : return $discountLp; } - public function isValidOrder($wcUser, $wcOrder) + public function isValidOrder($wcCustomer, $wcOrder) { - return !(!$wcUser || (isCorporateUserActivate($this->settings) && isCorporateOrder($wcUser, $wcOrder))); + return !(!$wcCustomer || (isCorporateUserActivate($this->settings) && isCorporateOrder($wcCustomer, $wcOrder))); } - public function applyLoyaltyDiscount($wcOrder, $discountLp, $createdOrder) + public function isValidUser($wcCustomer) + { + if (empty($wcCustomer)) { + return false; + } + + try { + $response = $this->getLoyaltyAccounts($wcCustomer->get_id()); + } catch (Throwable $exception) { + writeBaseLogs('Exception get loyalty accounts: ' . $exception->getMessage()); + + return false; + } + + return isset($response['loyaltyAccounts'][0]); + } + + public function applyLoyaltyDiscount($wcOrder, $createdOrder, $discountLp = 0) { $isPercentDiscount = false; $items = []; @@ -378,9 +395,14 @@ if (!class_exists('WC_Retailcrm_Loyalty')) : $items = $response['order']['items']; } + $this->calculateLoyaltyDiscount($wcOrder, $items); + } + + public function calculateLoyaltyDiscount($wcOrder, $orderItems) + { $wcItems = $wcOrder->get_items(); - foreach ($items as $item) { + foreach ($orderItems as $item) { $externalId = $item['externalIds'][0]['value']; $externalId = preg_replace('/^\d+\_/m', '', $externalId); @@ -402,6 +424,37 @@ if (!class_exists('WC_Retailcrm_Loyalty')) : $wcOrder->calculate_totals(); } + + public function getCrmItemsInfo($orderExternalId) + { + $discountType = null; + $crmItems = []; + + $response = $this->apiClient->ordersGet($orderExternalId); + + if (!$response instanceof WC_Retailcrm_Response || !$response->isSuccessful() || !isset($response['order'])) { + writeBaseLogs('Process order: Error when receiving an order from the CRM. Order Id: ' . $orderExternalId); + + return []; + } + + foreach ($response['order']['items'] as $item) { + $externalId = $item['externalIds'][0]['value']; + $externalId = preg_replace('/^\d+\_/m', '', $externalId); + $crmItems[$externalId] = $item; + + if (!$discountType) { + foreach ($item['discounts'] as $discount) { + if (in_array($discount['type'], ['bonus_charge', 'loyalty_level'])) { + $discountType = $discount['type']; + break; + } + } + } + } + + return ['items' => $crmItems, 'discountType' => $discountType]; + } } endif; diff --git a/src/include/class-wc-retailcrm-orders.php b/src/include/class-wc-retailcrm-orders.php index 6bfe6ec..0f28a55 100644 --- a/src/include/class-wc-retailcrm-orders.php +++ b/src/include/class-wc-retailcrm-orders.php @@ -44,6 +44,12 @@ if (!class_exists('WC_Retailcrm_Orders')) : /** @var array */ private $order = []; + /** @var bool */ + private $cancelLoyalty = false; + + /** @var string */ + private $loyaltyDiscountType = ''; + /** @var array */ private $payment = []; @@ -94,25 +100,34 @@ if (!class_exists('WC_Retailcrm_Orders')) : $this->order_payment->resetData(); $wcOrder = wc_get_order($orderId); - $privilegeType = 'none'; if ($this->loyalty) { + $wcCustomer = null; + $privilegeType = 'none'; $discountLp = $this->loyalty->deleteLoyaltyCouponInOrder($wcOrder); - $wcUser = $wcOrder->get_user(); + $dataOrder = $wcOrder->get_data(); - if (!$this->loyalty->isValidOrder($wcUser, $wcOrder)) { + if (isset($dataOrder['customer_id'])) { + $wcCustomer = new WC_Customer($dataOrder['customer_id']) ?? null; + } + + if (!$this->loyalty->isValidOrder($wcCustomer, $wcOrder)) { if ($discountLp > 0) { writeBaseLogs('The user does not meet the requirements for working with the loyalty program. Order Id: ' . $orderId); } $discountLp = 0; - } else { + $privilegeType = 'none'; + } elseif ($this->loyalty->isValidUser($wcCustomer)) { $privilegeType = 'loyalty_level'; } } $this->processOrder($wcOrder); - $this->order['privilegeType'] = $privilegeType; + + if (isset($privilegeType)) { + $this->order['privilegeType'] = $privilegeType; + } $response = $this->retailcrm->ordersCreate($this->order); @@ -124,7 +139,7 @@ if (!class_exists('WC_Retailcrm_Orders')) : } if (isset($discountLp) && $discountLp > 0) { - $this->loyalty->applyLoyaltyDiscount($wcOrder, $discountLp, $response['order']); + $this->loyalty->applyLoyaltyDiscount($wcOrder, $response['order'], $discountLp); } } catch (Throwable $exception) { writeBaseLogs( @@ -302,16 +317,39 @@ if (!class_exists('WC_Retailcrm_Orders')) : try { $wcOrder = wc_get_order($orderId); + $needRecalculate = false; $this->processOrder($wcOrder, true); - $response = $this->retailcrm->ordersEdit($this->order); + if ($this->cancelLoyalty) { + $this->cancelLoyalty = false; + $this->order_item->cancelLoyalty = false; + $needRecalculate = true; - // Allows you to verify order changes and perform additional actions + if ($this->loyaltyDiscountType === 'loyalty_level') { + $this->order['privilegeType'] = 'none'; + } + + if ($this->loyaltyDiscountType === 'bonus_charge') { + $responseCancelBonus = $this->retailcrm->cancelBonusOrder(['externalId' => $this->order['externalId']]); + + if (!$responseCancelBonus instanceof WC_Retailcrm_Response || !$responseCancelBonus->isSuccessful()) { + writeBaseLogs('Error when canceling bonuses'); + + return null; + } + } + } + + $response = $this->retailcrm->ordersEdit($this->order); $response = apply_filters('retailcrm_order_update_after', $response, $wcOrder); - if ($response instanceof WC_Retailcrm_Response && $response->isSuccessful()) { + if ($response instanceof WC_Retailcrm_Response && $response->isSuccessful() && isset($response['order'])) { $this->payment = $this->orderUpdatePaymentType($wcOrder); + + if ($needRecalculate) { + $this->loyalty->calculateLoyaltyDiscount($wcOrder, $response['order']['items']); + } } } catch (Throwable $exception) { writeBaseLogs( @@ -447,15 +485,32 @@ if (!class_exists('WC_Retailcrm_Orders')) : } $orderData['delivery']['address'] = $this->order_address->build($order)->getData(); + $orderItems = []; + $crmItems = []; + $wcItems = $order->get_items(); + + if ($this->loyalty && $update) { + $result = $this->loyalty->getCrmItemsInfo($order->get_id()); + + if ($result !== []) { + $crmItems = $result['items']; + + $this->cancelLoyalty = $this->order_item->isCancelLoyalty($wcItems, $crmItems); + $this->loyaltyDiscountType = $result['discountType']; + } + } /** @var WC_Order_Item_Product $item */ - foreach ($order->get_items() as $item) { - $orderItems[] = $this->order_item->build($item)->getData(); + foreach ($wcItems as $id => $item) { + $crmItem = $crmItems[$id] ?? null; + $orderItems[] = $this->order_item->build($item, $crmItem)->getData(); - $this->order_item->resetData(); + $this->order_item->resetData($this->cancelLoyalty); } + unset($crmItems, $crmItem); + $orderData['items'] = $orderItems; $orderData['discountManualAmount'] = 0; $orderData['discountManualPercent'] = 0; diff --git a/src/include/functions.php b/src/include/functions.php index b1afd25..2e099ea 100644 --- a/src/include/functions.php +++ b/src/include/functions.php @@ -213,7 +213,7 @@ function isCorporateUserActivate($settings) return isset($settings['corporate_enabled']) && $settings['corporate_enabled'] === WC_Retailcrm_Base::YES; } -function isCorporateOrder($wcUser, $wcOrder) +function isCorporateOrder($wcCustomer, $wcOrder) { - return !empty($wcUser->get_billing_company()) || !empty($wcOrder->get_billing_company()); + return !empty($wcCustomer->get_billing_company()) || !empty($wcOrder->get_billing_company()); } diff --git a/src/include/order/class-wc-retailcrm-order-item.php b/src/include/order/class-wc-retailcrm-order-item.php index b6186b5..53e593a 100644 --- a/src/include/order/class-wc-retailcrm-order-item.php +++ b/src/include/order/class-wc-retailcrm-order-item.php @@ -28,6 +28,9 @@ class WC_Retailcrm_Order_Item extends WC_Retailcrm_Abstracts_Data */ protected $settings = []; + /** @var bool */ + public $cancelLoyalty = false; + /** * WC_Retailcrm_Order_Item constructor. * @@ -43,13 +46,13 @@ class WC_Retailcrm_Order_Item extends WC_Retailcrm_Abstracts_Data * * @return self */ - public function build($item) + public function build($item, $crmItem = null) { $decimalPlaces = wc_get_price_decimals(); // Calculate price and discount $price = $this->calculatePrice($item, $decimalPlaces); - $discountPrice = $this->calculateDiscount($item, $price, $decimalPlaces); + $discountPrice = $this->calculateDiscount($item, $price, $decimalPlaces, $crmItem); $data['productName'] = $item['name']; $data['initialPrice'] = $price; @@ -112,12 +115,44 @@ class WC_Retailcrm_Order_Item extends WC_Retailcrm_Abstracts_Data * @param WC_Order_Item_Product $item * @param $price * @param int $decimalPlaces Price rounding from WC settings - * + * @param array|null $crmItem Current trade position in CRM * @return float|int */ - private function calculateDiscount(WC_Order_Item_Product $item, $price, int $decimalPlaces) - { - $productPrice = $item->get_total() ? $item->get_total() / $item->get_quantity() : 0; + private function calculateDiscount( + WC_Order_Item_Product $item, + $price, + int $decimalPlaces, + $crmItem = null + ) { + if ($crmItem) { + $loyaltyDiscount = 0; + + foreach ($crmItem['discounts'] as $discount) { + if (in_array($discount['type'], ['bonus_charge', 'loyalty_level'])) { + $loyaltyDiscount += $discount['amount']; + + break; + } + } + + /** + * The loyalty program discount is calculated within the CRM system. It must be deleted during transfer to avoid duplication. + */ + $productPrice = ($item->get_total() / $item->get_quantity()) + ($loyaltyDiscount / $crmItem['quantity']); + + if ($this->cancelLoyalty) { + if ($item->get_total() + $loyaltyDiscount <= $item->get_subtotal()) { + $item->set_total($item->get_total() + $loyaltyDiscount); + $item->calculate_taxes(); + $item->save(); + } + + $productPrice = $item->get_total() / $item->get_quantity(); + } + } else { + $productPrice = $item->get_total() ? $item->get_total() / $item->get_quantity() : 0; + } + $productTax = $item->get_total_tax() ? $item->get_total_tax() / $item->get_quantity() : 0; $itemPrice = $productPrice + $productTax; @@ -127,7 +162,7 @@ class WC_Retailcrm_Order_Item extends WC_Retailcrm_Abstracts_Data /** * Reset item data. */ - public function resetData() + public function resetData($cancelLoyalty) { $this->data = [ 'offer' => [], @@ -135,5 +170,63 @@ class WC_Retailcrm_Order_Item extends WC_Retailcrm_Abstracts_Data 'initialPrice' => 0.00, 'quantity' => 0.00 ]; + + $this->cancelLoyalty = $cancelLoyalty; + } + + /** + * Checking whether the loyalty program discount needs to be canceled. (Changing the sales items in the order) + * + * @param array $wcItems + * @param array $crmItems + * + * @return bool + */ + public function isCancelLoyalty($wcItems, $crmItems): bool + { + $loyaltyDiscount = 0; + + /** If the number of sales items does not match */ + if (count($wcItems) !== count($crmItems)) { + $this->cancelLoyalty = true; + + return true; + } + + foreach ($wcItems as $id => $item) { + /** If a trading position has been added/deleted */ + if (!isset($crmItems[$id])) { + $this->cancelLoyalty = true; + + return true; + } + + /** If the quantity of goods in a trade item does not match */ + if ($item->get_quantity() !== $crmItems[$id]['quantity']) { + $this->cancelLoyalty = true; + + return true; + } + + foreach ($crmItems[$id]['discounts'] as $discount) { + if (in_array($discount['type'], ['bonus_charge', 'loyalty_level'])) { + $loyaltyDiscount += $discount['amount']; + + break; + } + } + + /** + *If the sum of the trade item including discounts and loyalty program discount exceeds the cost without discounts. + * (Occurs when recalculating an order, deleting/adding coupons) + */ + if (($item->get_total() + $loyaltyDiscount) > $item->get_subtotal()) { + $this->cancelLoyalty = true; + + return true; + } + } + + return false; } } diff --git a/tests/loyalty/test-wc-retailcrm-client-v5.php b/tests/loyalty/test-wc-retailcrm-client-v5.php index 9c5ad52..0db27a9 100644 --- a/tests/loyalty/test-wc-retailcrm-client-v5.php +++ b/tests/loyalty/test-wc-retailcrm-client-v5.php @@ -124,7 +124,7 @@ class WC_Retailcrm_Loyalty_Client_Test extends WC_Retailcrm_Test_Case_Helper ], [ 'method' => 'cancelBonusOrder', - 'parameters' => ['site', ['order']] + 'parameters' => [['order']] ], ]; } diff --git a/tests/test-wc-retailcrm-loyalty.php b/tests/test-wc-retailcrm-loyalty.php index d0e11b3..8e53969 100644 --- a/tests/test-wc-retailcrm-loyalty.php +++ b/tests/test-wc-retailcrm-loyalty.php @@ -330,6 +330,7 @@ class WC_Retailcrm_Loyalty_Test extends WC_Retailcrm_Test_Case_Helper { $products = DataLoyaltyRetailCrm::createProducts(); $user = DataLoyaltyRetailcrm::createUsers()[0]; + $discountLoyalty = 50; $wcOrder = wc_create_order([ 'status' => null, @@ -391,7 +392,7 @@ class WC_Retailcrm_Loyalty_Test extends WC_Retailcrm_Test_Case_Helper $this->setMockResponse($this->apiMock, 'applyBonusToOrder', $response); $this->loyalty = new WC_Retailcrm_Loyalty($this->apiMock, []); - $this->loyalty->applyLoyaltyDiscount($wcOrder, 50, $createdCrmOrderResponse); + $this->loyalty->applyLoyaltyDiscount($wcOrder, $createdCrmOrderResponse, $discountLoyalty); foreach ($wcOrder->get_items() as $id => $item) { $this->assertNotEquals($item->get_total(), $currentItemsPrice[$id]); @@ -456,7 +457,7 @@ class WC_Retailcrm_Loyalty_Test extends WC_Retailcrm_Test_Case_Helper ] ]; - $this->loyalty->applyLoyaltyDiscount($wcOrder, 0, $createdCrmOrderResponse); + $this->loyalty->applyLoyaltyDiscount($wcOrder, $createdCrmOrderResponse); foreach ($wcOrder->get_items() as $id => $item) { $this->assertNotEquals($item->get_total(), $currentItemsPrice[$id]); diff --git a/tests/test-wc-retailcrm-orders.php b/tests/test-wc-retailcrm-orders.php index d8527f1..5c606d4 100644 --- a/tests/test-wc-retailcrm-orders.php +++ b/tests/test-wc-retailcrm-orders.php @@ -642,9 +642,12 @@ class WC_Retailcrm_Orders_Test extends WC_Retailcrm_Test_Case_Helper */ private function getRetailcrmOrders($retailcrm) { + $options = $this->getOptions(); + unset($options['loyalty']); + return new WC_Retailcrm_Orders( $retailcrm, - $this->getOptions(), + $options, new WC_Retailcrm_Order_Item($this->getOptions()), new WC_Retailcrm_Order_Address(), new WC_Retailcrm_Customers( @@ -669,4 +672,3 @@ class WC_Retailcrm_Orders_Test extends WC_Retailcrm_Test_Case_Helper ->getMock(); } } -