При разработке интеграций с Битрикс24 часто возникает задача программного обновления сделок через ядро CRM. Однако простое использование CCrmDeal::Update не всегда приводит к появлению изменений в истории сущности. В этой заметке я расскажу, как правильно обновлять сделки, чтобы все изменения фиксировались в таймлайне.
При стандартном обновлении сделки:

изменения могут не отобразиться в истории, хотя сами поля успешно обновляются.
use Bitrix\Crm\Service\Container;
use Bitrix\Main\Loader;public static function updateItemFields(int|string $id, int|string $typeId, array $fields): ?\Bitrix\Main\Result
{
$logger = LoggerFactory::create('updateItemFields', 'smart_element_update');
$logger->info('updateItemFields params', [
'$id' => $id,
'$typeId' => $typeId,
'$fields' => $fields,
]);
$factory = Container::getInstance()->getFactory($typeId);
$item = $factory->getItem((int)$id);
if (empty($item)) {
$logger->info('updateItemFields() item is empty', [
'$id' => $id,
'$typeId' => $typeId,
'$fields' => $fields,
]);
return null;
}
// Сохраняем старые значения
$oldValues = [];
foreach ($fields as $fieldName => $fieldValue) {
$oldValues[$fieldName] = $item->get($fieldName);
}
foreach ($fields as $fieldName => $fieldValue) {
$item->set($fieldName, $fieldValue);
}
$context = new \Bitrix\Crm\Service\Context();
$userId = CurrentUser::get()->getId() ?? ADMIN;
$context->setUserId($userId);
$saveOperation = $factory->getUpdateOperation($item, $context);
$saveOperation
->disableCheckFields()
->disableCheckAccess();
$operationResult = $saveOperation->launch();
// ПОСЛЕ ОБНОВЛЕНИЯ - добавляем запись в историю
if ($operationResult->isSuccess()) {
foreach ($fields as $fieldName => $newValue) {
$oldValue = $oldValues[$fieldName];
// Если значение изменилось
if ($oldValue != $newValue) {
// Получаем название поля
$fieldLabel = self::getFieldLabelSimple($fieldName);
// Добавляем запись в историю
self::addToHistory($typeId, (int)$id, $fieldLabel, $oldValue, $newValue, $userId);
$logger->info("Добавлено в историю: {$fieldLabel}");
}
}
}
if (!$operationResult->isSuccess()) {
$logger->error('updateItemFields() result', [
'isSuccess' => $operationResult->isSuccess(),
'getErrorMessages' => $operationResult->getErrorMessages(),
'getErrors' => $operationResult->getErrors(),
]);
}
return $operationResult;
} /**
* Простое получение названия поля
*/
private static function getFieldLabelSimple(string $fieldName): string
{
// Карта системных полей
$labels = [
'TITLE' => 'Название',
'STAGE_ID' => 'Стадия',
'ASSIGNED_BY_ID' => 'Ответственный',
'BEGINDATE' => 'Дата начала',
'CLOSEDATE' => 'Дата завершения',
'OPPORTUNITY' => 'Сумма',
'CURRENCY_ID' => 'Валюта',
'COMPANY_ID' => 'Компания',
'CONTACT_ID' => 'Контакт',
'COMMENTS' => 'Комментарий',
];
// Если это пользовательское поле
if (strpos($fieldName, 'UF_') === 0) {
return 'Поле: ' . $fieldName;
}
return $labels[$fieldName] ?? $fieldName;
} /**
* Добавление записи в историю
*/
private static function addToHistory(int $typeId, int $itemId, string $fieldName, $oldValue, $newValue, int $userId): void
{
try {
Loader::includeModule('crm');
// Определяем тип сущности для истории
$entityType = self::getEntityTypeForHistory($typeId);
if (!$entityType) {
return;
}
// Формируем текст события
$oldText = $oldValue !== null && $oldValue !== '' ? (string)$oldValue : '(не указано)';
$newText = $newValue !== null && $newValue !== '' ? (string)$newValue : '(не указано)';
// Создаем событие
$event = new \CCrmEvent();
$eventId = $event->Add(
[
'ENTITY_TYPE' => $entityType,
'ENTITY_ID' => $itemId,
'ENTITY_FIELD' => $fieldName,
'EVENT_TYPE' => 1, // 1 - изменение
'EVENT_NAME' => 'Изменение поля: ' . $fieldName,
'EVENT_TEXT_1' => 'Было: ' . $oldText,
'EVENT_TEXT_2' => 'Стало: ' . $newText,
'USER_ID' => $userId,
'DATE_CREATE' => ConvertTimeStamp(time(), 'FULL'),
],
false // не проверять права
);
// Логируем результат
$logger = LoggerFactory::create('addToHistory', 'history');
if ($eventId) {
$logger->info("Событие добавлено", [
'eventId' => $eventId,
'entityType' => $entityType,
'entityId' => $itemId,
'fieldName' => $fieldName
]);
} else {
$logger->error("Не удалось добавить событие", [
'entityType' => $entityType,
'entityId' => $itemId,
'fieldName' => $fieldName
]);
}
} catch (\Exception $e) {
$logger = LoggerFactory::create('addToHistory', 'history');
$logger->error('Ошибка добавления в историю', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
}
} /**
* Получение типа сущности для истории
*/
private static function getEntityTypeForHistory(int $typeId): ?string
{
// Стандартные типы
if ($typeId === \CCrmOwnerType::Deal) {
return 'DEAL';
}
if ($typeId === \CCrmOwnerType::Lead) {
return 'LEAD';
}
if ($typeId === \CCrmOwnerType::Contact) {
return 'CONTACT';
}
if ($typeId === \CCrmOwnerType::Company) {
return 'COMPANY';
}
if ($typeId === \CCrmOwnerType::Order) {
return 'ORDER';
}
// Для смарт-процессов - получаем имя динамической сущности
$factory = Container::getInstance()->getFactory($typeId);
if ($factory) {
$entityName = $factory->getEntityName();
if ($entityName) {
return strtoupper($entityName);
}
}
return null;
}
Нужно использовать метод с дополнительными параметрами:


Если нужно добавить комментарий в историю или создать кастомное событие:

Правильное использование этих параметров гарантирует, что все программные изменения сделок будут отображаться в истории, что критически важно для аудита и отслеживания работы с CRM.
