Your IP : 3.14.86.70


Current Path : /home/bitrix/ext_www/crm.klimatlend.ua/bitrix/components/bitrix/crm.kanban/
Upload File :
Current File : /home/bitrix/ext_www/crm.klimatlend.ua/bitrix/components/bitrix/crm.kanban/class.php

<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED!==true)
{
	die();
}

use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\Loader;
use \Bitrix\Main\Application;
use \Bitrix\Main\Config\Option;
use \Bitrix\Crm\Tracking;
use \Bitrix\Main\Result;
use \Bitrix\Main\Entity\AddResult;
use \Bitrix\Main\Error;
use \Bitrix\Crm\Order;
use \Bitrix\Crm\Category\DealCategory;
use \Bitrix\Crm\PhaseSemantics;
use \Bitrix\Crm\Attribute\FieldAttributeManager;
use \Bitrix\Crm\Color\PhaseColorSchemeManager;
use \Bitrix\Crm\Observer\Entity\ObserverTable;
use \Bitrix\Crm\Entity\EntityEditorConfigScope;
use \Bitrix\Crm\Search\SearchEnvironment;
use \Bitrix\Crm\Tracking\Internals\TraceChannelTable;
use \Bitrix\Crm\Tracking\Channel;
use \Bitrix\Sale;
use \Bitrix\Rest\Marketplace;

class CrmKanbanComponent extends \CBitrixComponent
{
	protected $type = '';
	protected $fieldSum = '';
	protected $currency = '';
	protected $statusKey = 'STATUS_ID';
	protected $categoryId = null;
	protected $formatLang = 'ru';
	protected $uid = 0;
	protected $blockPage = 1;
	protected $blockSize = 20;
	protected $maxSortSize = 1000;
	protected $application = null;
	protected $allowSemantics = array();
	protected $allowStages = array();
	protected $types = array();
	protected $contact = array();
	protected $company = array();
	protected $fmTypes = array();
	protected $usersIds = array();
	protected $additionalSelect = array();
	protected $additionalEdit = array();
	protected $additionalTypes = array();
	protected $items = array();
	protected $requiredFields = array();
	protected $schemeFields = array();
	protected $userFields = array();
	protected $dateFormats = array(
		'short' => array(
			'en' => 'F j',
			'de' => 'j. F',
			'ru' => 'j F'
		),
		'full' => array(
			'en' => 'F j, Y',
			'de' => 'j. F Y',
			'ru' => 'j F Y'
		)
	);
	protected $avatarSize = array('width' => 38, 'height' => 38);
	protected $allowedFMtypes = array('phone', 'email', 'im', 'web');
	protected $pathMarkers = array('#lead_id#', '#contact_id#', '#company_id#', '#deal_id#', '#quote_id#', '#invoice_id#', '#order_id#');
	protected $disableMoreFields = array(
		'ACTIVE_TIME_PERIOD', 'PRODUCT_ROW_PRODUCT_ID', 'COMPANY_ID', 'COMPANY_TITLE',
		'CONTACT_ID', 'CONTACT_FULL_NAME', 'EVENT_ID', 'EVENT_DATE', 'ACTIVITY_COUNTER',
		'IS_RETURN_CUSTOMER', 'IS_NEW', 'IS_REPEATED_APPROACH', 'CURRENCY_ID', 'WEBFORM_ID',
		'COMMUNICATION_TYPE', 'HAS_PHONE', 'HAS_EMAIL', 'STAGE_SEMANTIC_ID', 'CATEGORY_ID',
		'STATUS_ID', 'STATUS_SEMANTIC_ID', 'STATUS_CONVERTED', 'MODIFY_BY_ID', 'TRACKING_CHANNEL_CODE',
		'ADDRESS', 'ADDRESS_2', 'ADDRESS_CITY', 'ADDRESS_REGION', 'ADDRESS_PROVINCE',
		'ADDRESS', 'ADDRESS_POSTAL_CODE', 'ADDRESS_COUNTRY', 'CREATED_BY_ID', 'ORIGINATOR_ID', 'ORIGINATOR_ID',
		'UTM_SOURCE', 'UTM_MEDIUM', 'UTM_CAMPAIGN', 'UTM_CONTENT', 'UTM_TERM',
		'STAGE_ID_FROM_HISTORY', 'STAGE_ID_FROM_SUPPOSED_HISTORY', 'STAGE_SEMANTIC_ID_FROM_HISTORY'
	);
	protected $usersIdFields = array(
		'ASSIGNED_BY_ID', 'CREATED_BY_ID', 'MODIFY_BY_ID', 'RESPONSIBLE_ID', 'USER'
	);
	protected $selectPresets = array(
		'lead' => array('ID', 'STATUS_ID', 'TITLE', 'DATE_CREATE', 'OPPORTUNITY', 'OPPORTUNITY_ACCOUNT', 'CURRENCY_ID', 'ACCOUNT_CURRENCY_ID', 'CONTACT_ID', 'COMPANY_ID', 'MODIFY_BY_ID', 'IS_RETURN_CUSTOMER', 'ASSIGNED_BY'),
		'deal' => array('ID', 'STAGE_ID', 'TITLE', 'DATE_CREATE', 'BEGINDATE', 'OPPORTUNITY', 'OPPORTUNITY_ACCOUNT', 'EXCH_RATE', 'CURRENCY_ID', 'ACCOUNT_CURRENCY_ID', 'IS_REPEATED_APPROACH', 'IS_RETURN_CUSTOMER', 'CONTACT_ID', 'COMPANY_ID', 'MODIFY_BY_ID', 'ASSIGNED_BY', 'ORDER_STAGE'),
		'quote' => array('ID', 'STATUS_ID', 'TITLE', 'DATE_CREATE', 'BEGINDATE', 'OPPORTUNITY', 'OPPORTUNITY_ACCOUNT', 'CURRENCY_ID', 'ACCOUNT_CURRENCY_ID', 'CONTACT_ID', 'COMPANY_ID', 'MODIFY_BY_ID', 'ASSIGNED_BY'),
		'invoice' => array('ID', 'STATUS_ID', 'DATE_INSERT', 'DATE_INSERT_FORMAT', 'PAY_VOUCHER_DATE', 'DATE_BILL', 'ORDER_TOPIC', 'PRICE', 'CURRENCY', 'UF_CONTACT_ID', 'UF_COMPANY_ID', 'RESPONSIBLE_ID'),
		'order' => array('ID', 'ACCOUNT_NUMBER', 'STATUS_ID', 'DATE_INSERT', 'PAY_VOUCHER_DATE', 'DATE_PAYED', 'ORDER_TOPIC', 'PRICE', 'CURRENCY', 'RESPONSIBLE_ID')
	);
	protected $exclusiveFieldsReturnCustomer = array(
		'HONORIFIC' => true,
		'LAST_NAME' => true,
		'NAME' => true,
		'SECOND_NAME' => true,
		'BIRTHDATE' => true,
		'POST' => true,
		'COMPANY_TITLE' => true,
		'ADDRESS' => true,
		'PHONE' => true,
		'EMAIL' => true,
		'WEB' => true,
		'IM' => true
	);

	/**
	 * Init class' vars.
	 * @return bool
	 */
	protected function init()
	{
		Loc::loadMessages(__FILE__);

		if (!Loader::includeModule('crm'))
		{
			ShowError(Loc::getMessage('CRM_KANBAN_CRM_NOT_INSTALLED'));
			return false;
		}
		if (!\CCrmPerms::IsAccessEnabled())
		{
			return false;
		}

		$this->allowSemantics = array(
			PhaseSemantics::PROCESS
		);

		//type and types
		$this->types = array(
			'lead' => \CCrmOwnerType::LeadName,
			'deal' => \CCrmOwnerType::DealName,
			'quote' => \CCrmOwnerType::QuoteName,
			'invoice' => \CCrmOwnerType::InvoiceName,
			'order' => \CCrmOwnerType::OrderName,
		);
		$this->fmTypes = array(
			'EMAIL_WORK' => Loc::getMessage('CRM_KANBAN_EMAIL_TYPE_WORK'),
			'EMAIL_HOME' => Loc::getMessage('CRM_KANBAN_EMAIL_TYPE_HOME'),
			'EMAIL_OTHER' => Loc::getMessage('CRM_KANBAN_EMAIL_TYPE_OTHER'),
			'PHONE_MOBILE' => Loc::getMessage('CRM_KANBAN_PHONE_TYPE_MOBILE'),
			'PHONE_WORK' => Loc::getMessage('CRM_KANBAN_PHONE_TYPE_WORK'),
			'PHONE_FAX' => Loc::getMessage('CRM_KANBAN_PHONE_TYPE_FAX'),
			'PHONE_HOME' => Loc::getMessage('CRM_KANBAN_PHONE_TYPE_HOME'),
			'PHONE_PAGER' => Loc::getMessage('CRM_KANBAN_PHONE_TYPE_PAGER'),
			'PHONE_OTHER' => Loc::getMessage('CRM_KANBAN_PHONE_TYPE_OTHER'),
		);
		$this->type = mb_strtoupper(isset($this->arParams['ENTITY_TYPE'])? $this->arParams['ENTITY_TYPE'] : '');
		if (!$this->type || !in_array($this->type, $this->types))
		{
			return false;
		}
		if (
			$this->type == $this->types['deal'] &&
			isset($this->arParams['EXTRA']['CATEGORY_ID']) &&
			$this->arParams['EXTRA']['CATEGORY_ID'] > 0
		)
		{
			$this->categoryId = (int)$this->arParams['EXTRA']['CATEGORY_ID'];
		}
		$this->arParams['ENTITY_TYPE_CHR'] = array_flip($this->types);
		$this->arParams['ENTITY_TYPE_CHR'] = mb_strtoupper($this->arParams['ENTITY_TYPE_CHR'][$this->type]);
		$this->arParams['ENTITY_TYPE_INT'] = \CCrmOwnerType::resolveID($this->type);
		$this->arParams['ENTITY_PATH'] = $this->getEntityPath($this->type);
		$this->arParams['EDITOR_CONFIG_ID'] = $this->getEditorConfigId();
		$this->arParams['HIDE_CC'] = \CUserOptions::getOption(
			'crm',
			'kanban_cc_hide',
			false
		);
		if (
			(
				$this->type == $this->types['lead'] ||
				$this->type == $this->types['deal']
			) &&
			Loader::includeModule('rest') &&
			is_callable('\Bitrix\Rest\Marketplace\Url::getConfigurationPlacementUrl')
		)
		{
			$this->arParams['HIDE_REST'] = \CUserOptions::getOption(
				'crm',
				'kanban_rest_hide',
				false
			);
			$this->arParams['REST_DEMO_URL'] = Marketplace\Url::getConfigurationPlacementUrl(
				'crm_'.mb_strtolower($this->type),
				'crm_kanban'
			);
		}
		else
		{
			$this->arParams['HIDE_REST'] = true;
			$this->arParams['REST_DEMO_URL'] = '';
		}
		//additional select-edit fields
		if (
			$this->type == $this->types['lead'] ||
			$this->type == $this->types['deal'] ||
			$this->type == $this->types['order']
		)
		{
			$this->additionalSelect = \CUserOptions::getOption(
				'crm',
				'kanban_select_more_v4_'.
				mb_strtolower($this->type) . '_' . intval($this->categoryId) .
				($this->isEditorConfigCommon() ? '_common' : ''),
				null
			);
			if ($this->additionalSelect === null)
			{
				if ($this->type !== $this->types['order'])
				{
					$this->additionalSelect = [
						'TITLE' => '',
						'OPPORTUNITY' => '',
						'DATE_CREATE' => '',
						'ORDER_STAGE' => '',
						'CLIENT' => '',
						'PROBLEM_NOTIFICATION' => '',
					];
				}
				else
				{
					$this->additionalSelect = [
						'TITLE' => '',
						'PRICE' => '',
						'DATE_INSERT' => '',
						'CLIENT' => ''
					];
				}

			}
			$this->additionalEdit = (array)\CUserOptions::getOption(
				'crm',
				'kanban_edit_more_v4_'.mb_strtolower($this->type) . '_' . intval($this->categoryId),
				array()
			);
		}
		else
		{
			$this->additionalSelect = [
				'TITLE' => '',
				'OPPORTUNITY' => '',
				'DATE_CREATE' => '',
				'CLIENT' => ''
			];
		}
		//redefine price-field
		if ($this->type != $this->types['quote'] && $this->type != $this->types['order'])
		{
			$slots = \Bitrix\Crm\Statistics\StatisticEntryManager::prepareSlotBingingData($this->type .  '_SUM_STATS');
			if (is_array($slots) && isset($slots['SLOT_BINDINGS']) && is_array($slots['SLOT_BINDINGS']))
			{
				foreach ($slots['SLOT_BINDINGS'] as $slot)
				{
					if ($slot['SLOT'] == 'SUM_TOTAL')
					{
						$res = \CUserTypeEntity::getList(
							[],
							['FIELD_NAME' => $slot['FIELD']]
						);
						if ($row = $res->fetch())
						{
							$this->fieldSum = $slot['FIELD'];
							break;
						}
					}
				}
			}
		}
		//init arParams
		if (!isset($this->arParams['~NAME_TEMPLATE']))
		{
			$this->arParams['~NAME_TEMPLATE'] = \Bitrix\Crm\Format\PersonNameFormatter::getFormat();
		}
		else
		{
			$this->arParams['~NAME_TEMPLATE'] = str_replace(
				array('#NOBR#', '#/NOBR#'),
				array('', ''),
				trim($this->arParams['~NAME_TEMPLATE'])
			);
		}
		if (!isset($this->arParams['ADDITIONAL_FILTER']) || !is_array($this->arParams['ADDITIONAL_FILTER']))
		{
			$this->arParams['ADDITIONAL_FILTER'] = array();
		}
		if (isset($this->arParams['PAGE']) && $this->arParams['PAGE'] > 1)
		{
			$this->blockPage = intval($this->arParams['PAGE']);
		}
		if (!isset($this->arParams['ONLY_COLUMNS']) || $this->arParams['ONLY_COLUMNS'] != 'Y')
		{
			$this->arParams['ONLY_COLUMNS'] = 'N';
		}
		if (!isset($this->arParams['ONLY_ITEMS']) || $this->arParams['ONLY_ITEMS'] != 'Y')
		{
			$this->arParams['ONLY_ITEMS'] = 'N';
		}
		if (!isset($this->arParams['EMPTY_RESULT']) || $this->arParams['EMPTY_RESULT'] != 'Y')
		{
			$this->arParams['EMPTY_RESULT'] = 'N';
		}
		if (!isset($this->arParams['IS_AJAX']) || $this->arParams['IS_AJAX'] != 'Y')
		{
			$this->arParams['IS_AJAX'] = 'N';
		}
		if (!isset($this->arParams['GET_AVATARS']) || $this->arParams['GET_AVATARS'] != 'Y')
		{
			$this->arParams['GET_AVATARS'] = 'N';
		}
		if (!isset($this->arParams['EXTRA']) || !is_array($this->arParams['EXTRA']))
		{
			$this->arParams['EXTRA'] = array();
		}
		if (!isset($this->arParams['FORCE_FILTER']) || $this->arParams['FORCE_FILTER'] != 'Y')
		{
			$this->arParams['FORCE_FILTER'] = 'N';
		}
		if ($this->type == $this->types['deal'])
		{
			$this->statusKey = 'STAGE_ID';
		}
		if (LANGUAGE_ID == 'de' || LANGUAGE_ID == 'en')
		{
			$this->formatLang = LANGUAGE_ID;
		}

		$this->uid = \CCrmSecurityHelper::GetCurrentUserID();
		$this->arParams['USER_ID'] = $this->uid;
		if (isset($this->arParams['PATH_TO_IMPORT']))
		{
			$uriImport = new \Bitrix\Main\Web\Uri(
				$this->arParams['PATH_TO_IMPORT']
			);
			if ($this->categoryId)
			{
				$uriImport->addParams(array(
					'category_id' => $this->categoryId
				));
			}
			$uriImport->addParams(array(
				'from' => 'kanban'
			));
			$this->arParams['PATH_TO_IMPORT'] = $uriImport->getUri();
		}
		else
		{
			$this->arParams['PATH_TO_IMPORT'] = '';
		}
		if (!isset($this->arParams['PATH_TO_USER']))
		{
			$this->arParams['PATH_TO_USER'] = '/company/personal/user/#user_id#/';
		}
		if (!isset($this->arParams['PATH_TO_DEAL_KANBANCATEGORY']))
		{
			$this->arParams['PATH_TO_DEAL_KANBANCATEGORY'] = '';
		}
		// for invoice another base currency
		if ($this->type == $this->types['invoice'])
		{
			$this->currency = $this->arParams['CURRENCY'] = \CCrmCurrency::getInvoiceDefault();
		}
		else
		{
			$this->currency = $this->arParams['CURRENCY'] = \CCrmCurrency::GetAccountCurrencyID();
		}

		$this->application = $GLOBALS['APPLICATION'];

		if ($this->type === $this->types['order'])
		{
			Loader::includeModule('sale');
		}

		return true;
	}

	/**
	 * Set error for template.
	 * @param mixed $error
	 * @return void
	 */
	protected function setError($error)
	{
		$this->arResult['ERROR'] = $error;
	}

	/**
	 * Get status's color
	 * @param string $code Code color.
	 * @return array
	 */
	protected function getStatusColor($code)
	{
		$colorScheme = PhaseColorSchemeManager::resolveSchemeByName(
			$code
		);
		if ($colorScheme)
		{
			return $colorScheme->externalize();
		}
		else
		{
			return (array) unserialize(Option::get('crm', $code));
		}
	}

	/**
	 * Switch entity type to letter and returns it.
	 * @param string $entityType Letter entity type.
	 * @return string|null
	 */
	protected function makeEntityLetter(string $entityType): ?string
	{
		$entityType = mb_strtolower($entityType);

		if ($entityType == 'deal')
		{
			return 'D';
		}
		else if ($entityType == 'lead')
		{
			return 'L';
		}
		else if ($entityType == 'contact')
		{
			return 'C';
		}
		else if ($entityType == 'company')
		{
			return 'CO';
		}
		else if ($entityType == 'order')
		{
			return 'O';
		}

		return null;
	}

	/**
	 * Resolves full entity type from symbol.
	 * @param string $entityType Entity type.
	 * @return string
	 */
	protected function resolveEntityLetter(string $entityType): ?string
	{
		if ($entityType == 'D')
		{
			return 'deal';
		}
		else if ($entityType == 'L')
		{
			return 'lead';
		}
		else if ($entityType == 'C')
		{
			return 'contact';
		}
		else if ($entityType == 'CO')
		{
			return 'company';
		}
		else if ($entityType == 'O')
		{
			return 'order';
		}

		return null;
	}

	/**
	 * Gets CRM statuses from database.
	 * @param string $entityId Entity id.
	 * @return array
	 */
	protected function getStatusesDB($entityId)
	{
		static $db = [];

		if ($db)
		{
			return isset($db[$entityId]) ? $db[$entityId] : null;
		}

		$res = \CCrmStatus::getList(
			['SORT' => 'ASC']
		);
		while ($row = $res->fetch())
		{
			if (!isset($db[$row['ENTITY_ID']]))
			{
				$db[$row['ENTITY_ID']] = [];
			}
			$db[$row['ENTITY_ID']][$row['STATUS_ID']] = $row['NAME'];
		}

		return isset($db[$entityId]) ? $db[$entityId] : null;
	}

