Crm 26.900.0–26.1400.0: CRM V2 для PHP, CCrmDeal::Add только через Operation и белый список фильтра в crm.item.productrow.list
Ломающее обновление
Удалены или изменены публичные API — прикладной код может перестать работать.
В коробочный Битрикс24 одним днём пришли шесть версий CRM: 26.900.0, 26.1000.0, 26.1100.0, 26.1200.0, 26.1300.0 и 26.1400.0. PHP-разработчику достался типизированный слой CRM V2, на котором строится и REST 3.0, а интеграции перед обновлением стоит проверить на crm.item.productrow.list, записи в таймлайн и создание полей привязки к инфоблоку. Для администраторов это ещё и обновление безопасности, в шести версиях закрыто больше десяти дыр в правах.
Новое
Читайте и пишите элементы через CRM V2 (26.1000.0–26.1400.0)
В Bitrix\Crm\V2\Public появились провайдеры чтения: DealProvider, ContactProvider, SmartProcessProvider и ещё шесть, по одному на тип. Если тип известен только во время выполнения, провайдер вернёт ItemProvider::forEntityType(EntityType). Поля задаёт обязательный ItemSelect, фильтр ItemFilter с camelCase-ключами и привычными префиксами операций, сортировку ItemSort. withAccessCheck() включает проверку прав на чтение.
<?php declare(strict_types=1);
use Bitrix\Crm\V2\Public\Provider\Item\DealProvider;
use Bitrix\Crm\V2\Public\Provider\Item\Param\ItemFilter;
use Bitrix\Crm\V2\Public\Provider\Item\Param\ItemSelect;
use Bitrix\Crm\V2\Public\Provider\Item\Param\ItemSort;
use Bitrix\Main\Loader;
use Bitrix\Main\Provider\Params\Pager;
Loader::requireModule('crm');
$deals = (new DealProvider())
->withAccessCheck($userId)
->getList(
new ItemSelect('title', 'opportunity', 'stageId'),
new ItemFilter(['=assignedById' => $userId, '>=opportunity' => 100000]),
(new ItemSort())->desc('opportunity'),
new Pager(limit: 20),
);
foreach ($deals as $deal) {
echo $deal->getTitle(), ': ', $deal->getOpportunity(), PHP_EOL;
}
Для записи в V2 есть команды AddItemCommand, UpdateItemCommand, DeleteItemCommand и их типизированные версии вроде Deal\AddCommand. Перед run() можно отключить проверку прав, роботов или обязательных пользовательских полей (withoutPermissionCheck(), withoutAutomation(), withoutRequiredUserFieldsCheck()).
Что умеет тип, отвечает EntityTypeSettings::of(EntityType::deal()): hasCategories(), hasStages(), hasProducts() и ещё три десятка флагов.
ReplaceStagesCommand (26.1300.0) заменяет стадии воронки одной транзакцией: стадии без ID создаёт, непереданные удаляет, финальные и системные не трогает. Если на удаляемой стадии стоят элементы, вернёт DEPENDENT_ITEMS_EXIST и ничего не изменит. Для товарных строк есть ProductRow\{Add,Update,Delete,Replace}Command и ProductRowProvider.
AddFieldValueCommand (26.1400.0) дописывает одно значение в множественное поле: телефон, почту, наблюдателя, множественное пользовательское поле, в том числе файл. Парный DeleteFieldValueCommand (26.1200.0) одно значение удаляет. Поле больше не нужно перечитывать и перезаписывать целиком, одновременную запись команда закрывает блокировкой.
<?php declare(strict_types=1);
use Bitrix\Crm\V2\Public\Command\Item\AddFieldValueCommand;
use Bitrix\Crm\V2\Public\Entity\Item\FieldValueElement\Multifield;
use Bitrix\Crm\V2\Public\Entity\Item\PhoneValue;
use Bitrix\Crm\V2\Public\EntityType;
use Bitrix\Crm\V2\Public\ItemId;
use Bitrix\Main\Loader;
Loader::requireModule('crm');
$result = (new AddFieldValueCommand(
new ItemId(EntityType::contact(), $contactId),
new Multifield('phone', new PhoneValue('WORK', '+79001234567')),
$userId,
))->run();
if (!$result->isSuccess()) {
throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}
Включите REST 3.0 на стенде (26.900.0–26.1400.0)
К 26.1400.0 в REST 3.0 у crm есть CRUD сделок, лидов, контактов, компаний, предложений, смарт-счетов и смарт-процессов (crm.deal.add, crm.smartProcess<N>.list), addFieldValue и deleteFieldValue, воронки и стадии вплоть до crm.deal.category.stage.replace, товарные строки crm.deal.productRow.* и crm.requisite.list. Всё это работает, только когда включена фича RestV3CrudDeal (опция crm Feature_RestV3CrudDeal). По умолчанию она выключена, и секретной ссылкой её не включить. На стенде хватит \Bitrix\Crm\Feature::enable('RestV3CrudDeal');, метод заодно сбросит кеш схем REST 3.0. Почтовые методы из 26.900.0 (crm.activity.mail.getContent, crm.deal.timeline.activity.email.send и соседние) работают без флага.
Различайте ставки НДС по названию (26.1200.0)
У товарной строки появилось поле TAX_NAME, его заполняет ProductRow::normalizeTax() названием ставки из каталога. Две ставки с одинаковым процентом в карточке больше не сливаются. В выгрузке в учётную систему сопоставляйте НДС по TAX_NAME.
Включите кеш итогов канбана (26.1300.0)
Kanban\TotalSumsCache кеширует итоговые суммы канбана для каждого пользователя на 20 секунд. Изначально кеш выключен, включает его опция crm kanban_total_sums_cache.enabled = Y, а срок жизни меняет kanban_total_sums_cache.ttl.
Что заметят пользователи
- Черновики писем в CRM (26.1200.0), если в модуле mail включены внутренние черновики.
- Большие вложения уходят ссылкой на Диск и складываются в папку «Файлы из CRM», если модуль mail поддерживает отправку больших вложений через Диск (26.1000.0).
- Конверсии VK Ads для элементов из лид-форм VK (26.1200.0). Сделка в успешной стадии отправляет
SALE/WON. Лид в успешном статусе отправляетLEAD/QUALIFIED, если CRM не в простом режиме. - У бизнес-процессного триггера «Изменено поле» появились фильтр по воронке (26.1000.0) и режим «любое изменение» (26.1200.0).
Что сломается
Проверьте код вокруг CCrmDeal::Add (26.900.0)
У CCrmDeal, CCrmLead, CCrmContact, CCrmCompany и CCrmQuote методы Add, Update и Delete теперь всегда идут через Service\Operation, старый код удалён. Если на портале был выключен флаг {entity}_enable_factory, проверки, события и автоматизация переехали на новый путь, так что прогоните свои обработчики OnBeforeCrm* и OnAfterCrm* на копии.
Удалены Bitrix\Crm\Feature\OperationsApiIn{Lead,Deal,Contact,Company}, Feature\Category\OperationsApi и Reservation\Component\DealUpdateAction. Прямое обращение к этим классам упадёт.
Перенесите правки шаблонов писем и дел (26.1100.0)
bitrix:crm.activity.email.bodyпотерял шаблон.default, осталсяslider.- У
bitrix:crm.activity.emailудалёнtemplates/.default/template.php, просмотр письма всегда идёт черезslider. Ваша копияtemplate.phpв шаблоне сайта больше не подключится, а действияlogиlogitemвajax.phpбезtemplate=sliderвернутCRM_ACT_EMAIL_AJAX_ERROR. - У
bitrix:crm.activity.plannerудалёнview.php, просмотр всегда идёт черезview_slider. Запрос кajax.phpсajax_action=ACTIVITY_VIEWполучитUnknown action!, для просмотра естьslider.phpсaction=view.
Свои правки переносите в шаблоны slider и view_slider.
Пересмотрите фильтры crm.item.productrow.list (26.1300.0)
Метод принимает в фильтре только id, ownerId, ownerType и productId с операциями =, !=, !, >, <, >=, <=, @. Остальные ключи, включая productName, price и логические группы, отбрасываются без ошибки, и интеграция получает все строки владельца. Сортировка возможна только по id и sort, =ownerId принимает одно число, на список владельцев вернётся ошибка.
Запрашивайте строки по одному владельцу и фильтруйте остальное у себя.
Передавайте AUTHOR_ID в записи таймлайна (26.1300.0)
TimelineEntry::fetchParams() перестал подставлять текущего пользователя. Без AUTHOR_ID автором станет пользователь, явно заданный в контексте операции, а если его нет, первый активный администратор портала (запасной вариант ID 1). Это касается CommentEntry::create(), LogMessageEntry и остальных *Entry из Bitrix\Crm\Timeline.
Указывайте IBLOCK_ID у полей привязки к инфоблоку (26.1400.0)
Пользовательские поля CRM типа iblock_element и iblock_section без SETTINGS.IBLOCK_ID больше не создаются: CUserTypeEntity::Add() вернёт false, ошибку с id IBLOCK_ID отдаст $APPLICATION->GetException(). Если меняете SETTINGS такого поля, передавайте IBLOCK_ID вместе с остальными настройками. Миграция, которая создаёт поле, теперь выглядит так:
<?php declare(strict_types=1);
use Bitrix\Main\Loader;
Loader::requireModule('crm');
$fieldId = (new \CUserTypeEntity())->Add([
'ENTITY_ID' => 'CRM_DEAL',
'FIELD_NAME' => 'UF_CRM_WAREHOUSE',
'USER_TYPE_ID' => 'iblock_element',
'SETTINGS' => ['IBLOCK_ID' => $warehouseIblockId, 'DISPLAY' => 'LIST'],
'EDIT_FORM_LABEL' => ['ru' => 'Склад'],
]);
if (!$fieldId) {
global $APPLICATION;
$error = $APPLICATION->GetException();
throw new \RuntimeException($error ? $error->GetString() : 'Поле UF_CRM_WAREHOUSE не создано');
}
Учтите новые запреты для пользователей
- Дела-звонки телефонии удаляет только администратор CRM (26.900.0). Остальные не удалят такое дело ни из интерфейса, ни через REST
crm.activity.delete, там они получат «Access denied.». - Срок дела со встречей в календаре переносит только тот, кто может редактировать это событие (26.1000.0), иначе
ACCESS_DENIED. Без смены срока дело сохранится, но правки в событие календаря не попадут. Роботов это не касается. Интеграции, которые сохраняют дела черезEntity\ToDoот имени сотрудника, получат ту же ошибку. ToDo::save()с занятой переговорной возвращаетLOCATION_BUSY(26.1000.0), раньше пересекающаяся бронь создавалась молча.
Закрытые дыры в правах
- 26.900.0: REST-списки реквизитов, банковских реквизитов и адресов отдавали строки чужих владельцев (интеграция под не-админом теперь получит меньше строк);
crm.productrow.addпроверял только право создания на тип, теперь нужно право на изменение владельца; цепочку писем мог прочитать любой, кто видит хоть что-то в CRM. - 26.1000.0: в REST-списке групп покупателей проверка прав была перевёрнута; конфиг SMS отдавал телефоны клиента без проверки чтения; конвертация создавала элемент в воронке, где у пользователя нет права на создание.
- 26.1100.0: чужое дело можно было отредактировать через планировщик; добавление в список обзвона не проверяло права.
- 26.1300.0:
crm.item.productrow.listотдавал товарные строки чужих владельцев. - 26.1400.0: копирование сделки, лида, компании и контакта не проверяло чтение исходника; дело без владельца, например из списка обзвона, завершалось без проверки права на изменение; импорт местоположений принимал запросы без CSRF-токена и брал временный каталог из запроса.
Deprecated
AIManager::SUPPORTED_ENTITY_TYPE_IDSзаменяетAIManager::isEntityTypeSupported(int $entityTypeId)(26.1000.0).TaskActivityStatus::STATUSES_MANAGER_CAN_UPDATEзаменяютTaskActivityStatus::isTaskStatusProjection()иTask::syncStateWithTask()(26.1400.0).isUseOperation()у классических классов CRM,EnableFactory::isFactoryEnabled(),setFactoryEnabled()иCCrmEntityHelper::setEnabledFactoryFlagByRequest()оставлены заглушками (26.900.0). Замена не нужна, новый API включён всегда.
БД
b_crm_product_row: колонкаTAX_NAME varchar(50)(26.1200.0).b_crm_timeline_bind: колонкаCREATEDи индексIX_B_CRM_TIMELINE_BIND_3(26.1300.0). Лента истории теперь сортирует по ней (опцияcrmtimeline_bind_created_enabled).
Мелочи и находки
- Фильтр
crm.item.listпоDATE_CREATEи другим системным датам больше не падает на MySQL 8 с «Incorrect DATETIME value» (26.900.0). - Пересчёт последней активности не меняет «Кем изменён» и «Дата изменения» (26.1300.0). Интеграция, которая забирает свежие элементы по дате изменения, такие обновления не увидит.
Устаревшие и удалённые API в этой версии
| Символ | Статус | Чем заменять |
|---|---|---|
Bitrix\Crm\Integration\AI\AIManager::SUPPORTED_ENTITY_TYPE_IDS
|
Deprecated |
Bitrix\Crm\Integration\AI\AIManager::isEntityTypeSupported()
|
Bitrix\Crm\Activity\Provider\Tasks\TaskActivityStatus::STATUSES_MANAGER_CAN_UPDATE
|
Deprecated |
Bitrix\Crm\Activity\Provider\Tasks\TaskActivityStatus::isTaskStatusProjection(), Bitrix\Crm\Activity\Provider\Tasks\Task::syncStateWithTask()
|
CAllCrmDeal::isUseOperation()
|
Deprecated | — |
CAllCrmLead::isUseOperation()
|
Deprecated | — |
CAllCrmContact::isUseOperation()
|
Deprecated | — |
CAllCrmCompany::isUseOperation()
|
Deprecated | — |
CAllCrmQuote::isUseOperation()
|
Deprecated | — |
Bitrix\Crm\Settings\Traits\EnableFactory::isFactoryEnabled()
|
Deprecated | — |
Bitrix\Crm\Settings\Traits\EnableFactory::setFactoryEnabled()
|
Deprecated | — |
CCrmEntityHelper::setEnabledFactoryFlagByRequest()
|
Deprecated | — |
Bitrix\Crm\Feature\OperationsApiInDeal
|
Удалено | — |
Bitrix\Crm\Feature\OperationsApiInLead
|
Удалено | — |
Bitrix\Crm\Feature\OperationsApiInContact
|
Удалено | — |
Bitrix\Crm\Feature\OperationsApiInCompany
|
Удалено | — |
Bitrix\Crm\Feature\Category\OperationsApi
|
Удалено | — |
Bitrix\Crm\Reservation\Component\DealUpdateAction
|
Удалено | — |