| Current Path : /var/www/homesaver/www/bitrix/components/bitrix/catalog.store.document.product.list/ |
| Current File : /var/www/homesaver/www/bitrix/components/bitrix/catalog.store.document.product.list/class.php |
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true)
{
die();
}
use Bitrix\Catalog\Access\AccessController;
use Bitrix\Catalog\Access\ActionDictionary;
use Bitrix\Catalog\Access\Permission\PermissionDictionary;
use Bitrix\Catalog\Component\ImageInput;
use Bitrix\Catalog\Config\Feature;
use Bitrix\Catalog\Config\State;
use Bitrix\Catalog\GroupTable;
use Bitrix\Catalog\StoreBarcodeTable;
use Bitrix\Catalog\StoreDocumentBarcodeTable;
use Bitrix\Catalog\StoreDocumentElementTable;
use Bitrix\Catalog\StoreDocumentTable;
use Bitrix\Catalog\StoreProductTable;
use Bitrix\Catalog\StoreTable;
use Bitrix\Catalog\Url\InventoryBuilder;
use Bitrix\Catalog\v2\Sku\BaseSku;
use Bitrix\Crm\ProductRowTable;
use Bitrix\Main;
use Bitrix\Main\Grid\Editor\Types;
use Bitrix\Main\Grid\Panel\Snippet;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
use Bitrix\Currency\CurrencyManager;
use Bitrix\Catalog\v2\IoC\ServiceContainer;
use Bitrix\Main\Text\HtmlFilter;
use Bitrix\Main\Web\Json;
use Bitrix\Catalog\ProductTable;
use Bitrix\Catalog\Store\EnableWizard;
use Bitrix\Sale\PriceMaths;
use Bitrix\Sale\Tax\VatCalculator;
if (!Loader::includeModule('catalog'))
{
return;
}
final class CatalogStoreDocumentProductListComponent
extends \Bitrix\Catalog\Component\ProductList
implements Main\Engine\Contract\Controllerable, Main\Errorable
{
use Main\ErrorableImplementation;
private const NEW_ROW_ID_PREFIX = 'n';
private const PRODUCT_ID_MASK = '#PRODUCT_ID_MASK#';
private const VIEW_MODE_GRID_ID_POSTFIX = 'V';
private const EDIT_MODE_GRID_ID_POSTFIX = 'E';
/** @var Main\Grid\Options $gridConfig */
protected $gridConfig;
protected $storage = [];
protected $defaultSettings = [];
protected $rows = [];
/** @var Main\UI\PageNavigation $navigation */
protected $navigation;
protected $currency = [
'ID' => '',
'TEMPLATE' => '',
'TEXT' => '',
'FORMAT' => [],
];
protected $stores = [];
protected $newRowCounter = 0;
protected $externalDocument = [];
protected AccessController $accessController;
/**
* @var int[]
*/
protected array $accessibleStoresIds;
/**
* Base constructor.
*
* @param \CBitrixComponent|null $component Component object if exists.
*/
public function __construct($component = null)
{
parent::__construct($component);
$this->errorCollection = new Main\ErrorCollection();
$this->accessController = AccessController::getCurrent();
}
/**
* @return array
*/
public function configureActions()
{
return [];
}
/**
* @param $params
* @return array
*/
public function onPrepareComponentParams($params): array
{
/**
* GRID_ID - string - custom grid id
* NAVIGATION_ID - string - custom navigation id (maybe created from GRID_ID)
* FORM_ID - string - custom form identifier (maybe created from GRID_ID), default empty
* TAB_ID - string - custom product tab identifier, default empty
*
* AJAX_ID - string - ajax component identifier
* AJAX_MODE - string - is ajax enabled (Y/N), default Y
* AJAX_OPTION_JUMP - string - ajax option (Y/N), default N
* AJAX_OPTION_HISTORY - string - ajax option (Y/N), default N
* AJAX_LOADER - mixed|null - not used in titleflex template, default null
*
* SHOW_PAGINATION - bool or Y/N - show pagination block, default false
* SHOW_TOTAL_COUNTER - bool or Y/N - show count of rows, default false
* SHOW_PAGESIZE - bool or Y/N - show page size select, default false
* PAGINATION - array - pagination info (pages size, offset, etc.), default - empty array
*
* PRODUCTS - array|null - product list
* TOTAL_PRODUCTS_COUNT - int - full product rows quantity
*
* CUSTOM_SITE_ID - string - entity site identifier, default SITE_ID
* CUSTOM_LANGUAGE_ID - string - current lang identifier, default LANGUAGE_ID
* SET_ITEMS - bool - set rows (Y/N), default N
* ALLOW_EDIT - bool - allow to modify data (Y/N), default N
* ALLOW_ADD_PRODUCT - bool - add product to entity button (Y/N), default N
* ALLOW_CREATE_NEW_PRODUCT - bool - create fake products button (Y/N), default N
* if ALLOW_EDIT off - ALLOW_ADD_PRODUCT and ALLOW_CREATE_NEW_PRODUCT already off
*
* DOCUMENT_ID - string|int - parent entity id
*
* EXTERNAL_DOCUMENT - array|null - custom external documents
*
* PRESELECTED_PRODUCT_ID - int - preselected product (can be absent)
*/
$this->prepareEntityIds($params);
$this->prepareAjaxOptions($params);
$this->preparePaginationOptions($params);
$this->prepareProducts($params);
$this->prepareSettings($params);
$this->prepareEntitySettings($params);
$params['PRESELECTED_PRODUCT_ID'] = (int)($params['PRESELECTED_PRODUCT_ID'] ?? 0);
if ($params['PRESELECTED_PRODUCT_ID'] < 0)
{
$params['PRESELECTED_PRODUCT_ID'] = 0;
}
return $params;
}
/**
* @return void
*/
public function executeComponent(): void
{
$this->fillSettings();
if ($this->isExistErrors())
{
$this->showErrors();
return;
}
$this->loadData();
$this->rows = $this->prepareRowsForAccessRights($this->rows);
$this->prepareResult();
$this->includeComponentTemplate();
}
/**
* @return array
*/
protected function listKeysSignedParameters()
{
return [
// prepareEntityIds
'GRID_ID',
'NAVIGATION_ID',
'FORM_ID',
'TAB_ID',
'AJAX_ID',
'CATALOG_ID',
// prepareAjaxOptions
'AJAX_MODE',
'AJAX_OPTION_JUMP',
'AJAX_OPTION_HISTORY',
'AJAX_LOADER',
// preparePaginationOptions
'SHOW_PAGINATION',
'SHOW_TOTAL_COUNTER',
'SHOW_PAGESIZE',
// prepareSettings
'CUSTOM_SITE_ID',
'CUSTOM_LANGUAGE_ID',
'CURRENCY',
'SET_ITEMS',
'ALLOW_EDIT',
'ALLOW_ADD_PRODUCT',
'ALLOW_CREATE_NEW_PRODUCT',
'PREFIX',
'ID',
'PRODUCT_DATA_FIELD_NAME',
// prepareEntitySettings
'DOCUMENT_TYPE',
'DOCUMENT_ID',
'EXTERNAL_DOCUMENT',
];
}
/**
* @return bool
*/
protected function isExistErrors(): bool
{
return !$this->errorCollection->isEmpty();
}
/**
* @return void
*/
protected function showErrors(): void
{
foreach ($this->getErrors() as $error)
{
\ShowError($error);
}
}
/**
* @param string $message
* @return void
*/
protected function addErrorMessage(string $message): void
{
$this->errorCollection->setError(new Main\Error($message));
}
/**
* @param array &$params
* @return void
*/
protected function prepareEntityIds(array &$params): void
{
static::validateListParameters(
$params,
[
'GRID_ID',
'NAVIGATION_ID',
'FORM_ID',
'TAB_ID',
'AJAX_ID',
]
);
if (!empty($params['GRID_ID']))
{
if (empty($params['NAVIGATION_ID']))
{
$params['NAVIGATION_ID'] = static::createNavigationId($params['GRID_ID']);
}
if (!isset($params['FORM_ID']))
{
$params['FORM_ID'] = static::createFormId($params['GRID_ID']);
}
}
}
private function isAcceptableDocumentType($type): bool
{
$acceptableDocumentTypes = [
StoreDocumentTable::TYPE_ARRIVAL,
StoreDocumentTable::TYPE_STORE_ADJUSTMENT,
StoreDocumentTable::TYPE_DEDUCT,
StoreDocumentTable::TYPE_MOVING,
];
if (!empty($this->externalDocument['TYPE']))
{
$acceptableDocumentTypes[] = $this->externalDocument['TYPE'];
}
return in_array($type, $acceptableDocumentTypes, true);
}
/**
* @param array &$params
* @return void
*/
protected function prepareAjaxOptions(array &$params): void
{
$params['AJAX_MODE'] = isset($params['AJAX_MODE']) && $params['AJAX_MODE'] === 'N' ? 'N' : 'Y';
$params['AJAX_OPTION_JUMP'] = isset($params['AJAX_OPTION_JUMP']) && $params['AJAX_OPTION_JUMP'] === 'Y' ? 'Y' : 'N';
$params['AJAX_OPTION_HISTORY'] = isset($params['AJAX_OPTION_HISTORY']) && $params['AJAX_OPTION_HISTORY'] === 'Y' ? 'Y' : 'N';
$params['AJAX_LOADER'] = $params['AJAX_LOADER'] ?? null;
}
/**
* @param array &$params
* @return void
*/
protected function preparePaginationOptions(array &$params): void
{
static::validateBoolList(
$params,
[
'SHOW_PAGINATION',
'SHOW_TOTAL_COUNTER',
'SHOW_PAGESIZE',
]
);
if (empty($params['PAGINATION']) || !is_array($params['PAGINATION']))
{
$params['PAGINATION'] = [];
}
}
/**
* @param array &$params
* @return void
*/
protected function prepareProducts(array &$params): void
{
if (isset($params['PRODUCTS']) && !is_array($params['PRODUCTS']))
{
$params['PRODUCTS'] = null;
}
}
/**
* @param array &$params
* @return void
*/
protected function prepareSettings(array &$params): void
{
$params['CURRENCY'] = isset($params['CURRENCY']) && is_string($params['CURRENCY'])
? $params['CURRENCY']
: ''
;
$params['SET_ITEMS'] = isset($params['SET_ITEMS']) && $params['SET_ITEMS'] === 'Y';
$params['ALLOW_EDIT'] = isset($params['ALLOW_EDIT']) && $params['ALLOW_EDIT'] === 'Y';
$params['ALLOW_ADD_PRODUCT'] = isset($params['ALLOW_ADD_PRODUCT']) && $params['ALLOW_ADD_PRODUCT'] === 'Y';
$params['ALLOW_CREATE_NEW_PRODUCT'] = isset($params['ALLOW_CREATE_NEW_PRODUCT']) && $params['ALLOW_CREATE_NEW_PRODUCT'] === 'Y';
$params['CALCULATE_STORE_PURCHASING_PRICE'] = ($params['CALCULATE_STORE_PURCHASING_PRICE'] ?? 'N') === 'Y';
if (!$params['ALLOW_EDIT'])
{
$params['ALLOW_ADD_PRODUCT'] = false;
$params['ALLOW_CREATE_NEW_PRODUCT'] = false;
}
$params['BUILDER_CONTEXT'] =
isset($params['BUILDER_CONTEXT']) && is_string($params['BUILDER_CONTEXT'])
? trim($params['BUILDER_CONTEXT'])
: InventoryBuilder::TYPE_ID
;
$params['PREFIX'] = (isset($params['PREFIX']) && is_string($params['PREFIX']) ? trim($params['PREFIX']) : '');
$params['ID'] = (isset($params['ID']) && is_string($params['ID']) ? trim($params['ID']) : '');
$params['PRODUCT_DATA_FIELD_NAME'] = isset($params['PRODUCT_DATA_FIELD_NAME']) ? $params['PRODUCT_DATA_FIELD_NAME'] : 'PRODUCT_ROW_DATA';
$params['EXTERNAL_DOCUMENT'] = $params['EXTERNAL_DOCUMENT'] ?? [];
$this->externalDocument = $params['EXTERNAL_DOCUMENT'];
}
/**
* @param array &$params
* @return void
*/
protected function prepareEntitySettings(array &$params): void
{
if (empty($params['DOCUMENT_TYPE']) || !$this->isAcceptableDocumentType($params['DOCUMENT_TYPE']))
{
$this->addErrorMessage(Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_WRONG_DOCUMENT_TYPE'));
return;
}
$params['DOCUMENT_ID'] = (isset($params['DOCUMENT_ID']) ? (int)$params['DOCUMENT_ID'] : 0);
if ($params['DOCUMENT_ID'] < 0)
{
$this->addErrorMessage(Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_WRONG_DOCUMENT_ID'));
}
$params['CATALOG_ID'] = (isset($params['CATALOG_ID']) ? (int)$params['CATALOG_ID'] : 0);
if ($params['CATALOG_ID'] <= 0)
{
$this->addErrorMessage(Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_WRONG_CATALOG_ID'));
}
}
/**
* @return void
*/
protected function fillSettings(): void
{
$this->checkModules();
$this->initDefaultSettings();
$this->loadReferences();
$this->initSettings();
}
/**
* @return void
*/
protected function initDefaultSettings(): void
{
$this->defaultSettings = [
'GRID_ID' => $this->getDefaultGridId(),
];
$this->defaultSettings['NAVIGATION_ID'] = static::createNavigationId($this->defaultSettings['GRID_ID']);
$this->defaultSettings['FORM_ID'] = static::createFormId($this->defaultSettings['GRID_ID']);
$this->defaultSettings['TAB_ID'] = '';
$this->defaultSettings['AJAX_ID'] = '';
$this->defaultSettings['PAGE_SIZES'] = [5, 10, 20, 50, 100];
$this->defaultSettings['PRICE_PRECISION'] = 2;
$this->defaultSettings['AMOUNT_PRECISION'] = 4;
$this->defaultSettings['COMMON_PRECISION'] = 2;
$this->defaultSettings['CREATE_PRODUCT_PATH'] = $this->getElementDetailUrl($this->arParams['CATALOG_ID']);
$this->defaultSettings['NEW_ROW_POSITION'] = CUserOptions::GetOption(
'catalog.store.document.product.list',
'new.row.position',
'top'
);
$this->defaultSettings['BASE_PRICE_ID'] = GroupTable::getBasePriceTypeId();
}
protected function getDefaultSetting($name)
{
return $this->defaultSettings[$name] ?? null;
}
/**
* @return string
*/
public function getDefaultGridId(): string
{
$modePostfix = $this->isReadOnly() ? self::VIEW_MODE_GRID_ID_POSTFIX : self::EDIT_MODE_GRID_ID_POSTFIX;
return self::clearStringValue(self::class) . '_' . $this->getDocumentType() . '_' . $modePostfix;
}
protected function getDocumentId(): int
{
return (int)$this->arParams['DOCUMENT_ID'];
}
protected function getDocumentType(): ?string
{
return $this->arParams['DOCUMENT_TYPE'];
}
/**
* @param string $gridId
* @return string
*/
protected static function createNavigationId(string $gridId): string
{
return $gridId . '_NAVIGATION';
}
/**
* @param string $gridId
* @return string
*/
protected static function createFormId(string $gridId): string
{
return 'form_' . $gridId;
}
/**
* @return void
*/
protected function initSettings(): void
{
$paramsList = [
'GRID_ID',
'NAVIGATION_ID',
'PAGE_SIZES',
'FORM_ID',
'TAB_ID',
'AJAX_ID',
'NEW_ROW_POSITION',
'PRICE_PRECISION',
'AMOUNT_PRECISION',
'COMMON_PRECISION',
'CREATE_PRODUCT_PATH',
];
foreach ($paramsList as $param)
{
$value = !empty($this->arParams[$param]) ? $this->arParams[$param] : $this->defaultSettings[$param];
$this->setStorageItem($param, $value);
}
$this->initGrid();
}
/**
* @return void
*/
protected function loadReferences(): void
{
$this->loadCurrency();
$this->loadMeasures();
$this->loadStores();
}
/**
* @return void
*/
protected function loadCurrency(): void
{
$this->currency['ID'] =
!empty($this->arParams['CURRENCY'])
? $this->arParams['CURRENCY']
: CurrencyManager::getBaseCurrency()
;
$format = \CCurrencyLang::GetFormatDescription($this->currency['ID']);
$this->currency['TEMPLATE'] = $format['FORMAT_STRING'] ?? '';
$this->currency['TEXT'] =
isset($this->currency['TEMPLATE'])
? trim(\CCurrencyLang::applyTemplate('', $this->currency['TEMPLATE']))
: ''
;
$this->currency['FORMAT'] = $format;
}
/**
* @return array
*/
protected function getCurrency(): array
{
return $this->currency;
}
/**
* @return string
*/
protected function getCurrencyId(): string
{
return $this->currency['ID'];
}
/**
* @return string
*/
protected function getDefaultTotalCalculationField(): string
{
if (!empty($this->externalDocument['TOTAL_CALCULATION_FIELD']))
{
return (string)$this->externalDocument['TOTAL_CALCULATION_FIELD'];
}
return 'PURCHASING_PRICE';
}
/**
* @return string
*/
protected function getCurrencyTemplate(): string
{
return $this->currency['TEMPLATE'];
}
/**
* @return string
*/
protected function getCurrencyText(): string
{
return $this->currency['TEXT'];
}
/**
* @return array
*/
protected function getCurrencyFormat(): array
{
return $this->currency['FORMAT'];
}
protected function loadStores(): void
{
$this->stores = [];
$productStoreRaw = StoreTable::getList([
'select' => ['ID', 'TITLE', 'IS_DEFAULT', 'ADDRESS']
]);
while ($store = $productStoreRaw->fetch())
{
if ($store['TITLE'] === '')
{
$store['TITLE'] = $store['ADDRESS'];
}
$this->stores[$store['ID']] = $store;
}
}
protected function checkModules(): bool
{
if (!Loader::includeModule('catalog'))
{
$this->addErrorMessage('Module "catalog" is not installed.');
return false;
}
if (!Loader::includeModule('iblock'))
{
$this->addErrorMessage('Module "iblock" is not installed.');
return false;
}
if (!Loader::includeModule('currency'))
{
$this->addErrorMessage('Module "currency" is not installed.');
return false;
}
if (!Loader::includeModule('sale'))
{
$this->addErrorMessage('Module "sale" is not installed.');
return false;
}
return true;
}
/**
* @return void
*/
protected function initUiScope(): void
{
global $APPLICATION;
Main\UI\Extension::load($this->getUiExtensions());
foreach ($this->getUiStyles() as $styleList)
{
$APPLICATION->SetAdditionalCSS($styleList);
}
$scripts = $this->getUiScripts();
if (!empty($scripts))
{
$asset = Main\Page\Asset::getInstance();
foreach ($scripts as $row)
{
$asset->addJs($row);
}
unset($row, $asset);
}
unset($scripts);
}
/**
* @return void
*/
protected function loadData(): void
{
$this->rows = [];
if (
isset($this->arParams['REQUEST'], $this->arParams['~PRODUCTS'])
&& is_array($this->arParams['~PRODUCTS'])
)
{
$this->rows = $this->getProductRowsFromRequest();
return;
}
if (!empty($this->arParams['PRODUCTS']))
{
$documentProducts = $this->arParams['PRODUCTS'];
}
elseif (!$this->externalDocument && $this->getDocumentId() > 0)
{
$documentProducts = $this->getDocumentProducts();
}
elseif ($this->arParams['PRESELECTED_PRODUCT_ID'])
{
$documentProducts = $this->getPreselectDocumentProducts();
}
elseif (!empty($this->arParams['REQUEST']) && !empty($this->externalDocument['INITIAL_PRODUCTS']))
{
$documentProducts = $this->externalDocument['INITIAL_PRODUCTS'];
}
if (empty($documentProducts))
{
return;
}
$productIds = array_column($documentProducts, 'ELEMENT_ID');
$productIds = array_unique($productIds);
if (empty($productIds))
{
return;
}
$productInfo = $this->loadCatalog($productIds);
$restrictedProductTypes = ProductTable::getStoreDocumentRestrictedProductTypes();
$productIdsWithoutRestrictedTypes = array_keys(array_filter(
$productInfo,
static fn($product): bool => !in_array($product['FIELDS']['TYPE'], $restrictedProductTypes, true)
));
$productStoreInfo = $this->getProductStoreInfo($productIdsWithoutRestrictedTypes);
$barcodes = $this->getBarcodes($productIdsWithoutRestrictedTypes);
foreach ($documentProducts as $id => $document)
{
$productId = (int)($document['ELEMENT_ID'] ?? null);
if (isset($productInfo[$productId]))
{
$product = $productInfo[$productId]['FIELDS'];
if ($productInfo[$productId]['SKU'] instanceof \Bitrix\Catalog\v2\Sku\BaseSku)
{
$skuImageField = new ImageInput($productInfo[$productId]['SKU']);
$product['IMAGE_INFO'] = $skuImageField->getFormattedField();
}
}
$productName = $product['NAME'] ?? '';
$rowId = (int)($product['ID'] ?? 0);
if (
isset($product)
&& $productName === ''
&& is_numeric($product['ID'])
&& $rowId > 0
)
{
$productName = "[{$rowId}]";
}
if (!empty($document['BARCODE']))
{
$barcode = $document['BARCODE'];
}
else
{
$barcode = $barcodes[$productId] ?? '';
}
$existsStoreTo = isset($document['STORE_TO']) && (int)$document['STORE_TO'] > 0;
$existsStoreFrom = isset($document['STORE_FROM']) && (int)$document['STORE_FROM'] > 0;
$availableAmountTo = 0;
if ($productId && $existsStoreTo)
{
$availableAmountTo = $this->getAvailableProductAmountOnStore($productStoreInfo, $productId, $document['STORE_TO']);
}
$availableAmountFrom = 0;
if ($productId && $existsStoreFrom)
{
$availableAmountFrom = $this->getAvailableProductAmountOnStore($productStoreInfo, $productId, $document['STORE_FROM']);
}
$amount = (float)$document['AMOUNT'];
$basePrice = $document['BASE_PRICE'] ?? null;
$taxRate = $document['TAX_RATE'] ?? null;
$taxIncluded = $document['TAX_INCLUDED'] ?? 'N';
$taxSum = 0;
if ($taxRate && $basePrice)
{
$calculator = new VatCalculator($taxRate / 100);
$tax = $calculator->calc(
$basePrice,
$taxIncluded === 'Y',
false
);
$taxSum = PriceMaths::roundPrecision($tax * $amount);
}
$calculatedPrice = (float)($document[$this->getDefaultTotalCalculationField()] ?? 0.0);
$totalPrice = $amount * $calculatedPrice;
$additionalData = [
'ROW_ID' => $this->getRowIdPrefix($document['ID']),
'BARCODE' => $barcode,
'DOC_BARCODE' => $barcode,
'STORE_TO_AVAILABLE_AMOUNT' => $availableAmountTo,
'STORE_FROM_AVAILABLE_AMOUNT' => $availableAmountFrom,
'STORE_AMOUNT_MAP' => $productStoreInfo[$productId] ?? null,
'IBLOCK_ID' => $product['IBLOCK_ID'] ?? $this->arParams['IBLOCK_ID'],
'BASE_PRICE_ID' => $product['BASE_PRICE_ID'] ?? $this->getStorageItem('BASE_PRICE_ID'),
'PARENT_PRODUCT_ID' => $product['PARENT_PRODUCT_ID'] ?? null,
'OFFERS_IBLOCK_ID' => $product['OFFERS_IBLOCK_ID'] ?? null,
'SKU_ID' => $product['SKU_ID'] ?? null,
'PRODUCT_ID' => $product['PRODUCT_ID'] ?? null,
'SKU_TREE' => !empty($product['SKU_TREE']) ? Json::encode($product['SKU_TREE']) : null,
'DETAIL_URL' => $product['DETAIL_URL'] ?? null,
'IMAGE_INFO' => $product['IMAGE_INFO'] ?? null,
'MEASURE_NAME' => $product['MEASURE_NAME'] ?? null,
'MEASURE_CODE' => $product['MEASURE_CODE'] ?? null,
'NAME' => $productName,
'BASE_PRICE' => $basePrice,
'PURCHASING_PRICE' => $document['PURCHASING_PRICE'] ?? 0,
'TOTAL_PRICE' => $totalPrice,
'BASKET_ID' => $document['BASKET_ID'] ?? 0,
'TYPE' => $product['TYPE'] ?? null,
'TAX_SUM' => $taxSum,
'TAX_RATE' => $taxRate,
'TAX_INCLUDED' => $taxIncluded,
];
if ($existsStoreTo)
{
$additionalData['STORE_TO_TITLE'] = $this->stores[$document['STORE_TO']]['TITLE'] ?? '';
$additionalData['STORE_TO_AMOUNT'] = $productStoreInfo[$productId][$document['STORE_TO']]['AMOUNT'] ?? '';
$additionalData['STORE_TO_RESERVED'] = $productStoreInfo[$productId][$document['STORE_TO']]['QUANTITY_RESERVED'] ?? '';
}
else
{
$additionalData['STORE_TO_TITLE'] = '';
$additionalData['STORE_TO_AMOUNT'] = '';
$additionalData['STORE_TO_RESERVED'] = '';
}
if ($existsStoreFrom)
{
$additionalData['STORE_FROM_TITLE'] = $this->stores[$document['STORE_FROM']]['TITLE'] ?? '';
$additionalData['STORE_FROM_AMOUNT'] = $productStoreInfo[$productId][$document['STORE_FROM']]['AMOUNT'] ?? '';
$additionalData['STORE_FROM_RESERVED'] = $productStoreInfo[$productId][$document['STORE_FROM']]['QUANTITY_RESERVED'] ?? '';
}
else
{
$additionalData['STORE_FROM_TITLE'] = '';
$additionalData['STORE_FROM_AMOUNT'] = '';
$additionalData['STORE_FROM_RESERVED'] = '';
}
$documentProducts[$id] = array_merge($document, $additionalData);
}
$this->rows = $documentProducts;
}
/**
* Updating rows based on access rights.
*
* @return void
*/
private function prepareRowsForAccessRights(array $rows): array
{
$accessibleStoresIds = $this->getAccessibleStoresIds();
$hiddenFields = $this->getHiddenFieldsWithoutAccess();
$notHasAccessToPurchasingPrice = !AccessController::getCurrent()->check(ActionDictionary::ACTION_PRODUCT_PURCHASE_INFO_VIEW);
foreach ($rows as &$row)
{
$hasAccess = true;
$storeTo = (int)($row['STORE_TO'] ?? 0);
$storeFrom = (int)($row['STORE_FROM'] ?? 0);
if ($storeTo && $storeFrom)
{
$hasAccess =
in_array($storeTo, $accessibleStoresIds, true)
&& in_array($storeFrom, $accessibleStoresIds, true)
;
}
elseif ($storeTo)
{
$hasAccess = in_array($storeTo, $accessibleStoresIds, true);
}
elseif ($storeFrom)
{
$hasAccess = in_array($storeFrom, $accessibleStoresIds, true);
}
$realValues = null;
if (!$hasAccess)
{
$realValues = [];
foreach ($hiddenFields as $fieldName)
{
if (isset($row[$fieldName]))
{
$realValues[$fieldName] = $row[$fieldName];
$row[$fieldName] = null;
}
}
$row['REAL_VALUES'] = base64_encode(Json::encode($realValues));
}
$row['ACCESS_DENIED'] = !$hasAccess;
$row['ACCESS_DENIED_TO_PURCHASING_PRICE'] = $notHasAccessToPurchasingPrice;
}
return $rows;
}
/**
* Fields that are hidden when there is no access.
*
* @return array
*/
private function getHiddenFieldsWithoutAccess(): array
{
return [
'STORE_TO',
'STORE_TO_INFO',
'STORE_TO_TITLE',
'STORE_TO_AMOUNT',
'STORE_TO_RESERVED',
'STORE_TO_AVAILABLE_AMOUNT',
'STORE_FROM',
'STORE_FROM_INFO',
'STORE_FROM_TITLE',
'STORE_FROM_AMOUNT',
'STORE_FROM_RESERVED',
'STORE_FROM_AVAILABLE_AMOUNT',
'PURCHASING_PRICE',
'BASE_PRICE',
'TOTAL_PRICE',
'AMOUNT',
];
}
protected function getProductRowsFromRequest(): array
{
$rows = $this->arParams['~PRODUCTS'];
$rows = array_filter($rows);
$actionButton = $this->arParams['REQUEST']['action_button_' . $this->getGridId()] ?? null;
$actionAllRows = $this->arParams['REQUEST']['action_all_rows_' . $this->getGridId()] ?? null;
if (
$actionButton === 'delete'
&& $actionAllRows === 'Y'
)
{
return [];
}
$skuTreeItems = $this->getSkuTreeItems($rows);
foreach ($rows as $index => $row)
{
if (
$actionButton === 'delete'
&& is_array($this->arParams['REQUEST']['ID'])
&& in_array($row['ID'], $this->arParams['REQUEST']['ID'], true)
)
{
unset($rows[$index]);
continue;
}
if (!isset($row['ID']))
{
$rows[$index]['ID'] = $this->getNewRowId();
}
$intFields = [
'IBLOCK_ID',
'DOCUMENT_ID',
'PARENT_PRODUCT_ID',
'PRODUCT_ID',
'OFFERS_IBLOCK_ID',
'SKU_ID',
'DISCOUNT_TYPE_ID',
'SORT',
];
foreach ($intFields as $name)
{
if (isset($rows[$index][$name]))
{
$rows[$index][$name] = (int)$rows[$index][$name];
}
}
$floatFields = [
'AMOUNT',
'PURCHASING_PRICE',
'TOTAL_PRICE',
];
if ($rows[$index]['BASE_PRICE'] === '' || $rows[$index]['BASE_PRICE'] === null)
{
$rows[$index]['BASE_PRICE'] = null;
}
else
{
$floatFields[] = 'BASE_PRICE';
}
foreach ($floatFields as $name)
{
if (isset($rows[$index][$name]))
{
$rows[$index][$name] = (float)$rows[$index][$name];
}
}
$nullFloatFields = [
'TAX_RATE',
];
foreach ($nullFloatFields as $name)
{
if (isset($rows[$index][$name]))
{
$value = trim((string)$rows[$index][$name]);
$rows[$index][$name] =
$value === ''
? null
: (float)$value
;
}
}
if ($row["SKU_ID"] > 0)
{
$sku = $this->getSkuByProductId($row["SKU_ID"]);
if ($sku)
{
$skuImageField = new ImageInput($sku);
$rows[$index]['IMAGE_INFO'] = $skuImageField->getFormattedField();
$skuTree = $skuTreeItems[$row['IBLOCK_ID']][$row['PRODUCT_ID']][$row['SKU_ID']] ?? null;
$rows[$index]['SKU_TREE'] = $skuTree ? Json::encode($skuTree) : null;
}
}
}
return $rows;
}
private function getSkuTreeItems(array $rows): array
{
$iblockProductOfferIds = [];
foreach ($rows as $row)
{
if (empty($row['SKU_ID']))
{
continue;
}
$iblockProductOfferIds[$row['IBLOCK_ID']][$row['PRODUCT_ID']][] = (int)$row['SKU_ID'];
}
$skuTreeItems = [];
foreach ($iblockProductOfferIds as $iblockId => $productOfferIds)
{
$skuTree = \Bitrix\Catalog\v2\IoC\ServiceContainer::make('sku.tree', ['iblockId' => $iblockId]);
if (!$skuTree)
{
continue;
}
$skuTreeItems[$iblockId] = $skuTree->loadJsonOffers($productOfferIds);
}
return $skuTreeItems;
}
protected function getDocumentProducts(): array
{
$products = [];
$documentProductRaw = StoreDocumentElementTable::getList([
'filter' => [
'=DOC_ID' => $this->getDocumentId(),
],
]);
while($documentProduct = $documentProductRaw->fetch())
{
$documentProduct['BARCODE'] = '';
$products[$documentProduct['ID']] = $documentProduct;
}
$rowBarcodesRaw = StoreDocumentBarcodeTable::getList([
'select' => ['DOC_ELEMENT_ID', 'BARCODE'],
'filter' => ['=DOC_ID' => $this->getDocumentId()]
]);
while ($barcode = $rowBarcodesRaw->fetch())
{
$rowId = $barcode['DOC_ELEMENT_ID'];
$products[$rowId]['BARCODE'] = $barcode['BARCODE'];
}
return $products;
}
protected function getProductStoreInfo(array $productIds): array
{
$productStoreInfo = [];
$productStoreRaw = StoreProductTable::getList([
'filter' => ['=PRODUCT_ID' => $productIds],
'select' => [
'STORE_ID',
'PRODUCT_ID',
'AMOUNT',
'QUANTITY_RESERVED',
'STORE_TITLE' => 'STORE.TITLE'
]
]);
while ($productStore = $productStoreRaw->Fetch())
{
$productStoreInfo[$productStore['PRODUCT_ID']] = $productStoreInfo[$productStore['PRODUCT_ID']] ?? [];
$productStoreInfo[$productStore['PRODUCT_ID']][$productStore['STORE_ID']] = $productStore;
}
return $productStoreInfo;
}
protected function getBarcodes(array $productIds): array
{
$barcodes = [];
$barcodeRaw = StoreBarcodeTable::getList([
'filter' => [
'PRODUCT_ID' => $productIds,
]
]);
while ($barcode = $barcodeRaw->fetch())
{
$barcodes[$barcode['PRODUCT_ID']] = $barcode['BARCODE'];
}
return $barcodes;
}
/**
* @return void
*/
protected function prepareResult(): void
{
$this->initUiScope();
$this->arResult['ID'] = $this->getGridId();
$gridRows = $this->getGridRows();
$this->arResult['GRID'] = $this->getGridParams($gridRows);
$this->arResult['GRID_EDITOR_CONFIG'] = $this->getGridEditorConfig($gridRows);
$this->arResult['SETTINGS'] = $this->getSettings();
$this->arResult['HIDDEN_FIELDS'] = $this->getHiddenFieldsWithoutAccess();
$this->arResult['IS_EXTERNAL_CATALOG'] = State::isExternalCatalog();
$this->arResult += $this->getTotalSumDetails($gridRows);
}
protected function getGridParams(array $gridRows): array
{
return [
'GRID_ID' => $this->getGridId(),
'HEADERS' => array_values($this->getColumns()),
'SORT' => $this->getStorageItem('GRID_ORDER'),
'SORT_VARS' => $this->getStorageItem('GRID_ORDER_VARS'),
'ROWS' => $gridRows,
'SHOW_ROW_ACTIONS_MENU' => true,
'ALLOW_SORT' => false,
'ALLOW_ROWS_SORT' => false,
'ALLOW_ROWS_SORT_IN_EDIT_MODE' => false,
'ALLOW_ROWS_SORT_INSTANT_SAVE' => false,
'ENABLE_ROW_COUNT_LOADER' => false,
'HIDE_FILTER' => true,
'ENABLE_COLLAPSIBLE_ROWS' => false,
'ADVANCED_EDIT_MODE' => true,
'ALLOW_EDIT_SELECTION' => true,
'NAME_TEMPLATE' => (string)($arParams['~NAME_TEMPLATE'] ?? ''),
'SHOW_ACTION_PANEL' => true,
// 'SETTINGS_WINDOW_TITLE' => $arResult['ENTITY']['TITLE'],
'SHOW_NAVIGATION_PANEL' => false,
'SHOW_PAGINATION' => false,
'SHOW_TOTAL_COUNTER' => false,
'SHOW_PAGESIZE' => false,
'PAGINATION' => [],
'NAV_OBJECT' => $this->navigation,
'~NAV_PARAMS' => ['SHOW_ALWAYS' => false],
'SHOW_ROW_CHECKBOXES' => true,
'USE_CHECKBOX_LIST_FOR_SETTINGS_POPUP' => \Bitrix\Main\ModuleManager::isModuleInstalled('ui'),
'ENABLE_FIELDS_SEARCH' => 'Y',
'CONFIG' => [
'popupWidth' => 800,
],
'SHOW_SELECTED_COUNTER' => true,
'ACTION_PANEL' => $this->getGridActionPanel(),
// checked
'VISIBLE_COLUMNS' => array_values($this->getVisibleColumns()),
'AJAX_ID' => $this->getStorageItem( 'AJAX_ID'),
'AJAX_MODE' => $this->arParams['~AJAX_MODE'],
'AJAX_OPTION_JUMP' => $this->arParams['~AJAX_OPTION_JUMP'],
'AJAX_OPTION_HISTORY' => $this->arParams['~AJAX_OPTION_HISTORY'],
'AJAX_LOADER' => $this->arParams['~AJAX_LOADER'],
'FORM_ID' => $this->getStorageItem('FORM_ID'),
'TAB_ID' => $this->getStorageItem('TAB_ID'),
'TOTAL_ROWS_COUNT' => $this->arParams['~TOTAL_PRODUCTS_COUNT'] ?? count($gridRows),
];
}
protected function getGridActionPanel(): array
{
if ($this->isReadOnly())
{
return [];
}
$snippet = new Snippet();
$dropdownStores = [];
foreach ($this->getAccessibleStores() as $store)
{
$dropdownStores[] = ['NAME' => $store['TITLE'], 'VALUE' => (int)$store['ID']];
}
$actionList = [];
if ($dropdownStores)
{
$items = [
[
'NAME' => Loc::getMessage('CATALOG_DOCUMENT_ACTION_DEFAULT'),
'VALUE' => 'default',
'ACTION' => Main\Grid\Panel\Actions::RESET_CONTROLS,
],
];
$isExternalDocument = (bool)($this->externalDocument['TYPE'] ?? false);
if (
$isExternalDocument
|| $this->getDocumentType() === StoreDocumentTable::TYPE_MOVING
|| $this->getDocumentType() === StoreDocumentTable::TYPE_DEDUCT
|| $this->getDocumentType() === StoreDocumentTable::TYPE_SALES_ORDERS
)
{
$storeFromActionTitle =
$this->getDocumentType() === StoreDocumentTable::TYPE_MOVING
? Loc::getMessage('CATALOG_DOCUMENT_ACTION_SELECT_STORE_FROM')
: Loc::getMessage('CATALOG_DOCUMENT_ACTION_SELECT_STORE')
;
$items[] = $this->getDropdownActionField(
$snippet,
'STORE_FROM_INFO',
$dropdownStores,
$storeFromActionTitle
);
}
if (
!$isExternalDocument
&& $this->getDocumentType() !== StoreDocumentTable::TYPE_DEDUCT
&& $this->getDocumentType() !== StoreDocumentTable::TYPE_SALES_ORDERS
)
{
$storeToActionTitle =
$this->getDocumentType() === StoreDocumentTable::TYPE_MOVING
? Loc::getMessage('CATALOG_DOCUMENT_ACTION_SELECT_STORE_TO')
: Loc::getMessage('CATALOG_DOCUMENT_ACTION_SELECT_STORE')
;
$items[] = $this->getDropdownActionField(
$snippet,
'STORE_TO_INFO',
$dropdownStores,
$storeToActionTitle
);
}
$actionList = [
'TYPE' => Main\Grid\Panel\Types::DROPDOWN,
'ID' => 'actionListId',
'NAME' => 'actionList',
'ITEMS' => $items
];
}
return [
'GROUPS' => [
[
'ITEMS' => [
$snippet->getRemoveButton(),
$actionList,
$snippet->getForAllCheckbox(),
],
],
],
];
}
/**
* @param Snippet $snippet
* @param string $fieldId
* @param array $list
* @param string $title
* @return array
*/
private function getDropdownActionField(Snippet $snippet, string $fieldId, array $list, string $title): array
{
$action = [
'ACTION' => Main\Grid\Panel\Actions::CREATE,
'DATA' => [
[
'TYPE' => Main\Grid\Panel\Types::DROPDOWN,
'ID' => $fieldId,
'NAME' => $fieldId,
'ITEMS' => $list,
],
$snippet->getApplyButton([
'ONCHANGE' => [
[
'ACTION' => \Bitrix\Main\Grid\Panel\Actions::CALLBACK,
'DATA' => [
[
"JS" => "BX.Catalog.Store.ProductList.Instance.processApplyActionButtonClick('{$fieldId}')",
]
]
]
]
]),
]
];
return [
'NAME' => $title,
'VALUE' => $fieldId,
'ONCHANGE' => [$action]
];
}
/**
* @return array
*/
protected function getUiExtensions(): array
{
return [
'core',
'ajax',
'tooltip',
'ui.fonts.ruble',
'ui.notification',
'catalog.product-calculator',
'catalog.product-selector',
'catalog.store-selector',
'catalog.tool-availability-manager',
'currency',
];
}
/**
* @return array
*/
protected function getUiStyles(): array
{
return [];
}
/**
* @return array
*/
protected function getUiScripts(): array
{
return [];
}
/**
* @return array
*/
protected function getSettings(): array
{
return [
'SITE_ID' => $this->getSiteId(),
'LANGUAGE_ID' => $this->getLanguageId(),
'SET_ITEMS' => $this->arParams['SET_ITEMS'],
'ALLOW_EDIT' => $this->arParams['ALLOW_EDIT'],
'IS_READ_ONLY' => $this->isReadOnly(),
'IS_DISPLAY_TOTAL_SUM_DETAILS' => $this->arParams['IS_DISPLAY_TOTAL_SUM_DETAILS'] ?? false,
'CURRENCY' => $this->getCurrency(),
'NEW_ROW_ID_PREFIX' => self::NEW_ROW_ID_PREFIX,
'NEW_ROW_ID_COUNTER' => $this->getNewRowCounter(),
'NEW_ROW_POSITION' => $this->getStorageItem( 'NEW_ROW_POSITION'),
'CREATE_PRODUCT_PATH' => $this->getStorageItem( 'CREATE_PRODUCT_PATH'),
'TOTAL_SUM_CONTAINER_ID' => $this->getPrefix() . '_product_sum_total_container',
];
}
/* Storage tools */
/**
* @param array $nodeValues
* @return void
*/
protected function fillStorageNode(array $nodeValues): void
{
if (empty($nodeValues))
{
return;
}
$this->storage = array_merge($this->storage, $nodeValues);
}
/**
* @param string $item
* @param mixed $value
* @return void
*/
protected function setStorageItem(string $item, $value): void
{
$this->storage[$item] = $value;
}
/**
* @param string $item
* @return mixed|null
*/
protected function getStorageItem(string $item)
{
return $this->storage[$item] ?? null;
}
/**
* @return string
*/
protected function getGridId(): ?string
{
return $this->getStorageItem('GRID_ID');
}
/**
* @return string
*/
protected function getFormId(): ?string
{
return $this->getStorageItem('FORM_ID');
}
/**
* @return string
*/
protected function getNavigationId(): string
{
return $this->getStorageItem('NAVIGATION_ID');
}
/**
* @return array
*/
protected function getPageSizes(): array
{
return $this->getStorageItem('PAGE_SIZES');
}
/* Storage tools finish */
/**
* @return void
*/
protected function initGrid(): void
{
$this->initGridConfig();
$this->initGridColumns();
$this->initGridPageNavigation();
$this->initGridOrder();
}
/**
* @return void
*/
protected function initGridConfig(): void
{
$this->gridConfig = new Main\Grid\Options($this->getGridId());
}
/**
* @return void
*/
protected function initGridColumns(): void
{
$visibleColumns = [];
$visibleColumnsMap = [];
$defaultList = true;
$userColumnsOrder = [];
$userColumns = $this->getUserGridColumnIds();
if (!empty($userColumns))
{
$defaultList = false;
$userColumnsOrder = array_fill_keys($userColumns, true);
}
$defaultColumnsOrder = $this->getDefaultColumns();
$columnDescriptions = $this->getGridColumnsDescription();
if ($defaultList)
{
$userColumnsOrder = array_filter(
$defaultColumnsOrder,
static function($columnName) use ($columnDescriptions)
{
return
isset($columnDescriptions[$columnName]['default'])
&& $columnDescriptions[$columnName]['default'] === true
;
}
);
}
foreach ($userColumnsOrder as $key => $index)
{
if (!isset($columnDescriptions[$key]))
{
continue;
}
$visibleColumnsMap[$key] = true;
$visibleColumns[$key] = $columnDescriptions[$key];
}
$columns = [];
foreach ($defaultColumnsOrder as $columnCode)
{
if (isset($columnDescriptions[$columnCode]))
{
$columns[] = $columnDescriptions[$columnCode];
}
}
$this->fillStorageNode( [
'COLUMNS' => $columns,
'VISIBLE_COLUMNS' => $visibleColumns,
'VISIBLE_COLUMNS_MAP' => $visibleColumnsMap,
]);
}
/**
* @return void
*/
protected function initGridPageNavigation(): void
{
$naviParams = $this->getGridNavigationParams();
$this->navigation = new Main\UI\PageNavigation($this->getNavigationId());
$this->navigation->setPageSizes($this->getPageSizes());
$this->navigation->allowAllRecords(false);
$this->navigation->setPageSize($naviParams['nPageSize']);
// if (!$this->isUsedImplicitPageNavigation())
// {
$this->navigation->initFromUri();
// }
}
/**
* @return array
*/
protected function getGridNavigationParams(): array
{
return $this->gridConfig->getNavParams(['nPageSize' => 20]);
}
/**
* @return void
*/
protected function initGridOrder(): void
{
$result = ['ID' => 'DESC'];
$sorting = $this->gridConfig->getSorting(['sort' => $result]);
$order = strtolower(reset($sorting['sort']));
if ($order !== 'asc')
{
$order = 'desc';
}
$field = key($sorting['sort']);
$found = false;
foreach ($this->getVisibleColumns() as $column)
{
if (!isset($column['sort']))
continue;
if ($column['sort'] == $field)
{
$found = true;
break;
}
}
unset($column);
if ($found)
$result = [$field => $order];
$this->fillStorageNode(
[
'GRID_ORDER' => $this->modifyGridOrder($result),
'GRID_ORDER_VARS' => $sorting['vars'],
]
);
unset($found, $field, $order, $sorting, $result);
}
/**
* @param array $order
* @return array
*/
protected function modifyGridOrder(array $order): array
{
return $order;
}
protected function getCurrencyListForMoneyField(): array
{
return [
$this->getCurrencyId() => $this->getCurrencyText(),
];
}
protected function getDefaultColumns(): array
{
if (!empty($this->externalDocument['DEFAULT_COLUMNS']))
{
return $this->externalDocument['DEFAULT_COLUMNS'];
}
switch ($this->getDocumentType())
{
case StoreDocumentTable::TYPE_STORE_ADJUSTMENT:
case StoreDocumentTable::TYPE_ARRIVAL:
if ($this->isReadOnly())
{
return [
'MAIN_INFO','PURCHASING_PRICE', 'BASE_PRICE',
'AMOUNT', 'STORE_TO_INFO', 'STORE_TO_AMOUNT', 'BARCODE_INFO',
'TOTAL_PRICE', 'COMMENT',
];
}
return [
'MAIN_INFO', 'BARCODE_INFO', 'PURCHASING_PRICE', 'BASE_PRICE',
'AMOUNT', 'STORE_TO_INFO', 'STORE_TO_AMOUNT',
'TOTAL_PRICE', 'COMMENT',
];
case StoreDocumentTable::TYPE_DEDUCT:
if ($this->isReadOnly())
{
return [
'MAIN_INFO',
'STORE_FROM_INFO', 'STORE_FROM_AMOUNT', 'AMOUNT',
'PURCHASING_PRICE', 'BASE_PRICE', 'BARCODE_INFO',
'TOTAL_PRICE', 'COMMENT',
];
}
return [
'MAIN_INFO', 'BARCODE_INFO', 'AMOUNT',
'STORE_FROM_INFO', 'STORE_FROM_AMOUNT',
'PURCHASING_PRICE', 'BASE_PRICE',
'TOTAL_PRICE', 'COMMENT',
];
case StoreDocumentTable::TYPE_MOVING:
if ($this->isReadOnly())
{
return [
'MAIN_INFO',
'STORE_FROM_INFO', 'STORE_FROM_AVAILABLE_AMOUNT', 'STORE_FROM_AMOUNT',
'STORE_TO_INFO', 'STORE_TO_AVAILABLE_AMOUNT', 'STORE_TO_AMOUNT', 'AMOUNT',
'PURCHASING_PRICE', 'BASE_PRICE', 'BARCODE_INFO',
'TOTAL_PRICE', 'COMMENT',
];
}
return [
'MAIN_INFO', 'BARCODE_INFO', 'AMOUNT',
'STORE_FROM_INFO', 'STORE_FROM_AVAILABLE_AMOUNT', 'STORE_FROM_AMOUNT',
'STORE_TO_INFO', 'STORE_TO_AVAILABLE_AMOUNT', 'STORE_TO_AMOUNT',
'PURCHASING_PRICE', 'BASE_PRICE',
'TOTAL_PRICE', 'COMMENT',
];
}
return [];
}
/**
* @return array
*/
protected function getGridColumnsDescription(): array
{
$result = [];
$columnDefaultWidth = 150;
$result['MAIN_INFO'] = [
'id' => 'MAIN_INFO',
'name' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_MAIN_INFO'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_MAIN_INFO'),
'sort' => 'NAME',
'default' => true,
];
if (State::isExternalCatalog() !== true)
{
$result['BARCODE_INFO'] = [
'id' => 'BARCODE_INFO',
'name' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_BARCODE'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_BARCODE'),
'default' => true,
'width' => 300,
];
}
$priceEditable = [
'TYPE' => Types::MONEY,
'CURRENCY_LIST' => $this->getCurrencyListForMoneyField(),
'PLACEHOLDER' => '0',
'HTML_ENTITY' => true,
];
$purchasingPriceName = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_PURCHASING_PRICE');
$purchasingPriceName = $this->externalDocument['CUSTOM_COLUMN_NAMES']['PURCHASING_PRICE'] ?? $purchasingPriceName;
$purchasingPriceEditable =
$this->accessController->check(ActionDictionary::ACTION_PRODUCT_PURCHASE_INFO_VIEW)
&& !(
$this->getDocumentType() === StoreDocumentTable::TYPE_MOVING
|| $this->getDocumentType() === StoreDocumentTable::TYPE_DEDUCT
|| $this->getDocumentType() === StoreDocumentTable::TYPE_SALES_ORDERS
)
;
if (
$this->getDocumentType() !== StoreDocumentTable::TYPE_MOVING
&& (
$this->getDocumentType() !== StoreDocumentTable::TYPE_SALES_ORDERS
|| (Feature::isStoreBatchEnabled() && State::isProductBatchMethodSelected())
)
)
{
$result['PURCHASING_PRICE'] = [
'id' => 'PURCHASING_PRICE',
'name' => $purchasingPriceName,
'title' => $purchasingPriceName,
'sort' => 'PURCHASING_PRICE',
'default' => true,
'editable' => $purchasingPriceEditable ? $priceEditable : false,
'width' => $columnDefaultWidth,
];
}
$result['BASE_PRICE'] = [
'id' => 'BASE_PRICE',
'name' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_PRICE'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_PRICE'),
'sort' => 'BASE_PRICE',
'default' => true,
'editable' => $this->isEditableBasePrice() ? $priceEditable : false,
'width' => $columnDefaultWidth,
];
$result['TAX_RATE'] = [
'id' => 'TAX_RATE',
'name' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_TAX_RATE'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_TAX_RATE'),
'default' => true,
];
$result['TAX_INCLUDED'] = [
'id' => 'TAX_INCLUDED',
'name' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_TAX_INCLUDED'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_TAX_INCLUDED'),
'default' => true,
'width' => 180,
];
$storeFromName = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_FROM_INFO');
$storeFromAmountName = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_FROM_AMOUNT');
if ($this->getDocumentType() === StoreDocumentTable::TYPE_MOVING)
{
$storeFromName = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_FROM_INFO_MOVING');
$storeFromAmountName = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_FROM_AMOUNT_MOVING');
}
elseif ($this->getDocumentType() === StoreDocumentTable::TYPE_DEDUCT)
{
$storeFromName = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_INFO');
$storeFromAmountName = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_AMOUNT');
}
$storeFromName = $this->externalDocument['CUSTOM_COLUMN_NAMES']['STORE_FROM_INFO'] ?? $storeFromName;
$result['STORE_FROM_INFO'] = [
'id' => 'STORE_FROM_INFO',
'name' => $storeFromName,
'title' => $storeFromName,
'sort' => 'STORE_FROM',
'default' => true,
];
$storeFromAmountName = $this->externalDocument['CUSTOM_COLUMN_NAMES']['STORE_FROM_AMOUNT'] ?? $storeFromAmountName;
$result['STORE_FROM_AMOUNT'] = [
'id' => 'STORE_FROM_AMOUNT',
'name' => $storeFromAmountName,
'title' => $storeFromAmountName,
'sort' => 'STORE_FROM_AMOUNT',
'default' => !$this->isReadOnly(),
'editable' => false,
'width' => $columnDefaultWidth,
];
$result['STORE_FROM_RESERVED'] = [
'id' => 'STORE_FROM_RESERVED',
'name' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_FROM_AMOUNT_RESERVED'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_FROM_AMOUNT_RESERVED'),
'sort' => 'STORE_FROM_RESERVED',
'default' => true,
'editable' => false,
'width' => $columnDefaultWidth,
];
$storeFromCommonAmountName = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_FROM_AMOUNT_AVAILABLE');
$storeFromCommonAmountName = $this->externalDocument['CUSTOM_COLUMN_NAMES']['STORE_FROM_AVAILABLE_AMOUNT'] ?? $storeFromCommonAmountName;
$result['STORE_FROM_AVAILABLE_AMOUNT'] = [
'id' => 'STORE_FROM_AVAILABLE_AMOUNT',
'name' => $storeFromCommonAmountName,
'title' => $storeFromCommonAmountName,
'sort' => 'STORE_FROM_AVAILABLE_AMOUNT',
'default' => !$this->isReadOnly(),
'editable' => false,
'width' => $columnDefaultWidth,
];
$storeToInfoName = $this->getDocumentType() === StoreDocumentTable::TYPE_MOVING
? Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_TO_INFO')
: Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_INFO')
;
$result['STORE_TO_INFO'] = [
'id' => 'STORE_TO_INFO',
'name' =>$storeToInfoName,
'title' =>$storeToInfoName,
'sort' => 'STORE_TO',
'default' => true,
];
$result['STORE_TO_AMOUNT'] = [
'id' => 'STORE_TO_AMOUNT',
'name' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_TO_AMOUNT'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_TO_AMOUNT'),
'sort' => 'STORE_TO_AMOUNT',
'default' => !$this->isReadOnly(),
'editable' => false,
'width' => $columnDefaultWidth,
];
$result['STORE_TO_RESERVED'] = [
'id' => 'STORE_TO_RESERVED',
'name' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_AMOUNT_RESERVED'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_AMOUNT_RESERVED'),
'sort' => 'STORE_TO_RESERVED',
'default' => true,
'editable' => false,
'width' => $columnDefaultWidth,
];
$result['STORE_TO_AVAILABLE_AMOUNT'] = [
'id' => 'STORE_TO_AVAILABLE_AMOUNT',
'name' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_TO_AMOUNT_AVAILABLE'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_STORE_TO_AMOUNT_AVAILABLE'),
'sort' => 'STORE_TO_AVAILABLE_AMOUNT',
'default' => !$this->isReadOnly(),
'editable' => false,
'width' => $columnDefaultWidth,
];
$amountColumnName = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_AMOUNT');
if (
$this->getDocumentType() === StoreDocumentTable::TYPE_ARRIVAL
|| $this->getDocumentType() === StoreDocumentTable::TYPE_STORE_ADJUSTMENT
)
{
$amountColumnName = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_AMOUNT_ARRIVAL');
}
$result['AMOUNT'] = [
'id' => 'AMOUNT',
'name' => $amountColumnName,
'title' => $amountColumnName,
'sort' => 'AMOUNT',
'default' => true,
'editable' => [
'TYPE' => Types::MONEY,
'CURRENCY_LIST' => $this->getMeasureListForMoneyField(),
'PLACEHOLDER' => '0',
],
'width' => $columnDefaultWidth,
];
$result['TOTAL_PRICE'] = [
'id' => 'TOTAL_PRICE',
'name' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_TOTAL_PRICE'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_TOTAL_PRICE'),
'sort' => null,
'default' => true,
'editable' => false,
'width' => $columnDefaultWidth,
];
$result['COMMENT'] = [
'id' => 'COMMENT',
'name' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_COMMENT'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_COMMENT'),
'sort' => null,
'default' => false,
'editable' => true,
'width' => $columnDefaultWidth,
];
if ($this->getDocumentType() === StoreDocumentTable::TYPE_DEDUCT)
{
$result['COMMENT']['name'] = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_COMMENT_DEDUCT');
$result['COMMENT']['title'] = Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_COLUMN_COMMENT_DEDUCT');
$result['COMMENT']['default'] = true;
}
foreach ($result as &$item)
{
if (empty($item['editable']))
{
$item['editable'] = [
'TYPE' => Types::CUSTOM,
];
}
}
unset($item);
return $result;
}
protected function getMeasureListForMoneyField(): array
{
return array_column($this->measures, 'SYMBOL', 'CODE');
}
protected function getUserGridColumnIds(): array
{
$result = $this->gridConfig->GetVisibleColumns();
if (!empty($result) && !in_array('ID', $result, true))
{
array_unshift($result, 'ID');
}
return $result;
}
/**
* @return array
*/
protected function getColumns()
{
return $this->getStorageItem('COLUMNS');
}
/**
* @return array
*/
protected function getVisibleColumns()
{
return $this->getStorageItem('VISIBLE_COLUMNS');
}
protected function getGridEditorConfig(array $gridRows): array
{
$componentId = $this->randString();
$defaultRow = $this->getDefaultRow();
$editData = [
'template_0' => $this->prepareEditorRow($defaultRow),
];
$taxIncluded = $gridRows[0]['raw_data']['TAX_INCLUDED'] ?? null;
$taxIncludedFormatted = $gridRows[0]['data']['TAX_INCLUDED'] ?? null;
$taxIncludedFromFirstItem = $this->getTaxIncludedFromFirstItem();
$taxIncludedFromFirstItemFormatted = ($taxIncludedFromFirstItem === 'Y')
? Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_TAX_INCLUDED')
: Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_TAX_NOT_INCLUDED');
foreach ($gridRows as $row)
{
if ($row['editable'] === false)
{
continue;
}
$editData[$row['id']] = $row['data'];
}
return [
'componentName' => $this->getName(),
'documentType' => $this->getDocumentType(),
'signedParameters' => $this->getSignedParameters(),
'reloadUrl' => $this->getPath() . '/list.ajax.php',
'containerId' => $this->getPrefix() . '_catalog_document_product_list_container',
'totalBlockContainerId' => $this->getPrefix() . '_product_sum_total_container',
'gridId' => $this->getGridId(),
'formId' => $this->getFormId(),
'allowEdit' => !$this->isReadOnly(),
'dataFieldName' => $this->arParams['PRODUCT_DATA_FIELD_NAME'],
'rowIdPrefix' => $this->getRowIdPrefix(),
'pricePrecision' => $this->getStorageItem('PRICE_PRECISION'),
'quantityPrecision' => $this->getStorageItem('AMOUNT_PRECISION'),
'commonPrecision' => $this->getStorageItem('COMMON_PRECISION'),
'newRowPosition' => $this->getStorageItem('NEW_ROW_POSITION'),
'createProductPath' => $this->getStorageItem('CREATE_PRODUCT_PATH'),
'measures' => array_values($this->measures),
'stores' => $this->getAccessibleStores(),
'defaultMeasure' => $this->getDefaultMeasure(),
'currencyId' => $this->getCurrencyId(),
'totalCalculationSumField' => $this->getDefaultTotalCalculationField(),
'totalCalculationSumTaxField' => 'TAX_SUM',
'taxIncludedFormatted' => $taxIncludedFormatted,
'taxIncluded' => $taxIncluded,
'taxIncludedFromFirstItem' => $taxIncludedFromFirstItem,
'taxIncludedFromFirstItemFormatted' => $taxIncludedFromFirstItemFormatted,
'popupSettings' => $this->getPopupSettings(),
'languageId' => $this->getLanguageId(),
'siteId' => $this->getSiteId(),
'catalogId' => $componentId,
'componentId' => $this->randString(),
'jsEventsManagerId' => "PageEventsManager_{$componentId}",
'readOnly' => $this->isReadOnly(),
'items' => $this->getEditorItems(),
'rowSettings' => $this->getEditorRowSettings(),
'templateItemFields' => $defaultRow,
'templateIdMask' => self::PRODUCT_ID_MASK,
'paintedColumns' => ['AMOUNT'],
'templateGridEditData' => $editData,
'enabledCreateProductButton' => $this->isAllowedProductCreation(),
'productUrlBuilderContext' => htmlspecialcharsbx($this->arParams['BUILDER_CONTEXT']),
'restrictedProductTypes' => $this->getRestrictedProductTypesForSelector(),
'isCalculableStorePurchasingPrice' => $this->arParams['CALCULATE_STORE_PURCHASING_PRICE'],
'isOnecInventoryManagementRestricted' => (
EnableWizard\Manager::isOnecMode()
&& EnableWizard\TariffChecker::isOnecInventoryManagementRestricted()
),
];
}
private function getEditorItems(): array
{
$items = [];
foreach ($this->rows as $row)
{
$items[] = [
'rowId' => $row['ROW_ID'],
'fields' => $row,
];
}
return $items;
}
private function getEditorRowSettings(): array
{
$columns = $this->getDefaultColumns();
$storeHeaders = [];
foreach ($columns as $column)
{
if ($column === 'STORE_TO_INFO')
{
$storeHeaders[$column] = 'STORE_TO';
}
elseif ($column === 'STORE_FROM_INFO')
{
$storeHeaders[$column] = 'STORE_FROM';
}
}
return [
'storeHeaderMap' => $storeHeaders,
'isAllowedCreationProduct' => true,
'documentType' => $this->getDocumentType(),
];
}
/**
* @return array
*/
protected function getGridRows(): array
{
global $APPLICATION;
$rows = [];
foreach ($this->rows as $row)
{
$item = $this->prepareEditorRow($row);
$editable = !($row['ACCESS_DENIED'] ?? false);
$skuTree = '';
if (isset($row['SKU_TREE']) && $row['SKU_TREE'])
{
$skuTree = Json::decode($row['SKU_TREE']);
}
ob_start();
$APPLICATION->IncludeComponent(
'bitrix:catalog.grid.product.field',
'',
[
'BUILDER_CONTEXT' => $this->arParams['BUILDER_CONTEXT'],
'GRID_ID' => $this->getGridId(),
'ROW_ID' => $row['ID'],
'GUID' => 'catalog_document_grid_'.$row['ID'],
'PRODUCT_FIELDS' => [
'ID' => $row['PRODUCT_ID'],
'NAME' => $row['NAME'],
'IBLOCK_ID' => $row['IBLOCK_ID'] ?? null,
'SKU_IBLOCK_ID' => $row['OFFERS_IBLOCK_ID'] ?? null,
'SKU_ID' => $row['SKU_ID'] ?? null,
'BASE_PRICE_ID' => $row['BASE_PRICE_ID'] ?? null,
],
'SKU_TREE' => $skuTree,
'MODE' => 'view',
'VIEW_FORMAT' => 'short',
'ENABLE_SEARCH' => false,
'ENABLE_IMAGE_CHANGE_SAVING' => false,
'ENABLE_INPUT_DETAIL_LINK' => true,
'ENABLE_EMPTY_PRODUCT_ERROR' => false,
'ENABLE_SKU_SELECTION' => false,
'HIDE_UNSELECTED_ITEMS' => true,
'IS_NEW' => $row['IS_NEW'] ?? 'N',
]
);
$mainInfo = '<div class="main-grid-row-number"></div>' . ob_get_clean();
$rows[] = [
'id' => ($row['ID'] === self::PRODUCT_ID_MASK) ? 'template_0' : $row['ID'],
'raw_data' => $row,
'data' => $item,
'columns' => [
'MAIN_INFO' => $mainInfo,
'STORE_FROM_INFO' => HtmlFilter::encode($item['STORE_FROM_TITLE']),
'STORE_TO_INFO' => HtmlFilter::encode($item['STORE_TO_TITLE']),
'BARCODE_INFO' => HtmlFilter::encode($item['BARCODE']),
'BASE_PRICE' =>
$item['BASE_PRICE'] !== null
? \CCurrencyLang::formatValue($item['BASE_PRICE_FORMATTED'], $this->currency['FORMAT'])
: null
,
'PURCHASING_PRICE' => \CCurrencyLang::formatValue($item['PURCHASING_PRICE_FORMATTED'], $this->currency['FORMAT']),
'TOTAL_PRICE' => \CCurrencyLang::formatValue($item['TOTAL_PRICE_FORMATTED'], $this->currency['FORMAT']),
'AMOUNT' => (float)$row['AMOUNT'].' '.htmlspecialcharsbx($row['MEASURE_NAME']),
'STORE_FROM_AMOUNT' => $this->formatRowStoreAmount($row, 'STORE_FROM_AMOUNT'),
'STORE_TO_AMOUNT' => $this->formatRowStoreAmount($row, 'STORE_TO_AMOUNT'),
'STORE_FROM_RESERVED' => $this->formatRowStoreAmount($row, 'STORE_FROM_RESERVED'),
'STORE_TO_RESERVED' => $this->formatRowStoreAmount($row, 'STORE_TO_RESERVED'),
'STORE_FROM_AVAILABLE_AMOUNT' => $this->formatRowStoreAmount($row, 'STORE_FROM_AVAILABLE_AMOUNT'),
'STORE_TO_AVAILABLE_AMOUNT' => $this->formatRowStoreAmount($row, 'STORE_TO_AVAILABLE_AMOUNT'),
],
'editable' => !$this->isReadOnly() && $editable,
];
}
return $rows;
}
private function formatPrices($price)
{
return number_format(
$price,
$this->getStorageItem('PRICE_PRECISION'),
'.',
''
);
}
private static function formatTaxRate(null|int|float|string $rate): string
{
if ($rate === null || $rate === '')
{
return Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_NOT_TAX');
}
return $rate . ' %';
}
private function formatRowStoreAmount(array $row, string $amountFieldName): ?string
{
$restrictedProductTypes = $this->getRestrictedProductTypes();
if (
!isset($row[$amountFieldName])
|| !$row['PRODUCT_ID']
|| in_array((int)$row['TYPE'], $restrictedProductTypes, true))
{
return null;
}
$formattedValue = (float)$row[$amountFieldName] . ' ' . htmlspecialcharsbx($row['MEASURE_NAME']);
$isNegativeOrZeroStoreFromAvailableAmount =
$amountFieldName === 'STORE_FROM_AVAILABLE_AMOUNT' && $row['STORE_FROM_AVAILABLE_AMOUNT'] <= 0
;
$isNegativeOrZeroStoreToAvailableAmount =
$amountFieldName === 'STORE_TO_AVAILABLE_AMOUNT' && $row['STORE_TO_AVAILABLE_AMOUNT'] <= 0
;
if ($isNegativeOrZeroStoreFromAvailableAmount || $isNegativeOrZeroStoreToAvailableAmount)
{
$formattedValue = '<span class="text--danger">' . $formattedValue . '</span>';
}
return $formattedValue;
}
private function prepareEditorRow(array $row): array
{
$rowId = $row['ROW_ID'];
$priceFormatted = null;
if ($row['BASE_PRICE'] !== null)
{
$priceFormatted = $this->formatPrices($row['BASE_PRICE']);
}
$purchasingPriceFormatted = null;
if ($row['PURCHASING_PRICE'] !== null)
{
$purchasingPriceFormatted = $this->formatPrices($row['PURCHASING_PRICE']);
}
$row['TOTAL_PRICE'] ??= 0;
$totalPriceFormatted = $this->formatPrices($row['TOTAL_PRICE']);
$row['TAX_RATE'] ??= null;
$taxRateFormatted = self::formatTaxRate($row['TAX_RATE']);
$editorFields = [
'AMOUNT' => [
'PRICE' => [
'NAME' => $rowId .'_AMOUNT',
'VALUE' => $row['AMOUNT'],
],
'CURRENCY' => [
'NAME' => $rowId .'_MEASURE_CODE',
'VALUE' => $row['MEASURE_CODE'],
'DISABLED' => !$this->isCanChangeProductMeasure(),
],
],
'STORE_AMOUNT_MAP' => $row['STORE_AMOUNT_MAP'] ?? null,
'SKU_TREE' => $row['SKU_TREE'] ?? null,
'BASE_PRICE_EXTRA' => $row['BASE_PRICE_EXTRA'] ?? null,
'BASE_PRICE_EXTRA_RATE' => $row['BASE_PRICE_EXTRA_RATE'] ?? null,
'BASE_PRICE_FORMATTED' => $priceFormatted,
'TOTAL_PRICE_FORMATTED' => $totalPriceFormatted,
'PURCHASING_PRICE_FORMATTED' => $purchasingPriceFormatted,
'TAX_RATE' => $taxRateFormatted,
'TAX_INCLUDED' => isset($row['TAX_INCLUDED']) && $row['TAX_INCLUDED'] === 'Y'
? Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_TAX_INCLUDED')
: Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_TAX_NOT_INCLUDED'),
];
foreach($this->getColumns() as $column)
{
$columnId = $column['id'];
switch ($columnId)
{
case 'BASE_PRICE':
case 'TOTAL_PRICE':
case 'PURCHASING_PRICE':
if ($column['editable']['TYPE'] === Types::MONEY)
{
$editorFields[$columnId] = [
'PRICE' => [
'NAME' => $rowId . '_' . $columnId,
'VALUE' =>
$columnId === 'BASE_PRICE'
? $priceFormatted
: $purchasingPriceFormatted
,
],
'CURRENCY' => [
'NAME' => $rowId . '_' . $columnId . '_CURRENCY',
'VALUE' => $this->getCurrencyId(),
],
];
}
elseif ($row[$columnId] !== null)
{
$editorFields[$columnId] = \CCurrencyLang::CurrencyFormat($row[$columnId], $this->getCurrencyId());
}
break;
case 'STORE_TO_INFO':
$editorFields['STORE_TO'] = $row['STORE_TO'];
$editorFields['STORE_TO_AMOUNT'] = $this->formatRowStoreAmount($row, 'STORE_TO_AMOUNT');
$editorFields['STORE_TO_RESERVED'] = $this->formatRowStoreAmount($row, 'STORE_TO_RESERVED');
$editorFields['STORE_TO_AVAILABLE_AMOUNT'] = $this->formatRowStoreAmount($row, 'STORE_TO_AVAILABLE_AMOUNT');
break;
case 'STORE_FROM_INFO':
$editorFields['STORE_FROM'] = $row['STORE_FROM'];
$editorFields['STORE_FROM_AMOUNT'] = $this->formatRowStoreAmount($row, 'STORE_FROM_AMOUNT');
$editorFields['STORE_FROM_RESERVED'] = $this->formatRowStoreAmount($row, 'STORE_FROM_RESERVED');
$editorFields['STORE_FROM_AVAILABLE_AMOUNT'] = $this->formatRowStoreAmount($row, 'STORE_FROM_AVAILABLE_AMOUNT');
break;
}
}
return array_merge($row, $editorFields);
}
private function getDefaultRow(): array
{
$defaultStore = $this->getDefaultStore();
$defaultStoreId = $defaultStore['ID'] ?? null;
$defaultStoreTitle = $defaultStore['TITLE'] ?? null;
$defaultMeasure = $this->getDefaultMeasure();
$row = [
'ROW_ID' => $this->getRowIdPrefix(self::PRODUCT_ID_MASK),
'ID' => self::PRODUCT_ID_MASK,
'IBLOCK_ID' => $this->arParams['CATALOG_ID'],
'OFFERS_IBLOCK_ID' => 0,
'SKU_ID' => null,
'BASE_PRICE_ID' => $this->getDefaultSetting('BASE_PRICE_ID'),
'PRODUCT_ID' => null,
'NAME' => '',
'BARCODE' => '',
'DOC_BARCODE' => '',
'BASE_PRICE' => null,
'TOTAL_PRICE' => null,
'PURCHASING_PRICE' => null,
'CURRENCY' => $this->getCurrency(),
'MEASURE_NAME' => $defaultMeasure['SYMBOL'] ?? '',
'MEASURE_CODE' => $defaultMeasure['CODE'] ?? '',
'AMOUNT' => 0,
'STORE_TO' => $defaultStoreId ?? null,
'STORE_TO_TITLE' => $defaultStoreTitle ?? null,
'STORE_TO_AMOUNT' => 0,
'STORE_TO_RESERVED' => 0,
'STORE_TO_AVAILABLE_AMOUNT' => 0,
'STORE_FROM' => $defaultStoreId ?? null,
'STORE_FROM_TITLE' => $defaultStoreTitle ?? null,
'STORE_FROM_AMOUNT' => 0,
'STORE_AMOUNT_MAP' => null,
'STORE_FROM_RESERVED' => 0,
'STORE_FROM_AVAILABLE_AMOUNT' => 0,
'IS_NEW' => 'N',
'BASE_PRICE_EXTRA' => '',
'BASE_PRICE_EXTRA_RATE' => StoreDocumentElementTable::EXTRA_RATE_PERCENTAGE,
'TYPE' => 0,
'TAX_INCLUDED' => 'N',
];
return $this->prepareRowsForAccessRights([ $row ])[0];
}
protected function isReadOnly(): bool
{
return !$this->arParams['ALLOW_EDIT'];
}
/**
* @return string
*/
protected function getNewRowId(): string
{
$result = self::NEW_ROW_ID_PREFIX . $this->getNewRowCounter();
$this->newRowCounter++;
return $result;
}
/**
* @return int
*/
protected function getNewRowCounter(): int
{
return $this->newRowCounter;
}
/* Access rights tools */
protected function getPrefix(): string
{
return $this->arParams['PREFIX'] !== '' ? $this->arParams['PREFIX'] : $this->getDefaultPrefix();
}
protected function getRowIdPrefix(string $code = null): string
{
return $this->getPrefix() . '_product_row_' . $code;
}
/**
* @return string
*/
protected function getDefaultPrefix(): string
{
$suffix =
$this->getDocumentId() > 0
? strtolower($this->getDocumentType()) . '_' . $this->getDocumentId()
: 'new_' . strtolower($this->getDocumentType())
;
return "document_{$suffix}_product_editor";
}
/**
* @param string $value
* @return string
*/
private static function clearStringValue(string $value): string
{
return preg_replace('/[^a-zA-Z0-9_:\\[\\]]/', '', $value);
}
/**
* @param array &$params
* @param string $field
* @return void
*/
private static function validateSingleParameter(array &$params, string $field): void
{
$value = '';
if (isset($params[$field]) && is_string($params[$field]))
{
$value = static::clearStringValue($params[$field]);
}
$params[$field] = $value;
}
/**
* @param array &$params
* @param array $list
* @return void
*/
private static function validateListParameters(array &$params, array $list): void
{
foreach ($list as $field)
{
static::validateSingleParameter($params, $field);
}
}
/**
* @param array $params
* @param string $field
* @return void
*/
private static function validateBoolParameter(array &$params, string $field): void
{
if (!isset($params[$field]))
{
$params[$field] = false;
}
if (is_string($params[$field]))
{
$params[$field] = ($params[$field] === 'Y');
}
$params[$field] = (is_bool($params[$field]) && $params[$field]);
}
/**
* @param array $params
* @param array $list
* @return void
*/
private static function validateBoolList(array &$params, array $list): void
{
foreach ($list as $field)
{
static::validateBoolParameter($params, $field);
}
unset($field);
}
private function getPreselectDocumentProducts(): array
{
if ($this->arParams['PRESELECTED_PRODUCT_ID'] === 0)
{
return [];
}
$preselectedSku = $this->getSkuByProductId($this->arParams['PRESELECTED_PRODUCT_ID']);
if ($preselectedSku)
{
$basePriceEntity = $preselectedSku->getPriceCollection()->findBasePrice();
$defaultStore = $this->getDefaultStore();
$defaultStoreId = $defaultStore['ID'] ?? null;
$convertedPurchasingPrice = \CCurrencyRates::ConvertCurrency(
(float)$preselectedSku->getField('PURCHASING_PRICE'),
(string)$preselectedSku->getField('PURCHASING_CURRENCY'),
$this->getCurrencyId()
);
$basePrice = $basePriceEntity ? $basePriceEntity->getPrice() : null;
$basePriceCurrency = $basePriceEntity ? $basePriceEntity->getCurrency() : null;
$convertedBasePrice = \CCurrencyRates::ConvertCurrency(
(float)$basePrice,
(string)$basePriceCurrency,
$this->getCurrencyId()
);
$vatId = (int)$preselectedSku->getField('VAT_ID');
$tax = $vatId ? \Bitrix\Catalog\VatTable::getRowById($vatId) : null;
return [
[
'ID' => Main\Security\Random::getString(8, false),
'DOC_ID' => null,
'STORE_FROM' => $defaultStoreId,
'STORE_TO' => $defaultStoreId,
'ELEMENT_ID' => $preselectedSku->getId(),
'AMOUNT' => null,
'PURCHASING_PRICE' => $convertedPurchasingPrice,
'BASE_PRICE' => $convertedBasePrice,
'BASE_PRICE_EXTRA' => null,
'BASE_PRICE_EXTRA_RATE' => StoreDocumentElementTable::EXTRA_RATE_PERCENTAGE,
'TAX_RATE' => ($tax['RATE'] ?? null),
'TAX_INCLUDED' => $preselectedSku->getField('VAT_INCLUDED'),
],
];
}
return [];
}
private function getSkuByProductId(int $productId): ?BaseSku
{
$repositoryFacade = ServiceContainer::getRepositoryFacade();
return $repositoryFacade->loadVariation($productId);
}
private function getStores(): array
{
if (empty($this->stores))
{
$this->loadStores();
}
return $this->stores;
}
private function getAccessibleStores(): array
{
return array_intersect_key($this->getStores(), array_flip($this->getAccessibleStoresIds()));
}
/**
* Warehouses to which the user has access.
*
* @return array
*/
private function getAccessibleStoresIds(): array
{
if (isset($this->accessibleStoresIds))
{
return $this->accessibleStoresIds;
}
$storeIds = (array)$this->accessController->getPermissionValue(ActionDictionary::ACTION_STORE_VIEW);
if (in_array(PermissionDictionary::VALUE_VARIATION_ALL, $storeIds, true))
{
$storeIds = array_column($this->getStores(), 'ID');
}
$this->accessibleStoresIds = array_map('intval', $storeIds);
return $this->accessibleStoresIds;
}
private function getDefaultStore(): ?array
{
static $defaultStore;
if (isset($defaultStore))
{
return $defaultStore;
}
$accessibleStoresIds = $this->getAccessibleStoresIds();
if (empty($accessibleStoresIds))
{
return null;
}
$accessibleStores = array_filter(
$this->getStores(),
static function($store) use($accessibleStoresIds)
{
return in_array((int)$store['ID'], $accessibleStoresIds, true);
}
);
$filteredStores = array_filter(
$accessibleStores,
static function($store)
{
return $store['IS_DEFAULT'] === 'Y';
}
);
$defaultStore = reset($filteredStores) ?: reset($accessibleStores);
return $defaultStore;
}
/**
* Returns available amount on store (amount - quantity in reserve)
*
* @param array $productStoreInfo
* @param int $productId
* @param int $storeId
* @return float
*/
private function getAvailableProductAmountOnStore(array $productStoreInfo, int $productId, int $storeId): float
{
$amount = 0.0;
if (
isset(
$productStoreInfo[$productId][$storeId]['AMOUNT'],
$productStoreInfo[$productId][$storeId]['QUANTITY_RESERVED']
)
)
{
$amount =
$productStoreInfo[$productId][$storeId]['AMOUNT']
- $productStoreInfo[$productId][$storeId]['QUANTITY_RESERVED']
;
}
return $amount;
}
public function getPopupSettings(): array
{
return [
[
'id' => 'ADD_NEW_ROW_TOP',
'checked' => ($this->defaultSettings['NEW_ROW_POSITION'] !== 'bottom'),
'title' => Loc::getMessage('CATALOG_DOCUMENT_PRODUCT_LIST_SETTING_NEW_ROW_POSITION_TITLE'),
'desc' => '',
'action' => 'grid',
]
];
}
public function setGridSettingAction(string $settingId, $selected): Bitrix\Main\Engine\Response\AjaxJson
{
if (!$this->checkModules())
{
return Bitrix\Main\Engine\Response\AjaxJson::createError($this->errorCollection);
}
if ($settingId === 'ADD_NEW_ROW_TOP')
{
$direction = ($selected === 'true') ? 'top' : 'bottom';
\CUserOptions::SetOption('catalog.store.document.product.list', 'new.row.position', $direction);
}
return Bitrix\Main\Engine\Response\AjaxJson::createSuccess();
}
/**
* Returns converted base and purchasing prices for product list.
*
* @param array $products Products information.
* @param string $currencyId Currency identifier for result.
* @param string $oldCurrencyId Old currency identifier.
* @return null|array
*/
public function calculateProductPricesAction(array $products, string $currencyId, string $oldCurrencyId): ?array
{
$this->fillSettings();
if ($this->isExistErrors())
{
return null;
}
$response = [];
foreach ($products as $product)
{
$fields = $product['fields'] ?? [];
\CCurrencyRates::ConvertCurrency(
(float)$fields['BASE_PRICE'],
$oldCurrencyId,
$currencyId
);
$basePrice = null;
if ($fields['BASE_PRICE'] !== null)
{
$basePrice = $this->formatPrices(
\CCurrencyRates::ConvertCurrency(
(float)$fields['BASE_PRICE'],
$oldCurrencyId,
$currencyId
)
);
}
$response[$product['id']] = [
'BASE_PRICE' => $basePrice,
'PURCHASING_PRICE' => $this->formatPrices(
\CCurrencyRates::ConvertCurrency(
(float)$fields['PURCHASING_PRICE'],
$oldCurrencyId,
$currencyId
)
),
];
}
return $response;
}
/**
* Returns cost price for product.
*
* @param int $productId Product indentifier.
* @param float $quantity Product quantity.
* @param string $currency Currency identifier.
* @param int $storeId Store identifier.
* @return null|array
*/
public function calculateStoreCostPriceAction(int $productId, float $quantity, string $currency, int $storeId): ?float
{
if (!$this->checkModules())
{
return null;
}
return (new \Bitrix\Catalog\Product\Store\BatchManager($productId))->calculateCostPrice($quantity, $storeId, $currency);
}
private function getRestrictedProductTypesForSelector(): array
{
$restrictedProductTypes = $this->getRestrictedProductTypes();
if (!empty($this->externalDocument['RESTRICTED_PRODUCT_TYPES']))
{
$restrictedProductTypes = $this->externalDocument['RESTRICTED_PRODUCT_TYPES'];
}
return $restrictedProductTypes;
}
private function getRestrictedProductTypes(): array
{
return ProductTable::getStoreDocumentRestrictedProductTypes();
}
private function isEditableBasePrice(): bool
{
if (!$this->accessController->check(ActionDictionary::ACTION_PRICE_EDIT))
{
return false;
}
return ! in_array($this->getDocumentType(), [
StoreDocumentTable::TYPE_MOVING,
StoreDocumentTable::TYPE_DEDUCT,
], true);
}
private function isCanChangeProductMeasure(): bool
{
return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_EDIT);
}
public function isAllowedProductCreation(): bool
{
return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_ADD);
}
/**
* Calculate and return detailed information about the total amount
*
* @param array $gridRows
* @return float[] ('TOTAL_TAX', 'TOTAL_SUM', 'TOTAL_SUM_BEFORE_TAX')
*/
private function getTotalSumDetails(array $gridRows): array
{
$totalSumDetails = [
'TOTAL_TAX' => 0,
'TOTAL_SUM' => 0,
];
foreach ($gridRows as $row) {
$totalSumDetails['TOTAL_TAX'] += $row['raw_data']['TAX_SUM'] ?? 0;
$totalSumDetails['TOTAL_SUM'] += $row['raw_data']['TOTAL_PRICE'] ?? 0;
}
$totalSumDetails['TOTAL_SUM_BEFORE_TAX'] = $totalSumDetails['TOTAL_SUM'] - $totalSumDetails['TOTAL_TAX'];
return $totalSumDetails;
}
/**
* @return string|null
* @throws Main\ArgumentException
* @throws Main\ObjectPropertyException
* @throws Main\SystemException
*/
private function getTaxIncludedFromFirstItem(): ?string
{
$context = \Bitrix\Main\Application::getInstance()->getContext()->getRequest()->get('context');
$ownerId = (int)($context['OWNER_ID'] ?? null);
$ownerTypeId = (int)($context['OWNER_TYPE_ID'] ?? null);
$taxIncludedFromFirstItem = null;
if ($ownerId && $ownerTypeId === CCrmOwnerType::Deal)
{
$productRow = ProductRowTable::getRow(
[
'select' => ['TAX_INCLUDED'],
'filter' => [
'=OWNER_ID' => $ownerId,
'=OWNER_TYPE' => CCrmOwnerTypeAbbr::ResolveByTypeID($ownerTypeId)
],
]
);
$taxIncludedFromFirstItem = $productRow['TAX_INCLUDED'] ?? null;
}
return $taxIncludedFromFirstItem;
}
}