Your IP : 3.133.135.89


Current Path : /home/bitrix/ext_www/klimatlend.ua/bitrix/modules/sale/lib/delivery/services/
Upload File :
Current File : /home/bitrix/ext_www/klimatlend.ua/bitrix/modules/sale/lib/delivery/services/manager.php

<?php
namespace Bitrix\Sale\Delivery\Services;

use Bitrix\Main\Config\Option;

use Bitrix\Main\Error;
use Bitrix\Main\Event;
use Bitrix\Sale\Internals\Pool;
use Bitrix\Sale\Result;
use Bitrix\Main\IO\File;
use Bitrix\Sale\Shipment;
use Bitrix\Main\IO\Directory;
use Bitrix\Main\SystemException;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Entity\EventResult;
use Bitrix\Main\ArgumentNullException;
use Bitrix\Sale\Delivery\Restrictions;
use Bitrix\Sale\Delivery\ExtraServices;
use Bitrix\Sale\Internals\ShipmentTable;
use Bitrix\Sale\Delivery\CalculationResult;
use Bitrix\Sale\Internals\ServiceRestrictionTable;

Loc::loadMessages(__FILE__);

/**
 * Class Manager
 * Helps to manage delivery services.
 * @package Bitrix\Sale\Delivery\Services
 */
class Manager
{
	const SKIP_PROFILE_PARENT_CHECK = 0;
	const SKIP_CHILDREN_PARENT_CHECK = 1;

	protected static $handlers = null;
	protected static $cachedFields = array();

	/* Directories where we can found handlers */
	protected static $handlersDirectories = array();
	/** @var null|Pool  */
	protected static $objectsPool = null;

	/**
	 * Returns service field, caches results during hit.
	 * @param int $deliveryId
	 * @return array Service fields
	 * @throws SystemException
	 * @throws \Bitrix\Main\ArgumentException
	 */
	public static function getById($deliveryId)
	{
		$id = intval($deliveryId);

		if($id <= 0)
			throw new SystemException("deliveryId");

		if(!isset(self::$cachedFields[$deliveryId]) || !is_array(self::$cachedFields[$deliveryId]))
		{
			self::$cachedFields[$deliveryId] = array();

			$resSrvParams = Table::getList(array(
				'filter' => array("=ID" =>  $deliveryId)
			));

			if($srvParams = $resSrvParams->fetch())
				self::$cachedFields[$srvParams["ID"]] = $srvParams;
		}

		return self::$cachedFields[$deliveryId];
	}

	/**
	 * @param Shipment $shipment.
	 * @param int $restrictionMode MODE_CLIENT or MODE_MANAGER from Restrictions\Manager.
	 * @return Base[] delivery services objects
	 */
	public static function getRestrictedObjectsList(Shipment $shipment, $restrictionMode = Restrictions\Manager::MODE_CLIENT)
	{
		$result = array();
		$services = self::getRestrictedList($shipment, $restrictionMode);

		foreach($services as $srvParams)
		{
			if($srvParams["CLASS_NAME"]::canHasProfiles())
				continue;

			if(is_callable($srvParams["CLASS_NAME"]."::canHasChildren") && $srvParams["CLASS_NAME"]::canHasChildren())
				continue;

			$service = self::getPooledObject($srvParams);

			if(!$service)
				continue;

			if(!$service->isCompatible($shipment))
				continue;

			if($shipment->getCurrency() != $service->getCurrency())
			{
				$service->getExtraServices()->setOperationCurrency(
					$shipment->getCurrency()
				);
			}

			$result[$srvParams["ID"]] = $service;
		}

		return $result;
	}

	/**
	 * @param $deliveryId
	 * @return bool is service exists or not
	 * @throws SystemException
	 */
	public static function isServiceExist($deliveryId)
	{
		if(intval($deliveryId) <= 0)
			return false;

		$srv = self::getById($deliveryId);
		return !empty($srv);
	}

