From 4fa66768add0d2c93f587b1796cc4cdb5017d437 Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 8 Feb 2019 17:13:44 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix generating merchandise of excluded subcategories, Adding new functionality * fix bugs MoySkladICMLParser.php and fix README.md * fix MoySkladICMLParser.php and README.md * fix readme --- MoySkladICMLParser.php | 210 ++++++++++++++++++++++++++++++++--------- README.md | 40 +++++++- 2 files changed, 198 insertions(+), 52 deletions(-) diff --git a/MoySkladICMLParser.php b/MoySkladICMLParser.php index fe475c9..70f5e07 100644 --- a/MoySkladICMLParser.php +++ b/MoySkladICMLParser.php @@ -87,28 +87,27 @@ class MoySkladICMLParser */ public function generateICML() { - $assortiment = $this->parseAssortiment(); $countAssortiment = count($assortiment); - - if ($countAssortiment > 0) { - $categories = $this->parserFolder(); - } else { - $categories = array(); - } - - $icml = $this->ICMLCreate($categories, $assortiment); + $categories = $this->parserFolder(); $countCategories = count($categories); + if ($countCategories > 0) { + $assortiment = $this->deleteProduct($categories, $assortiment); + } else { + return; + } + + $icml = $this->ICMLCreate($categories, $assortiment); + if ($countCategories > 0 && $countAssortiment > 0) { $icml->asXML($this->getFilePath()); } - } /** * @param string $url - * @return JSON + * @return string */ protected function requestJson($url) { @@ -146,11 +145,11 @@ class MoySkladICMLParser $result = json_decode($responseBody, true); if ($statusCode >= 400) { - throw new Exception( - $this->getError($result) . + throw new Exception( + $this->getError($result) . " [errno = $errno, error = $error]", $statusCode - ); + ); } return $result; @@ -163,6 +162,7 @@ class MoySkladICMLParser */ protected function parserFolder() { + $categories = []; $offset = 0; $end = null; $ignoreCategories = $this->getIgnoreProductGroupsInfo(); @@ -174,7 +174,7 @@ class MoySkladICMLParser } while (true) { - + try { $response = $this->requestJson(self::BASE_URL . self::FOLDER_LIST_URL . '?expand=productFolder&limit=100&offset=' . $offset); } catch (Exception $e) { @@ -321,6 +321,7 @@ class MoySkladICMLParser } $products[$assortiment['id']] = array( + 'uuid' => $assortiment['id'], 'id' => !empty($assortiment['product']['externalCode']) ? ($assortiment['product']['externalCode'] . '#' . $assortiment['externalCode']) : $assortiment['externalCode'], @@ -330,13 +331,6 @@ class MoySkladICMLParser 'name' => $assortiment['name'], 'productName'=> isset($assortiment['product']['name']) ? $assortiment['product']['name'] : $assortiment['name'], - 'purchasePrice' => isset($assortiment['buyPrice']['value']) ? - (((float)$assortiment['buyPrice']['value']) / 100) : - ( - isset($assortiment['product']['buyPrice']['value']) ? - (((float)$assortiment['product']['buyPrice']['value']) / 100) : - 0 - ), 'weight' => isset($assortiment['weight']) ? $assortiment['weight'] : $assortiment['product']['weight'], @@ -353,6 +347,29 @@ class MoySkladICMLParser '' ), ); + if (isset($this->options['customFields'])) { + if (!empty($assortiment['attributes'])) { + $products[$assortiment['id']]['customFields'] = $this->getCustomFields($assortiment['attributes']); + } elseif (!empty($assortiment['product']['attributes'])){ + $products[$assortiment['id']]['customFields'] = $this->getCustomFields($assortiment['product']['attributes']); + } + } + + if (!empty($assortiment['barcodes'])){ + $products[$assortiment['id']]['barcodes'] = $assortiment['barcodes']; + } elseif (!empty($assortiment['product']['barcodes'])){ + $products[$assortiment['id']]['barcodes'] = $assortiment['product']['barcodes']; + } + + if (isset($this->options['loadPurchasePrice']) && $this->options['loadPurchasePrice'] === true) { + if (isset($assortiment['buyPrice']['value'])) { + $products[$assortiment['id']]['purchasePrice'] = (((float)$assortiment['buyPrice']['value']) / 100); + } elseif (isset($assortiment['product']['buyPrice']['value'])) { + $products[$assortiment['id']]['purchasePrice'] = (((float)$assortiment['product']['buyPrice']['value']) / 100); + } else { + $products[$assortiment['id']]['purchasePrice'] = 0; + } + } if (isset($assortiment['salePrices'][0]['value']) && $assortiment['salePrices'][0]['value'] != 0) { $products[$assortiment['id']]['price'] = (((float)$assortiment['salePrices'][0]['value']) / 100); @@ -407,7 +424,11 @@ class MoySkladICMLParser } elseif (isset($assortiment['product']['productFolder']['externalCode'])) { $products[$assortiment['id']]['categoryId'] = $assortiment['product']['productFolder']['externalCode']; } else { - $products[$assortiment['id']]['categoryId'] = ''; + $products[$assortiment['id']]['categoryId'] = 'warehouseRoot'; + } + + if ($products[$assortiment['id']]['categoryId'] == 'warehouseRoot') { + $this->noCategory = true; } if (isset($assortiment['article'])) { @@ -425,10 +446,6 @@ class MoySkladICMLParser } else { $products[$assortiment['id']]['vendor'] = ''; } - - if ($products[$assortiment['id']]['categoryId'] == null) { - $this->noCategory = true; - } if ($urlImage != '') { $products[$assortiment['id']]['image']['imageUrl'] = $urlImage; @@ -495,14 +512,35 @@ class MoySkladICMLParser $this->icmlAdd($offerXml, 'xmlId', $product['xmlId']); $this->icmlAdd($offerXml, 'price', number_format($product['price'], 2, '.', '')); - if (!isset($this->options['purchasePrice']) || $this->options['purchasePrice'] != false) { + if (isset($product['purchasePrice'])) { $this->icmlAdd($offerXml, 'purchasePrice', number_format($product['purchasePrice'], 2, '.', '')); } + if (isset($product['barcodes'])) { + foreach($product['barcodes'] as $barcode){ + $this->icmlAdd($offerXml, 'barcode', $barcode); + } + } + $this->icmlAdd($offerXml, 'name', htmlspecialchars($product['name'])); $this->icmlAdd($offerXml, 'productName', htmlspecialchars($product['productName'])); $this->icmlAdd($offerXml, 'vatRate', $product['effectiveVat']); + if (!empty($product['customFields'])) { + if (!empty($product['customFields']['dimensions'])){ + $this->icmlAdd($offerXml, 'dimensions', $product['customFields']['dimensions']); + } + + if (!empty($product['customFields']['param'])){ + + foreach($product['customFields']['param'] as $param){ + $art = $this->icmlAdd($offerXml, 'param', $param['value']); + $art->addAttribute('code', $param['code']); + $art->addAttribute('name', $param['name']); + } + } + } + if (!empty($product['url'])) { $this->icmlAdd($offerXml, 'url', htmlspecialchars($product['url'])); } @@ -515,16 +553,16 @@ class MoySkladICMLParser } if ($product['categoryId']) { - $this->icmlAdd($offerXml, 'categoryId', $product['categoryId']); - }else { - $this->icmlAdd($offerXml, 'categoryId', 'warehouseRoot'); - } + $this->icmlAdd($offerXml, 'categoryId', $product['categoryId']); + }else { + $this->icmlAdd($offerXml, 'categoryId', 'warehouseRoot'); + } if ($product['article']) { - $art = $this->icmlAdd($offerXml, 'param', $product['article']); - $art->addAttribute('code', 'article'); - $art->addAttribute('name', 'Артикул'); - } + $art = $this->icmlAdd($offerXml, 'param', $product['article']); + $art->addAttribute('code', 'article'); + $art->addAttribute('name', 'Артикул'); + } if ($product['weight']) { if (isset($this->options['tagWeight']) && $this->options['tagWeight'] === true) { @@ -537,26 +575,30 @@ class MoySkladICMLParser } if ($product['code']) { - $cod = $this->icmlAdd($offerXml, 'param', $product['code']); - $cod->addAttribute('code', 'code'); - $cod->addAttribute('name', 'Код'); + $cod = $this->icmlAdd($offerXml, 'param', $product['code']); + $cod->addAttribute('code', 'code'); + $cod->addAttribute('name', 'Код'); } if ($product['vendor']) { - $this->icmlAdd($offerXml, 'vendor', $product['vendor']); - } + $this->icmlAdd($offerXml, 'vendor', $product['vendor']); + } if (isset($product['image']['imageUrl']) && - !empty($this->options['imageDownload']['pathToImage']) && - !empty($this->options['imageDownload']['site'])) - { - $this->icmlAdd($offerXml, 'picture', $this->saveImage($product['image'])); + !empty($this->options['imageDownload']['pathToImage']) && + !empty($this->options['imageDownload']['site'])) + { + $imgSrc = $this->saveImage($product['image']); + + if (!empty($imgSrc)){ + $this->icmlAdd($offerXml, 'picture', $this->saveImage($product['image'])); } + } } } + return $xml; - } /** @@ -648,7 +690,14 @@ class MoySkladICMLParser } if (file_exists($root . $imgDirrectory . '/' . $image['name']) === false) { - $content = $this->requestJson($image['imageUrl']); + + try { + $content = $this->requestJson($image['imageUrl']); + } catch (Exception $e) { + echo $e->getMessage(); + + return ''; + } if ($content) { file_put_contents($root . $imgDirrectory . '/' . $image['name'], $content); @@ -746,4 +795,71 @@ class MoySkladICMLParser return "Internal server error (" . json_encode($result) . ")"; } + + /** + * Получение массива значений кастомных полей. + * + * @param array + * @return array + * @access private + */ + protected function getCustomFields($attributes) { + + $result = array(); + + if (isset($this->options['customFields']['dimensions'])) { + if (count($this->options['customFields']['dimensions']) == 3) { + $maskArray = $this->options['customFields']['dimensions']; + + foreach($attributes as $attribute){ + if (in_array($attribute['id'], $this->options['customFields']['dimensions'])){ + $attributeValue[$attribute['id']] = $attribute['value']; + } + } + + $attributeValue = array_merge(array_flip($maskArray),$attributeValue); + $result['dimensions'] = implode('/', $attributeValue); + + } elseif (count($this->options['customFields']['dimensions']) == 1) { + if (isset($this->options['customFields']['separate'])){ + foreach($attributes as $attribute){ + if (in_array($attribute['id'], $this->options['customFields']['dimensions'])){ + $result['dimensions'] = str_replace($this->options['customFields']['separate'], '/', $attribute['value']); + } + } + } + } + } + + if (isset($this->options['customFields']['paramTag'])) { + if ($this->options['customFields']['paramTag']) { + foreach ($this->options['customFields']['paramTag'] as $paramTag){ + $paramTag = explode('#',$paramTag); + + foreach($attributes as $attribute) { + + if ($attribute['id'] == $paramTag[1]) { + $result['param'][] = array('code' => $paramTag[0],'name' => $attribute['name'], 'value'=> htmlspecialchars($attribute['value'])); + } + } + } + } + } + + return $result; + } + + protected function deleteProduct($categories, $products) { + foreach ($categories as $category) { + $cat[] = $category['externalCode']; + } + + foreach ($products as $product) { + if (!in_array($product['categoryId'],$cat)){ + unset($products[$product['uuid']]); + } + } + + return $products; + } } diff --git a/README.md b/README.md index 8ce4fdd..98023af 100644 --- a/README.md +++ b/README.md @@ -59,19 +59,49 @@ e) При необходимости включения в генерацию а * `file` - Имя файла с итоговым icml без пути (по умолчанию: shopname.catalog.xml) * `directory` - Директория для итогового icml файла (по умолчанию: текущая директория) -* `'archivedGoods'` - опция для включения в генерацию архивных товаров и торговых предложений (принимает значения `true` или `false`) -* `'purchasePrice'` - флаг для управление генерацией закупочной цены. Если данная опция установлена в `false` то генерация закупочной цены из сервиса Мой Склад производиться не будет. - При активной интеграции RetailCRM -> Мой Склад данная опция должна быть `false`. +* `archivedGoods` - опция для включения в генерацию архивных товаров и торговых предложений (принимает значения `true` или `false`) * `ignoreCategories` - массив с ключами: * `ids` - Массив c `id` групп товаров, которые должны быть проигнорированы - * `externalCodes` - Массив c `внешними кодами` групп товаров, которые должны быть проигнорированы + * `externalCode` - Массив c `внешними кодами` групп товаров, которые должны быть проигнорированы * `ignoreNoCategoryOffers` - Если `true` товары, не принадлежащие ни к одной категории, будут проигнорированы * `imageDownload` - массив, содержащий информацию для загрузки изображений * `site` - адрес сайта откуда будут отдаваться изображения в retailCRM * `pathToImage` - путь от корня сайта до дирректории где будут храниться изображения * `tagWeight` - передача веса в теге `weight` вместо `param`. Единица измерения - килограмм. Формат: положительное число с точностью 0.001 (или 0.000001, в зависимости от настройки RetailCRM "Точность веса": граммы или миллиграммы соответственно), разделитель целой и дробной части - точка. -Указывается в свойствах товара сервиса Мой Склад +Указывается в свойствах товара сервиса Мой Склад. +* `loadPurchasePrice` - установка данной опции со значением `true` включает в генерацию закупочные цены. По умолчанию закупочные цены для товаров не генерируются. +* `customFields` - массив для указания для генерации габаритов (dimensions) и дополнительных параметров товаров. Включает в себя следующие опции: + * `dimensions` - массив с одним или тремя значениями, содержащий id пользовательских полей товара в МС. При указании 3 полей должен соблюдаться порядок 'Длина,Ширина,Высота'. +Пример заполнения: + + `'dimensions' => + [ + '00000000-0000-0000-0000-000000000000', + '00000000-0000-0000-0000-000000000000', + '00000000-0000-0000-0000-000000000000' + ]` + + Если для генерации планируется использовать одно поле, то нужно использовать дополнительный параметр `separate` в котором вы должны указать какой разделитель используется в поле между +значениями на стороне МС. Пример заполнения: + ` + 'separate' => '/', + 'dimensions' => + [ + '00000000-0000-0000-0000-000000000000' + ] + ` + + * `paramTag` - массив со значениями,складывающимися из кода, который должен использоваться для генерации данного дополнительного параметра и id пользовательского поля товара. Заполняется с разделетелем "#" следующим образом: + + `'paramTag'=> + [ + 'somecode1#00000000-0000-0000-0000-000000000000', + 'somecode2#00000000-0000-0000-0000-000000000000' + ]` + +Id пользовательских свойств товара можно получить, совершив GET-запрос к api МС по адресу `https://online.moysklad.ru/api/remap/1.1/entity/product/metadata`, используя для запроса ваш логин и пароль, используемый для генерации каталога. +Необходимые id будут указаны внутри индекса "attributes". Все доступные опции не обязательны для использования ## Добавление изображения