Парсинг, добавление разделов Битрикс из файла XLS

Вводные данные

  • Имеется файл с разделами XLSX

  • Требуется загрузить разделы через Битрикс API


Содержание

  1. Скачиваем модуль Библиотека PHPExcel (Библиотека PHPExcel с поддержкой mbstring.func_overload=0, mbstring.func_overload=2 и php8). 

  2. Пишем скрипт.

  3. Добавляем в crontab


parse_sections.php

<?php
global $USER;

use Bitrix\Catalog\Model\Product;
use Bitrix\Main\Diag\Debug;
use Bitrix\Main\Loader;

$_SERVER["DOCUMENT_ROOT"] = realpath(dirname(__FILE__) . "/../..");
define("NO_KEEP_STATISTIC", true);
define("NOT_CHECK_PERMISSIONS", true);
define('CHK_EVENT', true);
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_before.php");

include('simple_html_dom.php');
set_time_limit(0);

Debug::startTimeLabel('parseItemsLabel');

// Очищаем лог.
file_put_contents($_SERVER["DOCUMENT_ROOT"] . '/z_logs/products/parse_items.txt', '');
file_put_contents($_SERVER["DOCUMENT_ROOT"] . '/z_logs/products/parse_items_error.txt', '');
file_put_contents($_SERVER["DOCUMENT_ROOT"] . '/z_logs/products/parse_items_total.txt', '');

Debug::writeToFile(
    '========= СТАРТ ОБРАБОТЧИКА ' . date('d.m.Y H:i:s') . ' =========',
    '',
    'z_logs/products/parse_items.txt'
);

// price.xml
// В этом файле мы парсим элементы
$IBLOCK_ID = 10;
CModule::IncludeModule("iblock");
CModule::IncludeModule('search');
Cmodule::IncludeModule('catalog');
$el = new CIBlockElement;

// Выбираем существующие разделы
$addedSections = [];
$rsSect = CIBlockSection::GetList(
    ['sort' => 'asc'],
    ["IBLOCK_ID" => $IBLOCK_ID],
    false,
    ["ID", "IBLOCK_ID", "IBLOCK_SECTION_ID", "NAME", "DESCRIPTION", "UF_*"]
);
while ($arSect = $rsSect->GetNext()) {
    $addedSections[$arSect["UF_GUID"]] = $arSect["ID"];
}

// Выбираем существующие элементы
$addedElements = [];
$rsElem = CIBlockElement::GetList(
    ['sort' => 'asc'],
    ["IBLOCK_ID" => $IBLOCK_ID],
    false,
    false,
    [
        "ID",
        "PROPERTY_farmItemId",
    ],
);
while ($arElem = $rsElem->Fetch()) {
    $addedElements[$arElem["PROPERTY_FARMITEMID_VALUE"]] = $arElem["ID"];
}

// Заголовки инструкции
$instructionArr = [
    "INDICATION" => "Показания",
    "CONTRA_INDICATION" => "Противопоказания",
    "SPECIAL_INSTRUCTION" => "Особые указания",
    "PHARM_ACTION" => "Фармакологическое действие",
    "DOSAGE" => "Дозировка",
    "INTERACTION" => "Взаимодействие",
    "OVER_DOSAGE" => "Передозировка",
    "SIDE_EFFECT" => "Побочные действия",
];

$items = simplexml_load_string(file_get_contents($_SERVER["DOCUMENT_ROOT"] . '/efarma/files/price/price/price.xml'));

$i = 0;
$added = 0;
$updated = 0;
$addedErr = 0;
$updatedErr = 0;

$jj = 0;
$step = $argv[1] ? (int) $argv[1] : 1;
$stepCount = 20000;
//$stepCount = 4000;
$itemsCount = count($items->PRICES->priceItems);
$stepUp = false;

function setEfarmePrice($productId, $price, $PRICE_TYPE_ID = 2): void
{
    $arFields = [
        "PRODUCT_ID" => $productId,
        "CATALOG_GROUP_ID" => $PRICE_TYPE_ID,
        "PRICE" => $price,
        "CURRENCY" => "RUB",
    ];
    $res = CPrice::GetList(
        [],
        [
            "PRODUCT_ID" => $productId,
            "CATALOG_GROUP_ID" => $PRICE_TYPE_ID,
        ]
    );
    if ($arr = $res->Fetch()) {
        CPrice::Update($arr['PRODUCT_ID'], $arFields);
    } else {
        CPrice::Add($arFields);
    }
}