	/**
	 * Prepares and caches data during the hit
	 * @param bool $calculatingOnly If we need absolutely all, or calculating items only.
	 * @param array $restrictedIds If we have list of services ids, successful checked restrictions.
	 * @return array Array of active delivery services fields.
	 * @throws \Bitrix\Main\ArgumentException
	 */
	public static function getActiveList($calculatingOnly = false, $restrictedIds = null)
	{
		//If we alredy got all active services
		static $isDataPrepared = false;
		static $activeIds = array();
		static $canHasProfiles = array();
		static $canHasChildren = array();
		static $DontHaveRestrictionsIds = array();

		if(is_array($restrictedIds))
		{
			$unPreparedRestrictedIds = array_diff_key(
				array_flip(
					$restrictedIds
				),
				self::$cachedFields
			);

			$unPreparedRestrictedIds = array_keys($unPreparedRestrictedIds);
		}
		else
		{
			$unPreparedRestrictedIds = array();
		}

		if(!$isDataPrepared || !empty($unPreparedRestrictedIds))
		{
			$result = array();
			self::initHandlers();

			$filter = array(
				'=ACTIVE' => 'Y'
			);

			$params =  array(
				'order' => array('SORT' =>'ASC', 'NAME' => 'ASC'),
				'filter' => $filter
			);

			$params['runtime'] = array(
				new \Bitrix\Main\Entity\ExpressionField(
					'RESTRICTIONS_EXIST',
					'CASE WHEN EXISTS('.
					'SELECT 1 FROM b_sale_service_rstr SSR WHERE SSR.SERVICE_ID = %s AND SSR.SERVICE_TYPE = '.Restrictions\Manager::SERVICE_TYPE_SHIPMENT.') THEN 1 ELSE 0 END',
					'ID'
				)
			);

			$params['select'] = array('*', 'RESTRICTIONS_EXIST');

			if(!empty($unPreparedRestrictedIds))
			{
				if($isDataPrepared)
				{
					$params['filter']['ID'] = $unPreparedRestrictedIds;
				}
				else
				{
					$params['filter'][] = array(
						"LOGIC" => "OR",
						"ID" => $unPreparedRestrictedIds,
						'RESTRICTIONS_EXIST' => 0
					);
				}
			}

			$dbRes = Table::getList($params);
			$profilesParentsIds = array();

			while($service = $dbRes->fetch())
			{
				if(!is_subclass_of($service["CLASS_NAME"], 'Bitrix\Sale\Delivery\Services\Base'))
					continue;

				if(!$service['RESTRICTIONS_EXIST'])
					$DontHaveRestrictionsIds[] = $service['ID'];

				if($service["CLASS_NAME"]::canHasChildren())
					$canHasChildren[$service["ID"]] = true;

				if ($service['CLASS_NAME']::canHasProfiles())
					$canHasProfiles[$service["ID"]] = true;

				if($service["PARENT_ID"] != 0)
					$profilesParentsIds[$service["PARENT_ID"]] = $service['ID'];

				$result[$service["ID"]] = $service;

				if($service['ACTIVE'] == 'Y')
					$activeIds[$service["ID"]] = $service["ID"];
			}

			foreach(array_diff_key($canHasProfiles, $profilesParentsIds) as $id => $tmp)
				unset($result[$id]);

			self::$cachedFields = self::$cachedFields + $result;
			Restrictions\Manager::prepareData(array_keys($result));
			ExtraServices\Manager::prepareData(array_keys($result));

			//selected all active
			if(!$isDataPrepared && empty($unPreparedRestrictedIds))
				$isDataPrepared = true;
		}

		if(is_array($restrictedIds) && !empty($restrictedIds))
			$storedIds = array_diff($restrictedIds, $unPreparedRestrictedIds);
		else
			$storedIds = array();

		if(!empty($storedIds) && is_array($storedIds))
		{
			foreach($storedIds as $storedId)
			{
				if(empty(self::$cachedFields[$storedId]))
					continue;

				$service = self::$cachedFields[$storedId];

				if(!class_exists($service["CLASS_NAME"]))
					continue;

				if($service["CLASS_NAME"]::canHasChildren())
					$canHasChildren[$storedId] = true;

				if ($service['CLASS_NAME']::canHasProfiles())
					$canHasProfiles[$storedId] = true;

				if($service['ACTIVE'] == 'Y')
					$activeIds[$storedId] = $storedId;
			}
		}

		$active = array_intersect_key(self::$cachedFields, array_flip($activeIds));

		if(is_array($restrictedIds))
		{
			$result = array_intersect_key($active, array_flip($DontHaveRestrictionsIds));

			if(!empty($restrictedIds))
				$result = $result + array_intersect_key($active, array_flip($restrictedIds));
		}
		else
		{
			$result = $active;
		}

		/*
		 * Clean children if parent is not present in result.
		 * We mean that it doesn't pass restrictions checks.
		 * Or it is not active.
		 */

		foreach($result as $id => $fields)
		{
			if(intval($fields['PARENT_ID']) <= 0)
				continue;

			if(empty($result[$fields['PARENT_ID']]))
				unset($result[$id]);
		}

		if($calculatingOnly)
			$result = array_diff_key($result, $canHasChildren, $canHasProfiles);

		if(!empty($result))
			sortByColumn($result, array("SORT" => SORT_ASC, "NAME" => SORT_ASC), '', null, true);

		return $result;
	}

