Скрипт массового изменения цен

Скрипт массового изменения цен

Понадобилось одним постоянным клиентам сделать доработку - дать возможность массового изменения цены на заданный процент или на заданную величину с автоматическим округлением вверх до ближайшего числа, большего нуля.

Делюсь наработкой - может кому пригодится :)

Итак, для начала нужно было создать в административной панели сайта страницу, где данный скрипт будет доступен.

Как создавать свою страницу в административном разделе и как помещать в общее меню ссылку на данную страницу описывал в отдельном посте, поэтому тут просто рассмотрим сам файл со скриптом.

<?php
if (isset($_REQUEST['work_start'])) // отключаем статистику
{
	define("NO_AGENT_STATISTIC", true);
	define("NO_KEEP_STATISTIC", true);
}

require_once($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_admin_before.php");

$POST_RIGHT = $APPLICATION->GetGroupRight("catalog"); // проверяем, есть ли у текущего пользователя доступ к управлению каталогом
if ($POST_RIGHT == "D")
	$APPLICATION->AuthForm("Доступ запрещен");

use Bitrix\Main\Loader;
use Bitrix\Main;
use Bitrix\Main\Application;

use Bitrix\Main\Localization\Loc;

Loc::loadMessages(__FILE__);

// подключаем нужные модули
Main\Loader::includeModule('pai.tools');
Main\Loader::includeModule('iblock');
Main\Loader::includeModule('catalog');
Main\Loader::includeModule('sale');

CJSCore::Init(['jquery']);  // подключаем JQUERY (если муторно через BX собирать, но это, конечно же не корректно:))) )

$application = Application::getInstance();
$context = Application::getInstance()->getContext();
$request = $context->getRequest();

class PricesProcessor
{
	var $CatalogIblockId = 3;  // идентификатор инфоблока каталога товаров
	var $arResult;
	var $sid = false;
	var $brand = false;
	var $PRICE_TYPE_ID = 1;  // идентификатор типа цены, которую нужно поменять

	function __construct()
	{
		Main\Loader::includeModule('iblock');
	}

	function SetFilterBySection($sid)
	{
		$this->sid = $sid;
	}

	function SetFilterByBrand($brandid)
	{
		$this->brand = $brandid;
	}

	function GetProducts()
	{
		$filter = array('IBLOCK_ID' => $this->CatalogIblockId, 'ACTIVE' => 'Y');

		if ($this->sid && intval($this->sid) > 0)
		{
			$filter['SECTION_ID'] = intval($this->sid);
			$filter['INCLUDE_SUBSECTIONS'] = 'Y';
		}

		if ($this->brand && intval($this->brand) > 0)
		{
			$filter['PROPERTY_BP_BRAND'] = intval($this->brand);
		}

		$arRows = \Pai\Tools\ITools::GetIblockElementItems([
			'sort'   => ['ID' => 'asc'],
			'filter' => $filter,
			'select' => ['ID', 'IBLOCK_ID', 'IBLOCK_SECTION_ID', 'PROPERTY_BP_BRAND']
		]);

		return $arRows;
	}

	// получаем всех производителей из инфоблока с производителями
	function GetBrands()
	{
		$dbItem = \Bitrix\Iblock\ElementTable::getList(array(
			'select' => array('ID', 'IBLOCK_ID', 'NAME'),
			'filter' => array('IBLOCK_ID' => 10, 'ACTIVE' => 'Y'),
			'limit'  => 500,
			'order'  => array('NAME' => 'ASC'),
			'cache'  => array(
				'ttl'         => 60 * 60 * 24 * 30,
				'cache_joins' => true
			)

		));
		$arBrends = [];
		while ($arItem = $dbItem->fetch())
		{
			$arBrends[$arItem['ID']] = $arItem['NAME'];
		}

		return $arBrends;
	}

	// получаем список разделов каталога
	function GetSectionsList()
	{
		return \Pai\Tools\ITools::GetIblockSectionsTree($this->CatalogIblockId);
	}

	/**
	 * функция непосредственного изменения цены на товар
	 * @param $pid - идентификатор товара
	 * @param $incValue - на сколько изменить цену
	 * @param int $incType - тип изменения: 1 - процент, 2 - число
	 * @param bool $increase - направление изменения: true - увеличить, false - уменьшить
	 */
	function UpdatePriceForProduct($pid, $incValue, $incType = 1, $increase = true)
	{
		$curPrice = $this->GetCurrentPriceForProduct($pid);
		$mode = 1;
		if (!$increase)
		{
			// уменьшаем
			$mode = -1;
		}

		if($incType==2){ // если число - просто прибавляем / вычитаемданное число
			$newPrice = $curPrice['PRICE'] + $mode*$incValue;
		} else {
			$newPrice = $curPrice['PRICE'] + $mode*($curPrice['PRICE']*$incValue / 100);
		}

		$newPrice = $this->RoundTo50($newPrice);  // тут делаем округление до ближайшего значения, кратного 50 вверх

		if(floatval($newPrice)!==floatval($curPrice['PRICE'])){
			Loader::includeModule('sale');

			$arFields = Array(
				// "PRODUCT_ID"       => $pid,
				"CATALOG_GROUP_ID" => $this->PRICE_TYPE_ID,
				"PRICE" => $newPrice,
				// "CURRENCY"         => "RUB" /*код валюты*/
			);

			$res = \CPrice::Update($curPrice["ID"], $arFields,true);
			if($res){
				return 'ID товара '.$pid.'. Старая цена: '.$curPrice['PRICE'].'. Новая цена: '.$newPrice;
			}
		}

		return 'ID товара: '.$pid.'. Цена осталась без изменений';

	}

	// получаем текущую цену для товара:
	function GetCurrentPriceForProduct($pid)
	{
		Loader::includeModule('sale');
		$Priceres = \CPrice::GetList(
			array(),
			array(
				"PRODUCT_ID"       => $pid,
				"CATALOG_GROUP_ID" => $this->PRICE_TYPE_ID
			)
		);
		if ($arr = $Priceres->Fetch())
		{
			return $arr;
		} else
		{
			return false;
		}
	}

	// очищаем управляемый кеш для каталога
	function ClearCatalogCache(){
		Loader::includeModule('iblock');
		\CIBlock::clearIblockTagCache( $this->CatalogIblockId);
	}

	/**
	 * Функция для округления вверх до 50 или 100
	 * @param $x
	 * @return float|int
	 */
	function RoundTo50($x){
		$x = ceil($x);
		return ceil($x/50)*50;
	}

	/**
	 * Функция для округления вверх до 0.5 или 1
	 * @param $x
	 * @return float
	 */
	function RoundToHalf($x){
		return ceil($x/0.5)*0.5;
	}
}

$processor = new PricesProcessor();  // подключаем класс для обработки

if ($request->get('work_start') && $request->get('work_start') == 'Y')  // если был запущен процесс обработки
{
	$sid = (int)$request->get('sid');
	$brand = (int)$request->get('brand_id');
	$pos = (int)$request->get('pos');
	$step = (int)$request->get('step');

	if (!$pos) $pos = 0;

	$returnData = [];

	$arRows = [];

	if ($step == 1) // на первом шаге мы получаем выборку товаров для обработки
	{
		if ($sid > 0)
		{
			$processor->SetFilterBySection($sid); // устанавливаем фильтрацию по разделу, если нужно
		}

		if ($brand)
		{
			$processor->SetFilterByBrand($brand); // устанавливаем фильтрацию по производителю, если нужно
		}

		$arRows = $processor->GetProducts(); // получаем список товаров для обработки
		$APPLICATION->RestartBuffer();
		$pos = 0;
		if ($pos > count($arRows))
		{
			$pos = false;
		}

		$p = 0;
		$rowsCnt = count($arRows);
		if($rowsCnt>0){
			// выводим массив из идентификаторов товаров в обработчик ajax-а
			echo 'Rows = ' . \Bitrix\Main\Web\Json::encode(array_map(function ($arItem) {
					return $arItem['ID'];
				}, $arRows)) . ';';
		} else{
			// выводим пустой массив из идентификаторов товаров в обработчик ajax-а
			echo 'Rows = '.\Bitrix\Main\Web\Json::encode([]).';';
		}
		// ключевая строка в ответе аякса - содержит инструкции для дальнейшей работы
		echo 'CurrentStatus = ' . \Bitrix\Main\Web\Json::encode(["0", "0", 'Получено товаров для обработки: '.count($arRows)]) . ';';
		die();
	} elseif ($request->get('step') == 2 && intval($request->get('pid')) > 0) // на втором шаге идет непосредственное изменение
	{
		$rowsCnt = intval($request->get('rowsCnt'));

		$returnMsg = $processor->UpdatePriceForProduct(  // обновляем цену для текущего товара
			$request->get('pid'),
			$request->get('value'),
			($request->get('inch') == 'rur' ? 2 : 1),
			($request->get('sign') == 'minus' ? false : true)
		);

		$pos++;
		$p = round(100 * $pos / $rowsCnt, 2); // рассчитываем процент работы
		if($p>=100) $processor->ClearCatalogCache(); // если обработаны все 100% - обнуляем управляемый кеш

		// выводим самую важную строку в ответ:
		echo 'CurrentStatus = Array(' . $p . ',"' . ($p < 100 ? ($pos) : '') . '","Обработана запись ' . ($pos)
			. ' / ' . $rowsCnt .'. '. $returnMsg . '");';
		die();
	}

}
	// таблица, показывающая прогресс выполнения операции:
$clean_test_table = '<table id="result_table" cellpadding="0" cellspacing="0" border="0" width="100%" class="internal">' .
	'<tr class="heading">' .
	'<td>Текущее действие</td>' .
	'<td width="1%"> </td>' .
	'</tr>' .
	'</table>';

	// вкладки в админке - одна для запуска скрипта. Вторая - для отображения результата
$aTabs = array(
	array("DIV" => "file_conteiner", "TAB" => "Данные для обработки"),
	array("DIV" => "processing", "TAB" => "Обработка"),
);

$tabControl = new CAdminTabControl("tabControl", $aTabs);

$GLOBALS['APPLICATION']->SetTitle('Массовое изменение цены');

require_once($_SERVER["DOCUMENT_ROOT"] . BX_ROOT . "/modules/main/include/prolog_admin_after.php");
?>
    <script type="text/javascript">
        var Form;
        var $Form;
        var Rows;
        $(document).ready(function () {
            Form = BX('post_form');
            $Form = $('#post_form');
            $Form.on('change keyup', 'input[name="value"]', function () {
                if (this.value.length > 0) {
                    $Form.find('input[type="button"]').attr('disabled', false);
					// активируем кнопку запуска скрипта в том случае, если введено значение, на которое менять
                } else {
                    $Form.find('input[type="button"]').attr('disabled', true);
                }
            });

            toggleTabs(false); // устанавливаем вкладки в статус, по-умолчанию
        });

        var ProcessorOpts = {
            tab_start: 'file_conteiner',
            processingTab: 'processing',
            mode: 0,
            section_id: 0,
            brend_id: 0,
            sign: 'plus',
            inch: 'prsnts',
            value: 0,
            step: 1
        };

        var bWorkFinished = false;
        var bSubmit;

        var page_title = document.title; // запоминаем title браузера на момент начала обработки

        function process(mode) {

            ProcessorOpts.mode = mode;

            if (mode == 1) {

                ProcessorOpts.brend_id = $Form.find('select[name="brand"]').val();
                ProcessorOpts.section_id = $Form.find('select[name="section"]').val();
                ProcessorOpts.sign = $Form.find('select[name="sign"]').val();
                ProcessorOpts.inch = $Form.find('select[name="inch"]').val();
                ProcessorOpts.value = $Form.find('input[name="value"]').val();
                ProcessorOpts.step = 1;

                set_start(1); // запускаем обработчик
            }
        }

        function toggleTabs(show) {
            if (show) {
                tabControl.EnableTab(ProcessorOpts.processingTab);
                tabControl.SelectTab(ProcessorOpts.processingTab)
            } else {
                tabControl.DisableTab(ProcessorOpts.processingTab);
                tabControl.SelectTab(ProcessorOpts.tab_start)
            }
        }

        function set_start(val) {
            document.getElementById('work_start').disabled = val ? 'disabled' : '';
            document.getElementById('work_stop').disabled = val ? '' : 'disabled';
            document.getElementById('progress').style.display = val ? 'block' : 'none';

            if (val) { // если запущен обработчик
                startProgress();
                toggleTabs(true);

                CHttpRequest.Action = work_onload;  // устанавливаем функцию-обработчик для результата запроса
                CHttpRequest.Send('<?= $_SERVER["PHP_SELF"]?>?work_start=Y&lang=<?=LANGUAGE_ID?>&<?=bitrix_sessid_get()?>&mode='
                    + ProcessorOpts.mode + '&sid=' + ProcessorOpts.section_id + '&brand_id=' + ProcessorOpts.brend_id
                    + '&sign=' + ProcessorOpts.sign + '&inch=' + ProcessorOpts.inch + '&value=' + ProcessorOpts.value
                    + '&step=1'
                );
            } else
                CloseWaitWindow();
        }

        function work_onload(result) {
            try {
                eval(result); // обрабатываем наш результат - получим все переменные, описанные от сервера по запросу

                iPercent = CurrentStatus[0];
                strNextRequest = CurrentStatus[1];
                strCurrentAction = CurrentStatus[2];

                document.getElementById('percent').innerHTML = iPercent + '%';
                document.getElementById('indicator').style.width = iPercent + '%';

				// пишем в url процент выполнения задачи
                document.title = iPercent + '% ' + page_title;
                document.getElementById('status').innerHTML = 'Работаю...';

                if (strCurrentAction != 'null') {
                    oTable = document.getElementById('result_table');
                    oRow = oTable.insertRow(-1);
                    oCell = oRow.insertCell(-1);
                    oCell.innerHTML = strCurrentAction;
                    oCell = oRow.insertCell(-1);
                    oCell.innerHTML = '';
                }

                if (ProcessorOpts.step == 1) {
                    ProcessorOpts.step = 2;
                }

                if (
                    Rows !== null && Rows !== undefined && Rows[strNextRequest] !== undefined 
						&& Rows[strNextRequest] !== null && parseInt(Rows[strNextRequest]) > 0
                    && document.getElementById('work_start').disabled
                ) {
					// если в результирующем массиве получили строки для обработки и они еще не закончились -
					// отправляем запросы по каждому из товаров
                    setTimeout(function () {
                        CHttpRequest.Send('<?= $_SERVER["PHP_SELF"]?>?work_start=Y&lang='
							+'<?=LANGUAGE_ID?>&<?=bitrix_sessid_get()?>&mode='
                            + ProcessorOpts.mode
                            + '&sign=' + ProcessorOpts.sign + '&inch=' + ProcessorOpts.inch + '&value=' + ProcessorOpts.value
                            + '&step=2' + '&pos=' + strNextRequest + '&pid=' + Rows[strNextRequest] + '&rowsCnt=' + Rows.length
                        );
                    }, 700);
                } else {
                    set_start(0); // завершаем обработку
                    bWorkFinished = true;
                    document.title = page_title; // возвращаем url как был
                }

            } catch (e) {
                CloseWaitWindow();
                document.getElementById('work_start').disabled = '';
                alert('Сбой в получении данных' + result);
            }
        }

        function toggleProgress(val) {
            document.getElementById('work_start').disabled = val ? 'disabled' : '';
            document.getElementById('work_stop').disabled = val ? '' : 'disabled';
            document.getElementById('progress').style.display = val ? 'block' : 'none';
        }

        function toggleProcessContainer(val) {
            document.getElementById('processingContainer').style.display = val ? 'block' : 'none';
        }

        function startProgress(val) {
            ShowWaitWindow();
            document.getElementById('result').innerHTML = '<?=$clean_test_table?>';
            document.getElementById('status').innerHTML = 'Работаю...';

            document.getElementById('percent').innerHTML = '0%';
            document.getElementById('indicator').style.width = '0%';
        }

        function stopProgress() {
            alert('Обраотка завершена!');
            CloseWaitWindow();
        }

    </script>

    <form method="post" action="<? echo $APPLICATION->GetCurPage() ?>" 
		enctype="multipart/form-data" name="post_form"
          id="post_form">
		<?
		echo bitrix_sessid_post();
		$tabControl->Begin();
		$tabControl->BeginNextTab();
		?>
        <tr>
            <td><label>Раздел:</label></td>
            <td>
                <select name="section">
                    <option value="0" selected>Все</option><?
					// получаем разделы каталога для обработки
					$arSections = $processor->GetSectionsList();

					// выводим разделы для обработки в виде options
					echo \Pai\Tools\ITools::GetSectionChildsAsOptgroup($arSections);
					?></select>
            </td>
        </tr>
        <tr>
            <td><label>Бренд:</label></td>
            <td>
                <select name="brand">
                    <option value="0" selected>Все</option><?
					$arBrends = $processor->GetBrands(); // получаем производителей
					foreach ($arBrends as $id => $arBrend) // выводим производителей
					{
						?>
                        <option value="<?= $id ?>"><?= $arBrend; ?></option><?
					} ?></select>
            </td>
        </tr>
        <tr>
            <td><label>На сколько изменить цену:</label></td>
            <td>
                <select name="sign">
                    <option value="plus" selected>+</option>
                    <option value="minus">-</option>
                </select><input name="value" style="height: 1.3rem"
                                value="" type="number"><select name="inch">
                    <option value="prsnts" selected>%</option>
                    <option value="rur">руб.</option>
                </select>
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="button" value="Изменить цены" onclick="process(1)" disabled="disabled">
            </td>
        </tr>
		<?
		$tabControl->BeginNextTab();
		?>
        <tr>
            <td colspan="2">
                <input type=button value="Старт" id="work_start" onclick="set_start(1)" style="display: none"/>
                <input type=button value="Стоп" disabled id="work_stop" onclick="bSubmit=false;set_start(0)"/>
                <div id="progress" style="display:none;" width="100%">
                    <br/>
                    <div id="status"></div>
                    <table border="0" cellspacing="0" cellpadding="2" width="100%">
                        <tr>
                            <td height="10">
                                <div style="border:1px solid #B9CBDF">
                                    <div id="indicator" style="height:10px; width:0%; background-color:#B9CBDF"></div>
                                </div>
                            </td>
                            <td width=30> <span id="percent">0%</span></td>
                        </tr>
                    </table>
                </div>
                <div id="result" style="padding-top:10px"></div>
            </td>
        </tr>
		<?
		$tabControl->End();
		?>
    </form>

<? require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/epilog_admin.php");

Данный скрипт реализует изменение товаров с выборкой по разделам и производителям товара. Позволяет выбрать тип операции - увеличение или уменьшение цены. Также можно выбрать - изменить цену на фиксированное число, или на процент. При изменении цены происходит округление в большую сторону до ближайшего числа, кратного 50.

Если вам нужен такой механизм и вы не можете самостоятельно откорректировать предложенный скрипт под ваши нужны - обращайтесь!

Количество показов: 11
24.03.2020

Возврат к списку

Разработка сайта

Подайте заявку на разработку сайта на базе готового решения от компании 1С-Битрикс или одного из партнеров компании. Максимально подробно опишите, чему будет посвящен сайт, если это интернет-магазин - что он будет продавать, нужна ли мультиязычность, будут ли разные типы цен (розница, опт, крупный опт), будет ли интеграция с 1С, будет ли выгрузка товаров на различные торговые площадки...

Сопровождение сайта

Вы можете подать заявку на сопровождение вашего сайта на базе 1С-Битрикс. Сопровождение включает в себя: проверка актуальности обновлений сайта, проверка актуальности резервной копии, консультации по сайту. Опишите в заявке, какие еще объемы планируются на сопровождении и на какой срок вы планируете заключить договор на сопровождение - мы подберем подходящий вам бюджет на сопровождение

Работы по сайту

Вы можете подать заявку на выполнение определенного объема работ по сайту. Опишите в заявке объем работ. Это может быть разработка какого-то нового функционала, доработки по имеющемуся функционалу, доработки под требования сео-специалистов. На основании заявки вам будет сформирован бюджет работ, а также названы сроки на выполнение тех или иных работ.