	/**
	 * Get all CRM statuses, stages, etc.
	 * @param boolean $clear Clear static cache.
	 * @return array
	 */
	protected function getStatuses($clear = false)
	{
		static $statuses = null;

		if ($clear)
		{
			$statuses = null;
		}

		if ($statuses !== null)
		{
			return $statuses;
		}

		$statuses = array();
		$type = $this->type;
		$types = $this->types;
		$semantic = \CCrmStatus::GetEntityTypes();
		$semantic = array_merge($semantic, [Order\OrderStatus::NAME => Order\OrderStatus::getSemanticInfo()]);
		//colors
		$colors = array(
			'QUOTE_STATUS' => ($type == $types['quote']) ? $this->getStatusColor('CONFIG_STATUS_QUOTE_STATUS') : array(),
			'INVOICE_STATUS' => ($type == $types['invoice']) ? $this->getStatusColor('CONFIG_STATUS_INVOICE_STATUS') : array(),
			'ORDER_STATUS' => ($type == $types['order']) ? $this->getStatusColor('CONFIG_STATUS_ORDER_STATUS') : array(),
			'STATUS' => ($type == $types['lead']) ? $this->getStatusColor('CONFIG_STATUS_STATUS') : array(),
		);
		if ($this->categoryId)
		{
			$categories = DealCategory::getList(array(
				'filter' => array(
					'ID' => $this->categoryId
				)
			))->fetchAll();
			foreach ($categories as $cat)
			{
				$colors['DEAL_STAGE_' . $cat['ID']] = $this->getStatusColor(
					'CONFIG_STATUS_DEAL_STAGE_' . $cat['ID']
				);
			}
		}
		elseif ($type == $types['deal'])
		{
			$colors['DEAL_STAGE'] = $this->getStatusColor(
				'CONFIG_STATUS_DEAL_STAGE'
			);
		}

		//custom statuses
		$custom = array_keys($colors);
		$custom[] = 'DEAL_TYPE';
		$custom[] = 'SOURCE';
		$custom = array_flip($custom);
		foreach ($custom as $code => &$value)
		{
			if (empty($value) || !is_array($value))
			{
				unset($custom[$code]);
			}
		}
		unset($value);

		//common get
		$db = array();
		$res = \CCrmStatus::GetList(array('SORT' => 'ASC'));
		while ($row = $res->fetch())
		{
			if (!isset($custom[$row['ENTITY_ID']]))
			{
				$db[] = $row;
			}
		}
		foreach ($custom as $code => $value)
		{
			$db = array_merge($db, $value);
		}
		$orderStatuses = Order\OrderStatus::getListInCrmFormat($clear);
		$db = array_merge($db, $orderStatuses);

		foreach ($db as $row)
		{
			$row['STATUS_ID'] = htmlspecialcharsbx($row['STATUS_ID']);
			if (in_array($row['ENTITY_ID'], array('DEAL_TYPE', 'SOURCE')))
			{
				if ($row['ENTITY_ID'] == 'DEAL_TYPE')
				{
					$row['ENTITY_ID'] = 'TYPE_ID';
				}
				elseif ($row['ENTITY_ID'] == 'SOURCE')
				{
					$row['ENTITY_ID'] = 'SOURCE_ID';
				}
				if (!isset($this->additionalTypes[$row['ENTITY_ID']]))
				{
					$this->additionalTypes[$row['ENTITY_ID']] = array();
				}
				$this->additionalTypes[$row['ENTITY_ID']][$row['STATUS_ID']] = $row['NAME'];
				continue;
			}
			if (!isset($colors[$row['ENTITY_ID']]) || empty($colors[$row['ENTITY_ID']]))
			{
				continue;
			}
			if (!isset($statuses[$row['ENTITY_ID']]))
			{
				$statuses[$row['ENTITY_ID']] = array();
			}
			$row['COLOR'] = isset($colors[$row['ENTITY_ID']][$row['STATUS_ID']]) &&
							isset($colors[$row['ENTITY_ID']][$row['STATUS_ID']]['COLOR'])
							? htmlspecialcharsbx($colors[$row['ENTITY_ID']][$row['STATUS_ID']]['COLOR'])
							: '';
			if (isset($semantic[$row['ENTITY_ID']]) && isset($semantic[$row['ENTITY_ID']]['SEMANTIC_INFO'])
				&&
				(
					!isset($semantic[$row['ENTITY_ID']]['SEMANTIC_INFO']['FINAL_SORT']) ||
					$semantic[$row['ENTITY_ID']]['SEMANTIC_INFO']['FINAL_SORT'] == 0
				)
				&&
				(
					isset($semantic[$row['ENTITY_ID']]['SEMANTIC_INFO']['FINAL_SUCCESS_FIELD']) ||
					isset($semantic[$row['ENTITY_ID']]['SEMANTIC_INFO']['FINAL_UNSUCCESS_FIELD'])
				)
				&&
				(
					$semantic[$row['ENTITY_ID']]['SEMANTIC_INFO']['FINAL_SUCCESS_FIELD'] == $row['STATUS_ID'] ||
					$semantic[$row['ENTITY_ID']]['SEMANTIC_INFO']['FINAL_UNSUCCESS_FIELD'] == $row['STATUS_ID']
				)
			)
			{
				$semantic[$row['ENTITY_ID']]['SEMANTIC_INFO']['FINAL_SORT'] = $row['SORT'];
			}
			$statuses[$row['ENTITY_ID']][$row['STATUS_ID']] = $row;
		}

		//range statuses
		foreach ($statuses as $id => &$entity)
		{
			$finalSort = isset($semantic[$id]) && isset($semantic[$id]['SEMANTIC_INFO'])
						&& $semantic[$id]['SEMANTIC_INFO']['FINAL_SORT']
						? $semantic[$id]['SEMANTIC_INFO']['FINAL_SORT']
						: 0;
			foreach ($entity as &$status)
			{
				if ($finalSort == $status['SORT'])
				{
					$status['PROGRESS_TYPE'] = 'WIN';
				}
				elseif ($finalSort < $status['SORT'])
				{
					$status['PROGRESS_TYPE'] = 'LOOSE';
				}
				else
				{
					$status['PROGRESS_TYPE'] = 'PROGRESS';
				}
			}
			unset($status);
		}
		unset($entity);

		foreach ($statuses as $status)
		{
			if (!empty($status))
			{
				$statuses = $status;
			}
		}

		return $statuses;
	}

	/**
	 * Make select from presets.
	 * @return array
	 */
	protected function getSelect()
	{
		global $USER_FIELD_MANAGER;
		static $select = null;

		if ($select === null)
		{
			$types = array_flip($this->types);
			$select = $this->selectPresets[$types[$this->type]];

			if (!empty($this->additionalSelect))
			{
				$additionalFields = array_keys($this->additionalSelect);

				if ($this->type == $this->types['order'])
				{
					$ufSelect = preg_grep( "/^UF_/", $additionalFields);
					$additionalFields = array_intersect($additionalFields,  Order\Order::getAllFields());
					if (!empty($ufSelect))
					{
						$crmUserType = new CCrmUserType($USER_FIELD_MANAGER, Order\Order::getUfId());
						$userOrderFields = $crmUserType->GetFields();
						if (is_array($userOrderFields))
						{
							foreach ($ufSelect as $userFieldName)
							{
								if (isset($userOrderFields[$userFieldName]))
								{
									$additionalFields[] = $userFieldName;
								}
							}
						}
					}

					if (isset($this->additionalSelect['SOURCE_ID']))
					{
						$additionalFields['SOURCE_ID'] = 'TRADING_PLATFORM.TRADING_PLATFORM.NAME';
					}
					if (isset($this->additionalSelect['USER']))
					{
						$additionalFields[] = 'USER_ID';
					}
				}
				$select = array_merge($select, $additionalFields);
			}
			if ($this->fieldSum != '')
			{
				$select[] = $this->fieldSum;
			}
		}

		return $select;
	}

	/**
	 * Make filter from env.
	 * @return array
	 */
	protected function getFilter()
	{
		static $filter = null;

		if ($this->arParams['FORCE_FILTER'] == 'Y')
		{
			return [];
		}

		if ($filter === null)
		{
			if ($this->categoryId !== null)
			{
				\Bitrix\Crm\Kanban\Helper::setCategoryId(
					$this->categoryId
				);
			}
			$filter = array();
			$filterLogic = array(
				'TITLE', 'NAME', 'LAST_NAME', 'SECOND_NAME', 'POST', 'COMMENTS', 'COMPANY_TITLE'
			);
			$filterAddress = array(
				'ADDRESS', 'ADDRESS_2', 'ADDRESS_PROVINCE', 'ADDRESS_REGION', 'ADDRESS_CITY',
				'ADDRESS_COUNTRY', 'ADDRESS_POSTAL_CODE'
			);
			//from main.filter
			$grid = \Bitrix\Crm\Kanban\Helper::getGrid($this->type);
			$gridFilter = \Bitrix\Crm\Kanban\Helper::getFilter($this->type);
			$search = (array)$grid->GetFilter($gridFilter);
			\Bitrix\Crm\UI\Filter\EntityHandler::internalize($gridFilter, $search);
			if (!isset($search['FILTER_APPLIED']))
			{
				$search = array();
			}
			if (!empty($search))
			{
				foreach ($search as $key => $item)
				{
					unset($search[$key]);
					if (in_array(mb_substr($key, 0, 2), array('>=', '<=', '<>')))
					{
						$key = mb_substr($key, 2);
					}
					if (in_array(mb_substr($key, 0, 1), array('=', '<', '>', '@', '!')))
					{
						$key = mb_substr($key, 1);
					}
					$search[$key] = $item;
				}
				foreach ($gridFilter as $key => $item)
				{
					//fill filter by type
					if ($item['type'] == 'date')
					{
						if (isset($search[$key . '_from']) && $search[$key . '_from']!='')
						{
							$filter['>='.$key] = $search[$key . '_from'] . ' 00:00:00';
							if ($this->type !== $this->types['order'])
							{
								$filter['FLT_DATE_EXIST'] = 'Y';
							}
						}
						if (isset($search[$key . '_to']) && $search[$key . '_to']!='')
						{
							$filter['<='.$key] = $search[$key . '_to'] . ' 23:59:00';
							if ($this->type !== $this->types['order'])
							{
								$filter['FLT_DATE_EXIST'] = 'Y';
							}
						}
					}
					elseif ($item['type'] == 'number')
					{
						$fltType = isset($search[$key . '_numsel'])
									? $search[$key . '_numsel']
									: 'exact';
						if ($fltType == 'exact' && isset($search[$key . '_from']))
						{
							$filter[$key] = $search[$key . '_from'];
						}
						else if (
							$fltType == 'range' &&
							isset($search[$key . '_from']) &&
							isset($search[$key . '_to'])
						)
						{
							$filter['>='.$key] = $search[$key . '_from'];
							$filter['<='.$key] = $search[$key . '_to'];
						}
						else if (
							$fltType == 'more' &&
							isset($search[$key . '_from'])
						)
						{
							$filter['>'.$key] = $search[$key . '_from'];
						}
						else if (
							$fltType == 'less' &&
							isset($search[$key . '_to'])
						)
						{
							$filter['<'.$key] = $search[$key . '_to'];
						}
					}
					elseif (isset($search[$key]))
					{
						if (in_array($key, $filterLogic))
						{
							$filter['?' . $key] = $search[$key];
						}
						elseif (in_array($key, $filterAddress))
						{
							$filter['=%' . $key] = $search[$key] . '%';
						}
						elseif ($key == 'STATUS_CONVERTED')
						{
							$filter[$key === 'N' ? 'STATUS_SEMANTIC_ID' : '!STATUS_SEMANTIC_ID'] = 'P';
						}
						elseif ($key == 'ORDER_TOPIC' || $key == 'ACCOUNT_NUMBER')
						{
							$filter['~' . $key] ='%' . $search[$key] . '%';
						}
						elseif (
							$key == 'ENTITIES_LINKS' &&
							(
								$this->type == $this->types['quote'] ||
								$this->type == $this->types['invoice']
							)
						)
						{
							$ownerData = explode('_', $search[$key]);
							if (count($ownerData) > 1)
							{
								$ownerTypeName = \CCrmOwnerType::resolveName(
									\CCrmOwnerType::resolveID($ownerData[0])
								);
								$ownerID = intval($ownerData[1]);
								if (!empty($ownerTypeName) && $ownerID > 0)
								{
									if ($this->type == $this->types['invoice'])
									{
										$filter['UF_' . $ownerTypeName . '_ID'] = $ownerID;
									}
									else
									{
										$filter[$ownerTypeName . '_ID'] = $ownerID;
									}
								}
							}
						}
						elseif (
							($this->type == $this->types['quote']) &&
							in_array(
								$key, array(
									'PRODUCT_ID', 'STATUS_ID', 'COMPANY_ID', 'LEAD_ID',
									'DEAL_ID', 'CONTACT_ID', 'MYCOMPANY_ID'
								)
							)
						)
						{
							$filter['=' . $key] = $search[$key];
						}
						else
						{
							$filter[$key] = $search[$key];
						}
					}
					elseif (
						isset($item['alias']) &&
						isset($search[$item['alias']])
					)
					{
						$filter['=' . $key] = $search[$item['alias']];
					}
				}
				//search index
				if (isset($search['FIND']) && trim($search['FIND']) != '')
				{
					$search['FIND'] = trim($search['FIND']);
					$filter['SEARCH_CONTENT'] = $search['FIND'];
				}
			}
			if (isset($filter['COMMUNICATION_TYPE']))
			{
				if (!is_array($filter['COMMUNICATION_TYPE']))
				{
					$filter['COMMUNICATION_TYPE'] = array($filter['COMMUNICATION_TYPE']);
				}
				if (in_array(\CCrmFieldMulti::PHONE, $filter['COMMUNICATION_TYPE']))
				{
					$filter['HAS_PHONE'] = 'Y';
				}
				if (in_array(\CCrmFieldMulti::EMAIL, $filter['COMMUNICATION_TYPE']))
				{
					$filter['HAS_EMAIL'] = 'Y';
				}
				unset($filter['COMMUNICATION_TYPE']);
			}
			//overdue
			if (
				isset($filter['OVERDUE']) &&
				(
					$this->type == $this->types['quote'] ||
					$this->type == $this->types['invoice']
				)
			)
			{
				$key = $this->type == $this->types['quote'] ? 'CLOSEDATE' : 'DATE_PAY_BEFORE';
				$date = new \Bitrix\Main\Type\Date;
				if ($filter['OVERDUE'] == 'Y')
				{
					$filter['<'.$key] = $date;
					$filter['>'.$key] = \Bitrix\Main\Type\Date::createFromTimestamp(0);
				}
				else
				{
					$filter['>='.$key] = $date;
				}
			}
			// counters
			if (
				isset($filter['ACTIVITY_COUNTER']) &&
				($this->type == $this->types['lead'] || $this->type == $this->types['deal'])
			)
			{
				if (is_array($filter['ACTIVITY_COUNTER']))
				{
					$counterTypeID = Bitrix\Crm\Counter\EntityCounterType::joinType(
						array_filter($filter['ACTIVITY_COUNTER'], 'is_numeric')
					);
				}
				else
				{
					$counterTypeID = (int)$filter['ACTIVITY_COUNTER'];
				}

				$counter = null;
				if ($counterTypeID > 0)
				{
					// get assigned for this counter
					$counterUserIDs = array();
					if (isset($filter['ASSIGNED_BY_ID']))
					{
						if (is_array($filter['ASSIGNED_BY_ID']))
						{
							$counterUserIDs = array_filter($filter['ASSIGNED_BY_ID'], 'is_numeric');
						}
						elseif ($filter['ASSIGNED_BY_ID'] > 0)
						{
							$counterUserIDs[] = $filter['ASSIGNED_BY_ID'];
						}
					}
					// set counter to the filter
					try
					{
						$counter = Bitrix\Crm\Counter\EntityCounterFactory::create(
							$this->type == $this->types['lead']
							? \CCrmOwnerType::Lead
							: \CCrmOwnerType::Deal,
							$counterTypeID,
							0
						);
						$filter += $counter->prepareEntityListFilter(
							array(
								'MASTER_ALIAS' => $this->type == $this->types['lead']
													? \CCrmLead::TABLE_ALIAS
													: \CCrmDeal::TABLE_ALIAS,
								'MASTER_IDENTITY' => 'ID',
								'USER_IDS' => $counterUserIDs
							)
						);
						if (isset($filter['ASSIGNED_BY_ID']))
						{
							unset($filter['ASSIGNED_BY_ID']);
						}
					}
					catch (\Bitrix\Main\NotSupportedException $e)
					{
					}
					catch (\Bitrix\Main\ArgumentException $e)
					{
					}
				}
			}
			//deal
			if ($this->categoryId !== null)
			{
				$filter['CATEGORY_ID'] = $this->categoryId;
			}
			//invoice
			if (
				$this->type == $this->types['deal'] ||
				$this->type == $this->types['invoice']
			)
			{
				$filter['!IS_RECURRING'] = 'Y';
			}
			if (isset($filter['OVERDUE']))
			{
				unset($filter['OVERDUE']);
			}
			//detect success/fail columns
			$semanticIds = array();
			if (isset($filter['STATUS_SEMANTIC_ID']))
			{
				$semanticIds = (array)$filter['STATUS_SEMANTIC_ID'];
			}
			elseif (isset($filter['STAGE_SEMANTIC_ID']))
			{
				$semanticIds = (array)$filter['STAGE_SEMANTIC_ID'];
			}
			elseif (
				$this->type == $this->types['quote'] &&
				isset($filter['=STATUS_ID'])
			)
			{
				// gets semantic for quotes
				$res = \CCrmStatus::GetList(array(
					'SORT' => 'ASC'
				));
				while ($row = $res->fetch())
				{
					if (
						$row['ENTITY_ID'] == 'QUOTE_STATUS' &&
						in_array($row['STATUS_ID'], $filter['=STATUS_ID'])
					)
					{
						$this->allowStages[] = $row['STATUS_ID'];
					}
				}
			}
			if (in_array(PhaseSemantics::SUCCESS, $semanticIds))
			{
				$this->allowSemantics[] = PhaseSemantics::SUCCESS;
			}
			if (in_array(PhaseSemantics::FAILURE, $semanticIds))
			{
				$this->allowSemantics[] = PhaseSemantics::FAILURE;
			}
			if (isset($filter[$this->statusKey]))
			{
				$this->allowStages = $filter[$this->statusKey];
			}

			$entityTypeID = \CCrmOwnerType::resolveID($this->type);
			//region Apply Search Restrictions
			$searchRestriction = \Bitrix\Crm\Restriction\RestrictionManager::getSearchLimitRestriction();
			if(!$searchRestriction->isExceeded($entityTypeID))
			{
				SearchEnvironment::convertEntityFilterValues(
					\CCrmOwnerType::resolveID($this->type),
					$filter
				);
			}
			else
			{
				$arResult['LIVE_SEARCH_LIMIT_INFO'] = $searchRestriction->prepareStubInfo(
					array('ENTITY_TYPE_ID' => $entityTypeID)
				);
			}
			//endregion


			\CCrmEntityHelper::prepareMultiFieldFilter(
				$filter,
				array(),
				'=%',
				false
			);
		}

		return $filter;
	}