	/**
	 * @param Shipment $shipment
	 * @param int $restrictionMode MODE_CLIENT or MODE_MANAGER from Restrictions\Manager.
	 * @param array $skipChecks self::SKIP_CHILDREN_PARENT_CHECK || self::SKIP_PROFILE_PARENT_CHECK
	 * @return array Array of active delivery services fields filtered by restrictions.
	 */
	public static function getRestrictedList(Shipment $shipment = null, $restrictionMode, array $skipChecks = array())
	{
		$result = array();

		if(empty($skipChecks))
		{
			$skipChecks = array(
				self::SKIP_CHILDREN_PARENT_CHECK,
				self::SKIP_PROFILE_PARENT_CHECK
			);
		}

		//Have restrictions and this restrictions successfully checked
		$restrictedSrvIds = Restrictions\Manager::getRestrictedIds($shipment, $restrictionMode);
		//Don't have any restrictions + successfully checked
		$services = self::getActiveList(false, array_keys($restrictedSrvIds));

		foreach($services as $srvParams)
		{
			$srvParams["RESTRICTED"] = false;

			if(!in_array(self::SKIP_PROFILE_PARENT_CHECK, $skipChecks))
				if($srvParams["CLASS_NAME"]::canHasProfiles())
					continue;

			if(!in_array(self::SKIP_CHILDREN_PARENT_CHECK, $skipChecks))
				if(is_callable($srvParams["CLASS_NAME"]."::canHasChildren") && $srvParams["CLASS_NAME"]::canHasChildren())
					continue;

			if(isset($restrictedSrvIds[$srvParams["ID"]]) &&  $restrictedSrvIds[$srvParams["ID"]] == Restrictions\Manager::SEVERITY_SOFT)
				$srvParams["RESTRICTED"] = true;

			$result[$srvParams["ID"]] = $srvParams;
		}

		return $result;
	}

	/**
	 * @param array $srvParams Delivery service fields from DataBase to construct service object.
	 * @return Base|null Delivery service object
	 * All errors it writes to system log.
	 * It's better to use \Bitrix\Sale\Delivery\Services\Manager::getPooledObject for performance purposes
	 */
	public static function createObject(array $srvParams)
	{
		self::initHandlers();
		$errorMsg = "";
		$service = null;

		if(!isset($srvParams["PARENT_ID"]))
			$srvParams["PARENT_ID"] = 0;

		if(class_exists($srvParams['CLASS_NAME']))
		{
			try
			{
				$service = new $srvParams['CLASS_NAME']($srvParams);
			}
			catch(SystemException $e)
			{
				$errorMsg = $e->getMessage();
			}

			if($service && !($service instanceof Base))
				$errorMsg = "Can't create delivery object. Class ".$srvParams['CLASS_NAME'].' is not the instance of Bitrix\Sale\DeliveryService';
		}
		else
		{
			$errorMsg = "Can't create delivery object. Class \"".$srvParams['CLASS_NAME']."\" does not exist.";
		}

		if(strlen($errorMsg) > 0)
		{
			$eventLog = new \CEventLog;
			$eventLog->Add(array(
				"SEVERITY" => $eventLog::SEVERITY_ERROR,
				"AUDIT_TYPE_ID" => "SALE_DELIVERY_CREATE_OBJECT_ERROR",
				"MODULE_ID" => "sale",
				"ITEM_ID" => 'createObject()',
				"DESCRIPTION" => $errorMsg." Fields: ".serialize($srvParams),
			));

			return null;
		}

		return $service;
	}

	public static function getPooledObject(array $fields)
	{
		if(self::$objectsPool === null)
			self::$objectsPool = new ObjectPool();

		return self::$objectsPool->getObject($fields);
	}

	/**
	 * @param int $deliveryId Delivery service id
	 * @return Base Delivery service object
	 * @throws ArgumentNullException
	 * @throws SystemException
	 */
	public static function getObjectById($deliveryId)
	{
		if(intval($deliveryId) <= 0 )
			throw new ArgumentNullException("deliveryId");

		$srvParams = self::getById($deliveryId);

		if(empty($srvParams))
			return null;

		return self::getPooledObject($srvParams);
	}

	/**
	 * @param string $serviceCode Delivery service code
	 * @return Base Delivery service object.
	 * @throws ArgumentNullException
	 * @throws SystemException
	 * @throws \Bitrix\Main\ArgumentException
	 */
	public static function getObjectByCode($serviceCode)
	{
		if(strlen($serviceCode) <= 0 )
			throw new ArgumentNullException("serviceCode");

		$srvParams = array();

		foreach(self::$cachedFields as $id => $srv)
		{
			if($srv['CODE'] == $serviceCode)
			{
				$srvParams = $srv;
			}
		}

		if(empty($srvParams))
		{
			$resSrvParams = Table::getList(array(
				'filter' => array('=CODE' => $serviceCode)
			));

			if(!($srvParams = $resSrvParams->fetch()))
				throw new SystemException("Can't get delivery service data with code: \"".$serviceCode."\"");

			self::$cachedFields[$srvParams['ID']] = $srvParams;
		}

		return self::getPooledObject($srvParams);
	}

