%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /proc/self/root/home/bitrix/www/bitrix/modules/sale/lib/helpers/order/builder/
Upload File :
Create Path :
Current File : //proc/self/root/home/bitrix/www/bitrix/modules/sale/lib/helpers/order/builder/basketbuilder.php

<?
namespace Bitrix\Sale\Helpers\Order\Builder;

use Bitrix\Main\Config\Option;
use Bitrix\Main\Error;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
use Bitrix\Sale\Basket;
use Bitrix\Sale\BasketItem;
use Bitrix\Sale\BasketItemBase;
use Bitrix\Sale\Discount;
use Bitrix\Sale\DiscountCouponsManager;
use Bitrix\Sale\Helpers\Admin\Blocks\OrderBasket;
use Bitrix\Sale\Helpers\Admin\OrderEdit;
use Bitrix\Sale\Order;
use Bitrix\Sale\Provider;

/**
 * Class BasketBuilder
 * @package Bitrix\Sale\Helpers\Order\Builder
 * @internal
 */
abstract class BasketBuilder
{
	const BASKET_CODE_NEW = 'new';

	/** @var IBasketBuilderDelegate */
	protected $delegate = null;
	/** @var OrderBuilder  */
	protected $builder = null;
	/** @var int  */
	protected $maxBasketCodeIdx = 0;
	/** @var array  */
	protected $formData = [];
	/** @var array  */
	protected $needDataUpdate = array();
	/** @var array  */
	protected $basketCodeMap = [];
	/** @var array  */
	protected $cacheProductProviderData = false;
	/** @var array  */
	protected $catalogProductsIds = [];
	/** @var array  */
	protected $catalogProductsData = [];
	/** @var array  */
	protected $providerData = [];
	/** @var array  */
	protected $trustData = [];
	/** @var bool */
	protected $isProductAdded = false;

	public function __construct(OrderBuilder $builder)
	{
		$this->builder = $builder;
		$this->cacheProductProviderData = $this->builder->getSettingsContainer()->getItemValue('cacheProductProviderData');
	}

	public function initBasket()
	{
		$this->formData = $this->builder->getFormData();
		$this->delegate = $this->getDelegate($this->formData['ID']);
		\Bitrix\Sale\ProviderBase::setUsingTrustData(true);
		return $this;
	}

	/**
	 * @param int $orderId
	 * @return IBasketBuilderDelegate
	 */
	protected function getDelegate($orderId)
	{
		return (int)$orderId > 0 ? new BasketBuilderExist($this) : new BasketBuilderNew($this);
	}

	/**
	 * @return bool
	 */
	public function isNeedUpdateNewProductPrice()
	{
		return $this->builder->getSettingsContainer()->getItemValue('needUpdateNewProductPrice');
	}

	public function checkProductData(array $productData)
	{
		$result = true;

		if((int)$productData['QUANTITY'] <= 0)
		{
			$this->getErrorsContainer()->addError(
				new Error(
					Loc::getMessage(
						"SALE_HLP_ORDERBUILDER_QUANTITY_ERROR",
						['#PRODUCT_NAME#' => $productData['NAME']]
			)));

			$result = false;
		}

		if((int)$productData['PRICE'] < 0)
		{
			$this->getErrorsContainer()->addError(
				new Error(
					Loc::getMessage(
						"SALE_HLP_ORDERBUILDER_PRICE_ERROR",
						['#PRODUCT_NAME#' => $productData['NAME']]
			)));

			$result = false;
		}

		return $result;
	}

	public function preliminaryDataPreparation()
	{
		$sort = 100;

		foreach($this->formData["PRODUCT"] as $basketCode => $productData)
		{

			if(!$this->checkProductData($productData))
			{
				throw new BuildingException();
			}

			/*
			 * Fix collision if price of new product is larger than added earlier have.
			 * After sorting new product pick basket code from existing products.
			 * See below.
			 */
			$this->formData["PRODUCT"][$basketCode]["SORT"] = $sort;
			$sort += 100;

			if(self::isBasketItemNew($basketCode))
			{
				$basketInternalId = intval(substr($basketCode, 1));

				if($basketInternalId > $this->maxBasketCodeIdx)
					$this->maxBasketCodeIdx = $basketInternalId;

				// by the way let's mark rows for update data if need
				if($this->isNeedUpdateNewProductPrice()) //???is it right for edit orders
				{
					$this->needDataUpdate[] = $basketCode; //???is it needed by new orders
					unset($this->formData["PRODUCT"][$basketCode]["PROVIDER_DATA"]);
					unset($this->formData["PRODUCT"][$basketCode]["SET_ITEMS_DATA"]);
				}
			}
		}

		sortByColumn($this->formData["PRODUCT"], array("BASE_PRICE" => SORT_DESC, "PRICE" => SORT_DESC), '', null, true);
		return $this;
	}

