Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/cvetdv.ru/bitrix/modules/catalog/lib/component/ |
Upload File : |
<?php namespace Bitrix\Catalog\Component; use Bitrix\Catalog; use Bitrix\Catalog\Access\AccessController; use Bitrix\Catalog\Access\ActionDictionary; use Bitrix\Catalog\ProductTable; use Bitrix\Catalog\StoreDocumentElementTable; use Bitrix\Catalog\StoreDocumentTable; use Bitrix\Catalog\StoreProductTable; use Bitrix\Main\Entity\Base; use Bitrix\Main\Entity\ReferenceField; use Bitrix\Main\Loader; use Bitrix\Main\Localization\Loc; use Bitrix\Main\ORM\Fields\ExpressionField; use Bitrix\Main\ORM\Fields\Relations\Reference; use Bitrix\Main\ORM\Query\Join; use Bitrix\Main\ORM\Query\Query; use Bitrix\Main\Type\DateTime; use Bitrix\Sale\Internals\BasketTable; use Bitrix\Sale\Internals\ShipmentItemStoreTable; use Bitrix\Sale\Internals\ShipmentItemTable; use Bitrix\Sale\Internals\ShipmentTable; abstract class ReportProductList extends ProductList { protected int $storeId = 0; protected string $navParamName = 'page'; protected array $catalogData = []; protected array $defaultGridSort = ['PRODUCT_ID' => 'desc']; protected string $reportFilterClass; protected \Bitrix\Main\Grid\Options $gridOptions; abstract protected function getGridId(): string; abstract protected function getFilterId(): string; abstract protected function prepareProductFilter(array $productIds): array; abstract protected function getProductFilterDialogContext(): string; abstract protected function getReceivedQuantityData(int $storeId, array $formattedFilter): array; abstract protected function getOutgoingQuantityData(int $storeId, array $formattedFilter): array; abstract protected function getAmountSoldData(int $storeId, array $formattedFilter): array; abstract protected function getGridColumns(): array; protected static function getEmptyStub(): string { return Loc::getMessage('CATALOG_REPORT_PRODUCT_LIST_NO_PRODUCTS'); } public function onPrepareComponentParams($arParams) { $arParams['STORE_ID'] = (int)($arParams['STORE_ID'] ?? 0); return parent::onPrepareComponentParams($arParams); } public function executeComponent() { if (!$this->checkModules()) { $this->includeComponentTemplate(); return; } if (!$this->checkDocumentReadRights()) { $this->arResult['ERROR_MESSAGES'][] = Loc::getMessage('CATALOG_REPORT_PRODUCT_LIST_NO_READ_RIGHTS_ERROR'); $this->includeComponentTemplate(); return; } $this->init(); $this->loadMeasures(); $this->arResult['GRID'] = $this->getGridData(); $this->arResult['STORE_TITLE'] = htmlspecialcharsbx($this->getStoreTitle()); if (empty($this->arResult['STORE_TITLE'])) { $this->arResult['STORE_TITLE'] = Loc::getMessage('CATALOG_REPORT_PRODUCT_LIST_DEFAULT_STORE_NAME'); } $filterOptions = [ 'GRID_ID' => $this->getGridId(), 'FILTER_ID' => $this->getFilterId(), 'FILTER' => $this->getFilterFields(), 'FILTER_PRESETS' => [], 'ENABLE_LABEL' => true, 'THEME' => \Bitrix\Main\UI\Filter\Theme::LIGHT, ]; $this->arResult['FILTER_OPTIONS'] = $filterOptions; $this->includeComponentTemplate(); } protected function checkModules(): bool { if (!Loader::includeModule('catalog')) { $this->arResult['ERROR_MESSAGES'][] = 'Module Catalog is not installed'; return false; } if (!Loader::includeModule('report')) { $this->arResult['ERROR_MESSAGES'][] = 'Module Report is not installed'; return false; } return true; } protected function getGridRows(): ?array { $productData = $this->getProductData(); if (!$productData) { return null; } $rows = []; $this->catalogData = $this->loadCatalog(array_column($productData, 'PRODUCT_ID')); $formattedFilter = $this->getFormattedFilter(); $receivedQuantityData = $this->getReceivedQuantityData($this->storeId, $formattedFilter); $outgoingQuantityData = $this->getOutgoingQuantityData($this->storeId, $formattedFilter); $amountSoldData = $this->getAmountSoldData($this->storeId, $formattedFilter); $receivedQuantityAmountDifferenceData = []; $outgoingQuantityAmountDifferenceData = []; $amountSoldAmountDifferenceData = []; if (!empty($formattedFilter['REPORT_INTERVAL'])) { $differenceFilter = $formattedFilter; $currentTime = new DateTime(); $filterTimeTo = new DateTime($differenceFilter['REPORT_INTERVAL']['TO']); if ($currentTime > $filterTimeTo) { $differenceFilter['REPORT_INTERVAL']['FROM'] = $differenceFilter['REPORT_INTERVAL']['TO']; $differenceFilter['REPORT_INTERVAL']['TO'] = (new DateTime())->toString(); $receivedQuantityAmountDifferenceData = $this->getReceivedQuantityData($this->storeId, $differenceFilter); $outgoingQuantityAmountDifferenceData = $this->getOutgoingQuantityData($this->storeId, $differenceFilter); $amountSoldAmountDifferenceData = $this->getAmountSoldData($this->storeId, $differenceFilter); } } foreach ($productData as $key => $item) { $receivedQuantityAmountDifference = (float)($receivedQuantityAmountDifferenceData[$item['PRODUCT_ID']] ?? 0); $outgoingQuantityAmountDifference = (float)($outgoingQuantityAmountDifferenceData[$item['PRODUCT_ID']] ?? 0); $amountSoldAmountDifference = (float)($amountSoldAmountDifferenceData[$item['PRODUCT_ID']] ?? 0); $item['AMOUNT'] = $item['AMOUNT'] - $receivedQuantityAmountDifference + $outgoingQuantityAmountDifference + $amountSoldAmountDifference ; $receivedQuantity = (float)($receivedQuantityData[$item['PRODUCT_ID']] ?? 0); $outgoingQuantity = (float)($outgoingQuantityData[$item['PRODUCT_ID']] ?? 0); $amountSold = (float)($amountSoldData[$item['PRODUCT_ID']] ?? 0); $item['STARTING_QUANTITY'] = (float)$item['AMOUNT'] - $receivedQuantity + $outgoingQuantity + $amountSold; $item['RECEIVED_QUANTITY'] = (float)($receivedQuantityData[$item['PRODUCT_ID']] ?? 0); $item['AMOUNT_SOLD'] = (float)($amountSoldData[$item['PRODUCT_ID']] ?? 0); $item['QUANTITY'] = (float)$item['AMOUNT'] - (float)$item['QUANTITY_RESERVED']; $rows[] = [ 'id' => $item['ID'], 'data' => $item, 'columns' => $this->prepareItemColumn($item), ]; } return $rows; } protected function getGridData(): array { $navParams = $this->gridOptions->getNavParams(); $pageSize = (int)$navParams['nPageSize']; $pageNavigation = new \Bitrix\Main\UI\PageNavigation($this->navParamName); $pageNavigation->allowAllRecords(false)->setPageSize($pageSize)->initFromUri(); $totalCount = $this->getTotalCount(); $pageNavigation->setRecordCount($totalCount); $gridRows = $this->getGridRows(); return [ 'GRID_ID' => $this->getGridId(), 'COLUMNS' => $this->getGridColumns(), 'ROWS' => $gridRows, 'STUB' => $totalCount <= 0 ? ['title' => static::getEmptyStub()] : null, 'NAV_PARAM_NAME' => $this->navParamName, 'CURRENT_PAGE' => $pageNavigation->getCurrentPage(), 'NAV_OBJECT' => $pageNavigation, 'TOTAL_ROWS_COUNT' => $totalCount, 'AJAX_MODE' => 'Y', 'ALLOW_ROWS_SORT' => false, 'AJAX_OPTION_JUMP' => 'N', 'AJAX_OPTION_STYLE' => 'N', 'AJAX_OPTION_HISTORY' => 'N', 'AJAX_ID' => \CAjax::GetComponentID('bitrix:main.ui.grid', '', ''), 'SHOW_PAGINATION' => $totalCount > 0, 'SHOW_NAVIGATION_PANEL' => true, 'SHOW_PAGESIZE' => true, 'PAGE_SIZES' => [ ['NAME' => '10', 'VALUE' => '10'], ['NAME' => '20', 'VALUE' => '20'], ['NAME' => '50', 'VALUE' => '50'], ['NAME' => '100', 'VALUE' => '100'], ['NAME' => '200', 'VALUE' => '200'], ['NAME' => '500', 'VALUE' => '500'], ], 'SHOW_ROW_CHECKBOXES' => false, 'SHOW_CHECK_ALL_CHECKBOXES' => false, 'SHOW_ACTION_PANEL' => false, 'SHOW_GRID_SETTINGS_MENU' => false, 'SHOW_SELECTED_COUNTER' => false, 'HANDLE_RESPONSE_ERRORS' => true, 'ALLOW_STICKED_COLUMNS' => true, ]; } protected function getProductData(): array { $navParams = $this->gridOptions->getNavParams(); $pageSize = (int)$navParams['nPageSize']; $gridSort = $this->gridOptions->GetSorting(['sort' => $this->defaultGridSort]); $pageNavigation = new \Bitrix\Main\UI\PageNavigation($this->navParamName); $pageNavigation->allowAllRecords(false)->setPageSize($pageSize)->initFromUri(); $this->arResult['GRID']['ROWS'] = []; $offset = $pageNavigation->getOffset(); $order = $gridSort['sort']; $limit = $pageNavigation->getLimit(); $query = $this->buildDataQuery($order, $limit, $offset); return $query->exec()->fetchAll(); } /** * In this report we don't have to display products that haven't been stored in the store * at any point during the report period, so here we are looking for products that are * not in the store right now but that might have been there before (meaning that they have * been sold or moved to another store) * * @param $order * @param $limit * @param $offset * @return Query */ protected function buildDataQuery($order = null, $limit = null, $offset = null): Query { $storeId = $this->storeId; $filter = $this->getListFilter(); $baseStoreFilterValues = $filter['=STORE_ID'] ?? ''; unset($filter['=STORE_ID']); $reportInterval = $filter['REPORT_INTERVAL'] ?? []; unset($filter['REPORT_INTERVAL']); $storeDocsFilter = ['=DOCUMENT.STATUS' => 'Y']; $shipmentsFilter = ['=ORDER_DELIVERY.DEDUCTED' => 'Y']; if (!empty($reportInterval)) { $storeDocsFilter += [ '<=DOCUMENT.DATE_STATUS' => new DateTime($reportInterval['TO']), ]; $shipmentsFilter += [ '<=ORDER_DELIVERY.DATE_DEDUCTED' => new DateTime($reportInterval['TO']), ]; } if (!$this->isAllStoresGrid()) { $storeDocsFilter[] = [ 'LOGIC' => 'OR', '=DOCS_ELEMENT.STORE_FROM' => $storeId, '=DOCS_ELEMENT.STORE_TO' => $storeId, ]; $shipmentsFilter['=STORE_BARCODE.STORE_ID'] = $storeId; $filter[] = ['=STORE_ID' => $storeId]; } elseif (!empty($baseStoreFilterValues)) { $storeDocsFilter[] = $baseStoreFilterValues; $shipmentsFilter['=STORE_BARCODE.STORE_ID'] = $baseStoreFilterValues; $filter[] = ['=STORE_ID' => $baseStoreFilterValues]; } $filter[] = [ 'LOGIC' => 'OR', $storeDocsFilter, $shipmentsFilter ]; $storeQuery = StoreProductTable::query(); $storeQuery->setSelect(['ID' ,'PRODUCT_ID', 'AMOUNT', 'QUANTITY_RESERVED', 'MEASURE_ID' => 'PRODUCT.MEASURE']); $storeQuery->registerRuntimeField( new Reference( 'DOCS_ELEMENT', StoreDocumentElementTable::class, Join::on('this.PRODUCT_ID', 'ref.ELEMENT_ID') ) ); $storeQuery->registerRuntimeField( new Reference( 'DOCUMENT', StoreDocumentTable::class, Join::on('this.DOCS_ELEMENT.DOC_ID', 'ref.ID') ) ); $storeQuery->registerRuntimeField( new Reference( 'BASKET', BasketTable::class, Join::on('this.PRODUCT_ID', 'ref.PRODUCT_ID') ) ); $storeQuery->registerRuntimeField( new Reference( 'SHIPMENT_ITEM', ShipmentItemTable::class, Join::on('this.BASKET.ID', 'ref.BASKET_ID') ) ); $storeQuery->registerRuntimeField( new Reference( 'STORE_BARCODE', ShipmentItemStoreTable::class, Join::on('this.SHIPMENT_ITEM.ID', 'ref.ORDER_DELIVERY_BASKET_ID') ) ); $storeQuery->registerRuntimeField( new Reference( 'ORDER_DELIVERY', ShipmentTable::class, Join::on('this.SHIPMENT_ITEM.ORDER_DELIVERY_ID', 'ref.ID') ) ); $storeQuery->setFilter($filter); $storeQuery->setDistinct(); if (!$this->isAllStoresGrid()) { if (isset($order)) { $storeQuery->setOrder($order); } if (isset($limit)) { $storeQuery->setLimit($limit); } if (isset($offset)) { $storeQuery->setOffset($offset); } $storeQuery->countTotal(true); return $storeQuery; } $allStoreQuery = ProductTable::query(); $allStoreQuery->registerRuntimeField('', new ReferenceField( 'SUBQUERY', Base::getInstanceByQuery($storeQuery), ['this.ID' => 'ref.PRODUCT_ID'], ['join_type' => 'INNER'] ) ); $allStoreQuery->registerRuntimeField( new \Bitrix\Main\Entity\ExpressionField( 'AMOUNT', 'SUM(%s)', 'SUBQUERY.AMOUNT' ) ); $allStoreQuery->registerRuntimeField( new \Bitrix\Main\Entity\ExpressionField( 'QUANTITY_RESERVED', 'SUM(%s)', 'SUBQUERY.QUANTITY_RESERVED' ) ); $allStoreQuery->setSelect([ 'ID', 'PRODUCT_ID' => 'ID', 'AMOUNT', 'QUANTITY_RESERVED', 'MEASURE_ID' => 'MEASURE', ]); if (isset($order)) { $allStoreQuery->setOrder($order); } if (isset($limit)) { $allStoreQuery->setLimit($limit); } if (isset($offset)) { $allStoreQuery->setOffset($offset); } $allStoreQuery->countTotal(true); return $allStoreQuery; } protected function getFormattedFilter(): array { $result = []; $incomingFilter = $this->arParams['INCOMING_FILTER'] ?? []; if (!empty($incomingFilter)) { if (!empty($incomingFilter['PRODUCTS'])) { $result['PRODUCTS'] = $this->prepareProductFilter($incomingFilter['PRODUCTS']); } if ( !empty($incomingFilter['REPORT_INTERVAL_from']) && !empty($incomingFilter['REPORT_INTERVAL_to']) ) { $result['REPORT_INTERVAL'] = [ 'FROM' => $incomingFilter['REPORT_INTERVAL_from'], 'TO' => $incomingFilter['REPORT_INTERVAL_to'], ]; } if (!empty($incomingFilter['STORES']) && $this->isAllStoresGrid()) { $result['STORE_ID'] = $incomingFilter['STORES']; } } else { $getListFilter = $this->getListFilter(); if (!empty($getListFilter['=PRODUCT_ID'])) { $result['PRODUCTS'] = $getListFilter['=PRODUCT_ID']; } $userFilter = $this->getUserFilter(); if ( !empty($userFilter['>=REPORT_INTERVAL']) && !empty($userFilter['<=REPORT_INTERVAL']) ) { $result['REPORT_INTERVAL'] = [ 'FROM' => $userFilter['>=REPORT_INTERVAL'], 'TO' => $userFilter['<=REPORT_INTERVAL'], ]; } } return $result; } protected function prepareItemColumn(array $item): array { $column = $item; $column['PRODUCT_ID'] = $this->getProductView($column); foreach (['STARTING_QUANTITY', 'RECEIVED_QUANTITY', 'AMOUNT', 'QUANTITY_RESERVED', 'QUANTITY', 'AMOUNT_SOLD'] as $totalField) { $column[$totalField] = $this->formatNumberWithMeasure($column[$totalField], (int)$column['MEASURE_ID']); } unset($column['MEASURE_ID']); $column['QUANTITY_RESERVED'] = $this->getReservedDealListLink((int)$item['PRODUCT_ID'], $column['QUANTITY_RESERVED']); return $column; } protected function getReservedDealListLink(int $productId, string $quantityReservedView): string { return '<a class="main-grid-cell-content-store-amount-reserved-quantity" onclick="BX.SidePanel.Instance.open(\'' . $this->getReservedDealsSliderLink($productId) . '\')" >' . $quantityReservedView . '</a>' ; } protected function getReservedDealsSliderLink(int $productId): string { $sliderUrl = \CComponentEngine::makeComponentPath('bitrix:catalog.productcard.reserved.deal.list'); $sliderUrl = getLocalPath('components'.$sliderUrl.'/slider.php'); $sliderUrlEntity = new \Bitrix\Main\Web\Uri($sliderUrl); $sliderUrlEntity->addParams([ 'storeId' => $this->storeId, 'productId' => $productId, ]); return $sliderUrlEntity->getUri(); } protected function formatNumberWithMeasure($number, int $measureId) { if (!$measureId) { $measureId = $this->getDefaultMeasure()['ID']; } return Loc::getMessage( 'CATALOG_REPORT_PRODUCT_LIST_MEASURE_TEMPLATE', [ '#NUMBER#' => $number, '#MEASURE_SYMBOL#' => $this->getMeasureSymbol($measureId), ] ); } protected function getMeasureSymbol(int $measureId): string { return htmlspecialcharsbx($this->measures[$measureId]['SYMBOL']); } protected function getProductView(array $column): string { global $APPLICATION; $product = $this->catalogData[(int)$column['PRODUCT_ID']]; ob_start(); $APPLICATION->IncludeComponent( 'bitrix:catalog.grid.product.field', '', [ 'BUILDER_CONTEXT' => $this->arParams['BUILDER_CONTEXT'], 'GRID_ID' => $this->getGridId(), 'ROW_ID' => $column['ID'], 'GUID' => 'catalog_document_grid_' . $column['ID'], 'PRODUCT_FIELDS' => [ 'ID' => $product['FIELDS']['PRODUCT_ID'], 'NAME' => $product['FIELDS']['NAME'], 'IBLOCK_ID' => $product['FIELDS']['IBLOCK_ID'], 'SKU_IBLOCK_ID' => $product['FIELDS']['OFFERS_IBLOCK_ID'], 'SKU_ID' => $product['FIELDS']['OFFER_ID'] ?? null, 'BASE_PRICE_ID' => $product['FIELDS']['BASE_PRICE_ID'] ?? null, ], 'SKU_TREE' => $product['FIELDS']['SKU_TREE'], 'MODE' => 'view', 'VIEW_FORMAT' => 'short', 'ENABLE_SEARCH' => false, 'ENABLE_IMAGE_CHANGE_SAVING' => false, 'ENABLE_IMAGE_INPUT' => false, 'ENABLE_INPUT_DETAIL_LINK' => true, 'ENABLE_EMPTY_PRODUCT_ERROR' => false, 'ENABLE_SKU_SELECTION' => false, 'HIDE_UNSELECTED_ITEMS' => true, 'IS_NEW' => false, ] ); return ob_get_clean(); } protected function init(): void { $this->storeId = $this->arParams['STORE_ID']; $this->gridOptions = new \Bitrix\Main\Grid\Options($this->getGridId()); if ($this->arParams['OPENED_FROM_REPORT']) { $this->getFilterOptions()->reset(); } if (isset($this->arParams['INCOMING_FILTER']) && is_array($this->arParams['INCOMING_FILTER'])) { $this->initFilterFromIncomingData($this->arParams['INCOMING_FILTER']); } } protected function isAllStoresGrid(): bool { return $this->storeId <= 0; } protected function prepareFilterIncomingData(array $incomingFilter): array { $filterFields = []; if (isset($incomingFilter['PRODUCTS'], $incomingFilter['PRODUCTS_label'])) { $filterFields['PRODUCTS'] = $incomingFilter['PRODUCTS']; $filterFields['PRODUCTS_label'] = $incomingFilter['PRODUCTS_label']; } return $filterFields; } protected function initFilterFromIncomingData(array $incomingFilter): void { $filterFields = $this->prepareFilterIncomingData($incomingFilter); if (count($filterFields) > 0) { $this->setFilterFields($filterFields); } } protected function setFilterFields(array $filterFields): void { $filterOptions = $this->getFilterOptions(); $currentFilterSettings = $filterOptions->getFilterSettings('tmp_filter'); $currentFilterSettings['fields'] = $filterFields; $filterOptions->setFilterSettings( \Bitrix\Main\UI\Filter\Options::TMP_FILTER, $currentFilterSettings, true, false ); $filterOptions->save(); } protected function checkDocumentReadRights(): bool { if ( !AccessController::getCurrent()->check(ActionDictionary::ACTION_CATALOG_READ) || !AccessController::getCurrent()->check(ActionDictionary::ACTION_INVENTORY_MANAGEMENT_ACCESS) ) { return false; } return $this->arParams['STORE_ID'] > 0 ? AccessController::getCurrent()->checkByValue(ActionDictionary::ACTION_STORE_VIEW, $this->arParams['STORE_ID']) : AccessController::getCurrent()->check(ActionDictionary::ACTION_STORE_VIEW) ; } protected function getTotalCount(): int { return $this->buildDataQuery()->exec()->getCount(); } protected function getListFilter(): array { if (!$this->isAllStoresGrid()) { $filter = [ '=STORE_ID' => $this->storeId, ]; } else { $filter = AccessController::getCurrent() ->getEntityFilter( ActionDictionary::ACTION_STORE_VIEW, StoreProductTable::class ) ; $incomingFilter = $this->arParams['INCOMING_FILTER'] ?? []; if (isset($incomingFilter['STORES'])) { $filter['=STORE_ID'] = $incomingFilter['STORES']; } } $searchString = trim($this->getFilterOptions()->getSearchString()); if ($searchString) { $filter['%PRODUCT.IBLOCK_ELEMENT.SEARCHABLE_CONTENT'] = mb_strtoupper($searchString); } $userFilter = $this->getUserFilter(); if (!empty($userFilter['PRODUCTS'])) { $filter['=PRODUCT_ID'] = $this->prepareProductFilter($userFilter['PRODUCTS']); } if ( !empty($userFilter['>=REPORT_INTERVAL']) && !empty($userFilter['<=REPORT_INTERVAL']) ) { $filter['REPORT_INTERVAL'] = [ 'FROM' => $userFilter['>=REPORT_INTERVAL'], 'TO' => $userFilter['<=REPORT_INTERVAL'], ]; } $filter['@PRODUCT.TYPE'] = [ ProductTable::TYPE_PRODUCT, ProductTable::TYPE_OFFER, ]; return $filter; } protected function getStoreTitle(): string { $storeData = \Bitrix\Catalog\StoreTable::getList([ 'select' => ['TITLE'], 'filter' => ['=ID' => $this->storeId], 'limit' => 1, ])->fetch(); return $storeData['TITLE'] ?? ''; } protected function getFilterFields(): array { $entities = []; if (Loader::includeModule('crm')) { $entities[] = [ 'id' => 'product_variation', 'options' => [ 'iblockId' => \Bitrix\Crm\Product\Catalog::getDefaultId(), 'basePriceId' => Catalog\GroupTable::getBasePriceTypeId(), 'showPriceInCaption' => false, ], ]; } return [ 'PRODUCTS' => [ 'id' => 'PRODUCTS', 'name' => Loc::getMessage('CATALOG_REPORT_PRODUCT_LIST_FILTER_PRODUCTS_TITLE'), 'type' => 'entity_selector', 'default' => true, 'partial' => true, 'params' => [ 'multiple' => true, 'showDialogOnEmptyInput' => false, 'dropdownMode' => true, 'dialogOptions' => [ 'hideOnSelect' => false, 'context' => $this->getProductFilterDialogContext(), 'entities' => $entities, 'recentTabOptions' => [ 'stub' => true, 'stubOptions' => [ 'title' => Loc::getMessage('CATALOG_REPORT_PRODUCT_LIST_PRODUCT_FILTER_STUB'), ], ], 'events' => [ 'onBeforeSearch' => 'onBeforeDialogSearch', ], ], ], ] ]; } protected function getUserFilter(): array { $filterOptions = $this->getFilterOptions(); $filterFields = $this->getFilterFields(); return $filterOptions->getFilterLogic($filterFields); } protected function getFilterOptions(): \Bitrix\Main\UI\Filter\Options { static $filterOptions = null; if (is_null($filterOptions)) { $filterOptions = new \Bitrix\Main\UI\Filter\Options($this->getFilterId()); } return $filterOptions; } }