	/**
	 * Gets info about existing delivery services handlers
	 * Stores this information during the hit
	 * @return bool
	 * @throws SystemException
	 * @throws \Bitrix\Main\LoaderException
	 */
	protected static function initHandlers()
	{
		if(self::$handlers !== null)
			return true;

		self::$handlersDirectories = array(
			'LOCAL' => '/local/php_interface/include/sale_delivery/',
			'CUSTOM' => Option::get('sale', 'delivery_handles_custom_path', BX_PERSONAL_ROOT.'/php_interface/include/sale_delivery/'),
			'SYSTEM' => '/bitrix/modules/sale/handlers/delivery/'
		);

		$result = array(
			'\Bitrix\Sale\Delivery\Services\Group' => 'lib/delivery/services/group.php',
			'\Bitrix\Sale\Delivery\Services\Automatic' => 'lib/delivery/services/automatic.php',
			'\Bitrix\Sale\Delivery\Services\Configurable' => 'lib/delivery/services/configurable.php',
			'\Bitrix\Sale\Delivery\Services\AutomaticProfile' => 'lib/delivery/services/automatic_profile.php',
			'\Bitrix\Sale\Delivery\Services\EmptyDeliveryService' => 'lib/delivery/services/emptydeliveryservice.php'
		);

		\Bitrix\Main\Loader::registerAutoLoadClasses('sale', $result);

		/*
		 *	To add you own handler you must wrote smth. like this in for example init.php:
		 *
		 *	function addCustomDeliveryServices()
		 *	{
		 *		return new \Bitrix\Main\EventResult(
		 *			\Bitrix\Main\EventResult::SUCCESS,
		 *			array(
		 *					'\Sale\Handlers\Delivery\SimpleHandler' => '/bitrix/modules/sale/handlers/delivery/simple/handler.php'
		 *			),
		 *			'sale'
		 *		);
		 *	}
		 *
		 *	$eventManager->addEventHandler('sale', 'onSaleDeliveryHandlersClassNamesBuildList', 'addCustomDeliveryServices');
		 */

		$event = new Event('sale', 'onSaleDeliveryHandlersClassNamesBuildList');
		$event->send();
		$resultList = $event->getResults();

		if (is_array($resultList) && !empty($resultList))
		{
			$customClasses = array();

			foreach ($resultList as $eventResult)
			{
				/** @var  \Bitrix\Main\EventResult $eventResult*/
				if ($eventResult->getType() != EventResult::SUCCESS)
					continue;

				$params = $eventResult->getParameters();

				if(!empty($params) && is_array($params))
					$customClasses = array_merge($customClasses, $params);
			}

			if(!empty($customClasses))
			{
				\Bitrix\Main\Loader::registerAutoLoadClasses(null, $customClasses);
				$result = array_merge($result, $customClasses);
			}
		}

		$handlers =  self::getHandlersClasses();

		if(!empty($handlers))
		{
			\Bitrix\Main\Loader::registerAutoLoadClasses(null, $handlers);
			$result = array_merge($result, self::getHandlersClasses());
		}

		self::$handlers = array_keys($result);

		foreach(self::$handlers as $handler)
		{
			$profiles = $handler::getChildrenClassNames();

			if(!empty($profiles))
				self::$handlers = array_merge(self::$handlers, $profiles);
		}

		return true;
	}

	/**
	 * @return array Handler Classes
	 * @throws \Bitrix\Main\IO\FileNotFoundException
	 */
	protected static function getHandlersClasses()
	{
		$result = array();

		foreach(self::$handlersDirectories as $handlersDirectory)
		{
			$dirPath = $_SERVER['DOCUMENT_ROOT'].$handlersDirectory;

			if(!Directory::isDirectoryExists($dirPath))
				continue;

			$dir = new Directory($_SERVER['DOCUMENT_ROOT'].$handlersDirectory);

			foreach ($dir->getChildren() as $handler)
			{
				if (!$handler->isDirectory())
					continue;

				/** @var Directory $handler */

				$handlerDir = $handler->getPath();
				$handlerPath = $handlerDir.'/handler.php';

				if(!File::isFileExists($handlerPath))
					continue;

				$handlerClassName = ucfirst($handler->getName().'Handler');
				$fullClassName = '\Sale\Handlers\Delivery\\'.$handlerClassName;
				$result[$fullClassName] = $handlersDirectory.$handler->getName().'/handler.php';
				require_once($handlerPath);
			}
		}

		return $result;
	}

