Как известно у любой CRM-сущности в Битрикс24 есть поле Ответственный сотрудник. Данное поле хранит информацию о текущем сотруднике, ответственном за сущность.

Распространенной проблемой, связанной с работой этого поля является тот факт, что Битрикс24 хранит далеко не полную информацию о смене ответственных сотрудников в своей истории. Для многих же бизнесов данная информация является необходимой, так как при построении аналитических отчетов о работе менеджеров отдела продаж важно на какой стадии обработки сущности, как и когда именно менялся ответственный за данную сущность.
Ту информацию, которую сохраняет Битрикс24, можно увидеть во вкладке История выбранной сущности.

Однако, с этой информацией не удобно работать и использовать для построения отчетов. Если посмотреть на то, как Битрикс хранит эту информацию в своих внутренних таблицах (b_crm_event), на примере Сделки, то мы увидим следующее:

В нашем распоряжении только дата смены ответственного и ФИО менеджера, но не их ID. Как быть если работают однофамильцы или менеджер изменил в своем профиле имя? Более того, привязка события к самой сущности хранится в отдельной таблице b_crm_event_relations.
Да, еще есть таблица b_crm_deal_stage в которой хранятся смены статусов сделки и ID ответственных при этих сменах, но как можно заметить, записи там только на момент смены статуса.
Если ответственный сменился позже — информация не будет отражена в данной таблице. Кроме того, замечено что при смене статуса, предыдущие записи в этой таблице могут изменяться и информация о предыдущем ответственном затирается новым.
Таким образом, текущая схема хранения истории смены ответственных не может быть использована. Приходится делать дополнительную. Не будем ничего ломать, просто сохраним все смены в отдельную табличку с которой позже можно будет работать.
В качестве хранилища данных будем использовать Highload-инфоблок со следующей структурой:

где UF_ENTITY_TYPE хранит ид типа сущности, UF_ENTITY_ID хранит ID сущности, а UF_RESPONSIBLE_ID хранит ID пользователя, ставшего ответственным за сущность. Будем использовать один инфоблок для всех типов сущностей.
Напишем небольшой класс для работы с этим highload-блоком: https://gist.github.com/BedrosovaYulia/68787787dfcc5cbd09a7ec64fab8f379
class GsResponsibleHistory
{
const HL_ID = GS_RESP_HISTORY_APP['HL_ID'];
const HL_TABLE_NAME = 'c_responsible_history';
const ENTITY_TYPES = array(
"LEAD"=>1,
"DEAL"=>2,
"CONTACT"=>3,
"COMPANY"=>4
);
const UNKNOWN_USER_ID = 1;
public static function Add(
$ENTITY_TYPE,
$ENTITY_ID,
$RESPONSIBLE_ID){
$now = new DateTime();
$hl = self::GetHL();
$hl::add(array(
'UF_DATETIME'=>$now->format('Y-m-d H:i:s'),
'UF_ENTITY_TYPE'=>self::ENTITY_TYPES[$ENTITY_TYPE],
'UF_ENTITY_ID'=>intval($ENTITY_ID),
'UF_RESPONSIBLE_ID'=>intval($RESPONSIBLE_ID),
'UF_SOURCE'=>'onupdate',
));
}
private static function GetHL(){
CModule::IncludeModule('highloadblock');
$hlblock = Bitrix\Highloadblock\HighloadBlockTable::getById(
self::HL_ID
);
if($hlblock = $hlblock->fetch())
{
$entity = Bitrix\Highloadblock\HighloadBlockTable::compileEntity(
$hlblock);
return $entity->getDataClass();
}
return false;
}
}
private static function GetHL(){
CModule::IncludeModule('highloadblock');
hlblock = Bitrix\Highloadblock\HighloadBlockTable::getById(
self::HL_ID);
$hlblock = $hlblock->fetch()){
$entity = Bitrix\Highloadblock\HighloadBlockTable::compileEntity(
$hlblock);
$entity->getDataClass();
}
return false;
}
}
Подключаем наш класс в php_interface и регистрируем обработчики событий на изменение и создание Сделки (аналогично для других сущностей CRM):
$eventManager->addEventHandler('crm','OnAfterCrmDealUpdate',function(&$arFields)
{
GsResponsibleHistory::Add('DEAL', $arFields['ID'], $arFields['ASSIGNED_BY_ID']);
});
$eventManager->addEventHandler('crm','OnAfterCrmDealAdd',function(&$arFields)
{
GsResponsibleHistory::Add('DEAL', $arFields['ID'], $arFields['ASSIGNED_BY_ID']);
});
Внимание! Код дан для ознакомления и не является готовым к использованию. Необходимо учитывать, что событие OnAfterCrmDealUpdate вызывается всякий раз на изменении сделки, а не только при смене ответственного, кроме того, поле ASSIGNED_BY_ID в этом методе проставляется не только при смене ответственного но и при других событиях. Поэтому перед записью в наш журнал необходимо проверить отдельным запросом (на событии OnBeforeCrmDealUpdate) — действительно ли ответственный сменился.
На выходе мы получаем блок с регистрацией ID всех ответственных за сущность сотрудников. Далее можно расширить наш класс для работы с инфоблоком — например функцией поиска ответственного на определенную дату, или преобразованием всех записей из Истории сущностей в наш инфоблок ретроспективно.
Как можно использовать полученный расширенный журнал смены ответственных для построения отчета по CRM читайте в другом посте нашего блога: https://blog.bedrosova.ru/2021/08/09/four-weeks-crm-report/