	/**
	 * Get filter for orders
	 *
	 * @param array $runtime
	 *
	 * @return array
	 */
	protected function getOrderFilter(array &$runtime) : array
	{
		$grid = \Bitrix\Crm\Kanban\Helper::getGrid($this->type);
		$gridFilter = \Bitrix\Crm\Kanban\Helper::getFilter($this->type);
		$gridFilterFields = (array)$grid->GetFilter($gridFilter);
		\Bitrix\Crm\UI\Filter\EntityHandler::internalize($gridFilter, $gridFilterFields);

		$filterFields = [];
		$componentName = 'bitrix:crm.order.list';
		$className = \CBitrixComponent::includeComponentClass(
			$componentName
		);
		/** @var CCrmOrderListComponent $crmCmp */
		$crmCmp = new $className;
		$crmCmp->initComponent($componentName);
		if ($crmCmp->init())
		{
			$filterFields = $crmCmp->createGlFilter($gridFilterFields, $runtime);
		}

		if (!empty($filterFields['DELIVERY_SERVICE']))
		{
			$services = is_array($filterFields['DELIVERY_SERVICE']) ? $filterFields['DELIVERY_SERVICE'] : [$filterFields['DELIVERY_SERVICE']];
			$whereExpression = '';
			foreach ($services as $serviceId)
			{
				$serviceId = (int)$serviceId;
				if ($serviceId <= 0)
				{
					continue;
				}
				$whereExpression .= empty($whereExpression) ? "(" : " OR ";
				$whereExpression .= "DELIVERY_ID = {$serviceId}";
			}
			if (!empty($whereExpression))
			{
				$whereExpression .= ")";
				$expression = "CASE WHEN EXISTS (SELECT ID FROM b_sale_order_delivery WHERE ORDER_ID = %s AND SYSTEM=\"N\" AND {$whereExpression}) THEN 1 ELSE 0 END";
				$runtime[] = new \Bitrix\Main\Entity\ExpressionField(
					'REQUIRED_DELIVERY_PRESENTED',
					$expression,
					['ID'],
					['date_type' => 'boolean']
				);
				$filterFields['=REQUIRED_DELIVERY_PRESENTED'] = 1;
				unset($filterFields['DELIVERY_SERVICE']);
			}
		}

		if (!empty($filterFields['PAY_SYSTEM']))
		{
			$paySystems = is_array($filterFields['PAY_SYSTEM']) ? $filterFields['PAY_SYSTEM'] : [$filterFields['PAY_SYSTEM']];
			$whereExpression = '';
			foreach ($paySystems as $systemId)
			{
				$systemId = (int)$systemId;
				if ($systemId <= 0)
				{
					continue;
				}
				$whereExpression .= empty($whereExpression) ? "(" : " OR ";
				$whereExpression .= "PAY_SYSTEM_ID = {$systemId}";
			}
			if (!empty($whereExpression))
			{
				$whereExpression .= ")";
				$expression = "CASE WHEN EXISTS (SELECT ID FROM b_sale_order_payment WHERE ORDER_ID = %s AND {$whereExpression}) THEN 1 ELSE 0 END";
				$runtime[] = new \Bitrix\Main\Entity\ExpressionField(
					'REQUIRED_PAY_SYSTEM_PRESENTED',
					$expression,
					['ID'],
					['date_type' => 'boolean']
				);
				$filterFields['=REQUIRED_PAY_SYSTEM_PRESENTED'] = 1;
				unset($filterFields['PAY_SYSTEM']);
			}
		}

		return $filterFields;
	}

	/**
	 * Get path for entity from params or module settings.
	 * @param string $type
	 * @return string
	 */
	protected function getEntityPath($type)
	{
		$params = $this->arParams;
		$pathKey = 'PATH_TO_'.mb_strtoupper($type).'_DETAILS';
		$url = !array_key_exists($pathKey, $params) ? \CrmCheckPath($pathKey, '', '') : $params[$pathKey];
		if(!($url !== '' && CCrmOwnerType::IsSliderEnabled(CCrmOwnerType::ResolveID($type))))
		{
			$pathKey = 'PATH_TO_'.mb_strtoupper($type).'_SHOW';
			$url = !array_key_exists($pathKey, $params) ? \CrmCheckPath($pathKey, '', '') : $params[$pathKey];
		}

		return $url;
	}

	/**
	 * Get multi-fields for entity (phone, email, etc).
	 * @param array $items
	 * @param string $contragent
	 * @return array
	 */
	protected function fillFMfields(array $items, $contragent)
	{
		$isOneElement = false;

		if (!empty($items))
		{
			if (isset($items['ID']))
			{
				$isOneElement = true;
				$items = array($items['ID'] => $items);
			}
			$res = \CCrmFieldMulti::GetListEx(array(), array(
															'ENTITY_ID' => $contragent,
															'ELEMENT_ID' => array_keys($items)));
			while ($row = $res->fetch())
			{
				$row['TYPE_ID'] = mb_strtolower($row['TYPE_ID']);
				if (!in_array($row['TYPE_ID'], $this->allowedFMtypes))
				{
					continue;
				}
				if (!isset($items[$row['ELEMENT_ID']]['FM']))
				{
					$items[$row['ELEMENT_ID']]['FM'] = array();
					$items[$row['ELEMENT_ID']]['FM_VALUES'] = array();
				}
				if (!isset($items[$row['ELEMENT_ID']]['FM'][$row['TYPE_ID']]))
				{
					$items[$row['ELEMENT_ID']]['FM'][$row['TYPE_ID']] = array();
					$items[$row['ELEMENT_ID']]['FM_VALUES'][$row['TYPE_ID']] = array();
				}
				$items[$row['ELEMENT_ID']]['FM'][$row['TYPE_ID']][] = $row;
				$items[$row['ELEMENT_ID']]['FM_VALUES'][$row['TYPE_ID']][] = array(
					'value' => htmlspecialcharsbx($row['VALUE']),
					'title' => $this->fmTypes[$row['COMPLEX_ID']]
				);
			}
		}

		return $isOneElement ? array_pop($items) : $items;
	}

	/**
	 * Companies or contacts.
	 * @param string $contragent
	 * @return array
	 */
	protected function getContragents($contragent)
	{
		$items = array();

		$path = $this->getEntityPath($contragent);

		$contragent = mb_strtolower(trim($contragent));
		$provider = '\CCrm'.$contragent;
		if (
			is_callable(array($provider, 'getListEx')) &&
			isset($this->{$contragent}) && !empty($this->{$contragent})
		)
		{
			$select = array(
				'ID', 'NAME', 'LAST_NAME', 'SECOND_NAME', 'TITLE', 'HONORIFIC'
			);
			$res = $provider::getListEx(
				array(),
				array(
					'ID' => array_unique($this->{$contragent})
				),
				false,
				false,
				$select
			);
			while ($row = $res->fetch())
			{
				if ($contragent == 'contact')
				{
					$row['TITLE'] = \CCrmContact::prepareFormattedName(
						$row,
						$this->arParams['~NAME_TEMPLATE']
					);
				}
				$row['~TITLE'] = $row['TITLE'];
				$row['TITLE'] = htmlspecialcharsbx($row['TITLE']);
				$row['URL'] = str_replace($this->pathMarkers, $row['ID'], $path);
				$items[$row['ID']] = $row;
			}
		}

		$items = $this->fillFMfields($items, $contragent);

		return $items;
	}

	/**
	 * Get columns.
	 * @param boolean $clear Clear static var.
	 * @param boolean $withoutCache Without static cache.
	 * @param array $params Some additional params.
	 * @return array
	 */
	protected function getColumns($clear = false, $withoutCache = false, array $params = array())
	{
		static $columns = array();

		if ($withoutCache)
		{
			$clear = $withoutCache;
		}

		if ($this->arParams['ONLY_ITEMS'] == 'Y')
		{
			return $columns;
		}

		if ($clear)
		{
			$columns = array();
		}

		if (!isset($params['originalColumns']))
		{
			$params['originalColumns'] = false;
		}

		$getDeleteCol = function($params)
		{
			return array(
				'real_id' => 'DELETED',
				'real_sort' => $params['real_sort'],
				'id' => 'DELETED',
				'name' => Loc::getMessage('CRM_KANBAN_SYS_STATUS_DELETE'),
				'color' => '',
				'type' => '',
				'sort' => $params['sort'],
				'count' => 0,
				'total' => 0,
				'currency' => $this->currency,
				'dropzone' => true
			);
		};

		if (empty($columns))
		{
			$runtime = [];
			$baseCurrency = $this->currency;
			if ($this->type !== $this->types['order'])
			{
				$filter = $this->getFilter();
			}
			else
			{
				$filter = $this->getOrderFilter($runtime);
				if (isset($filter[$this->statusKey]))
				{
					$this->allowStages = $filter[$this->statusKey];
				}
			}
			$types = $this->types;
			$statusCode = $this->statusKey;
			$sort = 0;
			$winColumn = [];
			$commonRequireds = [];
			$userPerms = $this->getCurrentUserPermissions();
			$column = [
				'type' => 'PROGRESS'
			];
			// get all required fields
			if
			(
				$this->type == $types['lead'] ||
				$this->type == $types['deal']
			)
			{
				foreach ($this->userFields as $row)
				{
					if ($row['MANDATORY'] == 'Y')
					{
						$commonRequireds[] = $row['FIELD_NAME'];
					}
				}
				unset($row);
			}
			// prepare each status
			$firstDropzone = false;
			foreach ($this->getStatuses($clear) as $status)
			{
				$sort += 100;
				//set default color
				if ($status['COLOR'] == '')
				{
					if ($status['PROGRESS_TYPE'] == 'WIN')
					{
						$status['COLOR'] = \CCrmViewHelper::SUCCESS_COLOR;
					}
					elseif ($status['PROGRESS_TYPE'] == 'LOOSE')
					{
						$status['COLOR'] = \CCrmViewHelper::FAILURE_COLOR;
					}
					else
					{
						$status['COLOR'] = \CCrmViewHelper::PROCESS_COLOR;
					}
				}
				//detect drop zones
				if (
					!empty($this->allowStages) &&
					!in_array($status['STATUS_ID'], $this->allowStages)
				)
				{
					$dropzone = true;
				}
				else if (
					in_array($status['STATUS_ID'], $this->allowStages)
				)
				{
					$dropzone = false;
				}
				else if (
					(
						$status['PROGRESS_TYPE'] == 'WIN' &&
						!in_array(PhaseSemantics::SUCCESS, $this->allowSemantics)
					)
					||
					(
						$status['PROGRESS_TYPE'] == 'LOOSE' &&
						!in_array(PhaseSemantics::FAILURE, $this->allowSemantics)
					)
					||
					(
						$status['PROGRESS_TYPE'] != 'WIN' &&
						$status['PROGRESS_TYPE'] != 'LOOSE' &&
						!in_array(PhaseSemantics::PROCESS, $this->allowSemantics)
					)
				)
				{
					$dropzone = true;
				}
				else
				{
					$dropzone = false;
				}
				// first drop zone
				if (!$firstDropzone && $dropzone)
				{
					$firstDropzone = true;
				}
				// add 'delete' column
				if (
					$firstDropzone &&
					!$params['originalColumns']
				)
				{
					$firstDropzone = false;
					$columns['DELETED'] = $getDeleteCol([
						'real_sort' => $status['SORT'],
						'sort' => $sort
					]);
				}
				// format column
				$column = array(
					'real_id' => $status['ID'],
					'real_sort' => $status['SORT'],
					'id' => $status['STATUS_ID'],
					'name' => $status['NAME'],
					'color' => mb_strpos($status['COLOR'], '#') === 0? mb_substr($status['COLOR'], 1) : $status['COLOR'],
					'type' => $status['PROGRESS_TYPE'],
					'sort' => $sort,
					'count' => 0,
					'total' => 0,
					'currency' => $baseCurrency,
					'dropzone' => $dropzone,
					'canAddItem' => true
				);
				// check create prermissions
				if (
					$this->type == $types['lead'] ||
					$this->type == $types['deal']
				)
				{
					if ($this->type == $types['lead'])
					{
						$perm = \CCrmLead::getStatusCreatePermissionType(
							$column['id'], $userPerms
						);
					}
					else
					{
						$perm = \CCrmDeal::getStageCreatePermissionType(
							$column['id'], $userPerms, (int)$this->categoryId
						);
					}
					if ($perm == BX_CRM_PERM_NONE)
					{
						$column['canAddItem'] = false;
					}
				}
				// win column
				if (
					!$params['originalColumns'] &&
					$status['PROGRESS_TYPE'] == 'WIN'
				)
				{
					$winColumn[$status['STATUS_ID']] = $column;
				}
				else
				{
					$columns[$status['STATUS_ID']] = $column;
				}
				// gets required fields for each column
				if (
					(
						$this->type == $types['lead'] ||
						$this->type == $types['deal']
					)
					&&
					FieldAttributeManager::isEnabled()
				)
				{
					if ($this->type == $types['lead'])
					{
						$requiredFields = FieldAttributeManager::getRequiredFields(
							\CCrmOwnerType::Lead,
							0,
							array(
								'STATUS_ID' => $status['STATUS_ID']
							)
						);
					}
					else
					{
						$requiredFields = FieldAttributeManager::getRequiredFields(
							\CCrmOwnerType::Deal,
							0,
							array(
								'STAGE_ID' => $status['STATUS_ID']
							)
						);
					}
					foreach ($requiredFields as $requiredFieldsBlock)
					{
						foreach ($requiredFieldsBlock as $key)
						{
							if (!isset($this->requiredFields[$key]))
							{
								$this->requiredFields[$key] = [];
							}
							$this->requiredFields[$key][] = $status['STATUS_ID'];
						}
					}
					foreach ($commonRequireds as $key)
					{
						if (!isset($this->requiredFields[$key]))
						{
							$this->requiredFields[$key] = [];
						}
						$this->requiredFields[$key][] = $status['STATUS_ID'];
					}
				}
			}

			$columns += $winColumn;
			$lastColumn = array_pop(array_values($columns));

			if (!isset($columns['DELETED']))
			{
				$columns['DELETED'] = $getDeleteCol([
					'real_sort' => $lastColumn['real_sort'] + 10,
					'sort' => $lastColumn['sort'] + 10
				]);
			}

			//get sums and counts

			$fieldSum = $this->fieldSum ? $this->fieldSum : 'OPPORTUNITY_ACCOUNT';

			if ($this->type == $types['invoice'])
			{
				$stats = array();
				$res = \CCrmInvoice::GetList(array(), $filter,
											array('STATUS_ID', 'SUM' => 'PRICE'), false,
											array('STATUS_ID', 'PRICE')
						);
				while ($row = $res->fetch())
				{
					$stats[] = $row;
				}
			}
			elseif ($this->type == $types['order'])
			{
				$queryParameters = [
					'filter' => $filter,
					'select' => [
						'STATUS_ID',
						new \Bitrix\Main\Entity\ExpressionField('SUM', 'SUM(%s)', 'PRICE'),
						new \Bitrix\Main\Entity\ExpressionField('CNT', 'COUNT(1)'),
					]
				];
				if (!empty($runtime))
				{
					$queryParameters['runtime'] = $runtime;
				}
				$stats = array();
				$res = Order\Order::getList($queryParameters);
				while ($row = $res->fetch())
				{
					$stats[] = $row;
				}
			}
			else
			{
				$provider = '\CCrm'.$this->type;
				if (class_exists($provider))
				{
					if (method_exists($provider, 'getListEx'))
					{
						$options = [];
						$select = [$statusCode];
						if (mb_substr($fieldSum, 0, 3) == 'UF_')
						{
							$options['FIELD_OPTIONS'] = [
								'UF_FIELDS' => [
									$fieldSum => [
										'FIELD' => $fieldSum,
										'TYPE' => 'double'
									]
								]
							];
						}
						else
						{
							$select[] = $fieldSum;
						}
						$res = $provider::GetListEx(array(), $filter,
													array($statusCode, 'SUM' => $fieldSum), false,
													$select,
													$options);
					}
					else
					{
						$res = $provider::GetList(array(), $filter,
													array($statusCode, 'SUM' => $fieldSum), false,
													array($statusCode, $fieldSum));
					}
					while ($row = $res->fetch())
					{
						$stats[] = $row;
					}
				}
			}

			if ($stats)
			{
				foreach ($stats as $stat)
				{
					if (isset($columns[$stat[$statusCode]]))
					{
						//fill column
						if ($this->type == $types['invoice'])
						{
							$columns[$stat[$statusCode]]['count'] = $stat['CNT'];
							$columns[$stat[$statusCode]]['total'] = $stat['PRICE'];
							$columns[$stat[$statusCode]]['total_format'] = \CCrmCurrency::MoneyToString(round($stat['PRICE']), $baseCurrency);
						}
						elseif ($this->type == $types['order'])
						{
							$columns[$stat[$statusCode]]['count'] = $stat['CNT'];
							$columns[$stat[$statusCode]]['total'] = $stat['SUM'];
							$columns[$stat[$statusCode]]['total_format'] = \CCrmCurrency::MoneyToString(round($stat['SUM']), $baseCurrency);
						}
						else
						{
							$columns[$stat[$statusCode]]['count'] = $stat['CNT'];
							$columns[$stat[$statusCode]]['total'] = $stat[$fieldSum];
							$columns[$stat[$statusCode]]['total_format'] = \CCrmCurrency::MoneyToString(round($stat[$fieldSum]), $baseCurrency);
						}
					}
				}
			}
		}

		// without static cache
		if ($withoutCache)
		{
			$tmpColumns = $columns;
			$columns = array();
			return $tmpColumns;
		}
		else
		{
			return $columns;
		}
	}

	/**
	 * Insert new key/value in the array after the key.
	 * @param array $array Array for change.
	 * @param mixed $afterKey Insert after this key. If null then the value insert in beginning of the array.
	 * @param mixed $newKey New key.
	 * @param mixed $newValue New value.
	 * @return array
	 */
	protected function arrayInsertAfter(array $array, $afterKey, $newKey, $newValue)
	{
		if (isset($array[$newKey]))
		{
			return $array;
		}
		if (empty($array))
		{
			return array($newKey => $newValue);
		}
		if ($afterKey === null)
		{
			return array($newKey => $newValue) + $array;
		}
		if ($afterKey === null || array_key_exists ($afterKey, $array))
		{
			$newArray = array();
			foreach ($array as $k => $value)
			{
				$newArray[$k] = $value;
				if ($k == $afterKey)
				{
					$newArray[$newKey] = $newValue;
				}
			}
			return $newArray;
		}
		else
		{
			return $array;
		}
	}

	/**
	 * Remember last id of entity for user.
	 * @param int|bool $lastId If set, save in config.
	 * @return void|int
	 */
	protected function rememberLastId($lastId = false)
	{
		$lastIds = \CUserOptions::getOption(
			'crm',
			'kanban_sort_last_id',
			array()
		);
		if ($lastId !== false)
		{
			$lastIds[$this->type] = $lastId;
			\CUserOptions::setOption(
				'crm',
				'kanban_sort_last_id',
				$lastIds
			);
		}
		else
		{
			return isset($lastIds[$this->type]) ? $lastIds[$this->type] : 0;
		}
	}