	/**
	 * @return array Known delivery services handlers
	 * @throws SystemException
	 */
	public static function getHandlersList()
	{
		if(self::$handlers === null)
			self::initHandlers();

		return self::$handlers;
	}

	/**
	 * Calculates the price of the shipment
	 * @param Shipment $shipment
	 * @param int $deliveryId optional
	 * @param array  $extraServices optional
	 * @return \Bitrix\Sale\Delivery\CalculationResult
	 * @throws ArgumentNullException
	 */
	public static function calculateDeliveryPrice(Shipment $shipment, $deliveryId = 0, $extraServices = array())
	{
		if($deliveryId <=0)
			$deliveryId = $shipment->getDeliveryId();

		$delivery = self::getObjectById($deliveryId);

		if($delivery)
		{
			$result = $delivery->calculate($shipment, $extraServices);
		}
		else
		{
			$result = new CalculationResult();
			$result->addError(new Error("Can't create delivery service object with id: \"".$deliveryId."\""));
		}

		return $result;
	}

	/**
	 * Returns id of delivery service group.
	 * Creates if such group does not exist.
	 * @param string $name Group name
	 * @return int Group id
	 * @throws SystemException
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Exception
	 */
	public static function getGroupId($name)
	{
		$res = Table::getList( array(
			'select' => array("ID"),
			'filter' => array(
				"=NAME" => $name,
				"=CLASS_NAME" => '\Bitrix\Sale\Delivery\Services\Group'
			)
		));

		if($group = $res->fetch())
		{
			$result = $group["ID"];
		}
		else
		{
			$res = self::add(array(
				"NAME" => $name,
				"CLASS_NAME" => '\Bitrix\Sale\Delivery\Services\Group',
				"ACTIVE" => "Y"
			));

			if($res->isSuccess())
				$result = $res->getId();
			else
				throw new SystemException(implode("<br>\n",$res->getErrorMessages()));
		}

		return $result;
	}

	/**
	 * @param int $parentId Delivery service parent id
	 * @return array Delivery service fields
	 * @throws \Bitrix\Main\ArgumentException
	 */
	public static function getByParentId($parentId)
	{
		$result = array();
		$srvList = self::getActiveList();

		foreach($srvList as $id => $srv)
		{
			if($srv['PARENT_ID'] == $parentId)
			{
				$result[$srv['ID']] = $srv;
			}
		}

		return $result;
	}

	/**
	 * Returns entity link name for connection with Locations
	 * @return string
	 */
	public static function getLocationConnectorEntityName()
	{
		return	'Bitrix\Sale\Delivery\DeliveryLocation';
	}

	/**
	 * Adds delivery service
	 * @param array $fields
	 * @return \Bitrix\Main\Entity\AddResult
	 * @throws SystemException
	 * @throws \Exception
	 */
	public static function add(array $fields)
	{
		self::initHandlers();
		$res = \Bitrix\Sale\Delivery\Services\Table::add($fields);

		if($res->isSuccess())
		{
			if(!empty($fields["CLASS_NAME"]))
				$fields["CLASS_NAME"]::onAfterAdd($res->getId(), $fields);

			if(!empty($fields['CODE']))
				self::setIdCodeCached($res->getId(), $fields['CODE']);
		}

		return $res;
	}

	/**
	 * Updates delivery service
	 * @param int $id
	 * @param array $fields
	 * @return \Bitrix\Main\Entity\UpdateResult
	 * @throws SystemException
	 * @throws \Exception
	 */
	public static function update($id, array $fields)
	{
		self::initHandlers();
		$res = \Bitrix\Sale\Delivery\Services\Table::update($id, $fields);

		if($res->isSuccess())
		{
			if(!empty($fields["CLASS_NAME"]) && class_exists($fields["CLASS_NAME"]))
				$fields["CLASS_NAME"]::onAfterUpdate($res->getId(), $fields);

			if(isset($fields['CODE']))
				self::cleanIdCodeCached($id);
		}

		return $res;
	}

	/**
	 * Deletes delivery service
	 * @param int $id
	 * @return \Bitrix\Main\Result
	 * @throws ArgumentNullException
	 * @throws SystemException
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Exception
	 */
	public static function delete($id)
	{
		if(intval($id) <= 0)
			throw new ArgumentNullException('id');

		$res = self::checkServiceUsage($id);

		if(!$res->isSuccess())
			return $res;

		self::initHandlers();

		$res = Table::getList(array(
			'filter' => array(
				'ID' => $id
			),
			'select' => array('ID', 'CLASS_NAME', 'LOGOTIP')
		));

		$className = '';
		$logotip = 0;

		if($service = $res->fetch())
		{
			$className = $service['CLASS_NAME'];
			$logotip = intval($service['LOGOTIP']);
		}

		$res = \Bitrix\Sale\Delivery\Services\Table::delete($id);

		if($res->isSuccess())
		{
			if(!empty($className) && class_exists($className))
				$className::onAfterDelete($id);

			self::deleteRelatedEntities($id);

			if($logotip > 0)
				\CFile::Delete($logotip);
		}

		return $res;
	}

