Фабрики

Мы уже знаем что точкой входа в новое API является контейнер, но основной рабочей лошадкой является фабрика. Именно она выполняет функцию унификации в коде и отвечает за единообразие всех методов универсального api.

Каждая фабрика является наследником абстрактного класса \Bitrix\Crm\Service\Factory который содержит универсальные методы, работающие на любом наследнике.

Как получить фабрику?

Для того чтобы получить фабрику, необходимо получить экземпляр контейнера CRM и знать идентификатор типа сущности с которой мы собираемся работать. В качестве идентификатора типа сущности выступает числовой идентификатор, обычно получаемый из класса CCrmOwnerType.

Полный пример получения фабрики для типа сущности “Сделка”:

use \Bitrix\Main\Loader;
use \Bitrix\Crm\Service\Container;
Loader::requireModule('crm');

$dealFactory = Container::getInstance()->getFactory( \CCrmOwnerType::Deal );

В дальнейшем мы будем полагать что экземпляр фабрики уже получен и находится в $factory переменной.

Методы фабрики

Условно, каждый метод фабрики можно классифицировать на 4 группы:

Мы не будем разбирать методы первых двух групп - они будут рассмотрены в разделах Элементы и Операции соответственно.

Поскольку фабрика это элемент абстракции позволяющий работать с CRM сущностью она содержит ряд методов, который позволяет определить ее поведение или состав. Рассмотрим сущности “Компания”, “Сделка” и “Лид” - что можно выделить общего между ними?

  • Все сущности имеют разный состав полей (Например в лиде есть привязка к компании, а в компании Оборот).
  • В каждой сущности имеется повторяемый по смыслу набор полей (например - Ответственный, Идентификатор)

Можно ли сказать что все они имеют направления? Нет, поскольку в Лидах направления нет. Можно ли сказать что все они имеют стадии? Нет, поскольку в компании нет стадий.

Нам необходимо иметь ряд технических функций определяющий наличие возможности. Такими функциями являются:

  • isMultipleAssignedEnabled(): bool - Возвращает true, если у сущности может быть несколько ответственных.
  • isCategoriesSupported(): bool - Возвращает true, если сущность поддерживает работу с направлениями.
  • isCategoriesEnabled(): bool - Возвращает true, если сущность не просто поддерживает работу с направлениями, они еще и сконфигурированы. Например, технически смарт-процесс поддерживает (supported) направления, однако сами направления могут быть выключены в настройках смарт-процесса.
  • isStagesSupported(): bool - Возвращает true, если сущность поддерживает работу со стадиями.
  • isStagesEnabled(): bool - Возвращает true, если сущность не просто поддерживает работу со стадиями, они еще и сконфигурированы. Например, технически смарт-процесс поддерживает (supported) направления, однако сами направления могут быть выключены в настройках смарт-процесса.
  • isLinkWithProductsEnabled(): bool - Возвращает true, если сущность поддерживает работу с товарами.
  • isBeginCloseDatesEnabled(): bool - Возвращает true, если сущность поддерживает поля “Дата начала” и “Дата завершения”.
  • isClientEnabled(): bool - Возвращает true, если сущность поддерживает поле “Клиент” (поле с выбором и редактированием привязки к компании и контактам).
  • isCrmTrackingEnabled(): bool - Возвращает true, если сущность поддерживает работу с трекером (путь клиента).
  • isMyCompanyEnabled(): bool - возвращает true, если сущность поддерживает поле “Мои реквизиты”.
  • isSourceEnabled(): bool - возвращает true, если сущность поддерживает работу полей ‘SOURCE_ID’ и ‘SOURCE_DESCRIPTION’.
  • isUseInUserfieldEnabled(): bool - возвращает true, если сущность поддерживается в полях типа “Привязка к элементам crm”.
  • isRecyclebinEnabled(): bool - возвращает true, если сущность поддерижвает работу с корзиной.
  • isAutomationEnabled(): bool - возвращает true, если сущность поддерживает работы роботов/триггеров.
  • isBizProcEnabled(): bool - возвращает true, если сущность поддерживает работу бизнес-процессов
  • isObserversEnabled(): bool - возвращает true, если сущность поддерживает работу наблюдателей.
  • isMultiFieldsEnabled(): bool - возвращает true, если сущность поддерживает работу мультиполей (Телефон, Почта, Сайт, Мессенджер).
  • isPaymentsEnabled(): bool - возвращает true, если сущность поддерживает работу с оплатами.
  • isDeferredCleaningEnabled(): bool - возвращает true, если после удаления элемента сущности он перемещается в корзину для удаления на агенте.
  • isCountersEnabled(): bool - возвращает true, если сущность поддерживает работу счетчиков
  • isLastActivitySupported(): bool - возвращает true, если сущность поддерживает работу полей “Последняя активность”
  • isLastActivityEnabled(): bool - возвращает true, если поддержка полей “Последняя активность” включена. Опция может быть выключена в настройках модуля.
  • isInventoryManagementEnabled(): bool - возвращает true, если для сущности поддерживается складской учет

