Фабрики
Table of Contents
Мы уже знаем что точкой входа в новое 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 группы:
- Получение элементов
- Операции над элементами (создание/редактировать/удаление/копирование и т.п.)
- Вспомогательные функции (работа с направлениями, стадиями)
- Технические функции (используются в предыдущих 3 группах)
Мы не будем разбирать методы первых двух групп - они будут рассмотрены в разделах Элементы и Операции соответственно.
Поскольку фабрика это элемент абстракции позволяющий работать с 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();
}
}