	/**
	 * @return array
	 * registerEventHandler(
	 * 	'sale', 'OnGetBusinessValueConsumers', 'sale',
	 * 	'\Bitrix\Sale\Delivery\Services\Manager',
	 * 	'onGetBusinessValueConsumers');
	 */
	public static function onGetBusinessValueConsumers()
	{
		static $result = null;

		if($result !== null)
			return $result;

		$result = array();
		$handlers = self::getHandlersList();
		$consumers = array();

		/** @var Base $handlerClassName */
		foreach($handlers as $handlerClassName)
		{
			$tmpCons = $handlerClassName::onGetBusinessValueConsumers();

			/** @var string $handlerClassName*/
			if(!empty($tmpCons))
				$consumers[$handlerClassName] = $tmpCons;
		}

		$res = Table::getList(array(
			'filter' => array(
				'=CLASS_NAME' => array_keys($consumers),
				'=ACTIVE' => 'Y'
			),
			'select' => array('ID', 'CLASS_NAME', 'NAME')
		));

		while($dlv = $res->fetch())
		{
			$result['DELIVERY_'.$dlv['ID']] = $consumers[$dlv['CLASS_NAME']];
			$result['DELIVERY_'.$dlv['ID']]['NAME'] = $dlv['NAME'];
		}

		return $result;
	}

	public static function onGetBusinessValueGroups()
	{
		$result = array(
			'DELIVERY' => array('NAME' => Loc::getMessage('SALE_DLVR_MNGR_DLV_SRVS'), 'SORT' => 100),
		);

		$handlers = self::getHandlersList();

		/** @var Base $handlerClassName */
		foreach($handlers as $handlerClassName)
			$result = array_merge($result, $handlerClassName::onGetBusinessValueGroups());

		return $result;
	}

	/**
	 * Sets fields values to all children
	 * @param int $id
	 * @param array $fields
	 * @return int count modified children
	 * @throws \Bitrix\Main\ArgumentException
	 */
	public static function setChildrenFieldsValues($id, array $fields)
	{
		if(empty($fields))
			return 0;

		$counter = 0;

		$res = Table::getList(array(
			'filter' => array('PARENT_ID' => $id),
			'select' => array('ID')
		));

		while($child = $res->fetch())
		{
			$ures = self::update($child['ID'], $fields);

			if($ures->isSuccess())
				$counter++;

			$counter += self::setChildrenFieldsValues($child['ID'], $fields);
		}

		return $counter;
	}

	/**
	 * @param string $code
	 * @return int Service id
	 * @throws \Bitrix\Main\ArgumentException
	 */
	public static function getIdByCode($code)
	{
		$result = self::getIdCodeCached($code, "code");

		if($result !== false)
			return $result;

		foreach(self::$cachedFields as $id => $srv)
		{
			if($srv['CODE'] == $code)
			{
				$result = $id;
			}
		}

		if(intval($result) <= 0)
		{
			$res = Table::getList(array(
				'filter' => array(
					'=CODE' => $code
				)
			));

			if($handler = $res->fetch())
			{
				$result = $handler["ID"];
				self::$cachedFields[$handler["ID"]] = $handler;
			}
		}

		$result = intval($result);
		self::setIdCodeCached($result, $code);
		return $result;
	}

	/**
	 * @param int $id
	 * @return string Delivery service code
	 * @throws \Bitrix\Main\ArgumentException
	 */
	public static function getCodeById($id)
	{
		$result = self::getIdCodeCached($id, "id");

		if($result !== false)
			return $result;

		$srv = self::getById($id);

		$result = $srv['CODE'];
		self::setIdCodeCached($id, $result);
		return $result;
	}

	/**
	 *
	 * @param $value
	 * @param $type
	 * @return int|string Id or Code of delivery service
	 */
	protected static function getIdCodeCached($value, $type)
	{
		$result = false;
		$ttl = 315360000;
		$cacheId = "SALE_DELIVERY_ID_CODE_MAP_".($type == "id" ? "I" : "C")."_".$value;
		$cacheManager = \Bitrix\Main\Application::getInstance()->getManagedCache();

		if($cacheManager->read($ttl, $cacheId))
			$result = $cacheManager->get($cacheId);

		return $result;
	}

	/**
	 * Saves relation between Id an code of delivery service
	 * @param int $id
	 * @param string $code
	 */
	protected static function setIdCodeCached($id, $code)
	{
		$cacheManager = \Bitrix\Main\Application::getInstance()->getManagedCache();
		$cacheManager->set("SALE_DELIVERY_ID_CODE_MAP_I_".$id, $code);
		$cacheManager->set("SALE_DELIVERY_ID_CODE_MAP_C_".$code, $id);
	}

