Понадобилось одним постоянным клиентам сделать доработку - дать возможность массового изменения цены на заданный процент или на заданную величину с автоматическим округлением вверх до ближайшего числа, большего нуля.
Делюсь наработкой - может кому пригодится :)
Итак, для начала нужно было создать в административной панели сайта страницу, где данный скрипт будет доступен.
Как создавать свою страницу в административном разделе и как помещать в общее меню ссылку на данную страницу описывал в отдельном посте, поэтому тут просто рассмотрим сам файл со скриптом.
<?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.
Если вам нужен такой механизм и вы не можете самостоятельно откорректировать предложенный скрипт под ваши нужны - обращайтесь!
Разработка сайта
Подайте заявку на разработку сайта на базе готового решения от компании 1С-Битрикс или одного из партнеров компании. Максимально подробно опишите, чему будет посвящен сайт, если это интернет-магазин - что он будет продавать, нужна ли мультиязычность, будут ли разные типы цен (розница, опт, крупный опт), будет ли интеграция с 1С, будет ли выгрузка товаров на различные торговые площадки...
Сопровождение сайта
Вы можете подать заявку на сопровождение вашего сайта на базе 1С-Битрикс. Сопровождение включает в себя: проверка актуальности обновлений сайта, проверка актуальности резервной копии, консультации по сайту. Опишите в заявке, какие еще объемы планируются на сопровождении и на какой срок вы планируете заключить договор на сопровождение - мы подберем подходящий вам бюджет на сопровождение
Работы по сайту
Вы можете подать заявку на выполнение определенного объема работ по сайту. Опишите в заявке объем работ. Это может быть разработка какого-то нового функционала, доработки по имеющемуся функционалу, доработки под требования сео-специалистов. На основании заявки вам будет сформирован бюджет работ, а также названы сроки на выполнение тех или иных работ.