	/**
	 * Get last ID for current entity.
	 * @return int
	 */
	protected function getLastId()
	{
		static $lastId = null;

		if ($lastId !== null)
		{
			return $lastId;
		}

		$lastId = 0;
		$type = $this->type;
		$provider = '\CCrm'.$type;
		$method = method_exists($provider, 'getListEx') ? 'getListEx' : 'getList';

		if (method_exists($provider, 'getTopIDs'))
		{
			$lastId = $provider::getTopIDs(1, 'DESC');
			$lastId = !empty($lastId) ? array_shift($lastId) : 0;
		}
		else if (is_callable(array($provider, $method)))
		{
			$res = $provider::$method(
				array(
					'ID' => 'DESC'
				),
				array(
					//
				),
				false,
				array(
					'nTopCount' => 1
				),
				array(
					'ID'
				)
			);
			if ($row = $res->fetch())
			{
				$lastId = $row['ID'];
			}
		}

		return $lastId;
	}

	/**
	 * Sort items by user order.
	 * @param array $items Items for sort.
	 * @return array
	 */
	protected function sort(array $items)
	{
		static $lastId = null;
		static $lastIdremember = null;

		if ($lastIdremember === null)
		{
			$lastIdremember = $this->rememberLastId();
		}
		if ($lastId === null)
		{
			$lastId = $this->getLastId();
		}

		$sortedIds = array();
		$sort = \Bitrix\Crm\Kanban\SortTable::getPrevious(array(
			'ENTITY_TYPE_ID' => $this->type,
			'ENTITY_ID' => array_keys($items),
		));
		if (!empty($sort))
		{
			foreach ($sort as $id => $prev)
			{
				if ($prev>0 && !isset($items[$prev]))
				{
					continue;
				}
				if ($id == $prev)
				{
					continue;
				}
				$moveItem = $items[$id];
				unset($items[$id]);
				if ($prev == 0)
				{
					$prev = null;
				}
				$sortedIds[] = $id;
				$items = $this->arrayInsertAfter($items, $prev, $id, $moveItem);
			}

			// set all new items to the begin of array
			$newIds = array_reverse(array_diff(array_keys($items), $sortedIds));
			foreach ($newIds as $id)
			{
				if ($id > $lastIdremember)
				{
					$moveItem = $items[$id];
					unset($items[$id]);
					$items = $this->arrayInsertAfter($items, null, $id, $moveItem);
					\Bitrix\Crm\Kanban\SortTable::setPrevious(array(
						'ENTITY_TYPE_ID' => $this->type,
						'ENTITY_ID' => $id,
						'PREV_ENTITY_ID' => 0
					));
				}
			}
		}

		return $items;
	}

	/**
	 * Returns all traces by id.
	 * @param int|int[] $entityId Entity id, or ids.
	 * @return array
	 */
	protected function getTracesById($entityId)
	{
		static $actualSources = null;
		static $entityTypeId = null;
		static $channelNames = null;

		$entityId = (array) $entityId;
		$traces = [];
		$traceEntities = [];

		if ($entityTypeId === null)
		{
			$entityTypeId = \CCrmOwnerType::ResolveID($this->type);
		}
		if ($actualSources === null)
		{
			$actualSources = Tracking\Provider::getActualSources();
			$actualSources = array_combine(
				array_column($actualSources, 'ID'),
				array_values($actualSources)
			);
		}
		if ($channelNames === null)
		{
			$channelNames = Channel\Factory::getNames();
		}

		// get traces by entity
		$res = Tracking\Internals\TraceEntityTable::getList([
			'select' => [
				'ENTITY_ID', 'TRACE_ID'
			],
			'filter' => [
				'ENTITY_TYPE_ID' => $entityTypeId,
				'ENTITY_ID' => $entityId
			]
		]);
		while ($row = $res->fetch())
		{
			$traces[$row['ENTITY_ID']] = $row;
			$traceEntities[$row['TRACE_ID']] = [];
		}

		if (!$traceEntities)
		{
			return [];
		}

		// fill paths for traces
		$res = Tracking\Internals\TraceTable::getList([
			'select' => [
				'ID', 'SOURCE_ID'
			],
			'filter' => [
				'=ID' => array_keys($traceEntities)
			]
		]);
		while ($row = $res->fetch())
		{
			if (
				$row['SOURCE_ID'] &&
				isset($actualSources[$row['SOURCE_ID']])
			)
			{
				$source = $actualSources[$row['SOURCE_ID']];
				$traceEntities[$row['ID']] = [
					'NAME' => $source['NAME'],
					'DESC' => $source['DESCRIPTION'],
					'ICON' => $source['ICON_CLASS'],
					'ICON_COLOR' => $source['ICON_COLOR'],
					'IS_SOURCE' => true
				];
			}
		}

		// additional filling
		$res = TraceChannelTable::getList([
			'select' => [
				'TRACE_ID', 'CODE'
			],
			'filter' => [
				'TRACE_ID' => array_keys($traceEntities)
			]
		]);
		while ($row = $res->fetch())
		{
			$traceEntities[$row['TRACE_ID']] = [
				'NAME' => isset($channelNames[$row['CODE']])
						? $channelNames[$row['CODE']]
						: Channel\Base::getNameByCode($row['CODE']),
				'DESC' => '',
				'ICON' => '',
				'ICON_COLOR' => '',
				'IS_SOURCE' => true
			];
		}

		// fill entities by full path
		foreach ($traces as $id => $trace)
		{
			if (isset($traceEntities[$trace['TRACE_ID']]))
			{
				$traces[$id] = $traceEntities[$trace['TRACE_ID']];
			}
			else
			{
				unset($traces[$id]);
			}
		}

		return $traces;
	}

