Модуль CRM построен не только на базе сложных составных сущностей, в нем так же есть место перечислениям, которые называются “Справочники CRM” (могут упоминаться так же “статусы crm”, “crm_status”, справочники).

С точки зрения модуля все справочные значения находятся в базе данных в виде одной таблицы содержащей все справочные значения.

Представление в СУБД

В модуле справочники хранятся в таблице b_crm_status с определенными ключами:

Поле Тип Описание
ID int(18) Уникальный идентификатор
ENTITY_ID varchar(50) Симв. код справочника
STATUS_ID varchar(50) Симв. код хранимого значения
NAME varchar(100) Текущее название для хранимого значения
NAME_INIT varchar(100) Базовое название для хранимого значения
SORT int(18) Порядок отображения
SYSTEM char(1) Флаг системного свойства
CATEGORY_ID int(18) Идентификатор категории
COLOR char(10) Цветовой HEX-код (RBG)
SEMANTICS char(1) Семантика справочного значения

При взгляде на эту таблицу, можно заметить что типы данных указаны не в используемой системе эквиваленте (string, int), а в виде mysql типов данных с размерностью. Это сделано для того, чтобы показать читателю, что таблица является технической и реальный пользователь видит иную картину.

Общая логика работы с полями

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

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

При этом пользователю было бы не удобно работать с техническими кодами, поэтому за отображение пользователю отвечает столбец NAME. Если рассматривать справочник статусов лида, в публичной части, можно заметить что помимо возможности изменить название присутствует так же кнопка вернуть системные названия. Это обеспечивается столбцом NAME_INIT, который хранит имя по-умолчанию. Механизмов для использования ‘отката’ таких значений не существует и в своих справочниках можно ограничиться NAME столбцом.

Каждый справочник имеет порядок отображения элементов и за него отвечает столбец SORT. Дефолтно все справочники формируются по возрастанию этого столбца. Хорошей практикой является делать шаг в 10 единиц, начиная 10.

Технически справочники могут быть дополнены пользователем, однако зачастую существуют настолько важные значения, что нельзя давать пользователю возможность удалить их. За это отвечает параметр SYSTEM, который может находиться в двух состояниях Y и N. При наличии Y флага такое значение не может быть удалено пользователем. Мы рассмотрим его подробнее позже.

Специальные столбцы

Ранее мы говорили что справочники является простыми перечислениями, а после просмотра состава таблицы у читателя мог появиться вопрос о назначении других столбцов: CATEGORY_ID, COLOR, SEMANTICS.

Это ‘костыль’ который был добавлен в систему разработчиками, чтобы поддерживать механизм хранения статусов Лидов и Сделок, ведь по факту статусы этих сущностей являются справочниками.

API для работы со статусами

Работу со статусами можно вести двумя путями: используя DataManager от класса \Bitrix\Crm\StatusTable и класс CCrmStatus. В случае если необходимо указать в существующем ORM классе указать реляцию это можно сделать используя DataManager, его так же можно использовать в сложных ORM выборках, однако работать с изменением справочников категорически противопоказано (некоторые системные статусы кешируются и ваше действия могут повлечь за собой необратимые последствия для системы).

Получение значений

Получить все значения для справочника

Для получения всех значений может использоваться метод статичный метод CCrmStatus::GetStatus( $status ). Дополнительным преимуществом данного метода является статичное кеширование полученного справочника, таким образом при повторном вызове метода не произойдет запроса в базу данных.

Пример для получения всех значений справочника “Источник информации”:

/**
 * Interesting status
 * @var string
 */
$status = 'SOURCE';

$statuses = \CCrmStatus::GetStatus( $status );

var_dump($statuses);

Не полным результатом данного кода будет:

array(12) {
  ["CALL"]=>
  array(10) {
    ["ID"]=>
    string(1) "6"
    ["ENTITY_ID"]=>
    string(6) "SOURCE"
    ["STATUS_ID"]=>
    string(4) "CALL"
    ["NAME"]=>
    string(12) "Звонок"
    ["NAME_INIT"]=>
    string(12) "Звонок"
    ["SORT"]=>
    string(2) "10"
    ["SYSTEM"]=>
    string(1) "Y"
    ["COLOR"]=>
    NULL
    ["SEMANTICS"]=>
    NULL
    ["CATEGORY_ID"]=>
    NULL
  }
  ["EMAIL"]=>
  array(10) {
    ["ID"]=>
    string(1) "7"
    ["ENTITY_ID"]=>
    string(6) "SOURCE"
    ["STATUS_ID"]=>
    string(5) "EMAIL"
    ["NAME"]=>
    string(33) "Электронная почта"
    ["NAME_INIT"]=>
    string(0) ""
    ["SORT"]=>
    string(2) "20"
    ["SYSTEM"]=>
    string(1) "N"
    ["COLOR"]=>
    NULL
    ["SEMANTICS"]=>
    NULL
    ["CATEGORY_ID"]=>
    NULL
  }
  ...
}