	public function removeDeletedItems()
	{
		$itemsBasketCodes = [];

		foreach($this->formData["PRODUCT"] as $basketCode => $productData)
		{
			if (!isset($productData["PROPS"]))
			{
				$productData["PROPS"] = array();
			}

			$item = $this->getBasket()->getExistsItem($productData["MODULE"], $productData["OFFER_ID"], $productData["PROPS"]);

			if ($item == null)
			{
				DiscountCouponsManager::useSavedCouponsForApply(false);
			}

			if($item == null && $basketCode != \Bitrix\Sale\Helpers\Admin\OrderEdit::BASKET_CODE_NEW)
			{
				$item = $this->getBasket()->getItemByBasketCode($basketCode);
			}

			if($item && $item->isBundleChild())
			{
				continue;
			}

			if(!$item)
			{
				continue;
			}

			$itemsBasketCodes[] = $item->getBasketCode();
		}

		/** @var  \Bitrix\Sale\BasketItem  $item */
		$basketItems = $this->getBasket()->getBasketItems();

		foreach($basketItems as $item)
		{
			if(!in_array($item->getBasketCode(), $itemsBasketCodes))
			{
				$res = $item->delete();

				if (!$res->isSuccess())
				{
					$this->builder->getErrorsContainer()->addErrors($res->getErrors());
					throw new BuildingException();
				}
			}
		}

		return $this;
	}

	public function itemsDataPreparation()
	{
		foreach($this->formData["PRODUCT"] as $basketCode => $productData)
		{
			if($productData["IS_SET_ITEM"] == "Y")
				continue;

			if(!isset($productData["PROPS"]) || !is_array($productData["PROPS"]))
				$productData["PROPS"] = array();

			/** @var BasketItem $item */
			$item = $this->getItemFromBasket($basketCode, $productData);

			if($item)
			{
				$this->setItemData($basketCode, $productData, $item);
			}
			else
			{
				$item = $this->createItem($basketCode, $productData);

				if(!$this->isProductAdded)
				{
					$this->isProductAdded = true;
				}
			}

			/*
			 * Could be deleted and than added one more time product.
			 * Or just added product.
			 */
			if($basketCode != $item->getBasketCode())
				$this->basketCodeMap[$basketCode] = $item->getBasketCode();

			if(!empty($productData["PROPS"]) && is_array($productData["PROPS"]))
			{
				/** @var \Bitrix\Sale\BasketPropertiesCollection $property */
				$property = $item->getPropertyCollection();
				$property->setProperty($productData["PROPS"]);
			}
		}

		return $this;
	}

	public function basketCodeMap()
	{
		if(!empty($this->basketCodeMap))
		{
			foreach($this->basketCodeMap as $old => $new)
			{
				$this->formData['PRODUCT'][$new] = $this->formData['PRODUCT'][$old];
				unset($this->formData['PRODUCT'][$old]);
			}
		}

		return $this;
	}