	/**
	 * Base method for getting data.
	 * @param array $filter Additional filter.
	 * @param boolean $pagen Out by paging.
	 * @return array
	 */
	protected function getItems(array $filter = array(), $pagen = true)
	{
		static $path = null;
		static $currency = null;
		static $columns = null;

		$result = array();
		$type = $this->type;
		$typeId = \CCrmOwnerType::ResolveID($type);
		$types = $this->types;
		$provider = '\CCrm'.$type;
		if ($type == $types['order'])
		{
			$provider = Order\Order::class;
		}
		$method = method_exists($provider, 'getListEx') ? 'getListEx' : 'getList';
		$runtime = [];
		$select = $this->getSelect();
		if ($type !== $types['order'])
		{
			$filterCommon = $this->getFilter();
		}
		else
		{
			$filterCommon = $this->getOrderFilter($runtime);
		}

		$addFields = $this->getAdditionalFields();
		$addSelect = array_keys($this->additionalSelect);
		$addTypes = $this->additionalTypes;
		$navParams = $pagen ? array('iNumPage' => $this->blockPage, 'nPageSize' => $this->blockSize) : false;
		$statusKey = $this->statusKey;
		//$userFieldDispatcher = \Bitrix\Main\UserField\Dispatcher::instance();

		if ($this->requiredFields)
		{
			$select = array_merge(
				$select,
				array_keys($this->requiredFields)
			);
		}

		// remove conflict keys and merge filters
		$statusKeyColumn = null;
		$filter = array_merge($filterCommon, $filter);

		if ($path === null)
		{
			$path = $this->getEntityPath($type);
		}
		if ($currency === null)
		{
			$currency = $this->currency;
		}
		if ($columns === null)
		{
			$columns = $this->getColumns();
		}
		if (class_exists($provider))
		{
			$queryParameters = [
				'filter' => $filter,
				'select' => $select,
				'order' => ['ID' => 'DESC'],
			];

			if (!empty($runtime))
			{
				$queryParameters['runtime'] = $runtime;
			}
			//user sorting
			if (
				isset($filter[$statusKey]) &&
				is_string($filter[$statusKey]) &&
				isset($columns[$filter[$statusKey]]['count']) &&
				$columns[$filter[$statusKey]]['count'] <= $this->maxSortSize
			)
			{
				//get all and sort
				$sorting = true;
				$db = array();
				if ($this->type !== $types['order'])
				{
					$res = $provider::$method(array('ID' => 'DESC'), $filter, false, false, $select);
				}
				else
				{
					$res = $provider::$method($queryParameters);
				}
				while ($row = $res->fetch())
				{
					$db[$row['ID']] = $row;
				}
				if ($this->type === $types['order'] && !empty($db))
				{
					$db = $this->prepareOrderEntitiesFields($db);
				}
				$db = $this->sort($db);
				//init query
				$res = new CDBResult;
				$res->initFromArray($db);
				$res->navStart($this->blockSize, false, $this->blockPage);
			}
			else
			{
				$sorting = false;
				if ($this->type !== $types['order'])
				{
					$res = $provider::$method(
						['ID' => 'DESC'],
						$filter,
						false,
						false,
						$select,
						$pagen
							? [
							'QUERY_OPTIONS' => [
								'LIMIT' => $this->blockSize,
								'OFFSET' => $this->blockSize * ($this->blockPage - 1)
							]
						]
							: [

						]
					);
				}
				else
				{
					$orders = [];
					$queryParameters['limit'] = $this->blockSize;
					$queryParameters['offset'] = ($this->blockPage - 1) * $this->blockSize;
					$res = $provider::$method($queryParameters);
					while ($order = $res->fetch())
					{
						$orders[$order['ID']] = $order;
					}
					if (!empty($orders))
					{
						$orders = $this->prepareOrderEntitiesFields($orders);
					}
					$res = new CDBResult;
					$res->initFromArray($orders);
				}
			}
			$timeOffset = \CTimeZone::GetOffset();
			$timeFull = time() + $timeOffset;
			$pageCount = $res->NavPageCount;
			$specialReqKeys = [
				'OPPORTUNITY' => 'OPPORTUNITY_WITH_CURRENCY',
				'CONTACT_ID' => 'CLIENT'
			];
			$users = [];
			$fileIds = [];
			$crmEntities = [];
			$iblockSects = array();
			$iblockElements = array();
			while ($row = $res->fetch())
			{
				$row['FORMAT_TIME'] = true;
				//base
				if (!isset($row['ASSIGNED_BY']))
				{
					$row['ASSIGNED_BY'] = $row['RESPONSIBLE_ID'];
				}
				if ($type == $types['lead'])
				{
					$row['PRICE'] = $row['OPPORTUNITY'];
					$row['DATE'] = $row['DATE_CREATE'];
				}
				elseif ($type == $types['deal'])
				{
					$row['PRICE'] = $row['OPPORTUNITY'];
					$row['DATE'] = $row['DATE_CREATE'];
				}
				elseif ($type == $types['quote'])
				{
					$row['PRICE'] = $row['OPPORTUNITY'];
					if ($row['BEGINDATE'])
					{
						$row['FORMAT_TIME'] = false;
						$row['DATE'] = $row['BEGINDATE'];
					}
					else
					{
						$row['DATE'] = $row['DATE_CREATE'];
					}
				}
				elseif ($type == $types['invoice'])
				{
					$row['TITLE'] = $row['ORDER_TOPIC'];
					$row['FORMAT_TIME'] = false;
					$row['DATE'] = $row['PAY_VOUCHER_DATE'] ? $row['PAY_VOUCHER_DATE'] : $row['DATE_BILL'];
					$row['CONTACT_ID'] = $row['UF_CONTACT_ID'];
					$row['COMPANY_ID'] = $row['UF_COMPANY_ID'];
					$row['CURRENCY_ID'] = $row['CURRENCY'];
				}
				elseif ($type == $types['order'])
				{
					$row['FORMAT_TIME'] = false;
					$row['DATE'] = $row['DATE_INSERT'];
				}
				//redefine price
				if ($this->fieldSum && array_key_exists($this->fieldSum, $row))
				{
					$row['PRICE'] = $row[$this->fieldSum];
				}
				elseif (isset($row['OPPORTUNITY_ACCOUNT']) && $row['OPPORTUNITY_ACCOUNT']!='')
				{
					$row['PRICE'] = $row['OPPORTUNITY_ACCOUNT'];
				}
				if (isset($row['ACCOUNT_CURRENCY_ID']) && $row['ACCOUNT_CURRENCY_ID']!='')
				{
					$row['CURRENCY_ID'] = $row['ACCOUNT_CURRENCY_ID'];
				}

				//contragent
				if ($row['CONTACT_ID'] > 0)
				{
					$row['CONTACT_TYPE'] = 'CRM_CONTACT';
					$this->contact[$row['ID']] = $row['CONTACT_ID'];
				}

				//company
				if ($row['COMPANY_ID'] > 0)
				{
					$row['CONTACT_TYPE'] = ($row['CONTACT_TYPE'] ?? 'CRM_COMPANY');
					$row['CONTACT_ID'] = ($row['CONTACT_ID'] ?? $row['COMPANY_ID']);
					$this->company[$row['ID']] = $row['COMPANY_ID'];
				}

				$row['CONTACT_TYPE'] = ($row['CONTACT_TYPE'] ?? '');

				//price converted
				if ($row['CURRENCY_ID']=='' || $row['CURRENCY_ID'] == $currency)
				{
					$row['PRICE'] = doubleval($row['PRICE']);
					$row['PRICE_FORMATTED'] = \CCrmCurrency::MoneyToString($row['PRICE'], $currency);
				}
				else
				{
					$row['PRICE'] = \CCrmCurrency::ConvertMoney($row['PRICE'], $row['CURRENCY_ID'], $currency);
					$row['PRICE_FORMATTED'] = \CCrmCurrency::MoneyToString($row['PRICE'], $currency);
				}
				if ($row['DATE'] instanceof \Bitrix\Main\Type\Date)
				{
					$row['DATE_UNIX'] = $row['DATE']->getTimestamp();
				}
				else
				{
					$row['DATE_UNIX'] = \MakeTimeStamp($row['DATE']);
				}
				$dateFormat = $this->dateFormats[date('Y') == date('Y', $row['DATE_UNIX']) ? 'short' : 'full'][$this->formatLang];
				//additional fields
				$fields = array();
				foreach ($addSelect as $code)
				{
					if (array_key_exists($code, $row) && array_key_exists($code, $addFields))
					{
						if ($code == 'ASSIGNED_BY_ID' && isset($row['ASSIGNED_BY']))
						{
							$users[] = $row['ASSIGNED_BY'];
						}
						elseif ($code == 'RESPONSIBLE_ID' && isset($row['RESPONSIBLE_ID']))
						{
							$users[] = $row['RESPONSIBLE_ID'];
						}
						elseif ($code == 'USER' && isset($row['USER_ID']))
						{
							$users[] = $row['USER_ID'];
						}
						if (isset($addTypes[$code]) && isset($addTypes[$code][$row[$code]]))
						{
							$row[$code] = $addTypes[$code][$row[$code]];
						}
						if (
							!empty($row[$code]) ||
							($code == 'OPPORTUNITY')
						)
						{
							$isHtml = false;
							$boolFieldNames = ['CLOSED', 'PAYED', 'ALLOY_DELIVERY', 'DEDUCTED', 'CANCELED'];
							if (in_array($code, $boolFieldNames))
							{
								$row[$code] = Loc::getMessage('CRM_KANBAN_CHAR_' . $row[$code]);
							}
							else if ($code == 'OPPORTUNITY')
							{
								$row[$code] = \CCrmCurrency::MoneyToString(
									$row[$code],
									$row['CURRENCY_ID']
								);
							}
							else if ($code == 'PAYMENT' || $code == 'SHIPMENT')
							{
								$fieldValue = "";
								$row[$code] = is_array($row[$code]) ? $row[$code] : [$row[$code]];
								foreach ($row[$code] as $rowCodeItem)
								{
									if ($code == 'PAYMENT')
									{
										$pathSubItem = CComponentEngine::MakePathFromTemplate(
											$this->arParams['PATH_TO_ORDER_PAYMENT_DETAILS'],
											array(
												'payment_id' => $rowCodeItem['ID']
											)
										);
									}
									else
									{
										$pathSubItem = CComponentEngine::MakePathFromTemplate(
											$this->arParams['PATH_TO_ORDER_SHIPMENT_DETAILS'],
											array(
												'shipment_id' => $rowCodeItem['ID']
											)
										);
									}

									$price = ($code == 'PAYMENT') ? $rowCodeItem['SUM'] : $rowCodeItem['PRICE_DELIVERY'];
									$sum = \CCrmCurrency::MoneyToString(
										$price,
										$rowCodeItem['CURRENCY']
									);

									$title = '';

									$paySystemName =  ($code == 'PAYMENT') ? $rowCodeItem['PAY_SYSTEM_NAME'] : $rowCodeItem['DELIVERY_NAME'];
									$paySystemName = htmlspecialcharsbx($paySystemName);
									if (!empty($paySystemName))
									{
										$title .= $paySystemName. " ";
									}

									if (!empty($sum))
									{
										$title .= "({$sum})";
									}

									if (empty($title))
									{
										$title = htmlspecialcharsbx($rowCodeItem['ACCOUNT_NUMBER']);
									}

									$fieldValue .= "<a href='{$pathSubItem}'>{$title}</a></br>";
								}
								$row[$code] = $fieldValue;
							}
							else if ($code == 'ORDER_STAGE')
							{
								$orderStages = Order\OrderStage::getList();
								if (isset($orderStages[$row[$code]]))
								{
									$row[$code] = [
										'title' => $orderStages[$row[$code]],
										'code' => $row[$code]
									];
								}
								else
								{
									$row[$code] = [
										'title' => $row[$code],
										'code' => $row[$code]
									];
									continue;
								}
							}
							else if (
								$addFields[$code]['type'] == 'money'
							)
							{
								$moneyRow = is_array($row[$code])
											? $row[$code]
											: [$row[$code]];
								$row[$code] = [];
								foreach ($moneyRow as $moneyItem)
								{
									[$money, $moneyCurr] = explode('|', $moneyItem);
									$row[$code][] = \CCrmCurrency::MoneyToString(
										$money,
										$moneyCurr
									);
								}
								$row[$code] = implode('; ', $row[$code]);
							}
							else if (
								$addFields[$code]['type'] == 'employee'
							)
							{
								$this->usersIdFields[] = $code;
							}
							else if (
								$addFields[$code]['type'] == 'boolean'
							)
							{
								$row[$code] = Loc::getMessage('CRM_KANBAN_BOOLEAN_' . (int)$row[$code]);
							}
							else if (
								$addFields[$code]['type'] == 'enumeration'
							)
							{
								$row[$code] = implode(',', array_intersect_key(
									$addFields[$code]['enumerations'],
									array_flip((array)$row[$code])
								));
							}
							else if (
								$addFields[$code]['type'] == 'date' ||
								mb_strpos($code, 'DATE') !== false
							)
							{
								$row[$code] = (array) $row[$code];
								foreach ($row[$code] as &$rowDate)
								{
									if ($rowDate instanceof \Bitrix\Main\Type\Date || $rowDate instanceof \DateTime)
									{
										$timestamp = $rowDate->getTimestamp();
									}
									else
									{
										$timestamp = \MakeTimeStamp($rowDate);
									}
									$rowDate = \FormatDate($this->dateFormats['full'], $timestamp, $timeFull);
								}
								unset($rowDate);
								$row[$code] = implode(',', $row[$code]);
							}
							else if ($addFields[$code]['type'] == 'file')
							{
								$row[$code] = (array) $row[$code];
								$fileIds = array_merge(
									$fileIds,
									$row[$code]
								);
							}
							else if ($addFields[$code]['type'] == 'crm_status')
							{
								$entityType = isset($addFields[$code]['settings']['ENTITY_TYPE'])
											? $addFields[$code]['settings']['ENTITY_TYPE']
											: null;
								$statuses = $this->getStatusesDB($entityType);
								if (isset($statuses[$row[$code]]))
								{
									$row[$code] = $statuses[$row[$code]];
								}
								else
								{
									continue;
								}
							}
							else if ($addFields[$code]['type'] == 'crm')
							{
								$row[$code] = (array) $row[$code];
								$settings = isset($addFields[$code]['settings'])
											? (array) $addFields[$code]['settings']
											: [];
								$settingsFlip = array_flip($settings);
								$newRow = [];
								foreach ($row[$code] as $crmEntity)
								{
									if (!mb_strpos($crmEntity, '_'))
									{
										if (isset($settingsFlip['Y']))
										{
											$crmEntity = $this->makeEntityLetter($settingsFlip['Y']) . '_' . $crmEntity;
										}
									}
									$newRow[] = $crmEntity;
									if(mb_strpos($crmEntity, '_'))
									{
										[$crmEntityType, $crmEntity] = explode('_', $crmEntity);
									}
									else
									{
										continue;
									}
									if (!isset($crmEntities[$crmEntityType]))
									{
										$crmEntities[$crmEntityType] = [];
									}
									$crmEntities[$crmEntityType][] = $crmEntity;
								}
								$row[$code] = $newRow;
							}
							elseif ($addFields[$code]['type'] == 'iblock_section')
							{
								$row[$code] = (array) $row[$code];
								$iblockSects = array_merge(
									$iblockSects,
									$row[$code]
								);
							}
							elseif ($addFields[$code]['type'] == 'iblock_element')
							{
								$row[$code] = (array) $row[$code];
								$iblockElements = array_merge(
									$iblockElements,
									$row[$code]
								);
							}
							elseif ($addFields[$code]['type'] == 'address')
							{
								$row[$code] = (array) $row[$code];
								foreach ($row[$code] as &$location)
								{
									if (mb_strpos($location, '|') !== false)
									{
										[$location, ] = explode('|', $location);
									}
								}
								unset($location);
							}
							elseif ($addFields[$code]['type'] == 'url')
							{
								$row[$code] = (array) $row[$code];
								foreach ($row[$code] as &$url)
								{
									$url = \htmlspecialcharsbx($url);
									$url = '<a href="' . $url . '" target="_blank">' . $url . '</a>';
								}
								unset($url);
								$isHtml = true;
							}
							$fields[] = array(
								'code' => $code,
								'title' => $addFields[$code]['title'],
								'type' => $addFields[$code]['type'],
								'value' => $row[$code],
								'html' => $isHtml || in_array($code, ['COMMENTS', 'PAYMENT', 'SHIPMENT'])
							);
							if (in_array($code, $this->usersIdFields))
							{
								$users = array_merge(
									$users,
									(array)$row[$code]
								);
							}
						}
					}
				}
				$returnCustomer = isset($row['IS_RETURN_CUSTOMER']) && $row['IS_RETURN_CUSTOMER'] == 'Y';
				// collect required
				$required = [];
				$requiredFm = [];
				if ($this->requiredFields)
				{
					// fm fields check later
					foreach ($this->allowedFMtypes as $fm)
					{
						$fmUp = mb_strtoupper($fm);
						$requiredFm[$fmUp] = true;
						$row[$fmUp] = '';
					}
					// check each key
					foreach ($row as $key => $val)
					{
						if (
							$returnCustomer &&
							isset($this->exclusiveFieldsReturnCustomer[$key])
						)
						{
							continue;
						}
						if (isset($this->requiredFields[$key]) && !$val)
						{
							foreach ($this->requiredFields[$key] as $status)
							{
								if (!isset($required[$status]))
								{
									$required[$status] = [];
								}
								$required[$status][] = $key;
							}
						}
					}
					// special keys
					foreach ($specialReqKeys as $reqKeyOrig => $reqKey)
					{
						if (
							isset($this->requiredFields[$reqKey]) &&
							(
								!$row[$reqKeyOrig]
								||
								$reqKeyOrig == 'OPPORTUNITY' &&
								$row['OPPORTUNITY_ACCOUNT'] <= 0
							)
						)
						{
							foreach ($this->requiredFields[$reqKey] as $status)
							{
								if (!isset($required[$status]))
								{
									$required[$status] = [];
								}
								$required[$status][] = $reqKey;
							}
						}
					}
				}
				//add
				$result[$row['ID']] = array(
					'id' =>  $row['ID'],
					'name' => htmlspecialcharsbx($row['TITLE'] != '' ? $row['TITLE'] : '#' . $row['ID']),
					'link' => str_replace($this->pathMarkers, $row['ID'], $path),
					'columnId' => $columnId = htmlspecialcharsbx($row[$this->statusKey]),
					'columnColor' => isset($columns[$columnId]) ? $columns[$columnId]['color'] : '',
					'price' => $row['PRICE'],
					'price_formatted' => $row['PRICE_FORMATTED'],
					'date' => (
								!$row['FORMAT_TIME']
								? \FormatDate($dateFormat, $row['DATE_UNIX'], $timeFull)
								: (
									(time() - $row['DATE_UNIX']) / 3600 > 48
									? \FormatDate($dateFormat, $row['DATE_UNIX'], $timeFull)
									: \FormatDate('x', $row['DATE_UNIX'], $timeFull)
								)
							),
					'contactId' => (int)$row['CONTACT_ID'],
					'companyId' => (!empty($row['COMPANY_ID']) ? (int)$row['COMPANY_ID'] : null),
					'contactType' => $row['CONTACT_TYPE'],
					'modifyById' => isset($row['MODIFY_BY_ID']) ? $row['MODIFY_BY_ID'] : 0,
					'modifyByAvatar' => '',
					'activityShow' => 1,
					'activityErrorTotal' => 0,
					'activityProgress' => 0,
					'activityTotal' => 0,
					'page' => $this->blockPage,
					'pageCount' => $pageCount,
					'fields' => $fields,
					'return' => $returnCustomer,
					'returnApproach' => isset($row['IS_REPEATED_APPROACH']) && $row['IS_REPEATED_APPROACH'] == 'Y',
					'assignedBy' => $row['ASSIGNED_BY'],
					'required' => $required,
					'required_fm' => $requiredFm
				);
			}
			// get iblock sections
			if (
				$iblockSects &&
				Loader::includeModule('iblock')
			)
			{
				$res = \Bitrix\Iblock\SectionTable::getList([
					'select' => [
						'ID', 'NAME'
					],
					'filter' => [
						'ID' => $iblockSects
					]
				]);
				$iblockSects = [];
				while ($row = $res->fetch())
				{
					$iblockSects[$row['ID']] = $row['NAME'];
				}
				unset($res, $row);
			}
			// get iblock elements
			if (
				$iblockElements &&
				Loader::includeModule('iblock')
			)
			{
				$res = \Bitrix\Iblock\ElementTable::getList([
					'select' => [
						'ID', 'NAME'
					],
					'filter' => [
						'ID' => $iblockElements
					]
				]);
				$iblockElements = [];
				while ($row = $res->fetch())
				{
					$iblockElements[$row['ID']] = $row['NAME'];
				}
				unset($res, $row);
			}
			// get crm entities
			if ($crmEntities)
			{
				foreach ($crmEntities as $entityType => &$entityIds)
				{
					$entityType = $this->resolveEntityLetter($entityType);
					$class = '\Bitrix\Crm\\' . $entityType . 'Table';
					if (!$entityType || !class_exists($class))
					{
						continue;
					}
					if ($entityType == 'contact')
					{
						$select = ['ID', 'NAME', 'LAST_NAME'];
					}
					else
					{
						$select = ['ID', 'TITLE'];
					}
					$entityPath = $this->getEntityPath($entityType);
					$res = $class::getList([
						'select' => $select,
						'filter' => [
							'ID' => $entityIds
						]
					]);
					$entityIds = [];
					while ($row = $res->fetch())
					{
						$id = $row['ID'];
						unset($row['ID']);
						$entityIds[$id] = \htmlspecialcharsbx(implode(' ', $row));
						$entityPath = str_replace('#' . $entityType . '_id#', $id, $entityPath);
						$entityIds[$id] = '<a href="' . $entityPath . '">' . $entityIds[$id] . '</a>';
					}
					unset($entityPath, $res, $row, $id);
				}
				unset($entityType, $entityIds, $class);
			}
			// get observers
			$observers = [];
			if (in_array('OBSERVER', $select))
			{
				$res = ObserverTable::getList([
					'select' => [
						'USER_ID', 'ENTITY_ID'
					],
					'filter' => [
						'=ENTITY_TYPE_ID' => $typeId,
						'=ENTITY_ID' => array_keys($result)
					],
					'order' => [
						'SORT' => 'ASC'
					]
				]);
				while ($row = $res->fetch())
				{
					$users[] = $row['USER_ID'];
					if (!isset($observers[$row['ENTITY_ID']]))
					{
						$observers[$row['ENTITY_ID']] = [];
					}
					$observers[$row['ENTITY_ID']][] = $row['USER_ID'];
				}
				unset($res, $row);
			}
			// get all users in common
			if ($users)
			{
				$res = \Bitrix\Main\UserTable::getList(array(
					'select' => array(
						'ID', 'NAME', 'LAST_NAME', 'PERSONAL_PHOTO'
					),
					'filter' => array(
						'ID' => array_unique($users)
					)
				));
				$users = [];
				while ($row = $res->fetch())
				{
					if ($row['PERSONAL_PHOTO'])
					{
						$row['PERSONAL_PHOTO'] = \CFile::ResizeImageGet(
							$row['PERSONAL_PHOTO'],
							$this->avatarSize,
							BX_RESIZE_IMAGE_EXACT
						);
						if ($row['PERSONAL_PHOTO'])
						{
							$row['PERSONAL_PHOTO'] = $row['PERSONAL_PHOTO']['src'];
						}
					}
					$buyerLink = '';
					$link = str_replace(
						'#user_id#',
						$row['ID'],
						$this->arParams['PATH_TO_USER']
					);
					if (isset($this->arParams['PATH_TO_BUYER_PROFILE']))
					{
						$buyerLink = str_replace(
							'#user_id#',
							$row['ID'],
							$this->arParams['PATH_TO_BUYER_PROFILE']
						);
					}
					$users[$row['ID']] = [
						'id' => $row['ID'],
						'title' => \htmlspecialcharsbx(\CUser::FormatName(
							$this->arParams['~NAME_TEMPLATE'],
							$row, true, false
						)),
						'picture' => $row['PERSONAL_PHOTO'],
						'link' => $link
					];
					// tmp
					$userTitle = $users[$row['ID']]['title'];
					$users[$row['ID']]['html'] = '<a href="' . $link . '">' . $userTitle . '</a>';
					if (!empty($buyerLink))
					{
						$users[$row['ID']]['buyerHtml'] = '<a href="' . $buyerLink . '">' . $userTitle . '</a>';
					}
				}
				unset($res, $row);
			}
			// get file's infos
			if ($fileIds)
			{
				$fileViewer = new \Bitrix\Crm\UserField\FileViewer($typeId);
				$fileIds = array_unique($fileIds);
				$res = \Bitrix\Main\FileTable::getList([
					'select' => [
						'ID', 'ORIGINAL_NAME'
					],
					'filter' => [
						'ID' => $fileIds
					]
				]);
				$fileIds = [];
				while ($row = $res->fetch())
				{
					$fileIds[$row['ID']] = \htmlspecialcharsbx($row['ORIGINAL_NAME']);
				}
				unset($res, $row);
			}
			// tracking analytic
			$traces = [];
			if ($result && in_array('TRACKING_SOURCE_ID', $select))
			{
				$traces = $this->getTracesById(array_keys($result));
			}
			// refill result array, if we have any new data
			if ($users || $fileIds || $iblockSects || $iblockElements || $crmEntities || $traces)
			{
				foreach ($result as $id => &$item)
				{
					// add observers in each items
					if ($observers)
					{
						if (isset($observers[$id]))
						{
							foreach ($observers[$id] as &$obsId)
							{
								if (isset($users[$obsId]))
								{
									// tmp, @todo: make more universal
									$obsId = $users[$obsId]['html'];
								}
							}
							unset($obsId);
							$item['fields'][] = array(
								'code' => 'OBSERVER',
								'title' => Loc::getMessage('CRM_KANBAN_FIELD'),
								'value' => implode('; ', $observers[$id]),
								'html' => true
							);
						}
					}
					// add other users
					foreach ($item['fields'] as &$field)
					{
						if (in_array($field['code'], $this->usersIdFields))
						{
							$newValue = [];
							foreach ((array)$field['value'] as $uid)
							{
								if (isset($users[$uid]))
								{
									// tmp, @todo: make more universal
									if ($field['code'] == 'ASSIGNED_BY_ID' || $field['code'] == 'RESPONSIBLE_ID')
									{
										$newValue[] = $users[$uid];
									}
									elseif ($type == $types['order'] && $field['code'] == 'USER')
									{
										$newValue[] = $users[$uid]['buyerHtml'];
									}
									else
									{
										$newValue[] = $users[$uid]['html'];
									}
								}
							}
							// tmp, @todo: make more universal
							if ($field['code'] == 'ASSIGNED_BY_ID' || $field['code'] == 'RESPONSIBLE_ID' || $field['code'] == 'USER')
							{
								$field['value'] = array_shift($newValue);
							}
							else
							{
								$field['value'] = implode('; ', $newValue);
							}
							$field['html'] = true;
						}
					}
					unset($field);
					// add files' infos
					if ($fileIds)
					{
						foreach ($item['fields'] as &$field)
						{
							if ($field['type'] == 'file')
							{
								foreach ($field['value'] as &$fid)
								{
									if (isset($fileIds[$fid]))
									{
										$fid = '<a href="' . $fileViewer->getUrl($id, $field['code'], $fid) . '">' .
											   		$fileIds[$fid] .
												'</a>';
										$field['html'] = true;
									}
								}
								unset($fid);
								$field['value'] = implode('; ', $field['value']);
							}
						}
						unset($field);
					}
					// add iblock sections
					if ($iblockSects)
					{
						foreach ($item['fields'] as &$field)
						{
							if ($field['type'] == 'iblock_section')
							{
								foreach ($field['value'] as &$section)
								{
									if (isset($iblockSects[$section]))
									{
										$section = $iblockSects[$section];
									}
								}
								unset($section);
								$field['value'] = implode('; ', $field['value']);
							}
						}
						unset($field);
					}
					// add iblock elements
					if ($iblockElements)
					{
						foreach ($item['fields'] as &$field)
						{
							if ($field['type'] == 'iblock_element')
							{
								foreach ($field['value'] as &$element)
								{
									if (isset($iblockElements[$element]))
									{
										$element = $iblockElements[$element];
									}
								}
								unset($element);
								$field['value'] = implode('; ', $field['value']);
							}
						}
						unset($field);
					}
					// crm entities
					if ($crmEntities)
					{
						foreach ($item['fields'] as &$field)
						{
							if ($field['type'] == 'crm')
							{
								foreach ($field['value'] as &$entity)
								{
									[$entityType, $entityId] = explode('_', $entity);
									if (isset($crmEntities[$entityType][$entityId]))
									{
										$entity = $crmEntities[$entityType][$entityId];
									}
									unset($entityId, $entityType);
								}
								unset($entity);
								$field['value'] = implode('; ', $field['value']);
								$field['html'] = true;
							}
						}
						unset($field);
					}
					// tracking system
					if (isset($traces[$id]))
					{
						$item['fields'][] = array(
							'code' => 'TRACKING_SOURCE_ID',
							'title' => Loc::getMessage('CRM_KANBAN_TRACKING_SOURCE_ID'),
							'value' => $traces[$id]['NAME']
						);
					}
				}
				unset($item);
			}
			unset(
				$assignedIds, $users, $observers, $fileIds,
				$iblockSects, $iblockElements
			);
			if (!$sorting)
			{
				$result = $this->sort($result);
			}
		}

		return $result;
	}

	/**
	 * Get all activities by id's and type of entity.
	 * @param array $activity Id's.
	 * @param array $errors Count of errors (deadline).
	 * @return array
	 */
	protected function getActivityCounters($activity, &$errors = array())
	{
		if (empty($activity))
		{
			return array();
		}

		$return = array();
		$activity = array_unique($activity);

		//make filter
		$filter = array(
			'BINDINGS' => array()
		);
		$typeId = \CCrmOwnerType::ResolveID($this->type);
		foreach ($activity as $id)
		{
			$filter['BINDINGS'][] = array(
				'OWNER_ID' => $id,
				'OWNER_TYPE_ID' => $typeId
			);
		}

		// counters
		$date = new \Bitrix\Main\Type\DateTime;
		$date->add('-'.date('G').' hours')->add('-'.date('i').' minutes')->add('+1 day');//@ #81166
		$res = \CCrmActivity::GetList(
			array(),
			array_merge(
				$filter,
				array(
					'=COMPLETED' => 'N',
					'<=DEADLINE' => $date
				)
			),
			false, false,
			array(
				'ID', 'OWNER_ID'
			)
		);
		$fetched = array();
		while ($row = $res->fetch())
		{
			if (!isset($fetched[$row['ID']]))
			{
				$fetched[$row['ID']] = true;
				if (!isset($errors[$row['OWNER_ID']]))
				{
					$errors[$row['OWNER_ID']] = 0;
				}
				$errors[$row['OWNER_ID']]++;
			}
		}

		// gets multi bindings
		$multi = [];
		$res = \Bitrix\Crm\ActivityBindingTable::getList([
			'filter' => [
				'OWNER_ID' => $activity,
				'OWNER_TYPE_ID' => $typeId
			]
		]);
		while ($row = $res->fetch())
		{
			if (!isset($multi[$row['ACTIVITY_ID']]))
			{
				$multi[$row['ACTIVITY_ID']] = [];
			}
			$multi[$row['ACTIVITY_ID']][] = $row['OWNER_ID'];
		}

		// get base activity
		$res = \CCrmActivity::GetList(
			array(),
			$filter,
			false,
			false,
			array(
				'ID', 'COMPLETED', 'OWNER_ID'
			)
		);
		while ($row = $res->fetch())
		{
			if (!isset($return[$row['OWNER_ID']]))
			{
				$return[$row['OWNER_ID']] = array();
			}
			if (!isset($return[$row['OWNER_ID']][$row['COMPLETED']]))
			{
				$return[$row['OWNER_ID']][$row['COMPLETED']] = 0;
			}
			$return[$row['OWNER_ID']][$row['COMPLETED']]++;
			// multi
			if (isset($multi[$row['ID']]))
			{
				foreach ($multi[$row['ID']] as $ownerId)
				{
					if (!isset($return[$ownerId]))
					{
						$return[$ownerId] = [];
					}
					if (!isset($return[$ownerId][$row['COMPLETED']]))
					{
						$return[$ownerId][$row['COMPLETED']] = 0;
					}
					$return[$ownerId][$row['COMPLETED']]++;
				}
			}
		}

		// get waits
		$waits = \Bitrix\Crm\Pseudoactivity\WaitEntry::getRecentIDsByOwner(
			$typeId, $activity
		);
		if ($waits)
		{
			foreach ($waits as $row)
			{
				if (!isset($return[$row['OWNER_ID']]['N']))
				{
					$return[$row['OWNER_ID']]['N'] = 0;
				}
				$return[$row['OWNER_ID']]['N']++;
			}
		}

		return $return;
	}