Для некоторых полей есть комбинация методов “*Supported” и “*Enabled” которая описывает как техническую возможность, так и доступность этой механики. Например, существует возможность когда технически стадии будут поддерживаться, но не будут реализованы - например для Компаний/контактов, или например, когда будут существовать направления, но без стадий (пример - “Поставщики” технически это направление в компании, но сами компании не имеют стадии).

Метод getEntityTypeId(): int позволяет получить идентификатор типа сущности для которого создана эта фабрика. Этот метод бывает полезен, когда фабрика передается в функции/методы кастомного кода и нужно определить разное поведение для разных типов сущностей.

Метод getUserFieldEntityId(): string позволяет получить код пользовательских полей для сущности. Например, вызвав этот метод на фабрике сделок мы получим “CRM_DEAL”. Метод getEntityName(): string позволяет получить код сущности. Например, вызвав этот метод на фабрике сделок мы получим “DEAL”. Метод getEntityAbbreviation(): string позволяет получить аббревиатуру кода сущности. Например, вызвав этот метод на фабрике сделок мы получим “D”. Метод getEntityDescription(): string позволяет получить языкозависимое название сущности. Напримем, вызвав этот метод на фабрике сделок мы получим “Сделка” Метод getEntityDescriptionInPlural(): string позволяет получить языкозависимое название сущности во множественном числе. Напримем, вызвав этот метод на фабрике сделок мы получим “Сделки”

Набольшой фрагмент кода для тех кто не читает длинное описание или очень спешит:

use \Bitrix\Main\Loader;
use \Bitrix\Crm\Service\Container;
Loader::requireModule('crm');

$factory = Container::getInstance()->getFactory( \CCrmOwnerType::Deal );

var_dump([
	'getEntityTypeId'              => $factory->getEntityTypeId(),			// 2
	'getUserFieldEntityId'         => $factory->getUserFieldEntityId(),		// 'CRM_DEAL'
	'getEntityName'                => $factory->getEntityName(),			// 'DEAL'
	'getEntityAbbreviation'        => $factory->getEntityAbbreviation(),	// 'D'
	'getEntityDescription'         => $factory->getEntityDescription(),		// 'Сделка'
	'getEntityDescriptionInPlural' => $factory->getEntityDescriptionInPlural(), // 'Сделки'
]);

Метод isFieldExists(string $commonFieldName): bool позволяет определить имеет ли сущность поле с кодом $commonFieldName или нет. Чаще всего бывает полезен, когда заполнение сущности происходит из внешней системы - позволяет предотвратить ошибку заполнения несуществующего поля (ошибка).

Метод getFieldCaption(string $commonFieldName): string позволяет получить название для основных полей сущности. Не работает для пользовательских полей. В случае, если языковое значение не найдено вернет $commonFieldName значение.

Метод getFieldValueCaption(string $commonFieldName, $fieldValue): string - позволяет получить читаемое значение для основных полей сущности. Например, если на фабрике сделок вызвать метод с параметрами \Bitrix\Crm\Item::FIELD_NAME_CREATED_BY и 1 то можно получить имя пользователя ID:1 в формате сайта.

Получить полный набор полей CRM сущности можно комбинируя результаты методов getFieldsInfo(): array и getUserFieldsInfo(): array которые возвращают подготовленные описания полей для основных и пользовательских полей соответственно.

Направления