Получить список всех значений для справочника

Выполняет аналогичную работу, что и CCrmStatus::GetStatus( $status ), с одним отличием: возвращает ассоциативный список вида STATUS_ID => NAME

/**
 * Interesting status
 * @var string
 */
$status = 'SOURCE';

$flatStatuses = \CCrmStatus::GetStatusList( $status );

var_dump($flatStatuses);

Не полным результатом данного кода будет:

array(12) {
  ["CALL"]=>
  string(12) "Звонок"
  ["EMAIL"]=>
  string(33) "Электронная почта"
  ["WEB"]=>
  string(15) "Веб-сайт"
  ["ADVERTISING"]=>
  string(14) "Реклама"
  ["PARTNER"]=>
  string(37) "Существующий клиент"
  ["RECOMMENDATION"]=>
  string(29) "По рекомендации"
  ["TRADE_SHOW"]=>
  string(16) "Выставка"
  ["WEBFORM"]=>
  string(14) "CRM-форма"
  ["CALLBACK"]=>
  string(29) "Обратный звонок"
  ["RC_GENERATOR"]=>
  string(31) "Генератор продаж"
  ["STORE"]=>
  string(31) "Интернет-магазин"
  ["OTHER"]=>
  string(12) "Другое"
}

Существует и другая разновидность данного метода CCrmStatus::GetStatusListEx, она возвращает тот же список, но с html безопасно кодированными(htmlspecialcharsbx) символами. Например, для ООО "Ромашка" html-безопасным будет ООО "Ромашка"

Получить первый элемент справочника

По сути этот метод является надстройкой над методом \CCrmStatus::GetStatusList и возвращает исключительно STATUS_ID первой найденной записи.

/**
 * Interesting status
 * @var string
 */
$status = 'SOURCE';

$firstSourceStatus = \CCrmStatus::GetFirstStatusID( $status );

var_dump($firstSourceStatus);

Результатом данного кода будет:

string(4) "CALL"

Добавление

Существует только один метод на добавление значений в справочник: нестатический метод класс CCrmStatus::Add(array $arFields, bool $bCheckStatusId = true), где arFields - ассоциативный массив описывающий добавляемое значение, а bCheckStatusId - флаг, указывающий, что нужно проверять дубли. Если вы не уверены, что такого значения нет, рекомендуется всегда выставлять флаг bCheckStatusId в значение true (это его значение по-умолчанию)

Пример добавление нового системного значения в справочник “Источник информации” (SOURCE)

/**
 * Status definition fields
 * @var array
 */
$fields = [
    'STATUS_ID' => 'HABR_EFFECT',
    'NAME'      => 'Хабра-эффект',
    'SORT'      => '300',
    'SYSTEM'    => 'Y'
];

/**
 * Entity id, where insert
 * @var string
 */
$entityId = 'SOURCE';

$statusEntity = new \CCrmStatus($entityId);

/**
 * id or false
 * @var integer | boolean
 */
$addedStatusId = $statusEntity->Add($fields);

if ( !$addedStatusId )
{
    echo 'Error when add: ', $statusEntity->GetLastError();
}

Добавление нескольких значений

И хотя метод на добавление существует только один, есть синтаксическая прослойка позволяющая добавлять сразу много статусов. Преимуществом данного метода является удобство для первичного заполнения нового справочника, однако он никак не обрабатывает ошибки добавления самих статусов. Если что-то не сработало, вы об этом не узнаете.


/**
 * List of status definition fields
 * @var array
 */
$items = [
    [
        'STATUS_ID' => 'HABR_EFFECT',
        'NAME'      => 'Хабра-эффект',
        'SORT'      => '300',
        'SYSTEM'    => 'Y'
    ],
    [
        'STATUS_ID' => 'REDDIT_EFFECT',
        'NAME'      => 'Reddit-эффект',
        'SORT'      => '310',
        'SYSTEM'    => 'Y'
    ]
];

