Итак, для начала нужно было создать в административной панели сайта страницу, где данный скрипт будет доступен.
Как создавать свою страницу в административном разделе и как помещать в общее меню ссылку на данную страницу описывал в отдельном посте, поэтому тут просто рассмотрим сам файл со скриптом.
<?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.
Если вам нужен такой механизм и вы не можете самостоятельно откорректировать предложенный скрипт под ваши нужны - обращайтесь!