	// Preparing fields needed by provider
	protected function setItemsFieldsByFormData()
	{
		/** @var  \Bitrix\Sale\BasketItem  $item */
		$basketItems = $this->getBasket()->getBasketItems();

		foreach($basketItems as $item)
		{
			$basketCode = $item->getBasketCode();

			if(empty($this->formData['PRODUCT'][$basketCode]))
				continue;

			$productData = $this->formData['PRODUCT'][$basketCode];
			$isProductDataNeedUpdate = in_array($basketCode, $this->needDataUpdate);

			if(isset($productData["PRODUCT_PROVIDER_CLASS"]) && strlen($productData["PRODUCT_PROVIDER_CLASS"]) > 0)
			{
				$item->setField("PRODUCT_PROVIDER_CLASS", trim($productData["PRODUCT_PROVIDER_CLASS"]));
			}

			/*
			 * Let's extract cached provider product data from field
			 * in case activity is through ajax.
			 */
			if($this->cacheProductProviderData && !$isProductDataNeedUpdate)
			{
				self::sendProductCachedDataToProvider($item, $this->getOrder(), $productData);
			}

			$item->setField("NAME", $productData["NAME"]);
			$res = $item->setField("QUANTITY", $productData["QUANTITY"]);

			if(!$res->isSuccess())
			{
				$this->getErrorsContainer()->addErrors($res->getErrors());
				throw new BuildingException();
			}

			if(isset($productData["MODULE"]) && $productData["MODULE"] == "catalog")
			{
				$this->catalogProductsIds[] = $item->getField('PRODUCT_ID');
			}
			elseif(empty($productData["PRODUCT_PROVIDER_CLASS"]))
			{
				$availableFields = BasketItemBase::getAvailableFields();
				$availableFields = array_fill_keys($availableFields, true);
				$fillFields = array_intersect_key($productData, $availableFields);
				
				$orderCurrency = $this->getOrder()->getCurrency();
				if ($fillFields['CURRENCY'] !== $orderCurrency)
				{
					$fillFields['PRICE'] = \CCurrencyRates::ConvertCurrency(
						(float)$fillFields['PRICE'],
						$fillFields['CURRENCY'],
						$orderCurrency
					);
					$fillFields['BASE_PRICE'] = \CCurrencyRates::ConvertCurrency(
						(float)$fillFields['BASE_PRICE'],
						$fillFields['CURRENCY'],
						$orderCurrency
					);
					$fillFields['CURRENCY'] = $orderCurrency;
				}

				if (!empty($fillFields))
				{
					$r = $item->setFields($fillFields);

					if(!$r->isSuccess())
					{
						$this->getErrorsContainer()->getErrors($r->addErrors());
					}
				}
			}
		}
	}

	protected function obtainCatalogProductsData(array $fields = array())
	{
		if(empty($this->catalogProductsIds))
			return;

		$order = $this->getOrder();
		$this->catalogProductsIds = array();

		foreach($this->catalogProductsIds as  $id)
		{
			$details = OrderEdit::getProductDetails($id, $order->getUserId(), $order->getSiteId());

			if($details !== false)
				$this->catalogProductsData[$id] = $details;
		}

		$noCachedProductIds = array_diff($this->catalogProductsIds, array_keys($this->catalogProductsData));

		if(!empty($noCachedProductIds))
		{
			$noCachedData = \Bitrix\Sale\Helpers\Admin\Product::getData($noCachedProductIds, $order->getSiteId(), array_keys($fields));

			foreach($noCachedData as $productId => $productData)
			{
				$this->catalogProductsData[$productId] = $productData;
				OrderEdit::setProductDetails($productId, $order->getUserId(), $order->getSiteId(), $this->catalogProductsData[$productId]);
			}

			$emptyData = array_diff($this->catalogProductsIds, array_keys($this->catalogProductsData));

			foreach($emptyData as $productId)
				$this->catalogProductsData[$productId] = array();
		}
	}

	protected function obtainProviderProductsData()
	{
		$order = $this->getOrder();
		$basketItems = $this->getBasket()->getBasketItems();

		if($this->cacheProductProviderData && empty($this->needDataUpdate) && !$this->isNeedUpdateNewProductPrice())
			return;

		$params = array("AVAILABLE_QUANTITY");

		if($order->getId() <= 0)
			$params[] = "PRICE";

		$this->providerData = Provider::getProductData($this->getBasket(), $params);

		/** @var BasketItem $item */
		foreach($basketItems as $item)
		{
			$basketCode = $item->getBasketCode();

			if($order->getId() <= 0 && !empty($this->providerData[$basketCode]) && empty($this->providerData[$basketCode]['QUANTITY']))
			{

				$this->getErrorsContainer()->addError(
					new Error(
						Loc::getMessage(
							"SALE_ORDEREDIT_PRODUCT_QUANTITY_IS_EMPTY",
							array(
								"#NAME#" => $item->getField('NAME')
							)
						),
						'SALE_ORDEREDIT_PRODUCT_QUANTITY_IS_EMPTY'
					)
				);

				throw new BuildingException();
			}
		}
	}

	protected function isProductAvailable($basketCode, $productFormData, $productProviderData, $isProductDataNeedUpdate)
	{
		$result = true;

		if($this->getOrder()->getId() <= 0 && (empty($productProviderData[$basketCode]) || !$this->cacheProductProviderData || $isProductDataNeedUpdate))
		{
			if(empty($productProviderData[$basketCode]) && strlen($productFormData["PRODUCT_PROVIDER_CLASS"]) > 0)
			{
				$result = false;
			}
		}

		return $result;
	}