Debug::writeToFile(
    'Парсинг на этапе ' . $step . ' начат, элементов - ' . $itemsCount,
    date('d.m.Y H:i:s'),
    'z_logs/products/parse_items.txt'
);
foreach ($items->PRICES->priceItems as $item) {
    $jj++;

    //Если элемент не попадает в диапазон шага - пропускаем
    if ($jj > $stepCount * $step || $jj <= $stepCount * ($step - 1)) {
        continue;
    }

    Debug::writeToFile(
        'Код: ' . $item->goodsCode . ' Название: ' . $item->itemName,
        'Обрабатываю товар',
        'z_logs/products/parse_items.txt'
    );

    if ($jj === $stepCount * $step || $jj === $itemsCount) {
        if ($jj !== $itemsCount) {
            $stepUp = true;
        }
    }

    //Записываем свойства
    $farmItemID = "" . $item->farmItemId;

    $PROP = [];
    $PROP[45] = $farmItemID;            // farmItemId
    $PROP[46] = $item->esItemId;        // esItemId
    $PROP[47] = $item->country;         // Страна
    $PROP[48] = $item->manufName;       // Производитель
    $PROP[49] = $item->barcode;         // Штрих-код
    $PROP[50] = $item->INN;             // МНН
    $PROP[41] = $item->IS_PRESCRIPTION; // рецепт
    $PROP[40] = $item->goodsCode;       // goodsCode
    $PROP[44] = $item->dosage;          // Дозировка
    $PROP[51] = isset($item->IS_SALE_REMOTE) && (int) $item->IS_SALE_REMOTE == 0 ? 'Нет' : 'Да'; //Доступен для доставки
    $ourPred = [];
    // Хиты продаж
    if ($item->IS_HIT == 1) {
        //$ourPred[] = 55;
        $PROP[55] = 'Y';
    }
    //Мы советуем
    if ($item->IS_RECOMMENDED == 1) {
        //$ourPred[] = 56;
        $PROP[56] = 'Y';
    }
    // Новинки
    if ($item->IS_NEWS == 1) {
        //$ourPred[] = 57;
        $PROP[57] = 'Y';
    }
    // По акции
    if ($item->IS_ACTION_GOODS == 1) {
        //$ourPred[] = 54;
        $PROP[54] = 'Y';
    }

    $price = (string) $item->price;

    //$PROP[47] = $ourPred;

    Debug::writeToFile(
        $price,
        'Цена Ефарма',
        'z_logs/products/parse_items.txt'
    );

    $vatId = '';
    if ($item->taxRate) {
        $ob = CCatalogVat::GetList(
            [],
            ['RATE' => $item->taxRate],
            []
        );
        if ($res = $ob->GetNext()) {
            $vatId = $res['ID'];
        }
        $ob = CCatalogVat::GetList(
            ['CSORT' => 'ASC'],
            ['RATE' => $item->taxRate],
            []
        );
        if ($res = $ob->GetNext()) {
            $vatId = $res['ID'];
        }
    }

    // Собираем в массив группы которым принадлежит
    $sections = [];
    foreach ($item->groups->item as $section) {
        $section = "" . $section;
        if (array_key_exists($section, $addedSections)) {
            $sections[] = $addedSections[$section];
        }
    }

    $arLoadProductArray = [
        "MODIFIED_BY" => $USER->GetID(), //элемент изменен текущим пользователем
        "IBLOCK_SECTION_ID" => false,    //элемент лежит в корне раздела
        "IBLOCK_ID" => $IBLOCK_ID,
        "PROPERTY_VALUES" => $PROP,
        "NAME" => $item->itemName,
        "CODE" => CUtil::translit($item->itemName, 'ru', ["replace_space" => "-", "replace_other" => "-"]),
        // "ACTIVE" => "Y"
    ];

    //Если элемент не существует добавляем
    if (!array_key_exists($farmItemID, $addedElements)) {
        Debug::writeToFile(
            'Элемент НЕ существует: пытаюсь добавить',
            '',
            'z_logs/products/parse_items.txt'
        );

        $arLoadProductArray['ACTIVE'] = 'Y';
        if ($PRODUCT_ID = $el->Add($arLoadProductArray)) {
            $el->SetElementSection($PRODUCT_ID, $sections);
            try {
                setEfarmePrice($PRODUCT_ID, $price);
                $result = Product::update($PRODUCT_ID, ['VAT_ID' => $vatId, 'VAT_INCLUDED' => 'Y']
                );
            } catch (Exception $e) {
                Debug::dumpToFile(
                    $e->getMessage(),
                    'errorAddUpdate. goodsCode: ' . $item->goodsCode,
                    'z_logs/products/parse_items_error.txt'
                );
            }

            if ($result && $result->isSuccess() && !$result->getAffectedRowsCount()) {
                try {
                    $arFields = ['ID' => $PRODUCT_ID, 'VAT_ID' => $vatId, 'VAT_INCLUDED' => 'Y'];
                    Product::add(['fields' => $arFields]);
                } catch (Exception $e) {
                    Debug::dumpToFile(
                        $e->getMessage(),
                        'errorAddAdd. goodsCode: ' . $item->goodsCode,
                        'z_logs/products/parse_items_error.txt'
                    );
                }
            }
            //fwrite($fg, $farmItemID.": Элемент добавлен\r\n");
            $added++;
        } else {
            $addedErr++;
            //fwrite($fg, $farmItemID.": ".$el->LAST_ERROR."\r\n");
        }
        Debug::writeToFile(
            '-------------------------------------------------',
            $jj . ' Обработка товара завершена',
            'z_logs/products/parse_items.txt'
        );
        //Если элемент существует обновляем
    } else {
        Debug::writeToFile(
            'Элемент существует: пытаюсь обновить',
            '',
            'z_logs/products/parse_items.txt'
        );
        if ($el->Update($addedElements[$farmItemID], $arLoadProductArray)) {
            $el->SetElementSection($addedElements[$farmItemID], $sections);
            try {
                setEfarmePrice($addedElements[$farmItemID], $price);
                $result = Product::update(
                    $addedElements[$farmItemID],
                    ['VAT_ID' => $vatId, 'VAT_INCLUDED' => 'Y']
                );
            } catch (Exception $e) {
                Debug::dumpToFile(
                    $e->getMessage(),
                    'errorUpdateUpdate. goodsCode: ' . $item->goodsCode,
                    'z_logs/products/parse_items_error.txt'
                );
            }

            if ($result && $result->isSuccess() && !$result->getAffectedRowsCount()) {
                try {
                    $arFields = ['ID' => $addedElements[$farmItemID], 'VAT_ID' => $vatId, 'VAT_INCLUDED' => 'Y'];
                    Product::add(['fields' => $arFields]);
                } catch (Exception $e) {
                    Debug::dumpToFile(
                        $e->getMessage(),
                        'errorUpdateAdd. goodsCode: ' . $item->goodsCode,
                        'z_logs/products/parse_items_error.txt'
                    );
                }
            }

            //fwrite($fg, $farmItemID.": Элемент обновлен\r\n");
            $updated++;
        } else {
            $updatedErr++;
            //fwrite($fg, $farmItemID.": ".$el->LAST_ERROR."\r\n")
        }
        Debug::writeToFile(
            '-------------------------------------------------',
            $jj . ' Обработка товара завершена',
            'z_logs/products/parse_items.txt'
        );
    }

    //if($i==100){break;}
    $i++;
}