	/**
	 * Get additional fields for more-button.
	 * @param bool $clearCache Clear static cache.
	 * @return array
	 */
	protected function getAdditionalFields($clearCache = false)
	{
		static $additional = null;

		if ($clearCache)
		{
			$additional = null;
		}

		if ($additional === null)
		{
			$additional = array();
			$ufExist = false;
			$exist = $this->additionalSelect;

			//base fields
			foreach ($this->additionalSelect as $key => $title)
			{
				if (mb_strpos($key, 'UF_') === 0)
				{
					$ufExist = true;
				}
				else
				{
					$additional[$key] = array(
						'title' => \htmlspecialcharsbx($title),
						'type' => 'string',
						'code' => $key
					);
				}
			}
			unset($key, $title);

			//user fields
			if ($ufExist)
			{
				$enumerations = array();
				foreach ($this->userFields as $row)
				{
					if (isset($this->additionalSelect[$row['FIELD_NAME']]))
					{
						$additional[$row['FIELD_NAME']] = array(
							'title' => htmlspecialcharsbx($row['EDIT_FORM_LABEL']),
							'new' => !in_array($row['FIELD_NAME'], $exist) ? 1 : 0,
							'type' => $row['USER_TYPE_ID'],
							'code' => $row['FIELD_NAME'],
							'settings' => $row['SETTINGS'],
							'enumerations' => [],
						);
						if ($row['USER_TYPE_ID'] == 'enumeration')
						{
							$enumerations[$row['ID']] = $row['FIELD_NAME'];
						}
					}
				}
				unset($row);

				if (!empty($enumerations))
				{
					$enumUF = new CUserFieldEnum;
					$resEnum = $enumUF->getList(
						array(),
						array(
							'USER_FIELD_ID' => array_keys($enumerations)
						)
					);
					while ($rowEnum = $resEnum->fetch())
					{
						$additional[$enumerations[$rowEnum['USER_FIELD_ID']]]['enumerations'][$rowEnum['ID']] = $rowEnum['VALUE'];
					}
					unset($enumerations, $enumUF, $resEnum, $rowEnum);
				}
			}
		}

		return $additional;
	}

	/**
	 * Get additional fields for quick form.
	 * @return array
	 */
	protected function getAdditionalEditFields()
	{
		return $this->additionalEdit;
	}

	/**
	 * Gets sections for fields popup.
	 * @param array $config External config.
	 * @return array
	 */
	protected function getFieldsSections($config)
	{
		$sections = [];
		if (is_array($config) && !empty($config))
		{
			foreach ($config as $configSection)
			{
				if (
					isset($configSection['elements']) &&
					is_array($configSection['elements'])
				)
				{
					$tmpItems = $configSection['elements'];
					$configSection['elements'] = [];
					foreach ($tmpItems as $item)
					{
						if ($item['name'] == 'OPPORTUNITY_WITH_CURRENCY')
						{
							$configSection['elements']['OPPORTUNITY'] = [
								'name' => 'OPPORTUNITY',
								'title' => ''
							];
						}
						$configSection['elements'][$item['name']] = [
							'name' => $item['name'],
							'title' => $item['title']
						];
					}

					if (in_array($configSection['name'], ['main', 'additional', 'properties']))
					{
						if ($configSection['name'] == 'additional')
						{
							$configSection['elements'] = '*';
						}
						$sections[] = $configSection;
					}
				}
			}
		}

		return $sections;
	}

	/**
	 * Gets current user perms.
	 * @return CCrmPerms
	 */
	protected function getCurrentUserPermissions()
	{
		static $userPerms = null;
		if ($userPerms === null)
		{
			$userPerms = \CCrmPerms::getCurrentUserPermissions();
		}
		return $userPerms;
	}

	/**
	 * Make some actions (set, update, etc).
	 * @return void
	 */
	protected function makeAction()
	{
		$context = Application::getInstance()->getContext();
		$request = $context->getRequest();
		$action = $request->get('action');
		$id = $request->get('entity_id');

		// some actions for editor
		if ($action == 'get' && check_bitrix_sessid())
		{
			$ajaxParams = (array)$request->get('ajaxParams');
			if (isset($ajaxParams['editorReset']))
			{
				$this->resetCardFields();
			}
			if (isset($ajaxParams['editorSetCommon']))
			{
				$this->setCommonCardFields();
			}
		}

		//update fields
		if ($action && $id && check_bitrix_sessid())
		{
			$statuses = $this->getStatuses();
			$status = $request->get('status');
			$ids = is_array($id) ? $id : [$id];
			// skip action delete
			if ($action == 'status' && $status == 'DELETED')
			{
				$ids = [];
			}

			foreach ($ids as $id)
			{
				//delete
				if ($action == 'status' && $status == 'DELETED')
				{
					$this->actionDelete($id);
				}
				//change status / stage
				if ($action == 'status' && isset($statuses[$status]))
				{
					$result = $this->actionUpdateEntityStatus($id, $status, $statuses);
					if (!$result->isSuccess())
					{
						foreach ($result->getErrorMessages() as $errorMessage)
						{
							$this->setError($errorMessage);
						}
					}
				}
			}

			if ($this->arParams['IS_AJAX'] != 'Y')
			{
				$uri = new \Bitrix\Main\Web\Uri($request->getRequestUri());
				\LocalRedirect($uri->deleteParams(array('action', 'entity_id', 'status'))->getUri());
			}
		}

		//subscribe / unsunbscribe
		$supervisor = $request->get('supervisor');
		if ($request->get('apply_filter') == 'Y')
		{
			if (\Bitrix\Crm\Kanban\SupervisorTable::isSupervisor($this->type))
			{
				$supervisor = 'N';
			}
		}
		if ($supervisor)
		{
			\Bitrix\Crm\Kanban\SupervisorTable::set($this->type, $supervisor=='Y');
			$uri = new \Bitrix\Main\Web\Uri($request->getRequestUri());
			\LocalRedirect($uri->deleteParams(array('supervisor', 'clear_filter'))->getUri());
		}
	}

	/**
	 * Current user is crm admin?
	 * @return boolean
	 */
	protected function isCrmAdmin()
	{
		$crmPerms = new \CCrmPerms($this->uid);
		return $crmPerms->HavePerm('CONFIG', BX_CRM_PERM_CONFIG, 'WRITE');
	}

	/**
	 * Can current user edit settings or not.
	 * @return mixed
	 */
	protected function canEditSettings()
	{
		return $GLOBALS['USER']->canDoOperation('edit_other_settings');
	}

	/**
	 * Get admins and group moderators.
	 * @return array
	 */
	protected function getAdmins()
	{
		$users = array();

		$userQuery = new \Bitrix\Main\Entity\Query(
			\Bitrix\Main\UserTable::getEntity()
		);
		// set select
		$userQuery->setSelect(array(
			'ID', 'LOGIN', 'NAME', 'LAST_NAME',
			'SECOND_NAME', 'PERSONAL_PHOTO'
		));
		// set runtime for inner group ID=1 (admins)
		$userQuery->registerRuntimeField(
			null,
			new Bitrix\Main\Entity\ReferenceField(
				'UG',
				\Bitrix\Main\UserGroupTable::getEntity(),
				array(
					'=this.ID' => 'ref.USER_ID',
					'=ref.GROUP_ID' => new Bitrix\Main\DB\SqlExpression(1)
				),
				array(
					'join_type' => 'INNER'
				)
			)
		);
		// set filter
		$date = new \Bitrix\Main\Type\DateTime;
		$userQuery->setFilter(array(
			'=ACTIVE' => 'Y',
			'!ID' => $this->uid,
			array(
				'LOGIC' => 'OR',
				'<=UG.DATE_ACTIVE_FROM' => $date,
				'UG.DATE_ACTIVE_FROM' => false
			),
			array(
				'LOGIC' => 'OR',
				'>=UG.DATE_ACTIVE_TO' => $date,
				'UG.DATE_ACTIVE_TO' => false
			)
		));
		$res = $userQuery->exec();
		while ($row = $res->fetch())
		{
			if ($row['PERSONAL_PHOTO'])
			{
				$row['PERSONAL_PHOTO'] = \CFile::ResizeImageGet(
					$row['PERSONAL_PHOTO'],
					$this->avatarSize,
					BX_RESIZE_IMAGE_EXACT
				);
				if ($row['PERSONAL_PHOTO'])
				{
					$row['PERSONAL_PHOTO'] = $row['PERSONAL_PHOTO']['src'];
				}
			}
			$users[$row['ID']] = array(
				'id' => $row['ID'],
				'name' => \CUser::FormatName(
					$this->arParams['~NAME_TEMPLATE'],
					$row, true, false
				),
				'img' => $row['PERSONAL_PHOTO']
			);
		}

		return $users;
	}

	/**
	 * Prepare fields for inline edit scheme.
	 * @return array
	 */
	protected function getSchemeForInlineEditor()
	{
		if (
			$this->type == $this->types['lead'] ||
			$this->type == $this->types['deal'] ||
			$this->type == $this->types['order']
		)
		{
			$availableFields = array_keys($this->additionalSelect);

			// include external component class for call public method
			$componentName = 'bitrix:crm.'.mb_strtolower($this->type) . '.details';
			$className = \CBitrixComponent::includeComponentClass(
				$componentName
			);
			/** @var CCrmDealDetailsComponent $crmCmp */
			$crmCmp = new $className;
			$crmCmp->initComponent($componentName);
			$crmCmp->arResult = [
				'READ_ONLY' => false,
				'PATH_TO_USER_PROFILE' => ''
			];
			$crmCmp->setEntityID(0);
			if ($this->type === $this->types['order'])
			{
				/** @var CCrmOrderDetailsComponent $crmCmp */
				$crmCmp->obtainOrder();
			}
			$fieldInfos = $crmCmp->prepareFieldInfos();
			$this->userFields = $crmCmp->prepareEntityUserFields();

			$configuration =  ($this->type === $this->types['order']) ? $crmCmp->prepareKanbanConfiguration() : $crmCmp->prepareConfiguration();
			$this->arResult['FIELDS_SECTIONS'] = $this->getFieldsSections(
				$configuration
			);

			if (in_array('OPPORTUNITY', $availableFields))
			{
				$availableFields[] = 'OPPORTUNITY_WITH_CURRENCY';
			}

			// prepare fields
			foreach ($fieldInfos as $field)
			{
				if (in_array($field['name'], $availableFields))
				{
					$this->schemeFields[$field['name']] = $field;
				}
			}
			if (isset($this->schemeFields['TITLE']['isHeading']))
			{
				unset($this->schemeFields['TITLE']['isHeading']);
			}
			if (isset($this->schemeFields['TITLE']['visibilityPolicy']))
			{
				unset($this->schemeFields['TITLE']['visibilityPolicy']);
			}

			unset($crmCmp, $field, $componentName, $className, $fieldInfos);
		}

		// build scheme
		if ($this->schemeFields)
		{
			$scheme = [
				[
					'name' => 'main',
					'title' => '',
					'type' => 'section',
					'elements' => array_values($this->schemeFields)
				]
			];

			return $scheme;
		}

		return [];
	}

	/**
	 * Gets config ID for editors.
	 * @return string
	 */
	protected function getEditorConfigId()
	{
		static $configId = null;

		if ($configId === null)
		{
			$configId = 'quick_editor_v6_'.
				mb_strtolower($this->type) . '_' .
						(int) $this->categoryId;
		}


		return $configId;
	}

	/**
	 * Common or not editor config.
	 * @return bool
	 */
	protected function isEditorConfigCommon()
	{
		$scope = \CUserOptions::getOption(
			'crm.entity.editor',
			$this->getEditorConfigId() . '_scope'
		);
		if (!$scope)
		{
			return true;
		}
		return ($scope == EntityEditorConfigScope::COMMON);
	}

	/**
	 * Runs automation processes on change entity.
	 * @param int $id Entity id.
	 * @param array|null $fields Updated fields.
	 * @return void
	 */
	protected function runAutomationOnUpdate(int $id, array $fields = null): void
	{
		if (
			$this->type == $this->types['lead'] ||
			$this->type == $this->types['deal']
		)
		{
			$errors = null;
			\CCrmBizProcHelper::autoStartWorkflows(
				\CCrmOwnerType::resolveID($this->type),
				$id,
				\CCrmBizProcEventType::Edit,
				$errors
			);

			if ($fields)
			{
				$starter = new \Bitrix\Crm\Automation\Starter(
					($this->type == $this->types['lead']) ? \CCrmOwnerType::Lead : \CCrmOwnerType::Deal,
					$id
				);
				$starter->setUserIdFromCurrent()->runOnUpdate($fields, []);
			}
		}
	}

	/**
	 * Base executable method.
	 * @return array
	 */
	public function executeComponent()
	{
		if (!$this->init())
		{
			return;
		}

		$schemeInlineEdit = $this->getSchemeForInlineEditor();

		$this->arResult['ITEMS'] = array();
		$this->arResult['ADMINS'] = $this->getAdmins();
		$this->arResult['MORE_FIELDS'] = $this->getAdditionalFields();
		$this->arResult['MORE_EDIT_FIELDS'] = $this->getAdditionalEditFields();
		$this->arResult['FIELDS_DISABLED'] = $this->disableMoreFields;
		$this->arResult['CATEGORIES'] = array();

		$isOLinstalled = IsModuleInstalled('imopenlines');
		$context = Application::getInstance()->getContext();
		$request = $context->getRequest();
		$action = $request->get('action');
		$type = $this->type;
		$types = $this->types;

		//new actions format
		if ($action && is_callable(array($this, 'action' . $action)))
		{
			if (!check_bitrix_sessid())
			{
				return array(
					'ERROR' => Loc::getMessage('CRM_KANBAN_ERROR_SESSION_EXPIRED'),
					'FATAL' => true
				);
			}
			else
			{
				return $this->{'action' . $action}();
			}
		}
		else
		{
			$this->makeAction();
		}

		if ($this->arParams['EMPTY_RESULT'] != 'Y')
		{
			//check other perms (additional for lead converting)
			$userPermissions = $this->getCurrentUserPermissions();
			$this->arResult['ACCESS_CONFIG_PERMS'] = $this->isCrmAdmin();
			$this->arResult['ACCESS_IMPORT'] = $type == $types['lead']
													? \CCrmLead::CheckImportPermission($userPermissions)
													: \CCrmDeal::CheckImportPermission($userPermissions);
			if ($type == $types['lead'])
			{
				$this->arResult['CAN_CONVERT_TO_CONTACT'] = \CCrmContact::CheckCreatePermission($userPermissions);
				$this->arResult['CAN_CONVERT_TO_COMPANY'] = \CCrmCompany::CheckCreatePermission($userPermissions);
				$this->arResult['CAN_CONVERT_TO_DEAL'] = \CCrmDeal::CheckCreatePermission($userPermissions);
				$this->arResult['CONVERSION_CONFIG'] = \Bitrix\Crm\Conversion\DealConversionConfig::load();
				if ($this->arResult['CONVERSION_CONFIG'] === null)
				{
					$this->arResult['CONVERSION_CONFIG'] = \Bitrix\Crm\Conversion\DealConversionConfig::getDefault();
				}
			}

			//output
			if ($this->arParams['ONLY_COLUMNS'] == 'Y')
			{
				$this->arResult['ITEMS'] = array(
					'items' => array_values($this->items),
					'columns' => array_values($this->getColumns())
				);
			}
			else
			{
				$items = array();
				$columns = $this->getColumns();
				if (!empty($this->arParams['ADDITIONAL_FILTER']))
				{
					$filter = $this->arParams['ADDITIONAL_FILTER'];
					if (isset($filter['COLUMN']))
					{
						$filter[$this->statusKey] = $filter['COLUMN'];
						unset($filter['COLUMN']);
					}
					$items = $this->getItems($filter);
				}
				else
				{
					foreach ($columns as $k => $column)
					{
						if (!$column['dropzone'])
						{
							$filter = array();
							$filter[$this->statusKey] = $column['id'];
							$items += $this->getItems($filter);
						}
					}
				}

				$this->arResult['ITEMS'] = array(
					'columns' => array_values($columns),
					'items' => $items
				);
			}

			if ($this->arParams['ONLY_COLUMNS'] == 'N')
			{
				$contacts = $this->getContragents('contact');
				$companies = $this->getContragents('company');
				if ($type == $types['lead'])
				{
					$this->arResult['ITEMS']['items'] = $this->fillFMfields($this->arResult['ITEMS']['items'], 'lead');
				}

				//set contragents to items
				if (!empty($this->arResult['ITEMS']['items']))
				{
					foreach ($this->arResult['ITEMS']['items'] as &$item)
					{
						$contragent = [];
						$contragents = [];

						if (
							!empty($item['contactId'])
							&& isset($contacts[$item['contactId']])
						)
						{
							$contact = $contacts[$item['contactId']];
							$contragents['contact'] = $contact;
							$item['contactName'] = $contact['TITLE'];
							$item['contactLink'] = $contact['URL'];
							$item['contactTooltip'] = \CCrmViewHelper::PrepareEntityBaloonHtml([
								'ENTITY_TYPE_ID' => CCrmOwnerType::Contact,
								'ENTITY_ID' => $item['contactId'],
								'TITLE' => $contact['~TITLE'],
								'PREFIX' => $this->type . '_' . $item['id'],
							]);
						}

						if (
							!empty($item['companyId'])
							&& isset($companies[$item['companyId']])
						)
						{
							$company = $companies[$item['companyId']];
							$contragents['company'] = $company;
							$item['companyName'] = $company['TITLE'];
							$item['companyLink'] = $company['URL'];
							$item['companyTooltip'] = \CCrmViewHelper::PrepareEntityBaloonHtml([
								'ENTITY_TYPE_ID' => CCrmOwnerType::Company,
								'ENTITY_ID' => $item['companyId'],
								'TITLE' => $company['~TITLE'],
								'PREFIX' => $this->type . '_' . $item['id'],
							]);
						}

						//phone, email, chat
						foreach ($contragents as $contragentType => $contragent)
						{
							if (isset($contragent['FM_VALUES']) && !empty($contragent['FM_VALUES']))
							{
								foreach ($contragent['FM_VALUES'] as $code => $values)
								{
									if ($code == 'im') // we need only chat for im
									{
										if ($isOLinstalled)
										{
											foreach ($values as $val)
											{
												$val = isset($val['VALUE'])
													? $val['VALUE']
													: $val['value'];
												if ((mb_strpos($val, 'imol|') === 0))
												{
													$item[$code] = $val;
													break;
												}
											}
										}
									}
									else
									{
										$item[$code][$contragentType] = $values;
									}
									$item['required_fm'][mb_strtoupper($code)] = false;
								}
							}
						}
						//same from leads
						if (isset($item['FM_VALUES']))
						{
							foreach ($item['FM_VALUES'] as $code => $values)
							{
								if ($code == 'im') // we need only chat for im
								{
									if ($isOLinstalled)
									{
										foreach ($values as $val)
										{
											$val = isset($val['VALUE'])
													? $val['VALUE']
													: $val['value'];
											if (mb_strpos($val, 'imol|') === 0)
											{
												$item[$code] = $val;
												break;
											}
										}
									}
								}
								else
								{
									$item[$code] = $values;
								}
								$item['required_fm'][mb_strtoupper($code)] = false;
							}
							unset($item['FM_VALUES'], $item['FM']);
						}
					}
					unset($item);
				}

				//get activity
				if (!empty($this->arResult['ITEMS']['items']))
				{
					if ($type == $types['deal'] || $type == $types['lead'] || $type == $types['order'])
					{
						$activityCounters = $this->getActivityCounters(
							array_keys($this->arResult['ITEMS']['items']),
							$errors
						);
						foreach ($activityCounters as $id => $actCC)
						{
							$this->arResult['ITEMS']['items'][$id]['activityProgress'] = isset($actCC['N']) ? $actCC['N'] : 0;
							$this->arResult['ITEMS']['items'][$id]['activityTotal'] = isset($actCC['Y']) ? $actCC['Y'] : 0;
							if (isset($errors[$id]))
							{
								$this->arResult['ITEMS']['items'][$id]['activityErrorTotal'] = $errors[$id];
							}
						}
					}

					$this->arResult['ITEMS']['items'] = array_values($this->arResult['ITEMS']['items']);
				}
			}

			// get categories for deals
			if (
				$type == $types['deal']
			)
			{
				$catPerm = array_fill_keys(
					\CCrmDeal::GetPermittedToReadCategoryIDs($userPermissions),
					true
				);
				foreach (DealCategory::getAll(true) as $k => $catItem)
				{;
					if (isset($catPerm[$catItem['ID']]))
					{
						$this->arResult['CATEGORIES'][$k] = $catItem;
					}
				}
			}
		}

		$this->arResult['ITEMS']['last_id'] = $this->getLastId();
		$this->arResult['ITEMS']['scheme_inline'] = $schemeInlineEdit;
		$this->arResult['ITEMS']['customFields'] = array_keys($this->arResult['MORE_FIELDS']);

		// items for demo import
		if (
			isset($this->arResult['ITEMS']['columns'][0]['id']) &&
			isset($this->arResult['ITEMS']['columns'][0]['count']) &&
			(
				$this->blockPage * $this->blockSize
				>=
				$this->arResult['ITEMS']['columns'][0]['count']
			)
		)
		{
			if (
				$type == $types['lead'] ||
				$type == $types['deal']
			)
			{
				$this->arResult['ITEMS']['items'] = array_merge(
					$this->arResult['ITEMS']['items'],
					[[
						 'id' => -1,
						 'name' => null,
						 'countable' => false,
						 'droppable' => true,
						 'draggable' => true,
						 'columnId' => $this->arResult['ITEMS']['columns'][0]['id'],
						 'special_type' => 'import'
					 ]]
				);
			}
			$this->arResult['ITEMS']['items'] = array_merge(
				$this->arResult['ITEMS']['items'],
				[[
					 'id' => -2,
					 'name' => null,
					 'countable' => false,
					 'droppable' => true,
					 'draggable' => true,
					 'columnId' => $this->arResult['ITEMS']['columns'][0]['id'],
					 'special_type' => 'rest'
				 ]]
			);
		}

		if ($this->arParams['IS_AJAX'] == 'Y')
		{
			return $this->arResult;
		}
		else
		{
			$GLOBALS['APPLICATION']->setTitle(Loc::getMessage('CRM_KANBAN_TITLE2_' . $this->type));
			$this->IncludeComponentTemplate();
		}
	}