	//todo: \Bitrix\Catalog\Product\Basket::addProductToBasket()
	public function setItemsFields()
	{
		$order = $this->getOrder();
		$basketItems = $this->getBasket()->getBasketItems();

		$this->setItemsFieldsByFormData();
		$this->obtainCatalogProductsData();
		$this->obtainProviderProductsData();

		$productProviderData = array();

		/** @var BasketItem $item */
		foreach($basketItems as $item)
		{
			$basketCode = $item->getBasketCode();
			$productFormData = $this->formData['PRODUCT'][$basketCode];
			$isProductDataNeedUpdate = in_array($basketCode, $this->needDataUpdate);
			$productProviderData[$basketCode] = $item->getFieldValues();

			if(!empty($this->providerData[$basketCode]))
			{
				if ($this->builder->getSettingsContainer()->getItemValue('isRefreshData') === true)
				{
					unset($this->providerData[$basketCode]['QUANTITY']);
				}

				$productProviderData[$basketCode] = $this->providerData[$basketCode];
			}
			elseif(!empty($trustData[$basketCode]))
			{
				$productProviderData[$basketCode] = $trustData[$basketCode];
			}
			else
			{
				$productProviderData = Provider::getProductData($this->getBasket(), array("PRICE", "AVAILABLE_QUANTITY"), $item);

				if(is_array($productProviderData[$basketCode]) && !empty($productProviderData[$basketCode]))
					OrderEdit::setProviderTrustData($item, $order, $productProviderData[$basketCode]);
			}

			/* Get actual info from provider
			 *	cases:
			 *	 1) add new product to basket;
			 *	 2) saving operation;
			 * 	 3) changing quantity;
			 *   4) changing buyerId
			 */

			if(!$this->isProductAvailable($basketCode, $productFormData, $productProviderData, $isProductDataNeedUpdate))
			{
				$this->getErrorsContainer()->addError(
					new Error(
						Loc::getMessage(
							"SALE_HLP_ORDERBUILDER_PRODUCT_NOT_AVILABLE",
							array(
								"#NAME#" => $productFormData["NAME"] . (!empty($productFormData["PRODUCT_ID"]) ? " (".$productFormData['PRODUCT_ID'].")" : "")
							)
						)
					)
				);
			}

			$product = array();

			//merge catalog data
			if($item->getField("MODULE") == "catalog")
			{
				if(!empty($catalogData[$item->getProductId()]))
				{
					$product = array_merge($product, $catalogData[$item->getProductId()]);
					unset($productFormData["CURRENCY"]);
				}
			}

			//merge form data
			if(!$this->cacheProductProviderData || $isProductDataNeedUpdate)
			{
				$product = array_merge($productFormData, $product);
			}
			else
			{
				$needUpdateItemPrice = $this->isNeedUpdateNewProductPrice() && $this->isBasketItemNew($basketCode);
				$isPriceCustom = isset($productFormData['CUSTOM_PRICE']) && $productFormData['CUSTOM_PRICE'] == 'Y';

				if(($order->getId() <= 0 && !$isPriceCustom) || $needUpdateItemPrice)
					unset($productFormData['PRICE'], $productFormData['PRICE_BASE'], $productFormData['BASE_PRICE']);

				$product = array_merge($product, $productFormData);
			}

			if(isset($product["OFFER_ID"]) && intval($product["OFFER_ID"]) > 0)
				$product["PRODUCT_ID"] = $product["OFFER_ID"];

			//discard BasketItem redundant fields
			$product = array_intersect_key($product, array_flip($item::getAvailableFields()));

			if(isset($product["MEASURE_CODE"]) && strlen($product["MEASURE_CODE"]) > 0)
			{
				$measures = OrderBasket::getCatalogMeasures();

				if(isset($measures[$product["MEASURE_CODE"]]) && strlen($measures[$product["MEASURE_CODE"]]) > 0)
					$product["MEASURE_NAME"] = $measures[$product["MEASURE_CODE"]];
			}

			$product["CURRENCY"] = $order->getCurrency();

			if($productFormData["IS_SET_PARENT"] == "Y")
				$product["TYPE"] = BasketItem::TYPE_SET;

			OrderEdit::setProductDetails(
				$productFormData["OFFER_ID"],
				$order->getUserId(),
				$order->getSiteId(),
				array_merge($product, $productFormData)
			);

			self::setBasketItemFields($item, $product);
		}

		return $this;
	}

	public function getOrder()
	{
		return $this->builder->getOrder();
	}

	public function getSettingsContainer()
	{
		return $this->builder->getSettingsContainer();
	}

	public function getErrorsContainer()
	{
		return $this->builder->getErrorsContainer();
	}

	public function getFormData()
	{
		return $this->formData;
	}