\CCrmStatus::BulkCreate('SOURCE', $items);

Несмотря на наличие значительных ограничений, этот метод удобно использовать в паре с методом CCrmStatus::Erase, речь о котором пойдет далее.

Редактирование

Подобно созданию нового элемента справочника для редактирования тоже существует лишь один нестатический метод CCrmStatus::Update($ID, array $arFields, array $arOptions = array()), где ID - идентификатор изменяемого значения arFields - ассоциативный массив изменяемых полей arOptions - дополнительные опции.

Несмотря на то что метод принимает массив параметром arFields, в реальности изменению подвержены только ключи: SORT, NAME, SYSTEM, COLOR, SEMANTICS.

При наличии в arOptions значений ENABLE_STATUS_ID и ENABLE_NAME_INIT можно дополнительно изменить STATUS_ID и NAME_INIT соответственно.

Пример: предположим, что ранее добавленное значение HABR_EFFECT было добавлено с идентификатором 12345 и сейчас его нужно изменить. Тогда это можно было бы сделать следующим php-кодом:

/**
 * Row id to change
 * @var integer
 */
$statusId = 12345;

/**
 * Status definition fields
 * @var array
 */
$fields = [
    'STATUS_ID' => 'EFFECT_HABR',
    'NAME'      => 'Эффект хабра',
    'SORT'      => '10',
];

/**
 * Entity id, where insert
 * @var string
 */
$entityId = 'SOURCE';

$statusEntity = new \CCrmStatus($entityId);

/**
 * id or false
 * @var integer | boolean
 */
$statusId = $statusEntity->Update(
    $statusId,
    $fields,
    [
        'ENABLE_STATUS_ID' => true
    ]
);

if ( !$statusId )
{
    echo 'Error when update: ', $statusEntity->GetLastError();
}

Обратите внимание: если вы посмотрите в значения после изменения в NAME будет измененное значение Эффект хабра, в то время как в NAME_INIT останется Хабра-эффект.

Удаление

Удалить конкретное значение X для справочника Y

Обратите внимание, что, несмотря на то что в справочниках используется связка по STATUS_ID (что реально сохраняется в таблицу) и NAME (что отображается пользователю), удаление происходит по primary key ID. Если вы попытаетесь удалить несуществующий в таблице идентификатор, может возникнуть фатальная ошибка.


/**
 * Entity id, where you need to delete
 * @var string
 */
$entityId = 'SOME_ENTITY_ID';

/**
 * Row id, that will be deleted
 * @var int
 */
$statusId = 123;

$statusEntity = new \CCrmStatus($entityId);

if ( $statusEntity->Delete($statusId) )
{
    echo 'Error when delete: ', $statusEntity->GetLastError();
}

Интересный факт: несмотря на то что объект справочника инициализируется по симв. идентификатору справочника, в реальности при удалении значения не проверяется что удаляемый ID принадлежит этому справочнику. Сам же симв. идентификатор справочника используется, чтобы очистить сохраненный кеш.

Удалить все справочные значения определенного справочника


/**
 * Which entity will be erased
 * @var string
 */
$entityId = 'SOME_ENTITY_ID';

\CCrmStatus::Erase($entityId);

Полезные запросы

Получение всех зарегистрированных системных справочников

/**
 * List of entities
 * @var array
 */
$entities = \CCrmStatus::GetEntityTypes();
var_dump($entities);

Усеченный вывод:

array(13) {
  ["STATUS"]=>
  array(3) {
    ["ID"]=>
    string(6) "STATUS"
    ["NAME"]=>
    string(14) "Статусы"
    ["SEMANTIC_INFO"]=>
    array(4) {
      ["START_FIELD"]=>
      string(3) "NEW"
      ["FINAL_SUCCESS_FIELD"]=>
      string(9) "CONVERTED"
      ["FINAL_UNSUCCESS_FIELD"]=>
      string(4) "JUNK"
      ["FINAL_SORT"]=>
      int(0)
    }
  }
  ["SOURCE"]=>
  array(2) {
    ["ID"]=>
    string(6) "SOURCE"
    ["NAME"]=>
    string(18) "Источники"
  }
  ...
}

Полезные примечания