	/**
	 * Get request-var for http-hit.
	 * @param string $var Request-var code.
	 * @return mixed
	 */
	protected function request($var)
	{
		static $request = null;

		if ($request === null)
		{
			$context = Application::getInstance()->getContext();
			$request = $context->getRequest();
		}

		return $request->get($var);
	}

	/**
	 * Convert charset from utf-8 to site.
	 * @param mixed $data
	 * @param bool $fromUtf Direction - from (true) or to (false).
	 * @return mixed
	 */
	protected function convertUtf($data, $fromUtf)
	{
		if (SITE_CHARSET != 'UTF-8')
		{
			$from = $fromUtf ? 'UTF-8' : SITE_CHARSET;
			$to = !$fromUtf ? 'UTF-8' : SITE_CHARSET;
			if (is_array($data))
			{
				$data = $this->application->ConvertCharsetArray($data, $from, $to);
			}
			else
			{
				$data = $this->application->ConvertCharset($data, $from, $to);
			}
		}
		return $data;
	}

	/**
	 * Notify admin for get access.
	 * @return array
	 */
	protected function actionNotifyAdmin()
	{
		if (
			($userId = $this->request('userId')) &&
			Loader::includeModule('im')
		)
		{
			$admins = $this->getAdmins();
			if (isset($admins[$userId]))
			{
				$params = $this->arParams;
				//settings page
				if ($params['ENTITY_TYPE_CHR'] == 'DEAL')
				{
					$pathColumnEdit = '/crm/configs/status/?ACTIVE_TAB=status_tab_DEAL_STAGE';
				}
				elseif ($params['ENTITY_TYPE_CHR'] == 'LEAD')
				{
					$pathColumnEdit = '/crm/configs/status/?ACTIVE_TAB=status_tab_STATUS';
				}
				else
				{
					$pathColumnEdit = '/crm/configs/status/?ACTIVE_TAB=status_tab_'. $params['ENTITY_TYPE_CHR'] .'_STATUS';
				}
				\CIMNotify::Add(array(
					'TO_USER_ID' => $userId,
					'FROM_USER_ID' => $this->uid,
					'NOTIFY_TYPE' => IM_NOTIFY_FROM,
					'NOTIFY_MODULE' => 'crm',
					'NOTIFY_TAG' => 'CRM|NOTIFY_ADMIN|'.$userId.'|'.$this->userId,
					'NOTIFY_MESSAGE' => Loc::getMessage('CRM_ACCESS_NOTIFY_MESSAGE', array(
						'#URL#' => $pathColumnEdit
					))
				));
			}
		}
		return array(
			'status' => 'success'
		);
	}

	/**
	 * Add new stage, update stage, move stage.
	 * @return array
	 */
	protected function actionModifyStage()
	{
		if ($this->isCrmAdmin())
		{
			// vars
			$stages = $this->getColumns(
				true,
				true,
				array(
					'originalColumns' => true
				)
			);
			$delete = $this->request('delete');
			$columnId = $this->request('columnId');
			$columnName = $this->request('columnName');
			$columnColor = $this->request('columnColor');
			$afterColumnId = $this->request('afterColumnId');

			$sort = 0;
			if (
				$afterColumnId !== null &&
				$afterColumnId == '0'
			)
			{
				$sort = 0;
			}
			elseif (isset($stages[$afterColumnId]))
			{
				$sort = $stages[$afterColumnId]['real_sort'];
			}
			elseif (isset($stages[$columnId]))
			{
				$sort = $stages[$columnId]['real_sort'];
			}
			if ($columnName)
			{
				$columnName = $this->convertUtf($this->request('columnName'), true);
			}

			$fields = array(
				'ENTITY_ID' => $this->getStatusEntityId(),
				'SORT' => ++$sort
			);
			if ($columnName)
			{
				$fields['NAME'] = $columnName;
				$fields['NAME_INIT'] = $columnName;
			}
			if ($columnColor)
			{
				$fields['COLOR'] = $columnColor;
			}
			if ($columnId != '' && isset($stages[$columnId]))
			{
				if ($delete)
				{
					$deleteResult = [];
					$r = $this->deleteStage($columnId, $stages);
					if (!$r->isSuccess())
					{
						$errors = $r->getErrorMessages();
						$deleteResult = [
							'ERROR' => current($errors)
						];
					}

					return $deleteResult;
				}
				else
				{
					$internalId = ($this->type == $this->types['order']) ? $columnId : $stages[$columnId]['real_id'];
					$r = $this->updateStage($internalId, $fields);
				}
			}
			else
			{
				$r = $this->addStage($fields);
				$internalId = $r->getId();
			}
			if ($r->isSuccess())
			{
				if ($this->type == $this->types['order'])
				{
					$statusId = $internalId;
				}
				else
				{
					$statusEntityId = $this->getStatusEntityId();
					$status = new \CCrmStatus($statusEntityId);
					$newStatus = $status->GetStatusById($internalId);
					$statusId = $newStatus['STATUS_ID'];
				}

				$stages = $this->getColumns(
					true,
					true,
					array(
						'originalColumns' => true
					)
				);
				if (isset($stages[$statusId]))
				{
					// range sorts
					$sort = 10;
					foreach ($stages as $stage)
					{
						$internalId = ($this->type == $this->types['order']) ? $stage['id'] : $stage['real_id'];
						$this->updateStage($internalId, ['SORT' => $sort]);
						$sort += 10;
					}
					if (!empty($columnColor))
					{
						$stages[$statusId]['color'] = $columnColor;
					}
					return \htmlspecialcharsback($stages[$statusId]);
				}
				else
				{
					return array(
						'ERROR' => 'Unknown error'
					);
				}
			}
			else
			{
				$errors = $r->getErrorMessages();
				return ['ERROR' => current($errors)];
			}
		}
		return array();
	}

	private function resortColumns()
	{

	}

	/**
	 * Delete status stage for 'actionModifyStage' method
	 *
	 * @param $columnId
	 * @param $stages
	 */
	private function deleteStage($columnId, $stages)
	{
		$result = new Result();

		if ($stages[$columnId]['type'] == 'WIN')
		{
			$result->addError(new Error(Loc::getMessage('CRM_KANBAN_ERROR_STAGE_IS_WIN')));
			return $result;
		}

		if (!$this->isEmptyStage($columnId))
		{
			$result->addError(new Error(Loc::getMessage('CRM_KANBAN_ERROR_STAGE_IS_NOT_EMPTY')));
			return $result;
		}

		$statusEntityId = $this->getStatusEntityId();
		if (empty($statusEntityId))
		{
			return $result;
		}

		if ($this->type == $this->types['order'])
		{
			$defaultStatuses = Order\OrderStatus::getDefaultStatuses();
			if (isset($defaultStatuses[$columnId]))
			{
				$result->addError(new Error(Loc::getMessage('CRM_KANBAN_ERROR_STAGE_IS_SYSTEM')));
				return $result;
			}
			$statusCode = $columnId;

			if (!Loader::includeModule('sale'))
			{
				return $result;
			}

			$result = Sale\Internals\StatusTable::delete($columnId);
			if ($result->isSuccess())
			{
				$statusLanguageRaw = Sale\Internals\StatusLangTable::getList([
					'filter' => ['STATUS_ID' => $columnId],
					'select' => ['STATUS_ID', 'LID']
				]);

				while ($langPrimary = $statusLanguageRaw->fetch())
				{
					Sale\Internals\StatusLangTable::delete($langPrimary);
				}
			}
		}
		else
		{
			$internalId = $stages[$columnId]['real_id'];
			$statusObject = new \CCrmStatus($statusEntityId);
			$statusInfo = $statusObject->GetStatusById($internalId);
			if ($statusInfo['SYSTEM'] == 'Y')
			{
				$result->addError(new Error(Loc::getMessage('CRM_KANBAN_ERROR_STAGE_IS_SYSTEM')));
				return $result;
			}

			$statusCode = $statusInfo['STATUS_ID'];

			$statusObject->delete($internalId);
			if (!empty($statusObject->GetLastError()))
			{
				$result->addError(new Error($statusObject->GetLastError()));
			}
		}

		if ($result->isSuccess())
		{
			$this->unsetColorSetting($statusCode);
		}

		return $result;
	}

	/**
	 * @param $stageId
	 *
	 * @return bool
	 */
	private function isEmptyStage($columnId)
	{
		if ($this->type == $this->types['order'])
		{
			$orderData = Order\Order::getList([
				'filter' => [$this->statusKey => $columnId]
			]);

			return !$orderData->fetch();
		}
		else
		{
			$provider = '\CCrm'.$this->type;
			$method = method_exists($provider, 'getListEx') ? 'getListEx' : 'getList';
			if (is_callable(array($provider, $method)))
			{
				$checkFilter = array(
					$this->statusKey => $columnId,
					'CHECK_PERMISSIONS' => 'N'
				);
				if ($this->type == $this->types['deal'])
				{
					$checkFilter['CATEGORY_ID'] = intval($this->categoryId);
				}
				$entities = $provider::$method(
					array('ID' => 'DESC'),
					$checkFilter,
					false,
					false,
					array('ID')
				);

				return !$entities->fetch();
			}
		}

		return true;
	}

	/**
	 * Save changes into status stage for 'actionModifyStage' method
	 *
	 * @param $id
	 * @param array $fields
	 */
	private function updateStage($id, array $fields = [])
	{
		$result = new Result();

		if (isset($fields['COLOR']))
		{
			$columnColor = $fields['COLOR'];
		}

		if ($this->type !== $this->types['order'])
		{
			unset($fields['COLOR']);
			$statusEntityId = $this->getStatusEntityId();
			$status = new \CCrmStatus($statusEntityId);
			$status->update($id, $fields);
			if (!empty($status->GetLastError()))
			{
				$result->addError(new Error($status->GetLastError()));
			}

			$newStatus = $status->GetStatusById($id);
			$statusId = $newStatus['STATUS_ID'];
		}
		else
		{
			if (!Loader::includeModule('sale'))
			{
				return $result;
			}

			$updateFields = array_intersect_key($fields, array_flip(['SORT', 'COLOR']));
			if (!empty($updateFields['COLOR']))
			{
				$updateFields['COLOR'] = "#".$updateFields['COLOR'];
			}
			if (!empty($updateFields))
			{
				$result = Sale\Internals\StatusTable::update($id, $updateFields);
			}
			if ($result->isSuccess() && !empty($fields['NAME']))
			{
				Sale\Internals\StatusLangTable::update(
					[
						'STATUS_ID' => $id,
						'LID' => static::getLanguageId(),
					],
					['NAME' => $fields['NAME']]
				);
			}
			$statusId = $id;
		}

		if (!empty($columnColor) && $result->isSuccess())
		{
			$this->setColorSetting($columnColor, $statusId);
		}

		return $result;
	}

	/**
	 * Add new status stage
	 *
	 * @param array $fields
	 * @return AddResult
	 */
	private function addStage(array $fields = [])
	{
		$result = new AddResult();

		if (isset($fields['COLOR']))
		{
			$columnColor = $fields['COLOR'];
		}

		if ($this->type !== $this->types['order'])
		{
			unset($fields['COLOR']);
			$statusEntityId = $this->getStatusEntityId();
			$status = new \CCrmStatus($statusEntityId);
			$id = $status->add($fields);
			if (!empty($status->GetLastError()))
			{
				$result->addError(new Error($status->GetLastError()));
			}
			else
			{
				$result->setId($id);
			}

			$newStatus = $status->GetStatusById($id);
			$statusId = $newStatus['STATUS_ID'];
		}
		else
		{
			if (!Loader::includeModule('sale'))
			{
				return $result;
			}

			$createFields = array_intersect_key($fields, array_flip(['SORT', 'COLOR']));

			$orderStatusIds = [];
			$statusRaw = Order\OrderStatus::getList([
				'select' => ['ID']
			]);
			while ($data = $statusRaw->fetch())
			{
				$orderStatusIds[$data['ID']] = $data;
			}

			do
			{
				$newId = chr(rand(65, 90)); //A-Z
				if (is_array($result) && count($result) >= 27)
				{
					$newId .= chr(rand(65, 90));
				}
			}
			while (isset($orderStatusIds[$newId]));
			$createFields['ID'] = $newId;
			$createFields['TYPE'] = Order\OrderStatus::TYPE;
			$result = Sale\Internals\StatusTable::add($createFields);
			if ($result->isSuccess())
			{
				Sale\Internals\StatusLangTable::add([
					'STATUS_ID' => $result->getId(),
					'LID' => static::getLanguageId(),
					'NAME' => $fields['NAME']
				]);
			}
			$statusId = $result->getId();
		}

		if (!empty($columnColor) && $result->isSuccess())
		{
			$this->setColorSetting($columnColor, $statusId);
		}

		return $result;
	}

	/**
	 * @return string
	 */
	private function getStatusEntityId()
	{
		switch ($this->type)
		{
			case $this->types['quote']:
				return 'QUOTE_STATUS';
			case $this->types['invoice']:
				return 'INVOICE_STATUS';
			case $this->types['lead']:
				return 'STATUS';
			case $this->types['order']:
				return  Order\OrderStatus::NAME;
			case $this->types['deal']:
			{
				$filter = $this->getFilter();
				if (isset($filter['CATEGORY_ID']) && $filter['CATEGORY_ID'] > 0)
				{
					return 'DEAL_STAGE_' . $filter['CATEGORY_ID'];
				}
				else
				{
					return 'DEAL_STAGE';
				}
			}
		}

		return '';
	}

	/**
	 * @return string
	 */
	private function getStatusColorCode()
	{
		switch ($this->type)
		{
			case $this->types['quote']:
				return 'CONFIG_STATUS_QUOTE_STATUS';
			case $this->types['invoice']:
				return 'CONFIG_STATUS_INVOICE_STATUS';
			case $this->types['lead']:
				return 'CONFIG_STATUS_STATUS';
			case $this->types['order']:
				return  \Bitrix\Crm\Color\OrderStatusColorScheme::getName();
			case $this->types['deal']:
			{
				$filter = $this->getFilter();
				if (isset($filter['CATEGORY_ID']) && $filter['CATEGORY_ID'] > 0)
				{
					return 'CONFIG_STATUS_DEAL_STAGE_' . $filter['CATEGORY_ID'];
				}
				else
				{
					return 'CONFIG_STATUS_DEAL_STAGE';
				}
			}
		}

		return '';
	}

	/**
	 * Update CRM color status option.
	 *
	 * @param $colorValue
	 * @param $statusId
	 */
	private function setColorSetting($colorValue, $statusId)
	{
		$codeColor = $this->getStatusColorCode();
		$colors = $this->getStatusColor($codeColor);
		if (!isset($colors[$statusId]))
		{
			$colors[$statusId] = array();
		}
		$colors[$statusId]['COLOR'] = '#' . $colorValue;
		Option::set('crm', $codeColor, serialize($colors));
	}

	/**
	 * Remove CRM color status value from color option.
	 *
	 * @param $colorValue
	 * @param $statusId
	 */
	private function unsetColorSetting($statusId)
	{
		$codeColor = $this->getStatusColorCode();
		$colors = $this->getStatusColor($codeColor);
		if (isset($colors[$statusId]))
		{
			unset($colors[$statusId]);
		}
		Option::set('crm', $codeColor, serialize($colors));
	}

	private function resortStages()
	{

	}

	/**
	 * Remove all private cards, for setting common.
	 * @return void
	 */
	protected function setCommonCardFields()
	{
		if ($this->canEditSettings())
		{
			$name = 'kanban_select_more_v4_'.
				mb_strtolower($this->type) .
					'_' . intval($this->categoryId);
			\CUserOptions::deleteOptionsByName('crm', $name);
		}
	}

	/**
	 * Reset fields in card.
	 * @return void
	 */
	protected function resetCardFields()
	{
		$name = 'kanban_select_more_v4_'.
			mb_strtolower($this->type) .
				'_' . intval($this->categoryId);
		$fields = [
			'TITLE' => '',
			'OPPORTUNITY' => '',
			'DATE_CREATE' => '',
			'ORDER_STAGE' => '',
			'CLIENT' => ''
		];
		$this->additionalSelect = $fields;
		$this->arResult['MORE_FIELDS'] = $this->getAdditionalFields(true);

		if (
			$this->canEditSettings() &&
			$this->isEditorConfigCommon()
		)
		{
			\CUserOptions::setOption('crm', $name . '_common', $fields, true);
		}
		else
		{
			\CUserOptions::setOption('crm', $name, $fields);
		}
	}

