setup = $setup; $this->purchasePriceNull = RetailcrmConfigProvider::getCrmPurchasePrice(); $this->settingsService = SettingsService::getInstance([], ''); $this->vatRates = $this->settingsService->vatRates; $this->measures = $this->prepareMeasures($measure); $this->serverName = $serverName; Loader::includeModule("highloadblock"); Loader::includeModule('iblock'); Loader::includeModule('catalog'); } /** * @return \Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer */ public function build(): XmlOffer { $this->xmlOffer = new XmlOffer(); $this->xmlOffer->barcode = $this->barcode; $this->xmlOffer->picture = $this->productPicture; $this->addDataFromParams(); $this->addDataFromItem($this->productProps, $this->categories); return $this->xmlOffer; } /** * @param array $categories */ public function setCategories(array $categories) { $this->categories = $categories; } /** * @param mixed $skuHlParams */ public function setSkuHlParams($skuHlParams): void { $this->skuHlParams = $skuHlParams; } /** * @param mixed $productHlParams */ public function setProductHlParams($productHlParams): void { $this->productHlParams = $productHlParams; } /** * @param \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams $selectParams */ public function setSelectParams(SelectParams $selectParams): void { $this->selectParams = $selectParams; } /** * @param array $productProps */ public function setOfferProps(array $productProps): void { $this->productProps = $productProps; } /** * @param string $barcode */ public function setBarcode(string $barcode): void { $this->barcode = $barcode; } /** * @param \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo $catalogIblockInfo */ public function setCatalogIblockInfo(CatalogIblockInfo $catalogIblockInfo): void { $this->catalogIblockInfo = $catalogIblockInfo; } /** * @param string $getProductPicture */ public function setPicturesPath(string $getProductPicture): void { $this->productPicture = $getProductPicture; } public function setServerName(string $serverName): void { $this->serverName = $serverName; } /** * Добавляет в XmlOffer значения настраиваемых параметров, производителя, вес и габариты */ private function addDataFromParams(): void { $resultParams = array_merge($this->productHlParams, $this->skuHlParams); //достаем значения из обычных свойств $resultParams = array_merge($resultParams, $this->getSimpleParams( $resultParams, $this->selectParams->configurable, $this->productProps )); [$resultParams, $this->xmlOffer->dimensions] = $this->extractDimensionsFromParams( $this->setup->properties, $resultParams, $this->catalogIblockInfo->productIblockId ); [$resultParams, $this->xmlOffer->weight] = $this->extractWeightFromParams( $this->setup->properties, $resultParams, $this->catalogIblockInfo->productIblockId ); [$resultParams, $this->xmlOffer->vendor] = $this->extractVendorFromParams($resultParams); $resultParams = $this->dropEmptyParams($resultParams); $this->xmlOffer->params = $this->createParamObject($resultParams); } /** * Добавляет в объект XmlOffer информацию из GetList * * @param array $item * @param array $categoryIds */ private function addDataFromItem(array $item, array $categoryIds): void { $this->xmlOffer->id = $item['ID']; $this->xmlOffer->productId = $item['ID']; $this->xmlOffer->productType = (int) $item['CATALOG_TYPE']; $this->xmlOffer->quantity = $item['CATALOG_QUANTITY'] ?? ''; $this->xmlOffer->url = $item['DETAIL_PAGE_URL'] ? $this->serverName . $item['DETAIL_PAGE_URL'] : ''; $this->xmlOffer->price = $item['CATALOG_PRICE_' . $this->setup->basePriceId]; $this->xmlOffer->purchasePrice = $this->getPurchasePrice( $item, $this->setup->loadPurchasePrice, $this->purchasePriceNull ); $this->xmlOffer->categoryIds = $categoryIds; $this->xmlOffer->name = $item['NAME']; $this->xmlOffer->xmlId = $item['EXTERNAL_ID'] ?? ''; $this->xmlOffer->productName = $item['NAME']; $this->xmlOffer->vatRate = $this->getVatRate($item); $this->xmlOffer->unitCode = $this->getUnitCode($item['CATALOG_MEASURE'], $item['ID']); $this->xmlOffer->activity = $item['ACTIVE']; $this->xmlOffer->markable = $this->isMarkableOffer($item['ID']); } /** * Возвращает закупочную цену, если она требуется настройками * * @param array $product * @param bool|null $isLoadPrice * @param string $purchasePriceNull * @return float|null */ private function getPurchasePrice(array $product, ?bool $isLoadPrice, string $purchasePriceNull): ?float { if ($isLoadPrice) { if ($product['CATALOG_PURCHASING_PRICE']) { return $product['CATALOG_PURCHASING_PRICE']; } if ($purchasePriceNull === 'Y') { return 0; } } return null; } /** * Возвращает значение активной ставки НДС, если она указана в товаре * * @param array $product * @return string */ private function getVatRate(array $product): string { if ( !empty($product['VAT_ID']) && array_key_exists($product['VAT_ID'], $this->vatRates) && is_string($this->vatRates[$product['VAT_ID']]['RATE']) ) { return $this->vatRates[$product['VAT_ID']]['RATE']; } if ( !empty($product['CATALOG_VAT_ID']) && array_key_exists($product['CATALOG_VAT_ID'], $this->vatRates) && is_string($this->vatRates[$product['CATALOG_VAT_ID']]['RATE']) ) { return $this->vatRates[$product['CATALOG_VAT_ID']]['RATE']; } return 'none'; } /** * Возвращает массив обычных свойств * * @param array $resultParams * @param array $configurableParams * @param array $productProps * @return array */ private function getSimpleParams(array $resultParams, array $configurableParams, array $productProps): array { foreach ($configurableParams as $key => $params) { if (isset($resultParams[$key])) { continue; } $codeWithValue = strtoupper($params) . '_VALUE'; if (isset($productProps[$codeWithValue])) { $resultParams[$key] = $productProps[$codeWithValue]; } elseif (isset($productProps[$params])) { $resultParams[$key] = $productProps[$params]; } } return $resultParams; } /** * Удаляет параметры с пустыми и нулевыми значениями * * @param array $params * @return array */ private function dropEmptyParams(array $params): array { return array_diff($params, ['', 0, '0']); } /** * Разделяем вендора и остальные параметры * * @param array $resultParams * @return array */ private function extractVendorFromParams(array $resultParams): array { $vendor = null; if (isset($resultParams['manufacturer'])) { $vendor = $resultParams['manufacturer']; unset($resultParams['manufacturer']); } return [$resultParams, $vendor]; } /** * Преобразует вес товара в килограммы для ноды weight * * @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetupPropsCategories $xmlSetupPropsCategories * @param array $resultParams * @param int $iblockId * @return array */ private function extractWeightFromParams( XmlSetupPropsCategories $xmlSetupPropsCategories, array $resultParams, int $iblockId ): array { $factors = [ 'mg' => 0.000001, 'g' => 0.001, 'kg' => 1, ]; $unit = ''; if (!empty($xmlSetupPropsCategories->products->names[$iblockId]['weight'])) { $unit = $xmlSetupPropsCategories->products->units[$iblockId]['weight']; } elseif (!empty($xmlSetupPropsCategories->sku->names[$iblockId]['weight'])) { $unit = $xmlSetupPropsCategories->sku->units[$iblockId]['weight']; } if (isset($resultParams['weight'], $factors[$unit])) { $weight = $resultParams['weight'] * $factors[$unit]; } else { $weight = ''; } if (isset($resultParams['weight'])) { unset($resultParams['weight']); } return [$resultParams, $weight]; } /** * Получение данных для ноды dimensions * * Данные должны быть переведены в сантиметры * и представлены в формате Длина/Ширина/Высота * * @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetupPropsCategories $xmlSetupPropsCategories * @param array $resultParams * @param int $iblockId * @return array */ private function extractDimensionsFromParams( XmlSetupPropsCategories $xmlSetupPropsCategories, array $resultParams, int $iblockId ): array { $dimensionsParams = ['length', 'width', 'height']; $dimensions = ''; $factors = [ 'mm' => 0.1, 'cm' => 1, 'm' => 100, ]; foreach ($dimensionsParams as $key => $param) { $unit = ''; if (!empty($xmlSetupPropsCategories->products->names[$iblockId][$param])) { $unit = $xmlSetupPropsCategories->products->units[$iblockId][$param]; } elseif (!empty($xmlSetupPropsCategories->sku->names[$iblockId][$param])) { $unit = $xmlSetupPropsCategories->sku->units[$iblockId][$param]; } if (isset($factors[$unit], $resultParams[$param])) { $dimensions .= $resultParams[$param] * $factors[$unit]; } else { $dimensions .= '0'; } if (count($dimensionsParams) > $key + 1) { $dimensions .= '/'; } if (isset($resultParams[$param])) { unset($resultParams[$param]); } } return [$resultParams, $dimensions === '0/0/0' ? '' : $dimensions]; } /** * Собираем объект параметре заказа * * @param array $params * @return OfferParam[] */ private function createParamObject(array $params): array { $offerParams = []; $names = $this->settingsService->actrualPropList; foreach ($params as $code => $value) { if (!isset($names[$code])) { continue; } $offerParam = new OfferParam(); $offerParam->name = $names[$code]; $offerParam->code = $code; $offerParam->value = $value; $offerParams[] = $offerParam; } return $offerParams; } /** * Собираем объект единицы измерения для товара * * @param int $measureIndex * @return \Intaro\RetailCrm\Model\Bitrix\Xml\Unit */ private function createUnitFromCode(int $measureIndex): Unit { $unit = new Unit(); $unit->name = $this->measures[$measureIndex]['MEASURE_TITLE'] ?? ''; $unit->code = $this->measures[$measureIndex]['SYMBOL_INTL'] ?? ''; $unit->sym = $this->measures[$measureIndex]['SYMBOL_RUS'] ?? ''; return $unit; } /** * Удаляет запрещенные в unit сode символы * * @link https://docs.retailcrm.ru/Developers/modules/ICML * * @param array $measures * * @return array */ private function prepareMeasures(array $measures): array { foreach ($measures as &$measure) { if (isset($measure['SYMBOL_INTL'])) { $measure['SYMBOL_INTL'] = preg_replace("/[^a-zA-Z_\-]/",'', $measure['SYMBOL_INTL']); } } return $measures; } /** * @param array $currentMeasure * * @return \Intaro\RetailCrm\Model\Bitrix\Xml\Unit */ private function createUnitFromProductTable(array $currentMeasure): Unit { $clearCurrentMeasure = $this->prepareMeasures(array_shift($currentMeasure)); $unit = new Unit(); $unit->name = $clearCurrentMeasure['MEASURE']['MEASURE_TITLE'] ?? ''; $unit->code = $clearCurrentMeasure['MEASURE']['SYMBOL_INTL'] ?? ''; $unit->sym = $clearCurrentMeasure['MEASURE']['SYMBOL_RUS'] ?? ''; return $unit; } /** * @param int|null $measureId * @param int $itemId * * @return \Intaro\RetailCrm\Model\Bitrix\Xml\Unit|null */ private function getUnitCode(?int $measureId, int $itemId): ?Unit { if (isset($measureId) && !empty($measureId)) { return $this->createUnitFromCode($measureId); } try { $currentMeasure = ProductTable::getCurrentRatioWithMeasure($itemId); if (is_array($currentMeasure)) { return $this->createUnitFromProductTable($currentMeasure); } } catch (ArgumentException $exception) { Logger::getInstance()->write(GetMessage('UNIT_ERROR'), 'i_crm_load_log'); } return null; } /** * Метод для проверки можно ли маркировать товар. * * @param $offerId * * @return string|null */ private function isMarkableOffer($offerId): ?string { $idHlBlock = $this->getHighloadBlockIdByName('ProductMarkingCodeGroup'); $hlBlock = HighloadBlockTable::getById($idHlBlock)->fetch(); $hlBlockData = HighloadBlockTable::compileEntity($hlBlock)->getDataClass(); $userFieldManager = UserFieldHelper::getInstance()->getManager(); $userFieldsData = $userFieldManager->GetUserFields(ProductTable::getUfId(), $offerId); $ufProductGroup = $userFieldsData['UF_PRODUCT_GROUP']['VALUE'] ?? null; $isMarkableOffer = null; if ($ufProductGroup !== null) { $productGroup = $hlBlockData::getList(["select" => ["UF_NAME"], "filter" => ['ID' => $ufProductGroup]]); $isMarkableOffer = !empty($productGroup->Fetch()['UF_NAME']) ? 'Y' : 'N'; } return $isMarkableOffer; } /** * Метод для получения ID Highload-блока по названию блока. * * Таблица в БД - b_hlsys_marking_code_group * По умолчанию ID Highload-блока ProductMarkingCodeGroup - 1. * * @param $blockName * * @return mixed|null */ private function getHighloadBlockIdByName($blockName) { $hlBlock = HighloadBlockTable::getList(['filter' => ['NAME' => $blockName], 'select' => ['ID']])->fetch(); return $hlBlock['ID'] ?? 1; } }