	/**
	 * Cleans cache of delivery service id-code relation
	 * @param int $id
	 */
	protected static function cleanIdCodeCached($id)
	{
		$cacheManager = \Bitrix\Main\Application::getInstance()->getManagedCache();
		$code = self::getIdCodeCached($id, "id");
		$cacheManager->clean("SALE_DELIVERY_ID_CODE_MAP_I_".$id);

		if(strlen($code) > 0)
			$cacheManager->clean("SALE_DELIVERY_ID_CODE_MAP_C_".$code);
	}

	/**
	 * Returns is delivery service is already used in shipments
	 * @param $deliveryId
	 * @return bool
	 * @throws \Bitrix\Main\ArgumentException
	 */
	protected static function isDeliveryInShipments($deliveryId)
	{
		$res = ShipmentTable::getList(array(
			'filter' => array(
				"=DELIVERY_ID" => $deliveryId,
				"=SYSTEM" => "N"
			),
			'select' => array("ID")
		));

		if($res->fetch())
			$result = true;
		else
			$result = false;

		return $result;
	}

	/**
	 * Returns if delivery service and it's children are used in shipments
	 * @param $deliveryId
	 * @return Result
	 * @throws \Bitrix\Main\ArgumentException
	 */
	public static function checkServiceUsage($deliveryId)
	{
		$result = new Result();

		if(self::isDeliveryInShipments($deliveryId))
		{
			$result->addError(new Error(Loc::getMessage('SALE_DLVR_MNGR_ERR_DEL_IN_SHPMNTS_EXIST')));
		}
		else
		{
			$dbRes = Table::getList(array(
				'filter' => array(
					"PARENT_ID" => $deliveryId
				),
				'select' => array("ID")
			));

			while($child = $dbRes->fetch())
			{
				if(self::isDeliveryInShipments($child["ID"]))
				{
					$result->addError(new Error(Loc::getMessage('SALE_DLVR_MNGR_ERR_DEL_IN_SHPMNTS_EXIST_CHLD')));
					break;
				}
			}
		}

		return $result;
	}

	/**
	 * Deletes related entities
	 * @param int $deliveryId
	 * @return bool
	 * @throws ArgumentNullException
	 * @throws \Bitrix\Main\ArgumentException
	 * todo: restrictions, extra_services - can require some actions after deletion
	 */
	protected static function deleteRelatedEntities($deliveryId)
	{
		$con = \Bitrix\Main\Application::getConnection();
		$sqlHelper = $con->getSqlHelper();
		$id = $sqlHelper->forSql($deliveryId);

		$con->queryExecute("DELETE FROM b_sale_service_rstr WHERE SERVICE_ID=".$id);
		$con->queryExecute("DELETE FROM b_sale_delivery2location WHERE DELIVERY_ID=".$id);
		$con->queryExecute("DELETE FROM b_sale_delivery2paysystem WHERE DELIVERY_ID=".$id);
		$con->queryExecute("DELETE FROM b_sale_delivery_es WHERE DELIVERY_ID=".$id);

		$dbRes = Table::getList(array(
			'filter' => array(
				"PARENT_ID" => $deliveryId
			),
			'select' => array("ID")
		));

		while($child = $dbRes->fetch())
			self::delete($child["ID"]);

		self::cleanIdCodeCached($deliveryId);
		return true;
	}

	/**
	 * @return int Empty delivery service id
	 * @throws SystemException
	 */
	public static function getEmptyDeliveryServiceId()
	{
		self::initHandlers();
		return \Bitrix\Sale\Delivery\Services\EmptyDeliveryService::getEmptyDeliveryServiceId();
	}

	/**
	 * @internal
	 * @param int|int[] $deliveryId
	 * @param mixed $checkParams
	 * @param string $restrictionClassName
	 * @return bool|int[]
	 * @throws ArgumentNullException
	 * @throws SystemException
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\NotImplementedException
	 */
	public static function checkServiceRestriction($deliveryId, $checkParams, $restrictionClassName)
	{
		if(empty($deliveryId))
			throw new ArgumentNullException("deliveryId");

		if(!is_array($deliveryId))
			$deliveryId = array($deliveryId);

		$dbRstrRes = ServiceRestrictionTable::getList(array(
			'filter' => array(
				'=SERVICE_ID' => $deliveryId,
				'=SERVICE_TYPE' => Restrictions\Manager::SERVICE_TYPE_SHIPMENT,
				'=CLASS_NAME' => $restrictionClassName
			)
		));

		$tmp = array();
		/** @var Restrictions\Base $restriction */
		$restriction = static::getRestrictionObject($restrictionClassName);

		while($rstFields = $dbRstrRes->fetch())
		{
			$rstParams = is_array($rstFields["PARAMS"]) ? $rstFields["PARAMS"] : array();
			$tmp[$rstFields['SERVICE_ID']] = $restriction->check($checkParams, $rstParams, $rstFields['SERVICE_ID']);
		}

		if(count($deliveryId) == 1)
			return current($tmp);

		$result = array();

		foreach($deliveryId as $id)
			if(!isset($tmp[$id]) || (isset($tmp[$id]) && $tmp[$id] == true))
				$result[] = $id;

		return $result;
	}