	/**
	 * Save additional fields for card.
	 * @return array
	 */
	protected function actionSaveFields()
	{
		$name = (
					($this->request('type') == 'view')
					? 'kanban_select_more_v4_'.mb_strtolower($this->type)
					: 'kanban_edit_more_v4_'.mb_strtolower($this->type)
				) . '_' . intval($this->categoryId);
		$fields = $this->convertUtf($this->request('fields'), true);
		$fieldsKeys = $fields ? array_keys($fields) : [];

		$current = \CUserOptions::getOption('crm', $name);
		$current = $current ? array_keys($current) : [];
		$diffDel = array_diff($current, $fieldsKeys);
		$diffAdd = array_diff($fieldsKeys, $current);

		// reset common settings
		if (
			$this->canEditSettings() &&
			$this->isEditorConfigCommon()
		)
		{
			\CUserOptions::setOption('crm', $name . '_common', $fields, true);
		}
		else
		{
			\CUserOptions::setOption('crm', $name, $fields);
		}

		unset($name, $fields, $fieldsKeys, $current);

		return [
			'delete' => array_values($diffDel),
			'add' => array_values($diffAdd)
		];
	}

	/**
	 * Delete entity.
	 * @param int|array $ids Optionally id for delete.
	 * @return array
	 */
	protected function actionDelete($ids = null)
	{
		$ids = $ids ? $ids : $this->request('id');
		$ignore = $this->request('ignore');
		$type = $this->type;
		$types = $this->types;

		if ($type === $types['order'])
		{
			if (!$ids)
			{
				return [];
			}

			$ids = is_array($ids) ? $ids : [$ids];
			$userPerms = $this->getCurrentUserPermissions();
			foreach ($ids as $id)
			{
				$id = (int)$id;
				$checkPermission = Order\Permissions\Order::checkDeletePermission($id, $userPerms);
				if (!$checkPermission)
				{
					continue;
				}
				Order\Order::delete($id);
			}
			return [];
		}

		$provider = '\CCrm' . $type;

		if ($ids && method_exists($provider, 'delete'))
		{
			$entity = new $provider();
			if (!is_array($ids))
			{
				$ids = array($ids);
			}
			// set to ignore
			if (
				$ignore == 'Y' &&
				(
					!\Bitrix\Crm\Exclusion\Access::current()->canWrite() ||
					($type != $types['lead'] && $type != $types['deal'])
				)
			)
			{
				$ignore = 'N';
			}
			// delete
			foreach ($ids  as $id)
			{
				if ($ignore == 'Y')
				{
					\Bitrix\Crm\Exclusion\Manager::excludeEntity(
						($type == $types['lead'])
							? \CCrmOwnerType::Lead
							: \CCrmOwnerType::Deal,
						$id
					);
					if ($type == $types['lead'])
					{
						$entity->delete($id);
					}
				}
				else
				{
					$entity->delete($id);
				}
			}
		}
		return array();
	}

	/**
	 * Update statuses
	 *
	 * @param $id
	 * @param $status
	 * @param array $statuses
	 *
	 * @return Result
	 */
	private function actionUpdateEntityStatus($id, $status, array $statuses = [])
	{
		$result = new Result();
		$type = $this->type;
		$userPerms = $this->getCurrentUserPermissions();
		$request = Application::getInstance()->getContext()->getRequest();
		$provider = '\CCrm'.$this->type;

		if (!\CCrmPerms::IsAuthorized())
		{
			$result->addError(new Error(Loc::getMessage('CRM_KANBAN_ERROR_ACCESS_DENIED')));
			return $result;
		}

		if ($this->type == $this->types['order'])
		{
			$order = Order\Order::load($id);
			if (!$order)
			{
				$result->addError(new Error(Loc::getMessage('CRM_KANBAN_ERROR_ACCESS_DENIED')));
				return $result;
			}
			$row = $order->getFieldValues();
		}
		else
		{
			$entity = new $provider(false);
			$row = $entity->getById($id);
			if (!$row)
			{
				$result->addError(new Error(Loc::getMessage('CRM_KANBAN_ERROR_ACCESS_DENIED')));
				return $result;
			}
		}

		if ($this->type == $this->types['order'])
		{
			$checkPermission = Order\Permissions\Order::checkUpdatePermission($id, $userPerms);
		}
		else
		{
			$checkPermission = $provider::CheckUpdatePermission($id, $userPerms);
		}

		if (!$checkPermission)
		{
			$result->addError(new Error(Loc::getMessage('CRM_KANBAN_ERROR_ACCESS_DENIED')));
			return $result;
		}

		$statusKey = $this->statusKey;
		$ajaxParamsName = ($request->get('version') == 2) ? 'ajaxParams' : 'status_params';
		$newStateParams = (array)$request->get($ajaxParamsName);
		//add one more item for old column
		if ($row[$statusKey] != $status && isset($newStateParams['old_status_lastid']))
		{
			$oneMore = $this->getItems(array(
				$statusKey => $row[$statusKey],
				'<ID' => $newStateParams['old_status_lastid'],
				'!ID' => $id
			));
			if (count($oneMore) > 1)
			{
				$oneMore = array_shift($oneMore);
				$oneMore = array(
					$oneMore['id'] => $oneMore
				);
			}
			$this->items += $oneMore;
		}
		//change state

		if ($type == $this->types['invoice'])
		{
			$statusParams = array();
			$statusParams['REASON_MARKED'] = $this->convertUtf(
				isset($newStateParams['comment'])
					? $newStateParams['comment']
					: '',
				true
			);
			$statusParams[$status == 'P' ? 'PAY_VOUCHER_DATE' : 'DATE_MARKED'] = $this->convertUtf(
				isset($newStateParams['date'])
					? $newStateParams['date']
					: '',
				true
			);
			$statusParams['PAY_VOUCHER_NUM'] = $this->convertUtf(
				isset($newStateParams['docnum'])
					? $newStateParams['docnum']
					: '',
				true
			);
			$entity->SetStatus(
				$id,
				$status,
				$statusParams,
				array(
					'SYNCHRONIZE_LIVE_FEED' => true
				)
			);
		}
		elseif ($type == $this->types['order'])
		{
			$order->setField('STATUS_ID', $status);
			$order->save();
		}
		else
		{
			//if lead, check status
			if ($type == $this->types['lead'] && $row[$statusKey] != $status)
			{
				if ($statuses[$row[$statusKey]]['PROGRESS_TYPE'] == 'WIN')
				{
					$result->addError(new Error(Loc::getMessage('CRM_KANBAN_ERROR_LEAD_ALREADY_CONVERTED')));
					return $result;
				}
				elseif ($statuses[$status]['PROGRESS_TYPE'] == 'WIN')
				{
					return $result;
				}
			}
			//if deal, check status
			if ($type == $this->types['deal'] && $row[$statusKey] != $status)
			{
				$stageCategoryID = DealCategory::resolveFromStageID($status);
				$dealCategoryID = (int)$row['CATEGORY_ID'];
				if($dealCategoryID !== $stageCategoryID)
				{
					$result->addError(new Error(Loc::getMessage('CRM_KANBAN_ERROR_DEAL_STAGE_MISMATCH')));
					return $result;
				}
			}

			//update
			if ($row[$statusKey] != $status)
			{
				$fields = array($statusKey => $status);
				$entity->Update(
					$id,
					$fields,
					true,
					true,
					array(
						//'DISABLE_USER_FIELD_CHECK' => $isMulti,
						'REGISTER_SONET_EVENT' => true
					)
				);
				if (empty($entity->LAST_ERROR) && ($type == $this->types['lead'] || $type == $this->types['deal']))
				{
					$errors = array();
					\CCrmBizProcHelper::AutoStartWorkflows(
						($type == $this->types['lead']) ? \CCrmOwnerType::Lead : \CCrmOwnerType::Deal,
						$id, \CCrmBizProcEventType::Edit, $errors
					);
					$starter = new \Bitrix\Crm\Automation\Starter(
						($type == $this->types['lead']) ? \CCrmOwnerType::Lead : \CCrmOwnerType::Deal,
						$id
					);
					$starter->setUserIdFromCurrent()->runOnUpdate($fields, []);
				}
				elseif (!empty($entity->LAST_ERROR))
				{
					$result->addError(new Error($entity->LAST_ERROR));
				}
			}
		}

		\Bitrix\Crm\Kanban\SortTable::setPrevious(array(
			'ENTITY_TYPE_ID' => $type,
			'ENTITY_ID' => $id,
			'PREV_ENTITY_ID' => $request->get('prev_entity_id')
		));

		// remember last id
		$this->rememberLastId($this->getLastId());
		$this->getColumns(true);

		return $result;
	}

	private function prepareOrderEntitiesFields(array $orders = [])
	{
		$ids = array_keys($orders);
		static $currencies = [];
		static $personTypes = [];
		if (empty($currencies))
		{
			$currencies = CCrmCurrencyHelper::PrepareListItems();
		}
		if (empty($personTypes))
		{
			$personTypes = Order\PersonType::load(SITE_ID);
		}
		$orderClientRaw = \Bitrix\Crm\Binding\OrderContactCompanyTable::getList([
			'filter' => [
				'=ORDER_ID' => $ids
			]
		]);
		while ($orderClient = $orderClientRaw->fetch())
		{
			$orderId = $orderClient['ORDER_ID'];
			if ((int)$orderClient['ENTITY_TYPE_ID'] === \CCrmOwnerType::Contact)
			{
				if (empty($db[$orderId]['CONTACT_ID']) || $orderClient['IS_PRIMARY'] === 'Y')
				{
					$orders[$orderId]['CONTACT_ID'] = $orderClient['ENTITY_ID'];
				}
			}
			elseif ((int)$orderClient['ENTITY_TYPE_ID'] === \CCrmOwnerType::Company)
			{
				$orders[$orderId]['COMPANY_ID'] = $orderClient['ENTITY_ID'];
			}
		}

		$paymentRaw = Order\Payment::getList([
			'filter' => [
				'=ORDER_ID' => $ids
			]
		]);
		while ($payment = $paymentRaw->fetch())
		{
			$orderId = $payment['ORDER_ID'];
			$orders[$orderId]['PAYMENT'][$payment['ID']] = $payment;
		}

		$shipmentRaw = Order\Shipment::getList([
			'filter' => [
				'=ORDER_ID' => $ids,
				'SYSTEM' => 'N'
			]
		]);
		while ($shipment = $shipmentRaw->fetch())
		{
			$orderId = $shipment['ORDER_ID'];
			$orders[$orderId]['SHIPMENT'][$shipment['ID']] = $shipment;
		}

		$markersRaw = Order\EntityMarker::getList([
			'filter' => [
				'=ENTITY_TYPE' => Order\EntityMarker::ENTITY_TYPE_ORDER,
				'=ENTITY_ID' => $ids,
				'=SUCCESS' => 'N',
			],
			'select' => ['MESSAGE', 'ENTITY_ID']
		]);

		$markers = [];
		while ($marker = $markersRaw->fetch())
		{
			$markers[$marker['ENTITY_ID']][] = $marker['MESSAGE'];
		}

		if (!empty($markers))
		{
			foreach ($markers as $orderId => $marker )
			{
				if (count($marker) > 1)
				{
					$value = implode('<br>', $marker);
				}
				else
				{
					$value = $marker[0];
				}
				$orders[$orderId]['PROBLEM_NOTIFICATION'] = $value;
			}
		}

		$enumVariants = [];
		$enumPropertyVariantRaw = Order\PropertyVariant::getList([
			'select' => ['VALUE', 'NAME', 'ORDER_PROPS_ID']
		]);
		while ($variant = $enumPropertyVariantRaw->fetch())
		{
			$enumVariants[$variant['ORDER_PROPS_ID']][$variant['VALUE']] = $variant['NAME'];
		}

		$propertyValuesRaw = Order\PropertyValue::getList([
			'filter' => [
				'=ORDER_ID' => $ids
			],
			'select' => ['TYPE' => 'PROPERTY.TYPE', 'VALUE', 'ORDER_PROPS_ID', 'ORDER_ID']
		]);
		while ($propertyValue = $propertyValuesRaw->fetch())
		{
			$value = $propertyValue['VALUE'];
			$currentPropertyId = $propertyValue['ORDER_PROPS_ID'];
			if ($propertyValue['TYPE'] === 'ENUM')
			{
				$orders[$propertyValue['ORDER_ID']]['PROPERTY_'.$currentPropertyId] = $enumVariants[$currentPropertyId][$value];
			}
			if ($propertyValue['TYPE'] === 'Y/N')
			{
				$value = ($value === 'Y') ? 'Y' : 'N';
				$orders[$propertyValue['ORDER_ID']]['PROPERTY_'.$currentPropertyId] = Loc::getMessage('CRM_KANBAN_CHAR_' . $value);
			}
			else
			{
				$orders[$propertyValue['ORDER_ID']]['PROPERTY_'.$currentPropertyId] = $value;
			}
		}

		foreach ($orders as &$order)
		{
			if (isset($order['PERSON_TYPE_ID']))
			{
				$type = $personTypes[$order['PERSON_TYPE_ID']];
				$order['PERSON_TYPE_ID'] = $type['NAME'];
			}
			$order['CURRENCY_ID'] = $order['CURRENCY'];
			$order['USER'] = $order['USER_ID'];
			$order['CURRENCY'] = $currencies[$order['CURRENCY']];
			$order['TITLE'] = Loc::getMessage('CRM_KANBAN_ORDER_TITLE', [
				'#ACCOUNT_NUMBER#' => $order['ACCOUNT_NUMBER']
			]);
		}

		return $orders;
	}

	/**
	 * Refresh deals accounts.
	 * @return array
	 */
	protected function actionRefreshAccount()
	{
		$ids = $this->request('id');
		$idForUpdate = array();

		if ($ids && $this->type == $this->types['deal'])
		{
			$userPermissions = $this->getCurrentUserPermissions();
			if (!is_array($ids))
			{
				$ids = array($ids);
			}
			foreach ($ids  as $id)
			{
				$categoryId = $this->categoryId;
				if (\CCrmDeal::checkUpdatePermission($id, $userPermissions, $categoryId))
				{
					$idForUpdate[] = $id;
				}
			}
		}
		if (!empty($idForUpdate))
		{
			\CCrmDeal::refreshAccountingData($idForUpdate);
		}

		return array();
	}

	/**
	 * Set assigned id.
	 * @return array
	 */
	protected function actionSetAssigned()
	{
		$ids = $this->request('ids');
		$assignedId = $this->request('assignedId');
		$type = $this->type;
		$types = $this->types;
		$userPerms = $this->getCurrentUserPermissions();

		if ($type === $types['order'])
		{
			$assignedId = (int)$assignedId;
			if (!empty($ids) && $assignedId > 0)
			{
				if (!is_array($ids))
				{
					$ids = [$ids];
				}
				foreach ($ids as $id)
				{
					if (Order\Permissions\Order::checkUpdatePermission($id, $userPerms))
					{
						$order = Order\Order::load($id);
						$order->setField('RESPONSIBLE_ID', $assignedId);
						$order->save();
					}
				}
			}
			return [];
		}

		$provider = '\CCrm'.$type;

		if (
			$ids && $assignedId &&
			method_exists($provider, 'update')
		)
		{
			if (!is_array($ids))
			{
				$ids = array($ids);
			}
			$key = $type == $types['invoice'] ? 'RESPONSIBLE_ID' : 'ASSIGNED_BY_ID';
			$entity = new $provider();
			foreach ($ids as $id)
			{
				$fields = array(
					$key => $assignedId
				);
				$entity->update($id, $fields);
				$this->runAutomationOnUpdate($id, $fields);
			}
		}

		return array();
	}

	/**
	 * Make open / close.
	 * @return array
	 */
	protected function actionOpen()
	{
		$ids = $this->request('id');
		$flag = $this->request('flag');
		$type = $this->type;
		$types = $this->types;
		$provider = '\CCrm' . $type;

		if (
			$ids && $type != $types['invoice'] &&
			method_exists($provider, 'update')
		)
		{
			if (!is_array($ids))
			{
				$ids = array($ids);
			}
			$entity = new $provider();
			foreach ($ids as $id)
			{
				$fields = array(
					'OPENED' => $flag
				);
				$entity->update($id, $fields);
				$this->runAutomationOnUpdate($id, $fields);
			}
		}

		return array();
	}

	/**
	 * Change category of deals.
	 * @return array
	 */
	protected function actionChangeCategory()
	{
		$ids = $this->request('id');
		$category = (int)$this->request('category');
		$type = $this->type;
		$types = $this->types;
		$provider = '\CCrm' . $type;

		$userPermissions = $this->getCurrentUserPermissions();
		$catPerm = array_fill_keys(
			\CCrmDeal::GetPermittedToReadCategoryIDs($userPermissions),
			true
		);

		if (
			$ids &&
			isset($catPerm[$category]) &&
			$type == $types['deal'] &&
			method_exists($provider, 'update')
		)
		{

			if (!is_array($ids))
			{
				$ids = array($ids);
			}
			foreach ($ids as $id)
			{
				$id = (int)$id;
				if (
					$id > 0 &&
					\CCrmDeal::checkUpdatePermission($id, $userPermissions) &&
					\CCrmDeal::CheckCreatePermission($userPermissions, $category)
				)
				{
					// collect options
					$recurringData = \Bitrix\Crm\Recurring\Manager::getList(
						[
							'filter' => ['DEAL_ID' => $id],
							'limit' => 1
						],
						\Bitrix\Crm\Recurring\Manager::DEAL
					);
					$options = null;
					if ($recurringData->fetch())
					{
						$options = ['REGISTER_STATISTICS' => false];
					}
					// move
					$error = \CCrmDeal::moveToCategory($id, $category, $options);
					// automation
					if ($error === \Bitrix\Crm\Category\DealCategoryChangeError::NONE)
					{
						$errors = null;
						\CCrmBizProcHelper::autoStartWorkflows(
							\CCrmOwnerType::Deal,
							$id,
							\CCrmBizProcEventType::Edit,
							$errors
						);
						$dbResult = \CCrmDeal::GetListEx(
							array(),
							array('=ID' => $id, 'CHECK_PERMISSIONS' => 'N'),
							false,
							false,
							['STAGE_ID', 'CATEGORY_ID']
						);
						$newFields = $dbResult->Fetch();

						$starter = new Bitrix\Crm\Automation\Starter(CCrmOwnerType::Deal, $id);
						$starter->setUserIdFromCurrent()->runOnUpdate($newFields, []);
					}
				}
			}
			return [];
		}

		return [];
	}

	/**
	 * Show or hide contact center block in option.
	 * @return array
	 */
	protected function actionToggleCC()
	{
		$hidden = \CUserOptions::getOption(
			'crm',
			'kanban_cc_hide',
			false
		);
		\CUserOptions::setOption(
			'crm',
			'kanban_cc_hide',
			!$hidden
		);
		return [];
	}

	/**
	 * Show or hide REST demo block in option.
	 * @return array
	 */
	protected function actionToggleRest()
	{
		$hidden = \CUserOptions::getOption(
			'crm',
			'kanban_rest_hide',
			false
		);
		\CUserOptions::setOption(
			'crm',
			'kanban_rest_hide',
			!$hidden
		);
		return [];
	}
}