// Выполняем переиндексацию поиска.
$NS = false;
$NS = CSearch::ReIndexAll(true, 60, $NS);
while (is_array($NS)) {
    $NS = CSearch::ReIndexAll(true, 60, $NS);
}

Debug::writeToFile(
    'Индексация выполнена на этапе ' . $step,
    date('d.m.Y H:i:s'),
    'z_logs/products/parse_items.txt'
);

Debug::writeToFile(
    '========= ФИНАЛ ' . date('d.m.Y H:i:s') . ' =========',
    '',
    'z_logs/products/parse_items.txt'
);

Debug::endTimeLabel('parseItemsLabel');

$totalData = "Добавлено: "
    . $added
    . "\r\nОбновлено: "
    . $updated
    . "\r\nОшибки добавления: "
    . $addedErr
    . "\r\nОшибки обновления: "
    . $updatedErr;

Debug::writeToFile($totalData, date('d.m.Y H:i:s'), 'z_logs/products/parse_items_total.txt');
Debug::writeToFile(Debug::getTimeLabels(), 'Время выполнения скрипта', 'z_logs/products/parse_items_total.txt');

if ($stepUp) {
    $nextStep = $step + 1;
    exec('php ' . $_SERVER["DOCUMENT_ROOT"] . '/local/cron/parse_items.php ' . $nextStep);
}