• Модуль: crm
  • Путь к файлу: ~/bitrix/modules/crm/classes/general/crm_invoice.php
  • Класс: \CAllCrmInvoice
  • Вызов: CAllCrmInvoice::Add
public function Add($arFields, &$arRecalculated = false, $siteId = SITE_ID, $options = array())
{
	/** @global \CDatabase $DB */
	/** @global \CMain $APPLICATION */
	/** @var CApplicationException $ex */
	global $DB, $APPLICATION;

	if(!CModule::IncludeModule('sale'))
	{
		$this->LAST_ERROR = GetMessage('CRM_MODULE_SALE_NOT_INSTALLED');
		$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
		return false;
	}

	if(!is_array($options))
	{
		$options = array();
	}

	$bRecalculate = is_array($arRecalculated);
	$orderID = false;
	$tmpOrderId = (intval($arFields['ID']) <= 0) ? 0 : $arFields['ID'];
	if (isset($arFields['ID']))
	{
		unset($arFields['ID']);
	}

	$arPrevOrder = ($tmpOrderId !== 0) ? CCrmInvoice::GetByID($tmpOrderId, $this->bCheckPermission) : null;

	$userId = isset($options['CURRENT_USER'])
		? (int)$options['CURRENT_USER'] : CCrmSecurityHelper::GetCurrentUserID();

	if (!isset($arFields['RESPONSIBLE_ID']) || (int)$arFields['RESPONSIBLE_ID'] <= 0)
	{
		if (is_array($arPrevOrder) && isset($arPrevOrder['RESPONSIBLE_ID']) && intval($arPrevOrder['RESPONSIBLE_ID']) > 0)
			$arFields['RESPONSIBLE_ID'] = $arPrevOrder['RESPONSIBLE_ID'];
		else
			$arFields['RESPONSIBLE_ID'] = $userId;
	}

	$orderStatus = '';
	if (isset($arFields['STATUS_ID']))
	{
		$orderStatus = $arFields['STATUS_ID'];
		unset($arFields['STATUS_ID']);
	}
	else
	{
		$arFields['STATUS_ID'] = self::GetDefaultStatusId();
	}

	// prepare entity permissions
	$arAttr = [];
	if (!empty($arFields['OPENED']))
	{
		$arAttr['OPENED'] = $arFields['OPENED'];
	}

	$sPermission = ($tmpOrderId > 0) ? 'WRITE' : 'ADD';
	if($this->bCheckPermission)
	{
		$arEntityAttr = self::BuildEntityAttr($userId, $arAttr);
		$userPerms = ($userId == CCrmPerms::GetCurrentUserID()) ? $this->cPerms : CCrmPerms::GetUserPermissions($userId);
		$sEntityPerm = $userPerms->GetPermType(self::$TYPE_NAME, $sPermission, $arEntityAttr);
		if ($sEntityPerm == BX_CRM_PERM_NONE)
		{
			$this->LAST_ERROR = GetMessage('CRM_PERMISSION_DENIED');
			$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
			return false;
		}

		$responsibleID = intval($arFields['RESPONSIBLE_ID']);
		if ($tmpOrderId === 0 && $sEntityPerm == BX_CRM_PERM_SELF && $responsibleID != $userId)
		{
			$arFields['RESPONSIBLE_ID'] = $userId;
		}
		if ($sEntityPerm == BX_CRM_PERM_OPEN && $userId == $responsibleID)
		{
			$arFields['OPENED'] = 'Y';
		}
	}
	$responsibleID = intval($arFields['RESPONSIBLE_ID']);
	$arEntityAttr = self::BuildEntityAttr($responsibleID, $arAttr);
	$userPerms = ($responsibleID == CCrmPerms::GetCurrentUserID()) ? $this->cPerms : CCrmPerms::GetUserPermissions($responsibleID);
	$sEntityPerm = $userPerms->GetPermType(self::$TYPE_NAME, $sPermission, $arEntityAttr);
	$this->PrepareEntityAttrs($arEntityAttr, $sEntityPerm);

	// date fields
	if ($tmpOrderId === 0)
	{
		$arFields['~DATE_BILL'] = $DB->CharToDateFunction(
			isset($arFields['DATE_BILL']) && $arFields['DATE_BILL'] !== '' ?
				$arFields['DATE_BILL'] : ConvertTimeStamp(time() + CTimeZone::GetOffset(), 'SHORT', SITE_ID),
			'SHORT',
			false
		);
	}
	else if(isset($arFields['DATE_BILL']) && $arFields['DATE_BILL'] !== '')
	{
		$arFields['~DATE_BILL'] = $DB->CharToDateFunction($arFields['DATE_BILL'], 'SHORT', false);
	}
	unset($arFields['DATE_BILL']);
	if(array_key_exists('DATE_PAY_BEFORE', $arFields))
	{
		$dateValue = $arFields['DATE_PAY_BEFORE'];
		if ($dateValue instanceof Main\Type\DateTime || $dateValue instanceof Main\Type\Date)
		{
			$dateValue = (string)$dateValue;
		}
		if (is_string($dateValue) && $dateValue !== '')
		{
			$arFields['~DATE_PAY_BEFORE'] = $DB->CharToDateFunction($dateValue, 'SHORT', false);
			unset($arFields['DATE_PAY_BEFORE']);
		}
		else
		{
			$arFields['DATE_PAY_BEFORE'] = '';
		}
		unset($dateValue);
	}

	$paidStateCanceled = false;
	if ($tmpOrderId > 0 && is_array($arPrevOrder) && !$bRecalculate
		&& isset($arPrevOrder['STATUS_ID']) && $arPrevOrder['STATUS_ID'] === 'P'
		&& isset($arPrevOrder['PAYED']) && $arPrevOrder['PAYED'] === 'Y')
	{
		if (!Compatible\Helper::payOrder($tmpOrderId, false))
		{
			$this->LAST_ERROR = GetMessage('CRM_INVOICE_ERR_CANCEL_PAID_STATE');
			$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
			return false;
		}
		$paidStateCanceled = true;
	}

	if ($tmpOrderId !== 0 && !isset($arFields['PRODUCT_ROWS']) && !isset($arFields['INVOICE_PROPERTIES']))
	{
		if (!isset($arFields['ID']))
			$arFields['ID'] = $tmpOrderId;
		if (!empty($orderStatus))
			$arFields['STATUS_ID'] = $orderStatus;
		foreach (GetModuleEvents('crm', 'OnBeforeCrmInvoiceUpdate', true) as $arEvent)
		{
			if(ExecuteModuleEventEx($arEvent, array(&$arFields)) === false)
			{
				$errMsg = '';
				if(isset($arFields['RESULT_MESSAGE']))
				{
					$errMsg = trim(strval($arFields['RESULT_MESSAGE']));
				}
				else if ($ex = $APPLICATION->GetException())
				{
					$errMsg = trim(strval($ex->GetString()));
				}

				if ($errMsg === '')
				{
					$this->LAST_ERROR = GetMessage('CRM_INVOICE_UPDATE_CANCELED', array('#NAME#' => $arEvent['TO_NAME']));
				}
				else
				{
					$this->LAST_ERROR = $errMsg;
				}
				unset($errMsg);

				$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
				if ($paidStateCanceled)
				{
					Compatible\Helper::payOrder($tmpOrderId, true);
				}
				return false;
			}
		}
		unset($arFields['ID'], $arFields['STATUS_ID']);

		if(!is_array($arPrevOrder))
		{
			$this->LAST_ERROR = GetMessage('CRM_INVOICE_ERR_NOT_FOUND', array('#INVOICE_ID#' => $tmpOrderId));
			$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
			if ($paidStateCanceled)
			{
				Compatible\Helper::payOrder($tmpOrderId, true);
			}
			return false;
		}

		$prevResponsibleID = isset($arPrevOrder['RESPONSIBLE_ID']) ? intval($arPrevOrder['RESPONSIBLE_ID']) : 0;
		$responsibleID = isset($arFields['RESPONSIBLE_ID']) ? intval($arFields['RESPONSIBLE_ID']) : 0;

		// simple update order fields
		$simpleFields = $arFields;
		$userFields = $GLOBALS['USER_FIELD_MANAGER']->GetUserFields(self::$sUFEntityID);
		if (is_array($userFields))
		{
			foreach(array_keys($userFields) as $fieldName)
			{
				if (array_key_exists($fieldName, $simpleFields))
					unset($simpleFields[$fieldName]);
			}
		}
		if (is_array($simpleFields) && !empty($simpleFields))
			$orderID = Compatible\Helper::Update($tmpOrderId, $simpleFields);
		unset($simpleFields, $userFields);

		// update user fields
		CCrmEntityHelper::NormalizeUserFields($arFields, self::$sUFEntityID, $GLOBALS['USER_FIELD_MANAGER'], array('IS_NEW' => false));
		$GLOBALS['USER_FIELD_MANAGER']->Update(self::$sUFEntityID, $tmpOrderId, $arFields);

		$registerSonetEvent = isset($options['REGISTER_SONET_EVENT']) && $options['REGISTER_SONET_EVENT'] === true;

		if(is_int($orderID) && $orderID > 0)
		{
			if($registerSonetEvent)
			{
				$newDealID = isset($arFields['UF_DEAL_ID']) ? intval($arFields['UF_DEAL_ID']) : 0;
				$oldDealID = isset($arPrevOrder['UF_DEAL_ID']) ? intval($arPrevOrder['UF_DEAL_ID']) : 0;

				$newCompanyID = isset($arFields['UF_COMPANY_ID']) ? intval($arFields['UF_COMPANY_ID']) : 0;
				$oldCompanyID = isset($arPrevOrder['UF_COMPANY_ID']) ? intval($arPrevOrder['UF_COMPANY_ID']) : 0;

				$newContactID = isset($arFields['UF_CONTACT_ID']) ? intval($arFields['UF_CONTACT_ID']) : 0;
				$oldContactID = isset($arPrevOrder['UF_CONTACT_ID']) ? intval($arPrevOrder['UF_CONTACT_ID']) : 0;

				$parents = array();
				$parentsChanged = $newDealID !== $oldDealID || $newCompanyID !== $oldCompanyID || $newContactID !== $oldContactID;
				if($parentsChanged)
				{
					if($newDealID > 0)
					{
						$parents[] = array(
							'ENTITY_TYPE_ID' => CCrmOwnerType::Deal,
							'ENTITY_ID' => $newDealID
						);
					}

					if($newCompanyID > 0)
					{
						$parents[] = array(
							'ENTITY_TYPE_ID' => CCrmOwnerType::Company,
							'ENTITY_ID' => $newCompanyID
						);
					}

					if($newContactID > 0)
					{
						$parents[] = array(
							'ENTITY_TYPE_ID' => CCrmOwnerType::Contact,
							'ENTITY_ID' => $newContactID
						);
					}
				}

				$oldOrderStatus = isset($arPrevOrder['STATUS_ID']) ? $arPrevOrder['STATUS_ID'] : '';
				self::SynchronizeLiveFeedEvent(
					$orderID,
					array(
						'PROCESS_PARENTS' => $parentsChanged,
						'PARENTS' => $parents,
						'REFRESH_DATE' => $orderStatus !== $oldOrderStatus,
						'START_RESPONSIBLE_ID' => $prevResponsibleID,
						'FINAL_RESPONSIBLE_ID' => $responsibleID,
						'TOPIC' => isset($arPrevOrder['ORDER_TOPIC']) ? $arPrevOrder['ORDER_TOPIC'] : $orderID
					)
				);
			}

			if (
				$responsibleID !== $prevResponsibleID
				&& Settings\Crm::isLiveFeedRecordsGenerationEnabled()
			)
			{
				CCrmSonetSubscription::ReplaceSubscriptionByEntity(
					CCrmOwnerType::Invoice,
					$orderID,
					CCrmSonetSubscriptionType::Responsibility,
					$responsibleID,
					$prevResponsibleID,
					$registerSonetEvent
				);
			}
		}
	}
	else
	{
		if ($tmpOrderId === 0 && !$bRecalculate)
		{
			if (!empty($orderStatus))
				$arFields['STATUS_ID'] = $orderStatus;
			foreach (GetModuleEvents('crm', 'OnBeforeCrmInvoiceAdd', true) as $arEvent)
			{
				if(ExecuteModuleEventEx($arEvent, array(&$arFields)) === false)
				{
					$errMsg = '';
					if(isset($arFields['RESULT_MESSAGE']))
					{
						$errMsg = trim(strval($arFields['RESULT_MESSAGE']));
					}
					else if ($ex = $APPLICATION->GetException())
					{
						$errMsg = trim(strval($ex->GetString()));
					}

					if ($errMsg === '')
					{
						$this->LAST_ERROR = GetMessage('CRM_INVOICE_CREATION_CANCELED', array('#NAME#' => $arEvent['TO_NAME']));
					}
					else
					{
						$this->LAST_ERROR = $errMsg;
					}
					unset($errMsg);

					$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
					if ($paidStateCanceled)
					{
						Compatible\Helper::payOrder($tmpOrderId, true);
					}
					return false;
				}
			}
			unset($arFields['STATUS_ID']);
		}
		else if ($tmpOrderId !== 0 && !$bRecalculate)
		{
			if (!isset($arFields['ID']))
				$arFields['ID'] = $tmpOrderId;
			if (!empty($orderStatus))
				$arFields['STATUS_ID'] = $orderStatus;
			foreach (GetModuleEvents('crm', 'OnBeforeCrmInvoiceUpdate', true) as $arEvent)
			{
				if(ExecuteModuleEventEx($arEvent, array(&$arFields)) === false)
				{
					$errMsg = '';
					if(isset($arFields['RESULT_MESSAGE']))
					{
						$errMsg = trim(strval($arFields['RESULT_MESSAGE']));
					}
					else if ($ex = $APPLICATION->GetException())
					{
						$errMsg = trim(strval($ex->GetString()));
					}

					if ($errMsg === '')
					{
						$this->LAST_ERROR = GetMessage('CRM_INVOICE_UPDATE_CANCELED', array('#NAME#' => $arEvent['TO_NAME']));
					}
					else
					{
						$this->LAST_ERROR = $errMsg;
					}
					unset($errMsg);

					$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
					if ($paidStateCanceled)
					{
						Compatible\Helper::payOrder($tmpOrderId, true);
					}
					return false;
				}
			}
			unset($arFields['ID'], $arFields['STATUS_ID']);
		}

		// check product rows
		if (!isset($arFields['PRODUCT_ROWS']) ||
			!is_array($arFields['PRODUCT_ROWS']) ||
			count($arFields['PRODUCT_ROWS']) <= 0)
		{
			$this->LAST_ERROR = GetMessage('CRM_ERROR_EMPTY_INVOICE_SPEC');
			$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
			if ($paidStateCanceled)
			{
				Compatible\Helper::payOrder($tmpOrderId, true);
			}
			return false;
		}
		$arProduct = $arFields['PRODUCT_ROWS'];

		// prepare shopping cart data
		// 

		// get xml_id fields
		$catalogXmlId = CCrmCatalog::GetDefaultCatalogXmlId();
		$arNewProducts = array();
		$bGetBasketXmlIds = false;
		foreach ($arProduct as &$productRow)
		{
			if (!isset($productRow['ID']))
			{
				$productRow['ID'] = 0;
			}
			if (intval($productRow['ID']) === 0 && isset($productRow['PRODUCT_ID']))
			{
				$arNewProducts[] = $productRow['PRODUCT_ID'];
			}
			else
			{
				$bGetBasketXmlIds = true;
			}
		}
		unset($productRow);
		$arXmlIds = array();
		$oldProductRows = null;
		if ($bGetBasketXmlIds && intval($tmpOrderId) > 0)
		{
			$oldProductRows = CCrmInvoice::GetProductRows($tmpOrderId);
			if (count($oldProductRows) > 0)
			{
				foreach ($oldProductRows as $row)
				{
					$arXmlIds[intval($row['ID'])][$row['PRODUCT_ID']] = array(
						'CATALOG_XML_ID' => $row['CATALOG_XML_ID'],
						'PRODUCT_XML_ID' => $row['PRODUCT_XML_ID']
					);
				}
				unset($row);
			}
		}
		unset($bGetBasketXmlIds);
		if (count($arNewProducts) > 0)
		{
			$dbRes = CCrmProduct::GetList(array(), array('ID' => $arNewProducts), array('ID', 'XML_ID'));
			while ($row = $dbRes->Fetch())
			{
				$arXmlIds[0][$row['ID']] = array(
					'CATALOG_XML_ID' => $catalogXmlId,
					'PRODUCT_XML_ID' => $row['XML_ID']
				);
			}
			unset($dbRes, $row);
		}
		unset($arNewProducts, $arOldProducts);

		// products without measures
		$productMeasures = array();
		$productId = 0;
		$productIds = array();
		foreach ($arProduct as $productRow)
		{
			$productId = intval($productRow['PRODUCT_ID']);
			if ($productId > 0
				&& (!array_key_exists('MEASURE_CODE', $productRow) || intval($productRow['MEASURE_CODE']) <= 0))
			{
				$productIds[] = $productId;
			}
		}
		unset($productId, $productRow);
		if (count($productIds) > 0)
			$productMeasures = \Bitrix\Crm\Measure::getProductMeasures($productIds);
		unset($productIds);

		$currencyId = CCrmInvoice::GetCurrencyID($siteId);
		$i = 0;
		$defaultMeasure = null;
		$oldProductRowsById = null;
		foreach ($arProduct as &$productRow)
		{
			$productXmlId = $catalogXmlId = null;
			$rowIndex = intval($productRow['ID']);
			$productId = $productRow['PRODUCT_ID'];
			$isCustomized = (isset($productRow['CUSTOMIZED']) && $productRow['CUSTOMIZED'] === 'Y');
			$productRow['MODULE'] = $productRow['PRODUCT_PROVIDER_CLASS'] = '';
			if($productId > 0)
			{
				if (!$isCustomized)
				{
					$productRow['MODULE'] = 'catalog';
					$productRow['PRODUCT_PROVIDER_CLASS'] = 'CCatalogProductProvider';
				}
				if (is_array($arXmlIds[$rowIndex])
					&& isset($arXmlIds[$rowIndex][$productId]))
				{
					$catalogXmlId = $arXmlIds[$rowIndex][$productId]['CATALOG_XML_ID'];
					$productXmlId = $arXmlIds[$rowIndex][$productId]['PRODUCT_XML_ID'];
				}
				$productRow['CATALOG_XML_ID'] = $catalogXmlId;
				$productRow['PRODUCT_XML_ID'] = $productXmlId;
			}
			else
			{
				$productRow["PRODUCT_XML_ID"] = "CRM-".\Bitrix\Main\Security\Random::getString(8, true);
				$ri = new \Bitrix\Main\Type\RandomSequence($productRow["PRODUCT_XML_ID"]);
				$productRow["PRODUCT_ID"] = $ri->rand(1000000, 9999999);
				$productRow['CATALOG_XML_ID'] = '';
			}
			if($isCustomized)
				$productRow['CUSTOM_PRICE'] = 'Y';
			if (isset($productRow['PRODUCT_NAME']))
			{
				$productRow['NAME'] = $productRow['PRODUCT_NAME'];
				unset($productRow['PRODUCT_NAME']);
			}
			if (isset($productRow['PRICE']))
				$productRow['PRICE_DEFAULT'] = $productRow['PRICE'];
			if (!isset($productRow['CURRENCY']))
				$productRow['CURRENCY'] = $currencyId;

			// measures
			$bRefreshMeasureName = false;
			if (!array_key_exists('MEASURE_CODE', $productRow) || intval($productRow['MEASURE_CODE'] <= 0))
			{
				if ($oldProductRows === null && $tmpOrderId > 0)
				{
					$oldProductRows = CCrmInvoice::GetProductRows($tmpOrderId);
				}
				if (is_array($oldProductRows) && count($oldProductRows) > 0 && $oldProductRowsById === null)
				{
					$oldProductRowsById = array();
					foreach ($oldProductRows as $row)
						$oldProductRowsById[intval($row['ID'])] = $row;
					unset($row);
				}
				if (is_array($oldProductRowsById) && isset($oldProductRowsById[$rowIndex]))
				{
					$row = $oldProductRowsById[$rowIndex];
					if (intval($productId) === intval($row['PRODUCT_ID']))
					{
						if (isset($row['MEASURE_CODE']))
							$productRow['MEASURE_CODE'] = $row['MEASURE_CODE'];
						if (isset($row['MEASURE_NAME']))
							$productRow['MEASURE_NAME'] = $row['MEASURE_NAME'];
						else
							$bRefreshMeasureName = true;
						unset($row);
					}
				}
			}
			if (!isset($productRow['MEASURE_CODE']) || intval($productRow['MEASURE_CODE']) <= 0)
			{
				if ($productId > 0 && isset($productMeasures[$productId]))
				{
					$measure = is_array($productMeasures[$productId][0]) ? $productMeasures[$productId][0] : null;
					if (is_array($measure))
					{
						if (isset($measure['CODE']))
							$productRow['MEASURE_CODE'] = $measure['CODE'];
						if (isset($measure['SYMBOL']))
							$productRow['MEASURE_NAME'] = $measure['SYMBOL'];
					}
					unset($measure);
				}
			}
			if (!isset($productRow['MEASURE_CODE']) || intval($productRow['MEASURE_CODE']) <= 0)
			{
				if ($defaultMeasure === null)
					$defaultMeasure = \Bitrix\Crm\Measure::getDefaultMeasure();

				if (is_array($defaultMeasure))
				{
					$productRow['MEASURE_CODE'] = $defaultMeasure['CODE'];
					$productRow['MEASURE_NAME'] = $defaultMeasure['SYMBOL'];
				}
			}
			if (isset($productRow['MEASURE_CODE'])
				&& intval($productRow['MEASURE_CODE']) > 0
				&& (
					$bRefreshMeasureName ||
					!array_key_exists('MEASURE_NAME', $productRow)
					|| empty($productRow['MEASURE_NAME'])
				)
			)
			{
				$measure = \Bitrix\Crm\Measure::getMeasureByCode($productRow['MEASURE_CODE']);
				if (is_array($measure) && isset($measure['SYMBOL']))
					$productRow['MEASURE_NAME'] = $measure['SYMBOL'];
				unset($measure);
			}

			$i++;
		}
		unset($productRow, $productMeasures, $catalogXmlId, $productXmlId);

		$arOrderProductPrice = self::__fGetUserShoppingCart($arProduct, $siteId, 'N');

		foreach ($arOrderProductPrice as &$arItem) // tmp hack not to update basket quantity data from catalog
		{
			$arItem["ID_TMP"] = $arItem["ID"];
			$arItem["NAME_TMP"] = $arItem["NAME"];
			unset($arItem["ID"]);
		}
		unset($arItem);

		// user id for order
		$saleUserId = intval(CSaleUser::GetAnonymousUserID());
		if ($saleUserId <= 0)
		{
			$this->LAST_ERROR = GetMessage('CRM_INVOICE_ERR_GET_ANONYMOUS_ID');
			$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
			return false;
		}

		$arErrors = array();

		$arShoppingCart = Compatible\BasketHelper::doGetUserShoppingCart(
			$siteId, $saleUserId, $arOrderProductPrice, $arErrors, $tmpOrderId, true
		);
		if (!is_array($arShoppingCart) || count($arShoppingCart) === 0)
		{
			$this->LAST_ERROR = GetMessage('CRM_ERROR_EMPTY_INVOICE_SPEC');
			$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
			return false;
		}

		foreach ($arShoppingCart as $key => &$arItem)
		{
			$arItem["ID"] = $arItem["ID_TMP"];
			$arItem["NAME"] = $arItem["NAME_TMP"];
			unset($arItem["NAME_TMP"], $arItem["ID_TMP"]);

			//$arShoppingCart[$key]["ID"] = $arItem["ID"];
		}
		unset($key, $arItem);
		// 

		// person type
		$arPersonTypes = CCrmPaySystem::getPersonTypeIDs();
		if (!isset($arPersonTypes['COMPANY']) || !isset($arPersonTypes['CONTACT']))
		{
			$this->LAST_ERROR = GetMessage('CRM_INVOICE_ERR_PERSON_TYPES_INCORRECT');
			$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
			if ($paidStateCanceled)
			{
				Compatible\Helper::payOrder($tmpOrderId, true);
			}
			return false;
		}

		$personTypeId = isset($arFields['PERSON_TYPE_ID']) ? (int)$arFields['PERSON_TYPE_ID'] : 0;
		if (isset($arFields['UF_COMPANY_ID']) && intval($arFields['UF_COMPANY_ID']) > 0)
			$personTypeId = (int)$arPersonTypes['COMPANY'];
		else if (isset($arFields['UF_CONTACT_ID']) && intval($arFields['UF_CONTACT_ID']) > 0)
			$personTypeId = (int)$arPersonTypes['CONTACT'];
		if ($personTypeId !== intval($arPersonTypes['COMPANY']) && $personTypeId !== intval($arPersonTypes['CONTACT']))
		{
			$this->LAST_ERROR = GetMessage(
				'CRM_INVOICE_ERR_INVALID_PERSON_TYPE_ID',
				array(
					'#CONTACT#' => $arPersonTypes['CONTACT'],
					'#COMPANY#' => $arPersonTypes['COMPANY']
				)
			);
			$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
			if ($paidStateCanceled)
			{
				Compatible\Helper::payOrder($tmpOrderId, true);
			}
			return false;
		}
		$arFields['PERSON_TYPE_ID'] = $personTypeId;

		// preparing order to save
		// 
		$arOrderPropsValues = array();
		if (isset($arFields['INVOICE_PROPERTIES']) && is_array($arFields['INVOICE_PROPERTIES']) && count($arFields['INVOICE_PROPERTIES']) > 0)
			$arOrderPropsValues = $arFields['INVOICE_PROPERTIES'];
		if (isset($arFields['INVOICE_PROPERTIES']))
			unset($arFields['INVOICE_PROPERTIES']);
		if (count($arOrderPropsValues) <= 0)
		{
			$this->LAST_ERROR = GetMessage('CRM_INVOICE_ERR_EMPTY_INVOICE_PROPS');
			$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
			return false;
		}

		$deliveryId = null;
		$paySystemId = $arFields['PAY_SYSTEM_ID'];

		// let DoCalculateOrder know we send location in CODEs
		$arOptions = array('LOCATION_IN_CODES' => true, 'CART_FIX' => 'Y', 'CURRENCY' => $currencyId);

		$arErrors = $arWarnings = array();

		$invoiceCompatible = Compatible\Invoice::create($arFields);
		$arOptions['ORDER'] = $invoiceCompatible->getOrder();

		$arOrder = CSaleOrder::DoCalculateOrder(
			$siteId, $saleUserId, $arShoppingCart, $personTypeId, $arOrderPropsValues,
			$deliveryId, $paySystemId, $arOptions, $arErrors, $arWarnings
		);

		if (!is_array($arOrder) || empty($arOrder))
		{
			if (is_array($arErrors) && isset($arErrors[0]['TEXT']))
			{
				$this->LAST_ERROR = $arErrors[0]['TEXT'];
				$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
			}
			return false;
		}
		// 

		if ($bRecalculate)
		{
			foreach ($arOrder as $k => $v)
				$arRecalculated[$k] = $v;
			return true;
		}

		// merge order fields
		$arAdditionalFields = array();
		foreach ($arFields as $k => $v)
		{
			if ($k === 'PRODUCT_ROWS') continue;
			$arAdditionalFields[$k] = $v;
		}

		// skip user fields
		$userFields = $GLOBALS['USER_FIELD_MANAGER']->GetUserFields(self::$sUFEntityID);
		if (is_array($userFields))
		{
			foreach(array_keys($userFields) as $fieldName)
			{
				if (array_key_exists($fieldName, $arAdditionalFields))
					unset($arAdditionalFields[$fieldName]);
			}
		}
		unset($userFields);

		$arOrder['LOCATION_IN_CODES'] = true; // let DoSaveOrder know we send location in IDs

		// saving order
		$arErrors = array();
		$arAdditionalFields['CUSTOM_DISCOUNT_PRICE'] = true;
		$orderID = Compatible\Helper::doSaveOrder($arOrder, $arAdditionalFields, $tmpOrderId, $arErrors);

		if(!(is_int($orderID) && $orderID > 0))
		{
			if (is_array($arErrors) && isset($arErrors[0]))
				$this->LAST_ERROR = $arErrors[0];
			else
				$this->LAST_ERROR = GetMessage('CRM_INVOICE_ERR_UNK_ON_SAVE_ORDER');
			$GLOBALS['APPLICATION']->ThrowException($this->LAST_ERROR);
		}

		// update user fields
		if(is_int($orderID) && $orderID > 0)
		{
			CCrmEntityHelper::NormalizeUserFields($arFields, self::$sUFEntityID, $GLOBALS['USER_FIELD_MANAGER'], array('IS_NEW' => ($tmpOrderId === 0)));
			$GLOBALS['USER_FIELD_MANAGER']->Update(self::$sUFEntityID, $orderID, $arFields);
		}

		if(is_int($orderID) && $orderID > 0 && isset($options['REGISTER_SONET_EVENT']) && $options['REGISTER_SONET_EVENT'] === true)
		{
			$prevResponsibleID = is_array($arPrevOrder) && isset($arPrevOrder['RESPONSIBLE_ID'])
					? intval($arPrevOrder['RESPONSIBLE_ID']) : 0;
			$responsibleID = isset($arFields['RESPONSIBLE_ID']) ? intval($arFields['RESPONSIBLE_ID']) : 0;

			if($tmpOrderId <= 0)
			{
				if (!empty($orderStatus))
					$arFields['STATUS_ID'] = $orderStatus;
				self::RegisterLiveFeedEvent($arFields, $orderID, $userId);
				unset($arFields['STATUS_ID']);
				if ($responsibleID > 0 && Settings\Crm::isLiveFeedRecordsGenerationEnabled())
				{
					CCrmSonetSubscription::RegisterSubscription(
						CCrmOwnerType::Invoice,
						$orderID,
						CCrmSonetSubscriptionType::Responsibility,
						$responsibleID
					);
				}
			}
			else
			{
				$newDealID = isset($arFields['UF_DEAL_ID']) ? intval($arFields['UF_DEAL_ID']) : 0;
				$oldDealID = isset($arPrevOrder['UF_DEAL_ID']) ? intval($arPrevOrder['UF_DEAL_ID']) : 0;

				$newCompanyID = isset($arFields['UF_COMPANY_ID']) ? intval($arFields['UF_COMPANY_ID']) : 0;
				$oldCompanyID = isset($arPrevOrder['UF_COMPANY_ID']) ? intval($arPrevOrder['UF_COMPANY_ID']) : 0;

				$newContactID = isset($arFields['UF_CONTACT_ID']) ? intval($arFields['UF_CONTACT_ID']) : 0;
				$oldContactID = isset($arPrevOrder['UF_CONTACT_ID']) ? intval($arPrevOrder['UF_CONTACT_ID']) : 0;

				$parents = array();
				$parentsChanged = $newDealID !== $oldDealID || $newCompanyID !== $oldCompanyID || $newContactID !== $oldContactID;
				if($parentsChanged)
				{
					if($newDealID > 0)
					{
						$parents[] = array(
							'ENTITY_TYPE_ID' => CCrmOwnerType::Deal,
							'ENTITY_ID' => $newDealID
						);
					}

					if($newCompanyID > 0)
					{
						$parents[] = array(
							'ENTITY_TYPE_ID' => CCrmOwnerType::Company,
							'ENTITY_ID' => $newCompanyID
						);
					}

					if($newContactID > 0)
					{
						$parents[] = array(
							'ENTITY_TYPE_ID' => CCrmOwnerType::Contact,
							'ENTITY_ID' => $newContactID
						);
					}
				}

				$oldOrderStatus = isset($arPrevOrder['STATUS_ID']) ? $arPrevOrder['STATUS_ID'] : '';
				self::SynchronizeLiveFeedEvent(
					$orderID,
					array(
						'PROCESS_PARENTS' => $parentsChanged,
						'PARENTS' => $parents,
						'REFRESH_DATE' => $orderStatus !== $oldOrderStatus,
						'START_RESPONSIBLE_ID' => $prevResponsibleID,
						'FINAL_RESPONSIBLE_ID' => $responsibleID,
						'TOPIC' => isset($arPrevOrder['ORDER_TOPIC']) ? $arPrevOrder['ORDER_TOPIC'] : $orderID
					)
				);

				if (
					$responsibleID !== $prevResponsibleID
					&& Settings\Crm::isLiveFeedRecordsGenerationEnabled()
				)
				{
					CCrmSonetSubscription::ReplaceSubscriptionByEntity(
						CCrmOwnerType::Invoice,
						$orderID,
						CCrmSonetSubscriptionType::Responsibility,
						$responsibleID,
						$prevResponsibleID,
						true
					);
				}
			}
		}
	}

	if (intval($orderID) > 0)
	{
		if (!empty($orderStatus) && !(isset($options['SKIP_STATUS']) && $options['SKIP_STATUS']))
		{
			// set status
			$this->SetStatus($orderID, $orderStatus, false, array('SKIP_UPDATE' => true));
		}
		else if ($paidStateCanceled)
		{
			Compatible\Helper::payOrder($tmpOrderId, true);
		}

		$securityRegisterOptions = (new \Bitrix\Crm\Security\Controller\RegisterOptions())
			->setEntityAttributes($arEntityAttr)
		;
		\Bitrix\Crm\Security\Manager::getEntityController(CCrmOwnerType::Invoice)
			->register(self::$TYPE_NAME, $orderID, $securityRegisterOptions)
		;

		$newDealID = isset($arFields['UF_DEAL_ID']) ? (int)$arFields['UF_DEAL_ID'] : 0;
		$oldDealID = is_array($arPrevOrder) && isset($arPrevOrder['UF_DEAL_ID']) ? (int)$arPrevOrder['UF_DEAL_ID'] : 0;

		if($newDealID)
		{
			Bitrix\Crm\Statistics\DealInvoiceStatisticEntry::register($newDealID);
		}
		if($oldDealID > 0 && $oldDealID !== $newDealID)
		{
			Bitrix\Crm\Statistics\DealInvoiceStatisticEntry::register($oldDealID);
		}

		if(!isset($options['UPDATE_SEARCH']) ||  $options['UPDATE_SEARCH'] === true)
		{
			$arFilterTmp = Array('ID' => $orderID);
			if (!$this->bCheckPermission)
				$arFilterTmp["CHECK_PERMISSIONS"] = "N";
			CCrmSearch::UpdateSearch($arFilterTmp, 'INVOICE', true);
		}
	}

	if ($orderID > 0)
	{
		$isNew = $tmpOrderId <= 0;

		if(defined('BX_COMP_MANAGED_CACHE'))
		{
			$GLOBALS['CACHE_MANAGER']->CleanDir('b_crm_invoice');
		}

		//Statistics & History -->
		if((!isset($options['REGISTER_STATISTICS']) || $options['REGISTER_STATISTICS'] === true) && $arFields['IS_RECURRING'] !== 'Y')
		{
			Bitrix\Crm\History\InvoiceStatusHistoryEntry::register($orderID, null, array('IS_NEW' => $isNew));
			Bitrix\Crm\Statistics\InvoiceSumStatisticEntry::register($orderID, null);
		}
		//<-- Statistics & History

		//region Search content index
		Bitrix\Crm\Search\SearchContentBuilderFactory::create(CCrmOwnerType::Invoice)->build($orderID);
		//endregion Search content index

		if (!isset($arFields['ID']))
			$arFields['ID'] = $orderID;
		if (!empty($orderStatus))
			$arFields['STATUS_ID'] = $orderStatus;
		foreach (GetModuleEvents('crm', (($tmpOrderId <= 0) ? 'OnAfterCrmInvoiceAdd' : 'OnAfterCrmInvoiceUpdate'), true) as $arEvent)
			ExecuteModuleEventEx($arEvent, array(&$arFields));
	}

	return $orderID;
}