	public function getBasket()
	{
		return $this->builder->getOrder()->getBasket();
	}

	public static function isBasketItemNew($basketCode)
	{
		return (strpos($basketCode, 'n') === 0) && ($basketCode != self::BASKET_CODE_NEW);
	}

	protected function getItemFromBasket($basketCode, $productData)
	{
		/** @var BasketItem $item */
		$item = $this->delegate->getItemFromBasket($basketCode, $productData);

		if($item && $item->isBundleChild())
			$item = null;

		return $item;
	}

	protected function setItemData($basketCode, &$productData, &$item)
	{
		return $this->delegate->setItemData($basketCode, $productData, $item);
	}

	protected function createItem($basketCode, &$productData)
	{
		//todo: is it stil working?
		if($basketCode != self::BASKET_CODE_NEW)
			$setBasketCode = $basketCode;
		elseif(intval($this->maxBasketCodeIdx) > 0)
			$setBasketCode = 'n'.strval($this->maxBasketCodeIdx+1); //Fix collision part 2.
		else
			$setBasketCode = null;

		$item = $this->getBasket()->createItem(
			$productData["MODULE"],
			$productData["OFFER_ID"],
			$setBasketCode
		);

		if ($basketCode != $productData["BASKET_CODE"])
			$productData["BASKET_CODE"] = $item->getBasketCode();

		if($basketCode == self::BASKET_CODE_NEW)
		{
			//$result->setData(array("NEW_ITEM_BASKET_CODE" => $productData["BASKET_CODE"]));
			$needDataUpdate[] = $item->getBasketCode();
		}

		if(!empty($productData['REPLACED']) && $productData['REPLACED'] == 'Y')
			$this->needDataUpdate[] = $item->getBasketCode();

		return $item;
	}

	public function getCatalogMeasures()
	{
		static $result = null;
		$catalogIncluded = null;

		if(!is_array($result))
		{
			$result = array();

			if ($catalogIncluded === null)
			{
				$catalogIncluded = Loader::includeModule('catalog');
			}

			if ($catalogIncluded)
			{
				$dbList = \CCatalogMeasure::getList();

				while($arList = $dbList->Fetch())
				{
					$result[$arList["CODE"]] = ($arList["SYMBOL_RUS"] != '' ? $arList["SYMBOL_RUS"] : $arList["SYMBOL_INTL"]);
				}
			}

			if (empty($result))
			{
				$result[796] = GetMessage("SALE_ORDER_BASKET_SHTUKA");
			}
		}

		return $result;
	}

	public function sendProductCachedDataToProvider(BasketItem $item, Order $order, array &$productData)
	{
		if(empty($productData["PROVIDER_DATA"]) || !CheckSerializedData($productData["PROVIDER_DATA"]))
			return;

		$trustData = unserialize($productData["PROVIDER_DATA"]);

		//quantity was changed so data must be changed
		if(empty($trustData) || $trustData["QUANTITY"] == $productData["QUANTITY"])
			return;

		Provider::setTrustData($order->getSiteId(), $item->getField('MODULE'), $item->getProductId(), $trustData);

		if ($item->isBundleParent())
		{
			if ($bundle = $item->getBundleCollection())
			{
				/** @var \Bitrix\Sale\BasketItem $bundleItem */
				foreach ($bundle as $bundleItem)
				{
					$bundleItemData = $bundleItem->getFields()->getValues();
					Provider::setTrustData($order->getSiteId(), $bundleItem->getField('MODULE'), $bundleItem->getProductId(), $bundleItemData);
				}
			}
		}

		$this->trustData[$item->getBasketCode()] = $trustData;
	}

	public function setBasketItemFields(\Bitrix\Sale\BasketItem &$item, array $fields = array())
	{
		$result = $item->setFields($fields);

		if(!$result->isSuccess())
		{
			foreach($result->getErrors() as $error)
			{
				$containerErrors = $this->getErrorsContainer()->getErrors();

				//avoid duplication
				if(is_array($containerErrors) && !empty($containerErrors))
				{
					foreach($this->getErrorsContainer()->getErrors() as $existError)
					{
						if($error->getMessage() !== $existError->getMessage())
						{
							$this->getErrorsContainer()->addError($error);
						}
					}
				}
				else
				{
					$this->getErrorsContainer()->addError($error);
				}
			}

			throw new BuildingException();
		}
	}

	public function finalActions()
	{
		$this->delegate->finalActions();
		return $this;
	}

	public function isProductAdded()
	{
		return $this->isProductAdded;
	}
}

Zerion Mini Shell 1.0