	/**
	 * Check if given class is valid delivery service class
	 * inheritance of Bitrix\Sale\Delivery\Services\Base.
	 * @param string $class Checking class.
	 * @return bool
	 */
	public static function isDeliveryServiceClassValid($class)
	{
		if(strlen($class) <= 0)
			return false;

		self::initHandlers();

		if(!class_exists($class))
			return false;

		if(!is_subclass_of($class, 'Bitrix\Sale\Delivery\Services\Base'))
			return false;

		return true;
	}

	/*
	 * Deprecated methods. Will be removed in future versions.
	 */

	/**
	 * @deprecated  use \Bitrix\Sale\Delivery\Services\Manager::calculateDeliveryPrice()
	 */
	public static function calculate(Shipment $shipment)
	{
		$delivery = self::getObjectById($shipment->getDeliveryId());
		return $delivery->calculate($shipment);
	}

	/**
	 * @deprecated will be remove in next versions
	 */
	public static function getRestrictionObject($className)
	{
		if(!class_exists($className))
			throw new SystemException("Can't find class: ".$className);

		static $cache = array();

		if(isset($cache[$className]))
			return $cache[$className];

		$restriction = new $className;

		if(!($restriction instanceof Restrictions\Base))
			throw new SystemException('Object must be the instance of Bitrix\Sale\Delivery\Restrictions\Base');

		$cache[$className] = $restriction;
		return  $restriction;
	}

	/**
	 * @deprecated use Restrictions\Manager::checkService()
	 */
	public static function checkServiceRestrictions($deliveryId, Shipment $shipment, $restrictionsClassesToSkip = array())
	{
		return Restrictions\Manager::checkService($deliveryId, $shipment) == Restrictions\Manager::SEVERITY_NONE;
	}

	/**
	 * @deprecated use \Bitrix\Sale\Delivery\Services\Manager::getRestrictedObjectsList()
	 */
	public static function getServicesForShipment(Shipment $shipment)
	{
		return self::getRestrictedObjectsList($shipment);
	}

	/**
	 * @deprecated use \Bitrix\Sale\Delivery\Services\Manager::isServiceExist()
	 */
	public static function isExistService($deliveryId)
	{
		return self::isServiceExist($deliveryId);
	}

	/**
	 * @deprecated use \Bitrix\Sale\Delivery\Services\Manager::getActiveList()
	 */
	public static function getActive()
	{
		return self::getActiveList();
	}

	/**
	 * @deprecated use \Bitrix\Sale\Delivery\Services\Manager::getRestrictedList()
	 */
	public static function getServicesBriefsForShipment(Shipment $shipment = null, array $skipChecks = array(), $getAll = false)
	{
		return self::getRestrictedList($shipment, Restrictions\Manager::MODE_CLIENT, $skipChecks);
	}

	/**
	 * @deprecated use \Bitrix\Sale\Delivery\Services\Manager::createObject()
	 */
	public static function createServiceObject(array $srvParams)
	{
		return self::createObject($srvParams);
	}

	/**
	 * @deprecated use \Bitrix\Sale\Delivery\Services\Manager::getObjectById()
	 */
	public static function getService($deliveryId)
	{
		return self::getObjectById($deliveryId);
	}

	/**
	 * @deprecated use \Bitrix\Sale\Delivery\Services\Manager::getServiceByCode()
	 */
	public static function getServiceByCode($serviceCode)
	{
		return self::getObjectByCode($serviceCode);
	}

	/**
	 * @deprecated use \Bitrix\Sale\Delivery\Services\Manager::getHandlersList()
	 */
	public static function getHandlersClassNames()
	{
		return self::getHandlersList();
	}

	/**
	 * @deprecated use Restrictions\Manager::getClassesList()
	 */
	public static function getRestrictionClassNames()
	{
		return Restrictions\Manager::getClassesList();
	}

	/**
	 * @deprecated use Restrictions\Manager::getRestrictionsList()
	 */
	public static function getRestrictionsByDeliveryId($deliveryId)
	{
		return Restrictions\Manager::getRestrictionsList($deliveryId);
	}
}