public function Add(array &$arFields, $bUpdateSearch = true, $options = array())
{
global $DB;
if (!is_array($options))
{
$options = array();
}
$this->LAST_ERROR = '';
$this->checkExceptions = array();
$isRestoration = isset($options['IS_RESTORATION']) && $options['IS_RESTORATION'];
if ($this->isUseOperation())
{
return $this->getCompatibilityAdapter()->performAdd($arFields, $options);
}
$userID = isset($options['CURRENT_USER'])
? (int)$options['CURRENT_USER'] : CCrmSecurityHelper::GetCurrentUserID();
if($userID <= 0 && $this->bCheckPermission)
{
$arFields['RESULT_MESSAGE'] = $this->LAST_ERROR = GetMessage('CRM_PERMISSION_USER_NOT_DEFINED');
return false;
}
unset($arFields['ID']);
if(!($isRestoration && isset($arFields['DATE_CREATE'])))
{
unset($arFields['DATE_CREATE']);
$arFields['~DATE_CREATE'] = $DB->CurrentTimeFunction();
}
if(!($isRestoration && isset($arFields['DATE_MODIFY'])))
{
unset($arFields['DATE_MODIFY']);
$arFields['~DATE_MODIFY'] = $DB->CurrentTimeFunction();
}
if(!($isRestoration && isset($arFields['MOVED_TIME'])))
{
unset($arFields['MOVED_TIME']);
}
if(!($isRestoration && isset($arFields['MOVED_BY_ID'])))
{
unset($arFields['MOVED_BY_ID']);
}
if($userID > 0)
{
if(!(isset($arFields['CREATED_BY_ID']) && $arFields['CREATED_BY_ID'] > 0))
{
$arFields['CREATED_BY_ID'] = $userID;
}
if(!(isset($arFields['MODIFY_BY_ID']) && $arFields['MODIFY_BY_ID'] > 0))
{
$arFields['MODIFY_BY_ID'] = $userID;
}
if(!(isset($arFields['ASSIGNED_BY_ID']) && $arFields['ASSIGNED_BY_ID'] > 0))
{
$arFields['ASSIGNED_BY_ID'] = $userID;
}
}
if(!isset($arFields['TITLE']) || !is_string($arFields['TITLE']) || trim($arFields['TITLE']) === '')
{
$arFields['TITLE'] = self::GetDefaultTitle();
}
$fields = self::GetUserFields();
$this->fillEmptyFieldValues($arFields, $fields);
if (!$this->CheckFields($arFields, false, $options))
{
$result = false;
$arFields['RESULT_MESSAGE'] = &$this->LAST_ERROR;
}
else
{
if (!isset($arFields['IS_RECURRING']))
{
$arFields['IS_RECURRING'] = 'N';
}
if (!isset($arFields['MOVED_BY_ID']))
{
$arFields['MOVED_BY_ID'] = (int)$userID;
}
if (!isset($arFields['MOVED_TIME']))
{
$arFields['MOVED_TIME'] = (new \Bitrix\Main\Type\DateTime())->toString();
}
self::getLastActivityAdapter()->performAdd($arFields, $options);
self::getCommentsAdapter()->normalizeFields(null, $arFields);
//region Category
$categoryID = isset($arFields['CATEGORY_ID']) ? max((int)$arFields['CATEGORY_ID'], 0) : 0;
if($categoryID > 0 && !DealCategory::isEnabled($categoryID))
{
$categoryID = 0;
}
$arFields['CATEGORY_ID'] = $categoryID;
//endregion
//region StageID, SemanticID and IsNew
$permissionTypeId = (
$this->bCheckPermission
? Bitrix\Crm\Security\EntityPermissionType::CREATE
: Bitrix\Crm\Security\EntityPermissionType::UNDEFINED
);
if (!isset($arFields['STAGE_ID']) || (string)$arFields['STAGE_ID'] === '')
{
$arFields['STAGE_ID'] = self::GetStartStageID($categoryID, $permissionTypeId);
}
$viewMode = ($options['ITEM_OPTIONS']['VIEW_MODE'] ?? null);
$viewModeActivitiesStageId = null;
if ($viewMode === ViewMode::MODE_ACTIVITIES)
{
$viewModeActivitiesStageId = $arFields['STAGE_ID'];
$arFields['STAGE_ID'] = self::GetStartStageID($categoryID, $permissionTypeId);
}
$isStageExist = self::IsStageExists($arFields['STAGE_ID'], $categoryID);
$arFields['STAGE_SEMANTIC_ID'] = $isStageExist
? self::GetSemanticID($arFields['STAGE_ID'], $categoryID)
: Bitrix\Crm\PhaseSemantics::UNDEFINED
;
$arFields['IS_NEW'] = $arFields['STAGE_ID'] === self::GetStartStageID($categoryID) ? 'Y' : 'N';
//endregion
$observerIDs = isset($arFields['OBSERVER_IDS']) && is_array($arFields['OBSERVER_IDS'])
? $arFields['OBSERVER_IDS'] : null;
unset($arFields['OBSERVER_IDS']);
$arAttr = array();
if (!empty($arFields['STAGE_ID']))
{
$arAttr['STAGE_ID'] = $arFields['STAGE_ID'];
}
if (!empty($arFields['OPENED']))
{
$arAttr['OPENED'] = $arFields['OPENED'];
}
if(!empty($observerIDs))
{
$arAttr['CONCERNED_USER_IDS'] = $observerIDs;
}
$permissionEntityType = DealCategory::convertToPermissionEntityType($arFields['CATEGORY_ID']);
$sPermission = 'ADD';
if (isset($arFields['PERMISSION']))
{
if ($arFields['PERMISSION'] == 'IMPORT')
$sPermission = 'IMPORT';
unset($arFields['PERMISSION']);
}
$assignedByID = (int)$arFields['ASSIGNED_BY_ID'];
if($this->bCheckPermission)
{
$arEntityAttr = self::BuildEntityAttr($userID, $arAttr);
$userPerms = $userID == CCrmPerms::GetCurrentUserID() ? $this->cPerms : CCrmPerms::GetUserPermissions($userID);
$sEntityPerm = $userPerms->GetPermType($permissionEntityType, $sPermission, $arEntityAttr);
if ($sEntityPerm == BX_CRM_PERM_NONE)
{
$this->LAST_ERROR = GetMessage('CRM_PERMISSION_DENIED');
$arFields['RESULT_MESSAGE'] = &$this->LAST_ERROR;
return false;
}
if ($sEntityPerm == BX_CRM_PERM_SELF && $assignedByID != $userID)
{
$arFields['ASSIGNED_BY_ID'] = $assignedByID = $userID;
}
if ($sEntityPerm == BX_CRM_PERM_OPEN && $userID == $assignedByID)
{
$arFields['OPENED'] = 'Y';
}
}
$assignedByID = (int)$arFields['ASSIGNED_BY_ID'];
$arEntityAttr = self::BuildEntityAttr($assignedByID, $arAttr);
$userPerms = $assignedByID == CCrmPerms::GetCurrentUserID() ? $this->cPerms : CCrmPerms::GetUserPermissions($assignedByID);
$sEntityPerm = $userPerms->GetPermType($permissionEntityType, $sPermission, $arEntityAttr);
self::PrepareEntityAttrs($arEntityAttr, $sEntityPerm);
//Prepare currency & exchange rate
if(!isset($arFields['CURRENCY_ID']))
{
$arFields['CURRENCY_ID'] = CCrmCurrency::GetBaseCurrencyID();
}
if(!isset($arFields['EXCH_RATE']))
{
$arFields['EXCH_RATE'] = CCrmCurrency::GetExchangeRate($arFields['CURRENCY_ID']);
}
$arFields = array_merge($arFields, \CCrmAccountingHelper::calculateAccountingData($arFields, [], true));
//Scavenging
if(isset($arFields['BEGINDATE']) && (!is_string($arFields['BEGINDATE']) || trim($arFields['BEGINDATE']) === ''))
{
unset($arFields['BEGINDATE']);
}
if(isset($arFields['CLOSEDATE']) && (!is_string($arFields['CLOSEDATE']) || trim($arFields['CLOSEDATE']) === ''))
{
unset($arFields['CLOSEDATE']);
}
$currentDate = ConvertTimeStamp(time() + \CTimeZone::GetOffset(), 'SHORT', SITE_ID);
$arFields['~BEGINDATE'] = $DB->CharToDateFunction(
isset($arFields['BEGINDATE']) ? $arFields['BEGINDATE'] : $currentDate,
'SHORT',
false
);
if(isset($arFields['BEGINDATE']))
{
$arFields['__BEGINDATE'] = $arFields['BEGINDATE'];
unset($arFields['BEGINDATE']);
}
$isFinalStage = self::GetStageSemantics($arFields['STAGE_ID'], $categoryID) !== 'process';
$enableCloseDateSync = DealSettings::getCurrent()->isCloseDateSyncEnabled();
if(isset($options['ENABLE_CLOSE_DATE_SYNC']) && is_bool($options['ENABLE_CLOSE_DATE_SYNC']))
{
$enableCloseDateSync = $options['ENABLE_CLOSE_DATE_SYNC'];
}
$arFields['CLOSED'] = $isFinalStage ? 'Y' : 'N';
if($enableCloseDateSync && $isFinalStage)
{
$arFields['CLOSEDATE'] = $currentDate;
$arFields['~CLOSEDATE'] = $DB->CharToDateFunction($currentDate, 'SHORT', false);
}
elseif($isFinalStage && !isset($arFields['CLOSEDATE']))
{
$arFields['CLOSEDATE'] = $currentDate;
$arFields['~CLOSEDATE'] = $DB->CharToDateFunction($currentDate, 'SHORT', false);
}
elseif(isset($arFields['CLOSEDATE']))
{
$arFields['~CLOSEDATE'] = $DB->CharToDateFunction($arFields['CLOSEDATE'], 'SHORT', false);
}
if(isset($arFields['CLOSEDATE']))
{
$arFields['__CLOSEDATE'] = $arFields['CLOSEDATE'];
unset($arFields['CLOSEDATE']);
}
//region Preparation of contacts
$contactBindings = isset($arFields['CONTACT_BINDINGS']) && is_array($arFields['CONTACT_BINDINGS'])
? $arFields['CONTACT_BINDINGS'] : null;
$contactIDs = isset($arFields['CONTACT_IDS']) && is_array($arFields['CONTACT_IDS'])
? $arFields['CONTACT_IDS'] : null;
unset($arFields['CONTACT_IDS']);
//For backward compatibility only
$contactID = isset($arFields['CONTACT_ID']) ? max((int)$arFields['CONTACT_ID'], 0) : null;
if($contactID !== null && $contactIDs === null && $contactBindings === null)
{
$contactIDs = array();
if($contactID > 0)
{
$contactIDs[] = $contactID;
}
}
unset($arFields['CONTACT_ID']);
if(is_array($contactIDs) && !is_array($contactBindings))
{
$contactBindings = EntityBinding::prepareEntityBindings(
\CCrmOwnerType::Contact,
$contactIDs
);
EntityBinding::markFirstAsPrimary($contactBindings);
}
elseif(is_array($contactBindings))
{
if(EntityBinding::findPrimaryBinding($contactBindings) === null)
{
EntityBinding::markFirstAsPrimary($contactBindings);
}
$contactIDs = EntityBinding::prepareEntityIDs(
CCrmOwnerType::Contact,
$contactBindings
);
}
//endregion
//region Rise BeforeAdd event
$beforeEvents = GetModuleEvents('crm', 'OnBeforeCrmDealAdd');
while ($arEvent = $beforeEvents->Fetch())
{
if(ExecuteModuleEventEx($arEvent, array(&$arFields)) === false)
{
if(isset($arFields['RESULT_MESSAGE']))
{
$this->LAST_ERROR = $arFields['RESULT_MESSAGE'];
}
else
{
$this->LAST_ERROR = GetMessage('CRM_DEAL_CREATION_CANCELED', array('#NAME#' => $arEvent['TO_NAME']));
$arFields['RESULT_MESSAGE'] = &$this->LAST_ERROR;
}
return false;
}
}
//endregion
if(!isset($arFields['COMPANY_ID']))
{
$arFields['COMPANY_ID'] = 0;
}
unset($arFields['ID']);
$this->normalizeEntityFields($arFields);
$ID = (int) $DB->Add(self::TABLE_NAME, $arFields, [], 'FILE: '.__FILE__.'
LINE: '.__LINE__);
//Append ID to TITLE if required
if($ID > 0 && $arFields['TITLE'] === self::GetDefaultTitle())
{
$arFields['TITLE'] = self::GetDefaultTitle($ID);
$sUpdate = $DB->PrepareUpdate('b_crm_deal', array('TITLE' => $arFields['TITLE']));
if($sUpdate <> '')
{
$DB->Query("UPDATE b_crm_deal SET {$sUpdate} WHERE ID = {$ID}", false, 'FILE: '.__FILE__.'
LINE: '.__LINE__);
};
}
if(defined('BX_COMP_MANAGED_CACHE'))
{
$GLOBALS['CACHE_MANAGER']->CleanDir('b_crm_deal');
}
$result = $arFields['ID'] = $ID;
//Restore BEGINDATE and CLOSEDATE
if(isset($arFields['__BEGINDATE']))
{
$arFields['BEGINDATE'] = $arFields['__BEGINDATE'];
unset($arFields['__BEGINDATE']);
}
if(isset($arFields['__CLOSEDATE']))
{
$arFields['CLOSEDATE'] = $arFields['__CLOSEDATE'];
unset($arFields['__CLOSEDATE']);
}
//region User Field
CCrmEntityHelper::NormalizeUserFields($arFields, self::$sUFEntityID, $GLOBALS['USER_FIELD_MANAGER'], array('IS_NEW' => true));
$GLOBALS['USER_FIELD_MANAGER']->Update(self::$sUFEntityID, $ID, $arFields);
//endregion
//region Save Observers
if(!empty($observerIDs))
{
Crm\Observer\ObserverManager::registerBulk($observerIDs, \CCrmOwnerType::Deal, $ID);
}
//endregion
$securityRegisterOptions = (new \Bitrix\Crm\Security\Controller\RegisterOptions())
->setEntityAttributes($arEntityAttr)
;
Crm\Security\Manager::getEntityController(CCrmOwnerType::Deal)
->register($permissionEntityType, $ID, $securityRegisterOptions)
;
//region Save contacts
if(!empty($contactBindings))
{
DealContactTable::bindContacts($ID, $contactBindings);
if (isset($GLOBALS['USER']) && !empty($contactIDs))
{
CUserOptions::SetOption(
'crm',
'crm_contact_search',
array('last_selected' => $contactIDs[count($contactIDs) - 1])
);
}
}
//endregion
if(!empty($contactBindings))
{
$arFields['CONTACT_ID'] = EntityBinding::getPrimaryEntityID(CCrmOwnerType::Contact, $contactBindings);
}
self::SynchronizeCustomerData($ID, $arFields);
//Statistics & History -->
if ($arFields['IS_RECURRING'] !== 'Y')
{
DealSumStatisticEntry::register($ID, $arFields);
DealInvoiceStatisticEntry::synchronize($ID, $arFields);
DealStageHistoryEntry::register($ID, $arFields, array('IS_NEW' => true));
}
if(isset($arFields['LEAD_ID']) && $arFields['LEAD_ID'] > 0)
{
LeadConversionStatisticsEntry::processBindingsChange($arFields['LEAD_ID']);
}
//<-- Statistics & History
if (($options['DISABLE_TIMELINE_CREATION'] ?? null) !== 'Y')
{
if($isRestoration)
{
Bitrix\Crm\Timeline\DealController::getInstance()->onRestore($ID, array('FIELDS' => $arFields));
}
else
{
Bitrix\Crm\Timeline\DealController::getInstance()->onCreate(
$ID,
array(
'FIELDS' => $arFields,
'CONTACT_BINDINGS' => $contactBindings
)
);
}
}
CCrmEntityHelper::registerAdditionalTimelineEvents([
'entityTypeId' => \CCrmOwnerType::Deal,
'entityId' => $ID,
'fieldsInfo' => static::GetFieldsInfo(),
'previousFields' => [],
'currentFields' => $arFields,
'previousStageSemantics' => Crm\PhaseSemantics::UNDEFINED,
'currentStageSemantics' => $arFields['STAGE_SEMANTIC_ID'] ?? Crm\PhaseSemantics::UNDEFINED,
'options' => $options,
'bindings' => [
'entityTypeId' => \CCrmOwnerType::Contact,
'previous' => [],
'current' => $contactBindings,
]
]);
\Bitrix\Crm\Counter\Monitor::getInstance()->onEntityAdd(CCrmOwnerType::Deal, $arFields);
// tracking of entity
Tracking\Entity::onAfterAdd(CCrmOwnerType::Deal, $ID, $arFields);
//region save parent relations
Crm\Service\Container::getInstance()->getParentFieldManager()->saveParentRelationsForIdentifier(
new Crm\ItemIdentifier(\CCrmOwnerType::Deal, $ID),
$arFields
);
//endregion
if($bUpdateSearch)
{
$arFilterTmp = Array('ID' => $ID);
if (!$this->bCheckPermission)
$arFilterTmp["CHECK_PERMISSIONS"] = "N";
CCrmSearch::UpdateSearch($arFilterTmp, 'DEAL', true);
}
if (isset($GLOBALS['USER']) && isset($arFields['COMPANY_ID']) && intval($arFields['COMPANY_ID']) > 0)
{
CUserOptions::SetOption('crm', 'crm_company_search', array('last_selected' => $arFields['COMPANY_ID']));
}
//region Search content index
Bitrix\Crm\Search\SearchContentBuilderFactory::create(
CCrmOwnerType::Deal
)->build($ID, ['checkExist' => true]);
//endregion
self::getCommentsAdapter()->performAdd($arFields, $options);
if(isset($options['REGISTER_SONET_EVENT']) && $options['REGISTER_SONET_EVENT'] === true)
{
$opportunity = round((isset($arFields['OPPORTUNITY']) ? doubleval($arFields['OPPORTUNITY']) : 0.0), 2);
$currencyID = isset($arFields['CURRENCY_ID']) ? $arFields['CURRENCY_ID'] : '';
if($currencyID === '')
{
$currencyID = CCrmCurrency::GetBaseCurrencyID();
}
$assignedByID = intval($arFields['ASSIGNED_BY_ID']);
$createdByID = intval($arFields['CREATED_BY_ID']);
$companyID = (int)$arFields['COMPANY_ID'];
$liveFeedFields = array(
'USER_ID' => $createdByID,
'ENTITY_TYPE_ID' => CCrmOwnerType::Deal,
'ENTITY_ID' => $ID,
'TITLE' => GetMessage('CRM_DEAL_EVENT_ADD'),
'MESSAGE' => '',
'PARAMS' => array(
'TITLE' => isset($arFields['TITLE']) ? $arFields['TITLE'] : '',
'STAGE_ID' => isset($arFields['STAGE_ID']) ? $arFields['STAGE_ID'] : '',
'CATEGORY_ID' => isset($arFields['CATEGORY_ID']) ? $arFields['CATEGORY_ID'] : 0,
'OPPORTUNITY' => strval($opportunity),
'CURRENCY_ID' => $currencyID,
'COMPANY_ID' => $companyID,
'CONTACT_ID' => isset($arFields['CONTACT_ID']) ? intval($arFields['CONTACT_ID']) : 0,
'AUTHOR_ID' => intval($arFields['CREATED_BY_ID']),
'RESPONSIBLE_ID' => $assignedByID
)
);
//Register contact & company relations
$parents = array();
if($companyID > 0)
{
CCrmLiveFeed::PrepareOwnershipRelations(
CCrmOwnerType::Company,
array($companyID),
$parents
);
}
if(is_array($contactIDs))
{
CCrmLiveFeed::PrepareOwnershipRelations(
CCrmOwnerType::Contact,
$contactIDs,
$parents
);
}
if(!empty($parents))
{
$liveFeedFields['PARENTS'] = array_values($parents);
}
if (Crm\Settings\Crm::isLiveFeedRecordsGenerationEnabled())
{
CCrmSonetSubscription::RegisterSubscription(
CCrmOwnerType::Deal,
$ID,
CCrmSonetSubscriptionType::Responsibility,
$assignedByID
);
}
$logEventID = CCrmLiveFeed::CreateLogEvent($liveFeedFields, CCrmLiveFeedEvent::Add, ['CURRENT_USER' => $userID]);
if (
$logEventID !== false
&& $assignedByID != $createdByID
&& CModule::IncludeModule("im")
)
{
$url = CCrmOwnerType::GetEntityShowPath(CCrmOwnerType::Deal, $ID);
$serverName = (CMain::IsHTTPS() ? "https" : "http")."://".((defined("SITE_SERVER_NAME") && SITE_SERVER_NAME <> '') ? SITE_SERVER_NAME : COption::GetOptionString("main", "server_name", ""));
$arMessageFields = array(
"MESSAGE_TYPE" => IM_MESSAGE_SYSTEM,
"TO_USER_ID" => $assignedByID,
"FROM_USER_ID" => $createdByID,
"NOTIFY_TYPE" => IM_NOTIFY_FROM,
"NOTIFY_MODULE" => "crm",
"LOG_ID" => $logEventID,
//"NOTIFY_EVENT" => "deal_add",
"NOTIFY_EVENT" => "changeAssignedBy",
"NOTIFY_TAG" => "CRM|DEAL_RESPONSIBLE|".$ID,
"NOTIFY_MESSAGE" => GetMessage("CRM_DEAL_RESPONSIBLE_IM_NOTIFY", Array("#title#" => "".htmlspecialcharsbx($arFields['TITLE'])."")),
"NOTIFY_MESSAGE_OUT" => GetMessage("CRM_DEAL_RESPONSIBLE_IM_NOTIFY", Array("#title#" => htmlspecialcharsbx($arFields['TITLE'])))." (".$serverName.$url.")"
);
CIMNotify::Add($arMessageFields);
}
}
//region Rise AfterAdd event
$afterEvents = GetModuleEvents('crm', 'OnAfterCrmDealAdd');
while ($arEvent = $afterEvents->Fetch())
{
ExecuteModuleEventEx($arEvent, array(&$arFields));
}
//endregion
if(isset($arFields['ORIGIN_ID']) && $arFields['ORIGIN_ID'] !== '')
{
$afterEvents = GetModuleEvents('crm', 'OnAfterExternalCrmDealAdd');
while ($arEvent = $afterEvents->Fetch())
{
ExecuteModuleEventEx($arEvent, array(&$arFields));
}
}
if ($ID>0)
{
if (
$viewMode === ViewMode::MODE_ACTIVITIES
&& $viewModeActivitiesStageId
)
{
$deadline = (new Crm\Kanban\EntityActivityDeadline())->getDeadline($viewModeActivitiesStageId);
if ($deadline)
{
\Bitrix\Crm\Activity\Entity\ToDo::createWithDefaultDescription(
\CCrmOwnerType::Deal,
$ID,
$deadline
);
}
}
$item = Crm\Kanban\Entity::getInstance(self::$TYPE_NAME)
->createPullItem($arFields);
PullManager::getInstance()->sendItemAddedEvent(
$item,
[
'TYPE' => self::$TYPE_NAME,
'CATEGORY_ID' => \CCrmDeal::GetCategoryID($ID),
'SKIP_CURRENT_USER' => ($userID !== 0),
]
);
}
}
return $result;
}