* @license MIT * @link http://retailcrm.ru * @see http://retailcrm.ru/docs */ use Bitrix\Main\ArgumentException; use Bitrix\Main\ArgumentNullException; use Bitrix\Main\ArgumentOutOfRangeException; use Bitrix\Main\Context; use Bitrix\Main\NotSupportedException; use Bitrix\Main\SystemException; use Bitrix\Sale\Basket; use Bitrix\Sale\Delivery\Services\EmptyDeliveryService; use Bitrix\Sale\Delivery\Services\Manager; use Bitrix\Sale\Fuser; use Bitrix\Sale\Internals\PaymentTable; use Bitrix\Sale\Location\Search\Finder; use Bitrix\Sale\Order; use Bitrix\Sale\OrderUserProperties; use Bitrix\Sale\Payment; use Intaro\RetailCrm\Component\ServiceLocator; use Intaro\RetailCrm\Component\Builder\Bitrix\LoyaltyDataBuilder; use Intaro\RetailCrm\Service\ManagerService; use Intaro\RetailCrm\Service\OrderLoyaltyDataService; use Intaro\RetailCrm\Component\ConfigProvider; use Intaro\RetailCrm\Component\Constants; use Intaro\RetailCrm\Component\Handlers\EventsHandlers; use RetailCrm\Response\ApiResponse; IncludeModuleLangFile(__FILE__); /** * Class RetailCrmHistory * * @category RetailCRM * @package RetailCRM\History */ class RetailCrmHistory { public static $MODULE_ID = 'intaro.retailcrm'; public static $CRM_API_HOST_OPTION = 'api_host'; public static $CRM_API_KEY_OPTION = 'api_key'; public static $CRM_ORDER_TYPES_ARR = 'order_types_arr'; public static $CRM_DELIVERY_TYPES_ARR = 'deliv_types_arr'; public static $CRM_PAYMENT_TYPES = 'pay_types_arr'; public static $CRM_PAYMENT_STATUSES = 'pay_statuses_arr'; public static $CRM_PAYMENT = 'payment_arr'; public static $CRM_ORDER_LAST_ID = 'order_last_id'; public static $CRM_SITES_LIST = 'sites_list'; public static $CRM_ORDER_PROPS = 'order_props'; public static $CRM_LEGAL_DETAILS = 'legal_details'; public static $CRM_CUSTOM_FIELDS = 'custom_fields'; public static $CRM_CONTRAGENT_TYPE = 'contragent_type'; public static $CRM_ORDER_FAILED_IDS = 'order_failed_ids'; public static $CRM_ORDER_HISTORY = 'order_history'; public static $CRM_CUSTOMER_CORPORATE_HISTORY = 'customer_corp_history'; public static $CRM_CATALOG_BASE_PRICE = 'catalog_base_price'; public static $CRM_ORDER_NUMBERS = 'order_numbers'; public static $CRM_CANSEL_ORDER = 'cansel_order'; public static $CRM_CURRENCY = 'currency'; public static $CRM_DISCOUNT_ROUND = 'discount_round'; public static $CUSTOM_FIELDS_IS_ACTIVE = 'N'; const PAGE_LIMIT = 25; private static $optionsPayment = []; public static function customerHistory() { if (!RetailcrmDependencyLoader::loadDependencies()) { return false; } $page = 1; $historyFilter = []; $historyStart = RetailcrmConfigProvider::getCustomersHistorySinceId(); $api = new RetailCrm\ApiClient(RetailcrmConfigProvider::getApiUrl(), RetailcrmConfigProvider::getApiKey()); $matchedCustomFields = RetailcrmConfigProvider::getMatchedUserFields(); $matchedCustomFields = is_array($matchedCustomFields) ? array_flip($matchedCustomFields) : []; if (method_exists(RCrmActions::class, 'getTypeUserField') && method_exists(RCrmActions::class, 'convertCrmValueToCmsField') ) { self::$CUSTOM_FIELDS_IS_ACTIVE = RetailcrmConfigProvider::getCustomFieldsStatus(); } $customUserFieldTypes = []; if (self::$CUSTOM_FIELDS_IS_ACTIVE === 'Y') { $customUserFieldTypes = RCrmActions::getTypeUserField(); } if ($historyStart && $historyStart > 0) { $historyFilter['sinceId'] = $historyStart; } do { $historyResponse = RCrmActions::apiMethod($api, 'customersHistory', __METHOD__, $historyFilter); if ( !$historyResponse instanceof ApiResponse || !$historyResponse->isSuccessful() || empty($historyResponse['history']) || empty($historyResponse['pagination']) ) { return false; } $customerHistory = $historyResponse['history']; Logger::getInstance()->write($customerHistory, 'customerHistory'); $customers = self::assemblyCustomer($customerHistory); $GLOBALS['RETAIL_CRM_HISTORY'] = true; // Set sinceId for customer history $lastId = array_pop($customerHistory); $historyFilter['sinceId'] = $lastId['id']; RetailcrmConfigProvider::setCustomersHistorySinceId($lastId['id']); $newUser = new CUser(); $customerBuilder = new CustomerBuilder(); foreach ($customers as $customer) { if (function_exists('retailCrmBeforeCustomerSave')) { $newResCustomer = retailCrmBeforeCustomerSave($customer); if (is_array($newResCustomer) && !empty($newResCustomer)) { $customer = $newResCustomer; } elseif ($newResCustomer === false) { RCrmActions::eventLog('RetailCrmHistory::customerHistory', 'retailCrmBeforeCustomerSave()', 'UserCrmId = ' . $customer['id'] . '. Sending canceled after retailCrmBeforeCustomerSave'); continue; } } if (isset($customer['deleted'])) { continue; } if (RetailcrmConfigProvider::isPhoneRequired()) { if (empty($customer['phones'])) { Logger::getInstance()->write('$customer["phones"] is empty. Customer ' . $customer['id'] . ' cannot be created', 'createCustomerError'); continue; } } if (isset($customer['externalId']) && !is_numeric($customer['externalId'])) { unset($customer['externalId']); } $customerBuilder->setDataCrm($customer)->build(); $customFields = self::getCustomUserFields($customer, $matchedCustomFields, $customUserFieldTypes); if (!isset($customer['externalId'])) { if (!isset($customer['id'])) { continue; } $registerNewUser = true; if (!empty($customer['email'])) { $dbUser = CUser::GetList(($by = 'ID'), ($sort = 'DESC'), ['=EMAIL' => $customer['email']]); switch ($dbUser->SelectedRowsCount()) { case 0: $login = $customer['email']; $customerBuilder->setLogin($login); break; case 1: $arUser = $dbUser->Fetch(); $registeredUserID = $arUser['ID']; $registerNewUser = false; break; default: $lastBitrixUser = $dbUser->Fetch(); $registeredUserID = $lastBitrixUser['ID']; $registerNewUser = false; break; } } if ($registerNewUser === true) { $customerBuilder->buildPassword(); $registeredUserID = $newUser->Add(self::getDataUser($customerBuilder, $customFields)); if ($registeredUserID === false) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'CUser::Register', 'Error register user: ' . $newUser->LAST_ERROR ); continue; } if ( RCrmActions::apiMethod ( $api, 'customersFixExternalIds', __METHOD__, [['id' => $customer['id'], 'externalId' => $registeredUserID]] ) == false ) { continue; } } $customer['externalId'] = $registeredUserID; } if (isset($customer['externalId'])) { $customerBuilder->setDataCrm($customer); if (isset($customer['phones'])) { $customerBuilder->setUser( CUser::GetList( ($by = "ID"), ($order = "desc"), ['ID' => $customer['externalId']], ['FIELDS' => ['PERSONAL_PHONE', 'PERSONAL_MOBILE']] )->fetch() ); } $customerArray = $customerBuilder->getCustomer()->getObjectToArray(); $customerArray = array_merge($customerArray, $customFields); $queryUpdate = $newUser->Update($customer['externalId'], self::convertBooleanFields($customerArray)); if (!$queryUpdate) { RCrmActions::eventLog( 'RetailCrmHistory::customerHistory', 'Error update user', $newUser->LAST_ERROR ); } if (function_exists('retailCrmAfterCustomerSave')) { retailCrmAfterCustomerSave($customer); } } $customerBuilder->reset(); } $GLOBALS['RETAIL_CRM_HISTORY'] = false; $page++; if ($page > self::PAGE_LIMIT) { break; } } while ($historyResponse['pagination']['currentPage'] < $historyResponse['pagination']['totalPageCount']); } /** * @return bool * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\ArgumentNullException * @throws \Bitrix\Main\ArgumentOutOfRangeException * @throws \Bitrix\Main\ArgumentTypeException * @throws \Bitrix\Main\NotImplementedException * @throws \Bitrix\Main\NotSupportedException * @throws \Bitrix\Main\ObjectException * @throws \Bitrix\Main\ObjectNotFoundException * @throws \Bitrix\Main\ObjectPropertyException * @throws \Bitrix\Main\SystemException */ public static function orderHistory(): bool { global $USER; if (is_object($USER) === false) { $USER = new RetailUser(); } if (!RetailcrmDependencyLoader::loadDependencies()) { return false; } $optionsOrderTypes = RetailcrmConfigProvider::getOrderTypes(); $optionsDelivTypes = array_flip(RetailcrmConfigProvider::getDeliveryTypes()); $optionsPayStatuses = array_flip(RetailcrmConfigProvider::getPaymentStatuses()); // --statuses $optionsOrderProps = RetailcrmConfigProvider::getOrderProps(); $optionsLegalDetails = RetailcrmConfigProvider::getLegalDetails(); $optionsOrderNumbers = RetailcrmConfigProvider::getOrderNumbers(); $optionsCanselOrder = RetailcrmConfigProvider::getCancellableOrderPaymentStatuses(); $currency = RetailcrmConfigProvider::getCurrencyOrDefault(); $contragentTypes = array_flip(RetailcrmConfigProvider::getContragentTypes()); $shipmentDeducted = RetailcrmConfigProvider::getShipmentDeducted(); $optionsPayment = [ 'payTypes' => array_flip(RetailcrmConfigProvider::getPaymentTypes()), 'paymentList' => array_flip(RetailcrmConfigProvider::getPayment()), ]; if (RetailcrmConfigProvider::getSyncIntegrationPayment() === 'Y') { $substitutedPayment = RetailcrmConfigProvider::getSubstitutionPaymentList(); foreach ($substitutedPayment as $origCode => $subsCode) { if (isset($optionsPayment['payTypes'][$origCode])) { $optionsPayment['payTypes'][$subsCode] = $optionsPayment['payTypes'][$origCode]; } } } self::$optionsPayment = $optionsPayment; unset($optionsPayment); $matchedCustomUserFields = RetailcrmConfigProvider::getMatchedUserFields() ?? []; $matchedCustomUserFields = is_array($matchedCustomUserFields) ? array_flip($matchedCustomUserFields) : []; $matchedCustomOrderFields = RetailcrmConfigProvider::getMatchedOrderProps() ?? []; $matchedCustomOrderFields = is_array($matchedCustomOrderFields) ? array_flip($matchedCustomOrderFields) : []; if (method_exists(RCrmActions::class, 'getTypeUserField') && method_exists(RCrmActions::class, 'convertCrmValueToCmsField') ) { self::$CUSTOM_FIELDS_IS_ACTIVE = RetailcrmConfigProvider::getCustomFieldsStatus(); } $customUserFieldTypes = []; if (self::$CUSTOM_FIELDS_IS_ACTIVE === 'Y') { $customUserFieldTypes = RCrmActions::getTypeUserField(); } $api = new RetailCrm\ApiClient(RetailcrmConfigProvider::getApiUrl(), RetailcrmConfigProvider::getApiKey()); $page = 1; /* @var OrderLoyaltyDataService $orderLoyaltyDataService */ $orderLoyaltyDataService = ServiceLocator::get(OrderLoyaltyDataService::class); $historyFilter = []; $historyStart = COption::GetOptionString(self::$MODULE_ID, self::$CRM_ORDER_HISTORY); if ($historyStart && $historyStart > 0) { $historyFilter['sinceId'] = $historyStart; } do { $historyResponse = RCrmActions::apiMethod($api, 'ordersHistory', __METHOD__, $historyFilter); if ( !$historyResponse instanceof ApiResponse || !$historyResponse->isSuccessful() || empty($historyResponse['history']) || empty($historyResponse['pagination']) ) { return false; } $orderHistory = $historyResponse['history']; Logger::getInstance()->write($orderHistory, 'orderHistory'); $orders = self::assemblyOrder($orderHistory); $GLOBALS['RETAIL_CRM_HISTORY'] = true; // Set sinceId for order history $lastId = array_pop($orderHistory); $historyFilter['sinceId'] = $lastId['id']; COption::SetOptionString(self::$MODULE_ID, self::$CRM_ORDER_HISTORY, $lastId['id']); //orders with changes foreach ($orders as $order) { if (function_exists('retailCrmBeforeOrderSave')) { $newResOrder = retailCrmBeforeOrderSave($order); if (is_array($newResOrder) && !empty($newResOrder)) { $order = $newResOrder; } elseif ($newResOrder === false) { RCrmActions::eventLog('RetailCrmHistory::orderHistory', 'retailCrmBeforeOrderSave()', 'OrderCrmId = ' . $order['id'] . '. Sending canceled after retailCrmBeforeOrderSave' ); continue; } } Logger::getInstance()->write($order, 'assemblyOrderHistory'); if (isset($order['deleted'])) { if (isset($order['externalId'])) { self::cancelOrder($order['externalId']); } continue; } $site = self::getSite($order['site']); if (empty($site)) { RCrmActions::eventLog( __CLASS__ . '::' . __METHOD__, 'Bitrix\Sale\Order::create', 'Site = ' . $order['site'] . ' not found in setting. Order crm id=' . $order['id'] ); continue; } if (isset($order['customer']['externalId']) && !is_numeric($order['customer']['externalId'])) { unset($order['customer']['externalId']); } $corporateCustomerBuilder = new CorporateCustomerBuilder(); $corporateContact = []; $orderCustomerExtId = $order['customer']['externalId'] ?? null; $corporateCustomerBuilder->setOrderCustomerExtId($orderCustomerExtId) ->setContragentTypes($contragentTypes) ->setDataCrm($order) ->build(); $corporateCustomerBuilder->getCustomerBuilder()->buildPassword(); if (RetailCrmOrder::isOrderCorporate($order)) { // Fetch contact only if we think it's data is not fully present in order if (!empty($order['contact'])) { if (isset($order['contact']['email'])) { $corporateContact = $order['contact']; $orderCustomerExtId = $corporateContact['externalId'] ?? null; $corporateCustomerBuilder->setCorporateContact($corporateContact) ->setOrderCustomerExtId($orderCustomerExtId); } else { $response = false; if (isset($order['contact']['externalId'])) { $response = RCrmActions::apiMethod( $api, 'customersGet', __METHOD__, $order['contact']['externalId'], $order['site'] ); } elseif (isset($order['contact']['id'])) { $response = RCrmActions::apiMethod( $api, 'customersGetById', __METHOD__, $order['contact']['id'], $order['site'] ); } if ($response && isset($response['customer'])) { $corporateContact = $response['customer']; $orderCustomerExtId = $corporateContact['externalId'] ?? null; $corporateCustomerBuilder->setCorporateContact($corporateContact) ->setOrderCustomerExtId($orderCustomerExtId); } } } } if (!isset($order['externalId'])) { if (ConfigProvider::useCrmOrderMethods() === 'Y') { $orderMethods = ConfigProvider::getCrmOrderMethods(); // 1. Клиент активировал опцию, но не выбрал способы оформления - пропускаем все заказы. // 2. Если способа оформления заказа нет в выбранном в настройках списке - пропускаем заказ. if ($orderMethods === [] || !in_array($order['orderMethod'], $orderMethods, true)) { continue; } } if (empty($orderCustomerExtId)) { if (!isset($order['customer']['id']) || (RetailCrmOrder::isOrderCorporate($order) && (!isset($order['contact']['id']) || !isset($order['customer']['id']))) ) { continue; } if (RetailcrmConfigProvider::isPhoneRequired()) { if (empty($order['customer']['phones'])) { Logger::getInstance()->write('$customer["phones"] is empty. Order ' . $order['id'] . ' cannot be created', 'createCustomerError'); continue; } } $login = null; $registerNewUser = true; if (!isset($order['customer']['email']) || empty($order['customer']['email'])) { if (RetailCrmOrder::isOrderCorporate($order) && !empty($corporateContact['email'])) { $login = $corporateContact['email']; $order['customer']['email'] = $corporateContact['email']; $corporateCustomerBuilder->setLogin($login) ->setEmail($corporateContact['email']); } else { $login = uniqid('user_' . time()) . '@example.com'; $order['customer']['email'] = $login; $corporateCustomerBuilder->setLogin($login) ->setEmail($login); } } $dbUser = CUser::GetList( ($by = 'ID'), ($sort = 'DESC'), ['=EMAIL' => $order['customer']['email']] ); switch ($dbUser->SelectedRowsCount()) { case 0: $login = $order['customer']['email']; $corporateCustomerBuilder->setLogin($login); break; case 1: $arUser = $dbUser->Fetch(); $registeredUserID = $arUser['ID']; $registerNewUser = false; break; default: $lastBitrixUser = $dbUser->Fetch(); $registeredUserID = $lastBitrixUser['ID']; $registerNewUser = false; break; } if ($registerNewUser === true) { $userData = RetailCrmOrder::isOrderCorporate($order) ? $corporateContact : $order['customer']; $corporateCustomerBuilder->setCorporateContact($userData); $newUser = new CUser(); $customerArray = $corporateCustomerBuilder->getCustomer()->getObjectToArray(); $customFields = self::getCustomUserFields($userData, $matchedCustomUserFields, $customUserFieldTypes); $customerArray = array_merge($customerArray, $customFields); if (!array_key_exists('UF_SUBSCRIBE_USER_EMAIL', $customerArray)) { $customerArray['UF_SUBSCRIBE_USER_EMAIL'] = 'Y'; } $registeredUserID = $newUser->Add(self::convertBooleanFields($customerArray)); if ($registeredUserID === false) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'CUser::Register', 'Error register user' . $newUser->LAST_ERROR ); continue; } if(RCrmActions::apiMethod( $api, 'customersFixExternalIds', __METHOD__, [[ 'id' => $order['customer']['id'], 'externalId' => $registeredUserID ]]) == false ) { continue; } } $orderCustomerExtId = isset($registeredUserID) ? $registeredUserID : null; $corporateCustomerBuilder->setOrderCustomerExtId($orderCustomerExtId); } $buyerProfileToAppend = []; if (RetailCrmOrder::isOrderCorporate($order) && !empty($order['company'])) { $buyerProfile = $corporateCustomerBuilder->getBuyerProfile()->getObjectToArray(); $buyerProfileToAppend = OrderUserProperties::getList([ "filter" => $buyerProfile ])->fetch(); if (empty($buyerProfileToAppend)) { $buyerProfileInstance = new CSaleOrderUserProps(); if ($buyerProfileInstance->Add($buyerProfile)) { $buyerProfileToAppend = OrderUserProperties::getList([ "filter" => $buyerProfile ])->fetch(); } } } $newOrder = Order::create($site, $orderCustomerExtId, $currency); self::setManager($newOrder, $order); if (isset($buyerProfileToAppend['ID']) && isset($optionsLegalDetails['legalName'])) { $newOrder->setFields([ $optionsLegalDetails['legalName'] => $buyerProfileToAppend['NAME'], 'PERSON_TYPE_ID' => $buyerProfileToAppend['PERSON_TYPE_ID'], ]); } if (!is_object($newOrder) || !$newOrder instanceof Order) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'Bitrix\Sale\Order::create', 'Error order create' ); continue; } $externalId = $newOrder->getId(); $order['externalId'] = $externalId; } if (isset($order['externalId'])) { $itemUpdate = false; if ($order['externalId'] && is_numeric($order['externalId'])) { try { $newOrder = Order::load($order['externalId']); } catch (ArgumentNullException $e) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'Bitrix\Sale\Order::load', $e->getMessage() . ': ' . $order['externalId'] ); continue; } } if (!isset($newOrder)) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'Bitrix\Sale\Order::load', 'Error order load number=' . $order['number'] ); continue; } $site = self::getSite($order['site']); if (null === $site) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'Bitrix\Sale\Order::edit', sprintf( 'Site = %s not found in settings. Order number = %s', $order['site'], $order['number'] ) ); continue; } self::setManager($newOrder, $order); $propsRemove = false; $personType = $newOrder->getField('PERSON_TYPE_ID'); if (RetailCrmOrder::isOrderCorporate($order) || (!empty($order['contragentType']) && in_array($order['contragentType'], ['legal-entity', 'enterpreneur'])) ) { $personType = $contragentTypes['legal-entity']; $newOrder->setField('PERSON_TYPE_ID', $personType); $propsRemove = true; } else { if (isset($order['orderType']) && $order['orderType']) { $nType = []; $tList = RCrmActions::OrderTypesList([['LID' => $site]]); foreach ($tList as $type) { if (isset($optionsOrderTypes[$type['ID']])) { $nType[$optionsOrderTypes[$type['ID']]] = $type['ID']; } } $newOptionsOrderTypes = $nType; if ($newOptionsOrderTypes[$order['orderType']]) { if ($personType != $newOptionsOrderTypes[$order['orderType']] && $personType != 0) { $propsRemove = true; } $personType = $newOptionsOrderTypes[$order['orderType']]; $newOrder->setField('PERSON_TYPE_ID', $personType); } elseif ($personType == 0) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'orderType not found', 'PERSON_TYPE_ID = 0' ); } } } //status if ($optionsPayStatuses[$order['status']]) { $newOrder->setField('STATUS_ID', $optionsPayStatuses[$order['status']]); if ( is_array($optionsCanselOrder) && in_array($optionsPayStatuses[$order['status']], $optionsCanselOrder) ) { self::unreserveShipment($newOrder); $newOrder->setFieldNoDemand('CANCELED', 'Y'); } else { $newOrder->setFieldNoDemand('CANCELED', 'N'); } } if (array_key_exists('statusComment', $order)) { self::setProp( $newOrder, RCrmActions::fromJSON($order['statusComment']), 'REASON_CANCELED' ); } //props $propertyCollection = $newOrder->getPropertyCollection(); $propertyCollectionArr = $propertyCollection->getArray(); $nProps = []; foreach ($propertyCollectionArr['properties'] as $orderProp) { if ($orderProp['ID'][0] == 'n') { $orderProp['ID'] = substr($orderProp['ID'], 1); $property = $propertyCollection->getItemById($orderProp['ID']); if ($property) { $orderProp['ID'] = $property->getField('ORDER_PROPS_ID'); } else { continue; } } $nProps[] = $orderProp; } $orderDump = []; $propertyCollectionArr['properties'] = $nProps; if ($propsRemove) {//delete props foreach ($propertyCollectionArr['properties'] as $orderProp) { if ($orderProp['PROPS_GROUP_ID'] == 0) { $somePropValue = $propertyCollection->getItemByOrderPropertyId($orderProp['ID']); self::setProp($somePropValue); } } $orderCrm = RCrmActions::apiMethod($api, 'orderGet', __METHOD__, $order['id']); $orderDump = $order; $order = $orderCrm['order']; } $propsKey = []; foreach ($propertyCollectionArr['properties'] as $prop) { if ($prop['PROPS_GROUP_ID'] != 0) { $propsKey[$prop['CODE']]['ID'] = $prop['ID']; $propsKey[$prop['CODE']]['TYPE'] = $prop['TYPE']; } } // fio if ($order['firstName'] || $order['lastName'] || $order['patronymic']) { $fio = ''; foreach ($propertyCollectionArr['properties'] as $prop) { if (in_array($optionsOrderProps[$personType]['fio'], $prop)) { $getFio = $newOrder->getPropertyCollection()->getItemByOrderPropertyId($prop['ID']); if (method_exists($getFio, 'getValue')) { $fio = $getFio->getValue(); } } } $fio = RCrmActions::explodeFio($fio); $newFio = []; if ($fio) { $newFio[] = isset($order['lastName']) ? RCrmActions::fromJSON($order['lastName']) : ($fio['lastName'] ?? ''); $newFio[] = isset($order['firstName']) ? RCrmActions::fromJSON($order['firstName']) : ($fio['firstName'] ?? ''); $newFio[] = isset($order['patronymic']) ? RCrmActions::fromJSON($order['patronymic']) : ($fio['patronymic'] ?? ''); } else { $newFio[] = isset($order['lastName']) ? RCrmActions::fromJSON($order['lastName']) : ''; $newFio[] = isset($order['firstName']) ? RCrmActions::fromJSON($order['firstName']) : ''; $newFio[] = isset($order['patronymic']) ? RCrmActions::fromJSON($order['patronymic']) : ''; } $order['fio'] = trim(implode(' ', $newFio)); } if (array_key_exists('fio', $order)) { $order['fio'] = str_replace("clear", "", $order['fio']); } //optionsOrderProps if ($optionsOrderProps[$personType]) { foreach ($optionsOrderProps[$personType] as $key => $orderProp) { if (array_key_exists($key, $order)) { $somePropValue = $propertyCollection ->getItemByOrderPropertyId($propsKey[$orderProp]['ID']); if ($key == 'fio' && '' !== trim($order['fio'])) { self::setProp($somePropValue, $order[$key]); } else { self::setProp($somePropValue, RCrmActions::fromJSON($order[$key])); } } elseif ($propsKey[$orderProp]['TYPE'] === 'LOCATION' && ( isset($order['delivery']['address']['city']) || isset($order['delivery']['address']['region']) ) ) { try { $parameters = [ 'filter' => [ 'NAME.LANGUAGE_ID' => 'ru' ], 'limit' => 1, 'select' => ['*'] ]; $somePropValue = $propertyCollection ->getItemByOrderPropertyId($propsKey[$orderProp]['ID']) ; $codeLocation = $somePropValue->getValue(); $subParameters = $parameters; $subParameters['filter']['=CODE'] = RCrmActions::fromJSON($codeLocation); $subLocation = Finder::find($subParameters)->fetch(); if (!isset($order['delivery']['address']['city'])) { $parameters['filter']['=ID'] = RCrmActions::fromJSON($subLocation['CITY_ID']); } else { // В системе город пишется с префиксом (г. Москва) $loc = explode('.', $order['delivery']['address']['city']); if (count($loc) === 1) { $parameters['filter']['=NAME.NAME'] = RCrmActions::fromJSON(trim($loc[0])); } else { $parameters['filter']['=NAME.NAME'] = RCrmActions::fromJSON(trim($loc[1])); } } if (!isset($order['delivery']['address']['region'])) { $parameters['filter']['=PARENT.ID'] = RCrmActions::fromJSON($subLocation['REGION_ID']); } else { $parameters['filter']['PARENT.NAME.NAME'] = RCrmActions::fromJSON(trim($order['delivery']['address']['region'])); } $location = Finder::find($parameters)->fetch(); //При существовании района в локации, фильтр по региону изменяется if (empty($location)) { if (!isset($order['delivery']['address']['region'])) { $parameters['filter']['=PARENT.PARENT.ID'] = RCrmActions::fromJSON($subLocation['REGION_ID']); unset($parameters['filter']['=PARENT.ID']); } else { $parameters['filter']['PARENT.PARENT.NAME.NAME'] = RCrmActions::fromJSON(trim($order['delivery']['address']['region'])); unset($parameters['filter']['PARENT.NAME.NAME']); } $location = Finder::find($parameters)->fetch(); } if (!empty($location)) { self::setProp($somePropValue, $location['CODE']); } else { if (isset($order['externalId'])) { $message = 'ExternalId: ' . $order['externalId']; } else { $message = 'CRM id: ' . $order['id']; } RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'RetailCrmHistory::setProp', 'Ошибка обновления локации в заказе. ' . $message ); } } catch (\Exception $exception) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'RetailCrmHistory::setProp', 'Error when updating a location. Order: ' . $order['number'] . ' message:' . $exception->getMessage() ); } } elseif (is_array($order['delivery']['address']) && array_key_exists($key, $order['delivery']['address'])) { $somePropValue = $propertyCollection ->getItemByOrderPropertyId($propsKey[$orderProp]['ID']); self::setProp( $somePropValue, RCrmActions::fromJSON($order['delivery']['address'][$key]) ); } } } if (isset($order['company']['id'])) { if (!empty($order['company']['name'])) { $order["legalName"] = $order['company']['name']; } } // Corporate clients section if ($optionsLegalDetails[$personType]) { foreach ($optionsLegalDetails[$personType] as $key => $orderProp) { if (array_key_exists($key, $order)) { $somePropValue = $propertyCollection ->getItemByOrderPropertyId($propsKey[$orderProp]['ID']); self::setProp($somePropValue, RCrmActions::fromJSON($order[$key])); } elseif(is_array($order['contragent']) && array_key_exists($key, $order['contragent'])) { $somePropValue = $propertyCollection ->getItemByOrderPropertyId($propsKey[$orderProp]['ID']); self::setProp($somePropValue, RCrmActions::fromJSON($order['contragent'][$key])); } elseif (isset($order['company']) && (array_key_exists($key, $order['company']) || array_key_exists( lcfirst(str_replace('legal', '', $key)), $order['company']) ) ) { $somePropValue = $propertyCollection ->getItemByOrderPropertyId($propsKey[$orderProp]['ID']); // fallback for order[company][name] if ($key == 'legalName') { $key = 'name'; } self::setProp( $somePropValue, RCrmActions::fromJSON( $key == 'legalAddress' ? ($order['company']['address']['text'] ?? '') : $order['company'][$key] ) ); } elseif (isset($order['company']['contragent']) && array_key_exists($key, $order['company']['contragent']) ) { $somePropValue = $propertyCollection ->getItemByOrderPropertyId($propsKey[$orderProp]['ID']); self::setProp($somePropValue, RCrmActions::fromJSON($order['company']['contragent'][$key])); } } } if ($propsRemove) { $order = $orderDump; } //comments if (array_key_exists('customerComment', $order)) { self::setProp($newOrder, RCrmActions::fromJSON($order['customerComment']), 'USER_DESCRIPTION'); } if (array_key_exists('managerComment', $order)) { self::setProp($newOrder, RCrmActions::fromJSON($order['managerComment']), 'COMMENTS'); } //items $basket = $newOrder->getBasket(); if (!$basket) { $basket = Basket::create($site); $newOrder->setBasket($basket); } $fUserId = $basket->getFUserId(true); if (!$fUserId) { $fUserId = Fuser::getIdByUserId($order['customer']['externalId']); $basket->setFUserId($fUserId); } if (isset($order['contact']['id'])) { $ExtId = null; if (isset($order['contact']['externalId'])) { $ExtId = $order['contact']['externalId']; } if (isset($ExtId)) { $newOrder->setFieldNoDemand('USER_ID', $ExtId); } else { $response = RCrmActions::apiMethod( $api, 'customersGetById', __METHOD__, $order['contact']['id'], $order['site'] ); if (!$response) { Logger::getInstance()->write( sprintf('Заказ %s не выгружен. Не удалось получить клиента', $order['id']), 'orderAgent' ); continue; } $newUser = new CUser(); $customerBuilder = new CustomerBuilder(); $customerBuilder->setDataCrm($response['customer'])->build(); if (!empty($response['customer']['email'])) { $registerNewUser = true; $dbUser = CUser::GetList( ($by = 'ID'), ($sort = 'DESC'), ['=EMAIL' => $response['customer']['email']] ); switch ($dbUser->SelectedRowsCount()) { case 0: $login = $response['customer']['email']; $customerBuilder->setLogin($login); $customerBuilder->buildPassword(); break; case 1: $arUser = $dbUser->Fetch(); $registeredUserID = $arUser['ID']; $registerNewUser = false; break; default: $lastBitrixUser = $dbUser->Fetch(); $registeredUserID = $lastBitrixUser['ID']; $registerNewUser = false; break; } if ($registerNewUser === true) { $customFields = self::getCustomUserFields($response['customer'], $matchedCustomUserFields, $customUserFieldTypes); $registeredUserID = $newUser->Add(self::getDataUser($customerBuilder, $customFields)); if ($registeredUserID === false) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'CUser::Register', 'Error register user: ' . $newUser->LAST_ERROR ); continue; } if(RCrmActions::apiMethod( $api, 'customersFixExternalIds', __METHOD__, [[ 'id' => $response['customer']['id'], 'externalId' => $registeredUserID ]] ) == false ) { continue; } } $newOrder->setFieldNoDemand('USER_ID', $registeredUserID); } } } $editBasketInfo = []; $deleteBasketInfo = []; $bonusesChargeTotal = null; $loyaltyDiscount = null; if (isset($order['items'])) { $itemUpdate = true; $response = RCrmActions::apiMethod($api, 'orderGet', __METHOD__, $order['id']); if (isset($response['order'])) { $orderTemp = $response['order']; $duplicateItems = []; foreach ($orderTemp['items'] as $item) { $duplicateItems[$item['id']]['externalId'] = $item['offer']['externalId']; $duplicateItems[$item['id']]['quantity'] += $item['quantity']; $duplicateItems[$item['id']]['discountTotal'] += $item['quantity'] * $item['discountTotal']; $duplicateItems[$item['id']]['initialPrice'] = (float) $item['initialPrice']; $duplicateItems[$item['id']]['price_sum'] = ($item['quantity'] * $item['initialPrice']) - ($item['quantity'] * $item['discountTotal']); } unset($orderTemp); } else { continue; } $collectItems = []; foreach ($duplicateItems as $it) { $collectItems[$it['externalId']]['quantity'] += $it['quantity']; $collectItems[$it['externalId']]['price_sum'] += $it['price_sum']; $collectItems[$it['externalId']]['discountTotal_sum'] += $it['discountTotal']; if (isset($collectItems[$it['externalId']]['initialPrice_max'])) { if ($collectItems[$it['externalId']]['initialPrice_max'] < $it['initialPrice']) { $collectItems[$it['externalId']]['initialPrice_max'] = $it['initialPrice']; } } else { $collectItems[$it['externalId']]['initialPrice_max'] = $it['initialPrice']; } $collectItems[$it['externalId']]['initialPricesList'][] = $it['initialPrice']; } foreach ($collectItems as $key => $itemData) { if (count($itemData['initialPricesList']) > 1) { $discountDelta = 0; foreach ($itemData['initialPrices'] as $initialPriceItem) { $delta = $itemData['initialPrice_max'] - (float) $initialPriceItem; if ($delta !== 0) { $discountDelta += $delta; } } $collectItems[$key]['discountTotal_sum'] += $discountDelta; } } Logger::getInstance()->write($duplicateItems, 'duplicateItemsOrderHistory'); Logger::getInstance()->write($collectItems, 'collectItemsOrderHistory'); $optionDiscRound = COption::GetOptionString(self::$MODULE_ID, self::$CRM_DISCOUNT_ROUND, 0); foreach ($order['items'] as $product) { if ($collectItems[$product['offer']['externalId']]['quantity']) { $product['quantity'] = $collectItems[$product['offer']['externalId']]['quantity']; } $item = self::getExistsItem($basket, 'catalog', $product['offer']['externalId']); if (!$item) { if ($product['delete']) { continue; } $item = $basket->createItem('catalog', $product['offer']['externalId']); if ($item instanceof \Bitrix\Sale\BasketItem) { $elem = self::getInfoElement($product['offer']['externalId']); $vatRate = null; if (RetailcrmConfigProvider::getOrderVat() === 'Y') { if ($product['vatRate'] === 0 || $product['vatRate'] === 'none') { $vatRate = 0; } elseif($product['vatRate'] !== null) { $vatRate = $product['vatRate'] / 100; } } $item->setFields([ 'CURRENCY' => $newOrder->getCurrency(), 'LID' => $site, 'BASE_PRICE' => $collectItems[$product['offer']['externalId']]['initialPrice_max'], 'NAME' => $product['offer']['name'] ? RCrmActions::fromJSON($product['offer']['name']) : $elem['NAME'], 'DETAIL_PAGE_URL' => $elem['URL'], 'PRODUCT_PROVIDER_CLASS' => 'CCatalogProductProvider', 'DIMENSIONS' => $elem['DIMENSIONS'], 'WEIGHT' => $elem['WEIGHT'], 'NOTES' => GetMessage('PRICE_TYPE'), 'PRODUCT_XML_ID' => $elem["XML_ID"], 'CATALOG_XML_ID' => $elem["IBLOCK_XML_ID"], 'VAT_INCLUDED' => $vatRate !== null ? 'Y' : 'N', 'VAT_RATE' => $vatRate, ]); } else { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'createItem', 'Error item add' ); continue; } } if ($product['delete'] && $collectItems[$product['offer']['externalId']]['quantity'] <= 0) { if (is_int($item->getBasketCode())) { $deleteBasketInfo[] = $item->getBasketCode(); } $item->delete(); continue; } if ($product['quantity']) { $item->setFieldNoDemand('QUANTITY', $product['quantity']); } if (array_key_exists('initialPrice_max', $collectItems[$product['offer']['externalId']])) { $item->setField( 'BASE_PRICE', $collectItems[$product['offer']['externalId']]['initialPrice_max'] ); } if (array_key_exists('discountTotal_sum', $collectItems[$product['offer']['externalId']])) { $item->setField('CUSTOM_PRICE', 'Y'); $item->setField('DISCOUNT_NAME', ''); $item->setField('DISCOUNT_VALUE', ''); // Полную цену позиции с учётом скидок делим на количество - получаем цену каждой единицы // товара с учётом скидок. $price = $collectItems[ $product['offer']['externalId'] ]['price_sum'] / $collectItems[$product['offer']['externalId']]['quantity']; if ('Y' == $optionDiscRound) { $price = self::truncate($price, 2); } $item->setField('PRICE', $price); } $manualProductDiscount = 0; $bonusesChargeProduct = $product['bonusesCharge'] ?? null; foreach ($product['discounts'] as $productDiscount) { if ('manual_product' === $productDiscount['type']) { $manualProductDiscount = $productDiscount['amount']; } if ('loyalty_level' === $productDiscount['type']) { $loyaltyDiscount += $productDiscount['amount']; } } $editBasketInfo[] = [ 'productId' => $item->getProductId(), 'productDiscount' => $manualProductDiscount / $item->getQuantity(), 'bonusesCharge' => $bonusesChargeProduct, ]; if (null !== $bonusesChargeProduct) { $bonusesChargeTotal += $bonusesChargeProduct; } } } self::orderSave($newOrder); if ($optionsOrderNumbers === 'Y' && isset($order['number'])) { $newOrder->setField('ACCOUNT_NUMBER', $order['number']); } $orderSumm = 0; foreach ($basket as $item) { $orderSumm += $item->getFinalPrice(); } if (is_array($order['delivery']) && array_key_exists('cost', $order['delivery'])) { $deliverySumm = $order['delivery']['cost']; } else { $deliverySumm = $newOrder->getDeliveryPrice(); } $orderSumm += $deliverySumm; $order['summ'] = $orderSumm; //payment $newHistoryPayments = []; if (array_key_exists('payments', $order)) { if (!isset($orderCrm)) { $orderCrm = RCrmActions::apiMethod($api, 'orderGet', __METHOD__, $order['id']); } if ($orderCrm) { self::paymentsUpdate($newOrder, $orderCrm['order'], $newHistoryPayments); } } //delivery if (array_key_exists('delivery', $order)) { $itemUpdate = true; //delete empty if (!isset($orderCrm)) { $orderCrm = RCrmActions::apiMethod($api, 'orderGet', __METHOD__, $order['id']); } if ($orderCrm) { self::deliveryUpdate($newOrder, $optionsDelivTypes, $orderCrm['order']); } } if ($itemUpdate === true && $newOrder->getField('CANCELED') !== 'Y') { self::shipmentItemReset($newOrder); } if (isset($orderCrm)) { unset($orderCrm); } if (isset($order['fullPaidAt']) && is_string($order['fullPaidAt'])) { $newOrder->setFieldNoDemand('PAYED', 'Y'); } if ($shipmentDeducted === 'Y') { $collection = $newOrder->getShipmentCollection()->getNotSystemItems(); if (isset($order['shipped'])) { if ($order['shipped']) { if ($collection->count() === 0) { $collection = $newOrder->getShipmentCollection(); $shipment = $collection->createItem(); $shipment->setField('DEDUCTED', 'Y'); } else { foreach ($collection as $shipment) { $shipment->setField('DEDUCTED', 'Y'); } } } else { foreach ($collection as $shipment) { $shipment->setField('DEDUCTED', 'N'); } } } } $newOrder->setField('PRICE', $orderSumm); if (self::$CUSTOM_FIELDS_IS_ACTIVE === 'Y' && !empty($matchedCustomOrderFields)) { foreach ($order['customFields'] as $code => $value) { if (isset($matchedCustomOrderFields[$code])) { $arrayIdentifier = explode('#', $matchedCustomOrderFields[$code], 2); $property = $propertyCollection->getItemByOrderPropertyId($arrayIdentifier[0]); $value = RCrmActions::convertCrmValueToCmsField($value, $property->getType()); $queryResult = $property->setField('VALUE', $value); if (!$queryResult->isSuccess()) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'CustomOrderPropSave', 'Error when saving a property: ' . $arrayIdentifier[1] ); } } } } self::orderSave($newOrder); //items loyalty info to HL if (!empty($editBasketInfo)) { $newBasket = $newOrder->getBasket(); $calculateItemsInput = []; $basketItemIds = []; foreach ($editBasketInfo as $itemInfo) { $basketItem = self::getExistsItem($newBasket, 'catalog', $itemInfo['productId']); if (false !== $basketItem) { $calculateItemsInput[$basketItem->getId()]['SHOP_ITEM_DISCOUNT'] = $itemInfo['productDiscount']; $calculateItemsInput[$basketItem->getId()]['BONUSES_CHARGE'] = $itemInfo['bonusesCharge']; $basketItemIds[] = $basketItem->getId(); } } $orderLoyaltyDataService->saveBonusAndDiscToOrderProps( $newOrder->getPropertyCollection(), $loyaltyDiscount, $bonusesChargeTotal ); $hlInfoBuilder = new LoyaltyDataBuilder(); $hlInfoBuilder->setOrder($newOrder); $hlInfoBuilder->setCalculateItemsInput($calculateItemsInput); $hlInfoBuilder->setBonusCountTotal($bonusesChargeTotal); $orderLoyaltyDataService->saveLoyaltyInfoToHl($hlInfoBuilder->build($basketItemIds)->getResult()); self::orderSave($newOrder); } if (!empty($deleteBasketInfo)) { $orderLoyaltyDataService->deleteLoyaltyInfoFromHl($deleteBasketInfo); } if (!empty($newHistoryPayments)) { foreach ($newOrder->getPaymentCollection() as $orderPayment) { if (is_array($newHistoryPayments) && array_key_exists($orderPayment->getField('XML_ID'), $newHistoryPayments) ) { $paymentId = $orderPayment->getId(); $paymentExternalId = RCrmActions::generatePaymentExternalId($paymentId); if (is_null($paymentId)) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'paymentsUpdate', 'Save payment error, order=' . $order['number'] ); continue; } $newHistoryPayments[$orderPayment->getField('XML_ID')]['externalId'] = $paymentExternalId; RCrmActions::apiMethod( $api, 'paymentEditById', __METHOD__, $newHistoryPayments[$orderPayment->getField('XML_ID')] ); if ($paymentId) { PaymentTable::update($paymentId, ['XML_ID' => '']); } } } } if (!$order['externalId']) { $order["externalId"] = $newOrder->getId(); if (RCrmActions::apiMethod( $api, 'ordersFixExternalIds', __METHOD__, [['id' => $order['id'], 'externalId' => $newOrder->getId()]]) == false ) { continue; } } if (function_exists('retailCrmAfterOrderSave')) { retailCrmAfterOrderSave($order); } } unset($newOrder); } $GLOBALS['RETAIL_CRM_HISTORY'] = false; $page++; if ($page > self::PAGE_LIMIT) { break; } } while ($historyResponse['pagination']['currentPage'] < $historyResponse['pagination']['totalPageCount']); return false; } /** * @param string $shopCode * * @return string|null */ public static function getSite(string $shopCode): ?string { $optionsSitesList = RetailcrmConfigProvider::getSitesList(); if ($optionsSitesList) { $searchResult = array_search($shopCode, $optionsSitesList, true); return !empty($searchResult) ? (string) $searchResult : null; } $defaultSite = CSite::GetDefSite(); return is_string($defaultSite) ? $defaultSite : null; } /** * @param $array * @param $value * * @return array */ public static function search_array_by_value($array, $value) { $results = []; if (is_array($array)) { $found = array_search($value, $array); if ($found) { $results[] = $found; } foreach ($array as $subarray) $results = array_merge($results, static::search_array_by_value($subarray, $value)); } return $results; } public static function assemblyCustomer($customerHistory) { $customerHistory = self::filterHistory($customerHistory, 'customer'); $server = Context::getCurrent()->getServer()->getDocumentRoot(); $fields = []; if (file_exists($server . '/bitrix/modules/intaro.retailcrm/classes/general/config/objects.xml')) { $objects = simplexml_load_file($server . '/bitrix/modules/intaro.retailcrm/classes/general/config/objects.xml'); foreach ($objects->fields->field as $object) { $fields[(string)$object["group"]][(string)$object["id"]] = (string)$object; } } $customers = []; foreach ($customerHistory as $change) { $change['customer'] = self::removeEmpty($change['customer']); if ($customers[$change['customer']['id']]) { $customers[$change['customer']['id']] = array_merge($customers[$change['customer']['id']], $change['customer']); } else { $customers[$change['customer']['id']] = $change['customer']; } if ($change['customer']['contragent']['contragentType']) { $change['customer']['contragentType'] = self::newValue($change['customer']['contragent']['contragentType']); unset($change['customer']['contragent']); } if ($change['field'] == 'segments') { if ($change['newValue']['code'] == "genshchini") { $customers[$change['customer']['id']]["sex"] = "F"; } } if ($change['field'] == 'segments') { if ($change['newValue']['code'] == "mugchini") { $customers[$change['customer']['id']]["sex"] = "M"; } } if ($fields['customer'][$change['field']] == 'phones') { if (is_array($customers[$change['customer']['id']]['phones'])) { $key = count($customers[$change['customer']['id']]['phones']); } if (isset($change['oldValue'])) { $customers[$change['customer']['id']]['phones'][$key]['old_number'] = $change['oldValue']; } if (isset($change['newValue'])) { $customers[$change['customer']['id']]['phones'][$key]['number'] = $change['newValue']; } } else { if ($fields['customerAddress'][$change['field']]) { $customers[$change['customer']['id']]['address'][$fields['customerAddress'][$change['field']]] = $change['newValue']; } elseif ($fields['customerContragent'][$change['field']]) { $customers[$change['customer']['id']]['contragent'][$fields['customerContragent'][$change['field']]] = $change['newValue']; } elseif ($fields['customer'][$change['field']]) { $customers[$change['customer']['id']][$fields['customer'][$change['field']]] = self::newValue($change['newValue']); } elseif (strripos($change['field'], 'custom_') !== false) { $customers[$change['customer']['id']]['customFields'][str_replace('custom_', '', $change['field'])] = self::newValue($change['newValue']); } if (isset($change['created'])) { $customers[$change['customer']['id']]['create'] = 1; } if (isset($change['deleted'])) { $customers[$change['customer']['id']]['deleted'] = 1; } } } return $customers; } public static function assemblyOrder($orderHistory) { $orderHistory = self::filterHistory($orderHistory, 'order'); $server = Context::getCurrent()->getServer()->getDocumentRoot(); if (file_exists($server . '/bitrix/modules/intaro.retailcrm/classes/general/config/objects.xml')) { $objects = simplexml_load_file( $server . '/bitrix/modules/intaro.retailcrm/classes/general/config/objects.xml' ); foreach ($objects->fields->field as $object) { $fields[(string)$object["group"]][(string)$object["id"]] = (string)$object; } } $orders = []; foreach ($orderHistory as $change) { $change['order'] = self::removeEmpty($change['order']); unset($change['order']['status']); if ($change['order']['items']) { $items = []; foreach ($change['order']['items'] as $item) { if (isset($change['created'])) { $item['create'] = 1; } $items[$item['id']] = $item; } $change['order']['items'] = $items; } if ($change['field'] == 'number') { $orders[$change['order']['id']]['number'] = $change['newValue']; } if ($change['field'] == 'manager') { $orders[$change['order']['id']]['managerId'] = $change['newValue']; } if (isset($change['oldValue']) && $change['field'] == 'customer') { $orders[$change['order']['id']]['customer'] = $change['newValue']; } if (isset($change['oldValue']) && $change['field'] == 'contact') { $orders[$change['order']['id']]['contact'] = $change['newValue']; } if (isset($change['oldValue']) && $change['field'] == 'company') { $orders[$change['order']['id']]['company'] = $change['newValue']; } if ($change['order']['payments']) { $payments = []; foreach ($change['order']['payments'] as $payment) { $payments[$payment['id']] = $payment; } $change['order']['payments'] = $payments; } if (is_array($change['order']['contragent']) && isset($change['order']['contragent']) && count($change['order']['contragent']) > 0) { foreach ($change['order']['contragent'] as $name => $value) { $change['order'][$name] = self::newValue($value); } unset($change['order']['contragent']); } if ($orders[$change['order']['id']]) { $orders[$change['order']['id']] = array_merge($orders[$change['order']['id']], $change['order']); } else { $orders[$change['order']['id']] = $change['order']; } if ($change['item']) { if ($orders[$change['order']['id']]['items'][$change['item']['id']]) { $orders[$change['order']['id']]['items'][$change['item']['id']] = array_merge($orders[$change['order']['id']]['items'][$change['item']['id']], $change['item']); } else { $orders[$change['order']['id']]['items'][$change['item']['id']] = $change['item']; } if (empty($change['oldValue']) && $change['field'] == 'order_product') { $orders[$change['order']['id']]['items'][$change['item']['id']]['create'] = 1; unset($orders[$change['order']['id']]['items'][$change['item']['id']]['delete']); } if (empty($change['newValue']) && $change['field'] == 'order_product') { $orders[$change['order']['id']]['items'][$change['item']['id']]['delete'] = 1; } if (/*!$orders[$change['order']['id']]['items'][$change['item']['id']]['create'] && */$fields['item'][$change['field']]) { $orders[$change['order']['id']]['items'][$change['item']['id']][$fields['item'][$change['field']]] = $change['newValue']; } } elseif ($change['payment']) { if ($orders[$change['order']['id']]['payments'][$change['payment']['id']]) { $orders[$change['order']['id']]['payments'][$change['payment']['id']] = array_merge($orders[$change['order']['id']]['payments'][$change['payment']['id']], $change['payment']); } else { $orders[$change['order']['id']]['payments'][$change['payment']['id']] = $change['payment']; } if (empty($change['oldValue']) && $change['field'] == 'payments') { $orders[$change['order']['id']]['payments'][$change['payment']['id']]['create'] = 1; unset($orders[$change['order']['id']]['payments'][$change['payment']['id']]['delete']); } if (empty($change['newValue']) && $change['field'] == 'payments') { $orders[$change['order']['id']]['payments'][$change['payment']['id']]['delete'] = 1; } if (!$orders[$change['order']['id']]['payments'][$change['payment']['id']]['create'] && $fields['payment'][$change['field']]) { $orders[$change['order']['id']]['payments'][$change['payment']['id']][$fields['payment'][$change['field']]] = $change['newValue']; } } else { if ($fields['delivery'][$change['field']] == 'service') { $orders[$change['order']['id']]['delivery']['service']['code'] = self::newValue($change['newValue']); } elseif ($fields['delivery'][$change['field']]) { $orders[$change['order']['id']]['delivery'][$fields['delivery'][$change['field']]] = self::newValue($change['newValue']); } elseif ($fields['orderAddress'][$change['field']]) { $orders[$change['order']['id']]['delivery']['address'][$fields['orderAddress'][$change['field']]] = $change['newValue']; } elseif ($fields['integrationDelivery'][$change['field']]) { $orders[$change['order']['id']]['delivery']['service'][$fields['integrationDelivery'][$change['field']]] = self::newValue($change['newValue']); } elseif ($fields['customerContragent'][$change['field']]) { $orders[$change['order']['id']][$fields['customerContragent'][$change['field']]] = self::newValue($change['newValue']); } elseif (strripos($change['field'], 'custom_') !== false) { $orders[$change['order']['id']]['customFields'][str_replace('custom_', '', $change['field'])] = self::newValue($change['newValue']); } elseif ($fields['order'][$change['field']]){ $orders[$change['order']['id']][$fields['order'][$change['field']]] = self::newValue($change['newValue']); } if (isset($change['created'])) { $orders[$change['order']['id']]['create'] = 1; } if (isset($change['deleted'])) { $orders[$change['order']['id']]['deleted'] = 1; } } if ($change['field'] == 'last_name') { if (true == is_null($change['newValue'])) { $orders[$change['order']['id']]['lastName'] = 'clear'; } } if ($change['field'] == 'first_Name') { if (true == is_null($change['newValue'])) { $orders[$change['order']['id']]['firstName'] = 'clear'; } } if ($change['field'] == 'patronymic') { if (true == is_null($change['newValue'])) { $orders[$change['order']['id']]['patronymic'] = 'clear'; } } } return $orders; } /** * Filters out history by these terms: * - Changes from current API key will be added only if CMS changes are more actual than history. * - All other changes will be merged as usual. * It fixes these problems: * - Changes from current API key are merged when it's not needed. * - Changes from CRM can overwrite more actual changes from CMS due to ignoring current API key changes. * * @param array $historyEntries Raw history from CRM * @param string $recordType Entity field name, e.g. `customer` or `order`. * * @return array */ private static function filterHistory($historyEntries, $recordType) { $history = []; $organizedHistory = []; $notOurChanges = []; foreach ($historyEntries as $entry) { if (!isset($entry[$recordType]['externalId'])) { if ($entry['source'] == 'api' && isset($change['apiKey']['current']) && $entry['apiKey']['current'] == true && $entry['field'] != 'externalId' ) { continue; } $history[] = $entry; continue; } $externalId = $entry[$recordType]['externalId']; $field = $entry['field']; if (!isset($organizedHistory[$externalId])) { $organizedHistory[$externalId] = []; } if (!isset($notOurChanges[$externalId])) { $notOurChanges[$externalId] = []; } if ($entry['source'] == 'api' && isset($entry['apiKey']['current']) && $entry['apiKey']['current'] == true ) { if (isset($notOurChanges[$externalId][$field]) || $entry['field'] == 'externalId') { $organizedHistory[$externalId][] = $entry; } else { continue; } } else { $organizedHistory[$externalId][] = $entry; $notOurChanges[$externalId][$field] = true; } } unset($notOurChanges); foreach ($organizedHistory as $historyChunk) { $history = array_merge($history, $historyChunk); } return $history; } /** * Update shipment in order * * @param \Bitrix\Sale\Order $bitrixOrder * @param array $optionsDelivTypes * @param array $crmOrder * * @return bool|null * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\ArgumentNullException * @throws \Bitrix\Main\ObjectNotFoundException * @throws \Bitrix\Main\SystemException */ public static function deliveryUpdate(Bitrix\Sale\Order $bitrixOrder, $optionsDelivTypes, $crmOrder) { if (!$bitrixOrder instanceof Bitrix\Sale\Order) { return false; } if ($bitrixOrder->getId()) { $update = true; } else { $update = false; } $crmCode = $crmOrder['delivery']['code'] ?? false; $noDeliveryId = EmptyDeliveryService::getEmptyDeliveryServiceId(); if ($crmCode === false || !isset($optionsDelivTypes[$crmCode])) { $deliveryId = $noDeliveryId; } else { $deliveryId = $optionsDelivTypes[$crmCode]; if (isset($crmOrder['delivery']['service']['code'])) { $deliveryCode = Manager::getCodeById($deliveryId); $serviceCode = $crmOrder['delivery']['service']['code']; $service = Manager::getService($deliveryId); if (is_object($service)) { $services = $service->getProfilesList(); if (!array_key_exists($serviceCode, $services)) { $serviceCode = strtoupper($serviceCode); $serviceCode = str_replace('-', "_", $serviceCode); } } if ($deliveryCode) { try { $deliveryService = Manager::getObjectByCode($deliveryCode . ':' . $serviceCode); } catch (SystemException $systemException) { RCrmActions::eventLog('RetailCrmHistory::deliveryEdit', '\Bitrix\Sale\Delivery\Services\Manager::getObjectByCode', $systemException->getMessage()); } if (isset($deliveryService)) { $deliveryId = $deliveryService->getId(); } } } } $delivery = Manager::getObjectById($deliveryId); $shipmentColl = $bitrixOrder->getShipmentCollection(); if ($delivery) { $baseFields = [ 'BASE_PRICE_DELIVERY' => $crmOrder['delivery']['cost'], 'PRICE_DELIVERY' => $crmOrder['delivery']['cost'], 'CURRENCY' => $bitrixOrder->getCurrency(), 'DELIVERY_NAME' => $delivery->getName(), 'CUSTOM_PRICE_DELIVERY' => 'Y', ]; if (RetailcrmConfigProvider::getTrackNumberStatus() === 'Y') { $baseFields['TRACKING_NUMBER'] = $crmOrder['delivery']['data']['trackNumber'] ?? ''; } //В коллекции всегда есть одна скрытая системная доставка, к которой относятся нераспределенные товары //Поэтому, если есть только системная доставка, то нужно создать новую if (!$update || $shipmentColl->count() === 1) { $shipment = $shipmentColl->createItem($delivery); $shipment->setFields($baseFields); } else { $baseFields['DELIVERY_ID'] = $deliveryId; foreach ($shipmentColl as $shipment) { if (!$shipment->isSystem()) { $shipment->setFields($baseFields); } } } } return true; } /** * Update shipment item colletion * * @param \Bitrix\Sale\Order $order * * @return void | boolean */ public static function shipmentItemReset($order) { $shipmentCollection = $order->getShipmentCollection(); $basket = $order->getBasket(); foreach ($shipmentCollection as $shipment) { if (!$shipment->isSystem() && !$shipment->isShipped()) { $reserved = false; if ($shipment->needReservation()) { $reserved = true; } $shipmentItemColl = $shipment->getShipmentItemCollection(); if ($reserved === true) { $shipment->tryUnreserve(); } try { $shipmentItemColl->resetCollection($basket); if ($reserved === true) { $shipment->tryReserve(); } } catch (\Bitrix\Main\NotSupportedException $NotSupportedException) { RCrmActions::eventLog('RetailCrmHistory::shipmentItemReset', '\Bitrix\Sale\ShipmentItemCollection::resetCollection()', $NotSupportedException->getMessage()); return false; } } } } /** * Unreserve items if order canceled * * @param \Bitrix\Sale\Order $order * * @return void | boolean */ public static function unreserveShipment($order) { $shipmentCollection = $order->getShipmentCollection(); foreach ($shipmentCollection as $shipment) { if (!$shipment->isSystem()) { try { $shipment->tryUnreserve(); } catch (ArgumentOutOfRangeException $ArgumentOutOfRangeException) { RCrmActions::eventLog('RetailCrmHistory::unreserveShipment', '\Bitrix\Sale\Shipment::tryUnreserve()', $ArgumentOutOfRangeException->getMessage()); return false; } catch (NotSupportedException $NotSupportedException) { RCrmActions::eventLog('RetailCrmHistory::unreserveShipment', '\Bitrix\Sale\Shipment::tryUnreserve()', $NotSupportedException->getMessage()); return false; } } } } /** * Update payment in order * * @param object $order * @param array $paymentsCrm * @param array $newHistoryPayments * * @return void */ public static function paymentsUpdate($order, $paymentsCrm, &$newHistoryPayments = []) { $optionsPayTypes = self::$optionsPayment['payTypes']; $optionsPayment = self::$optionsPayment['paymentList']; $allPaymentSystems = RCrmActions::PaymentList(); foreach ($allPaymentSystems as $allPaymentSystem) { $arPaySysmems[$allPaymentSystem['ID']] = $allPaymentSystem['NAME']; } $paymentsList = []; $paymentColl = $order->getPaymentCollection(); foreach ($paymentColl as $paymentData) { $data = $paymentData->getFields()->getValues(); $paymentsList[$data['ID']] = $paymentData; } //data from crm $paySumm = 0; foreach ($paymentsCrm['payments'] as $paymentCrm) { if (isset($paymentCrm['externalId']) && !empty($paymentCrm['externalId'])) { //find the payment $nowPaymentId = RCrmActions::getFromPaymentExternalId($paymentCrm['externalId']); $nowPayment = $paymentsList[$nowPaymentId]; //update data if ($nowPayment instanceof Payment) { $nowPayment->setField('SUM', $paymentCrm['amount']); if ($optionsPayTypes[$paymentCrm['type']] != $nowPayment->getField('PAY_SYSTEM_ID')) { $nowPayment->setField('PAY_SYSTEM_ID', $optionsPayTypes[$paymentCrm['type']]); $nowPayment->setField('PAY_SYSTEM_NAME', $arPaySysmems[$optionsPayTypes[$paymentCrm['type']]]); } if (isset($optionsPayment[$paymentCrm['status']])) { $nowPayment->setField('PAID', $optionsPayment[$paymentCrm['status']]); } unset($paymentsList[$nowPaymentId]); } } elseif (array_key_exists($paymentCrm['type'], $optionsPayTypes)) { $newHistoryPayments[$paymentCrm['id']] = $paymentCrm; $newPayment = $paymentColl->createItem(); $newPayment->setField('SUM', $paymentCrm['amount']); $newPayment->setField('PAY_SYSTEM_ID', $optionsPayTypes[$paymentCrm['type']]); $newPayment->setField('PAY_SYSTEM_NAME', $arPaySysmems[$optionsPayTypes[$paymentCrm['type']]]); $newPayment->setField('PAID', $optionsPayment[$paymentCrm['status']] ? $optionsPayment[$paymentCrm['status']] : 'N'); $newPayment->setField('CURRENCY', $order->getCurrency()); $newPayment->setField('IS_RETURN', 'N'); $newPayment->setField('PRICE_COD', '0.00'); $newPayment->setField('EXTERNAL_PAYMENT', 'N'); $newPayment->setField('UPDATED_1C', 'N'); $newPayment->setField('XML_ID', $paymentCrm['id']); $newPayment->setField('ACCOUNT_NUMBER', $order->getField('ACCOUNT_NUMBER')); $newPaymentId = $newPayment->getId(); unset($paymentsList[$newPaymentId]); } else { RCrmActions::eventLog('RetailCrmHistory::orderHistory', 'paymentsUpdate', 'Save payment error, incorrect type: ' . $paymentCrm['type']); } if ($optionsPayment[$paymentCrm['status']] == 'Y') { $paySumm += $paymentCrm['amount']; } } foreach ($paymentsList as $payment) { if ($payment->isPaid()) { $payment->setPaid("N"); } $payment->delete(); } if ($paymentsCrm['totalSumm'] == $paySumm) { $order->setFieldNoDemand('PAYED', 'Y'); } else { $order->setFieldNoDemand('PAYED', 'N'); } } public static function newValue($value) { if (is_array($value) && array_key_exists('code', $value)) { return $value['code']; } else { return $value; } } public static function removeEmpty($inputArray) { $outputArray = []; if (!empty($inputArray)) { foreach ($inputArray as $key => $element) { if (!empty($element) || $element === 0 || $element === '0') { if (is_array($element)) { $element = self::removeEmpty($element); } $outputArray[$key] = $element; } } } return $outputArray; } /** * setProp * * @param \Bitrix\Sale\PropertyValueBase|\Bitrix\Sale\Order $obj * @param string $value * @param string $prop * * @return bool * @throws \Bitrix\Main\ArgumentNullException * @throws \Bitrix\Main\ArgumentOutOfRangeException * @throws \Bitrix\Main\NotImplementedException * @throws \Bitrix\Main\ObjectNotFoundException * @throws \Bitrix\Main\ArgumentException */ public static function setProp($obj, $value = '', $prop = '') { if (!isset($obj) || empty($obj)) { return false; } if ($prop && $value || $prop && !$value) { $obj->setField($prop, $value); } elseif ($value && !$prop) { $obj->setValue($value); } elseif (!$value && !$prop) { $obj->delete(); } return true; } public static function getExistsItem($basket, $moduleId, $productId) { foreach ($basket as $basketItem) { $itemExists = ($basketItem->getField('PRODUCT_ID') == $productId && $basketItem->getField('MODULE') == $moduleId); if ($itemExists) { return $basketItem; } } return false; } public static function setManager($newOrder, $order) { if (array_key_exists('managerId', $order)) { $service = ManagerService::getInstance(); $newOrder->setField('RESPONSIBLE_ID', $service->getManagerBitrixId($order['managerId'])); // If the order exists in CRM, need to save the changes in the CMS order if (!empty($order['externalId'])) { self::orderSave($newOrder); } } } public static function getInfoElement($offerId) { $elementInfo = CIBlockElement::GetByID($offerId)->fetch(); $url = CAllIBlock::ReplaceDetailUrl($elementInfo['DETAIL_PAGE_URL'], $elementInfo, false, 'E'); $catalog = CCatalogProduct::GetByID($offerId); $info = [ 'NAME' => $elementInfo['NAME'], 'URL' => $url, 'DIMENSIONS' => serialize( [ 'WIDTH' => $catalog['WIDTH'], 'HEIGHT' => $catalog['HEIGHT'], 'LENGTH' => $catalog['LENGTH'], ] ), 'WEIGHT' => $catalog['WEIGHT'], 'XML_ID' => $elementInfo["XML_ID"], 'IBLOCK_XML_ID' => $elementInfo["IBLOCK_EXTERNAL_ID"] ]; return $info; } /** * @param $order * * @return boolean */ private static function orderSave($order) { try { $order->save(); return true; } catch (\Exception $exception) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'Order saving', $exception->getMessage() ); return false; } } /** * Truncate a float number * * @param float $val * @param int f Number of precision * * @return float */ public static function truncate($val, $precision = "0") { if(($p = strpos($val, '.')) !== false || ($p = strpos($val, ',')) !== false ) { $val = floatval(substr($val, 0, $p + 1 + $precision)); } return $val; } /** * @param mixed $externalId * * @return void */ public static function cancelOrder($externalId) { try { $newOrder = Order::load($externalId); } catch (ArgumentNullException $e) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'Bitrix\Sale\Order::load', $e->getMessage() . ': ' . $externalId ); return; } if (!$newOrder instanceof Order) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'Bitrix\Sale\Order::load', 'Error order load: ' . $externalId ); return; } try { $newOrder->setField('CANCELED', 'Y'); $newOrder->save(); } catch (Exception $exception) { RCrmActions::eventLog( 'RetailCrmHistory::orderHistory', 'Bitrix\Sale\Order::cancelOrder', 'Error order canceled: ' . $externalId ); } } /** * @param array $array * @return array */ public static function convertBooleanFields($array) { foreach ($array as $key => $value) { if ($value === 'N') { $array[$key] = false; } } return $array; } /** * @param $customerBuilder * @return array */ private static function getDataUser($customerBuilder, $customFields) { $customerArray = $customerBuilder->getCustomer()->getObjectToArray(); if (!array_key_exists('UF_SUBSCRIBE_USER_EMAIL', $customerArray)) { $customerArray['UF_SUBSCRIBE_USER_EMAIL'] = 'Y'; } $customerArray = array_merge($customerArray, $customFields); return self::convertBooleanFields($customerArray); } private static function getCustomUserFields($customer, $matchedCustomFields, $customUserFieldTypes) { $customFields = []; if (self::$CUSTOM_FIELDS_IS_ACTIVE === 'Y' && isset($customer['customFields']) && is_array($customer['customFields']) ) { foreach ($customer['customFields'] as $code => $value) { if (isset($matchedCustomFields[$code]) && !empty($customUserFieldTypes[$matchedCustomFields[$code]])) { $type = $customUserFieldTypes[$matchedCustomFields[$code]]; $customFields[$matchedCustomFields[$code]] = RCrmActions::convertCrmValueToCmsField($value, $type); } } } return $customFields; } }