Если сущность поддерживает работу с направлениями, то можно получить или изменять список этих направлений.

Для простоты изложения мы будем считать что работаем с фабрикой сделок, где поддержка категорий включена и доступна. Для остальных сущностей правильнее сначала проверить доступность методом isCategoriesEnabled().

Получить список категорий можно через метод getCategories()

/**
 * List of categories
 *
 * @var        Bitrix\Crm\Category\Entity\Category[]
 */
$categories = $factory->getCategories();
var_dump($categories);

Метод возвращает массив объектов категорий. Если категорий не существует (не создавались), то вернется массив с единственным элементом - Bitrix\Crm\Category\Entity\DealDefaultCategory (ее нельзя удалить или переместить).

Получить категорию по-умолчанию можно используя метод getDefaultCategory(): ?Category - он вернет направление которое является по-умолчанию, в случае если такая категория существует.

Отсутствие категории по-умолчанию для сущности поддерижвающих работу с категорией является ошибкой(!)

Существует несколько методов для проверки существования категории или их получения:

  • isCategoryExists(int $categoryId):bool - проверка существования категории по идентификатору
  • getCategory(int $id): ?Category - получение категории по идентификатору
  • getCategoryByCode(string $code): ?Category - получение категории по симв.коду.

Все эти методы являются прослойкой для простой циклической проверки:

foreach($this->getCategories() as $category)
{
	if($category->getId() === $id)
	{
		return $category;
	}
}

return null;

Это значит, что они созданы исключительно для удобства и простоты и не несут никаких оптимизаций.

Как создать направление?

Пример кода создающего направление:

$categoryObject = $factory->createCategory([
	'NAME'=>'ABC'
]);

$createCategoryResult = $categoryObject->save();
if ( !$createCategoryResult->isSuccess() )
{
	// Error when save category.
	// Get errors with $createCategoryResult->getErrors
	// $createCategoryResult instance of Bitrix\Main\Result
}

Приведенный выше код создает направление и предзаполняет его стадиями по-умолчанию.

Как удалить направление?

Удаление направления осуществляется не через методы фабрики, а через объект направления. Получить объект направления можно, например через метод getCategory(int $id): ?Category.

Пример кода, удаляющего направление ID:34


$categoryObject = $factory->getCategory(34);

$deleteCategoryResult = $categoryObject->delete();
if ( !$deleteCategoryResult->isSuccess() )
{
	// Error when delete category.
	// Get errors with $deleteCategoryResult->getErrors
	// $deleteCategoryResult instance of Bitrix\Main\Result
}

Стадии

Со стадиями ситуация более сложная. Поскольку стадии являются часть справочников для работы со стадиями необходимо узнать уникальный stage entity id - идентификатор стадий для указанного типа элементов, а он свою очередь зависит не только от префикса и идентификатора типа сущности, но и от направления.

Для получения идентификатор а типа стадий следует использовать метод getStagesEntityId(?int $categoryId = null): ?string особенно комбинируя с методом getDefaultCategory(): ?Category. Например для фабрики сделок результатом такого вызова будет идентификатор стадии общей воронки DEAL_STAGE.

Метод getStages(int $categoryId = null): EO_Status_Collection позволяет получить набор стадий для указанного направления. Метод getStage(string $statusId): ?EO_Status позволяет получить объект стадии по идентификатору стадии. Метод function getStageSemantics(string $stageId): ?string позволяет получить семантический статус для указанной стадии. Например для стадии NEW вернется P.

Комбинация методов направления и стадий позволяет написать код который вернет нам список стадий вида [<Название направления>] <Название стадии> (в случае наличия стадий) и <Название стадии>:

$allStages = [];

if ($factory->isCategoriesSupported())
{
	foreach ($factory->getCategories() as $category)
	{
		foreach ($factory->getStages($category->getId()) as $stage)
		{
			$allStages[ $stage->getStatusId() ] = sprintf(
				"[%s] %s",
				$category->getName(),
				$stage->getName()
			); 
		}
	}
}
else
{
	foreach ($factory->getStages($category->getId()) as $stage)
	{
		$allStages[ $stage->getStatusId() ] = $stage->getName(); 
	}
}

Полезные ссылки

Паттерн Абстрактная фабрика

Паттерн Фабрика